vue开发一款h5弹幕项目

两天时间用vue写一个手持弹幕,(没有完成所有功能)

先看一下效果

视频演示

这里使用vue 写的,只支持vue语法。后期有时间会考虑写成npm 插件。欢迎大神PR/

有两个组件 , 贴一下代码,如果不懂,欢迎留言

第一个

vue-bullet-chat.vue



<template>
  <div class="vue-bullet-chat-wrapper" :style="background" @click="bulletChatClick">
    <div class="vbc-top">
      <div class="vbc-lock-wrapper">
        <svg t="1612009086242" class="vbc-icon-svg vbc-icon-open vbc-show-active" viewBox="0 0 1223 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11149" width="128" height="128"><path d="M874.207389 0.2134C694.712336 6.861365 556.767063 159.764558 556.767063 337.59762v119.663369h-498.59737C26.59186 458.92298 0 483.852849 0 515.430682v452.061616c0 31.577833 26.59186 56.507702 56.507702 56.507702h731.276143c31.577833 0 56.507702-26.59186 56.507702-56.507702V515.430682c0-31.577833-26.59186-56.507702-56.507702-56.507702H698.036318v-127.973325C698.036318 221.258234 792.769819 133.172698 904.123231 143.144646c98.057483 9.971947 169.523106 94.7335 169.523106 192.790983v187.805009c0 16.619912 13.29593 29.915842 29.915843 29.915843h81.43757c16.619912 0 29.915842-13.29593 29.915842-29.915843V329.287664C1216.577584 143.144646 1062.012399-6.434565 874.207389 0.2134z" p-id="11150"></path></svg>
        <svg t="1612009127525" class="vbc-icon-svg vbc-icon-close" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12557" width="128" height="128"><path d="M842.224081 429.259288h-72.934698V258.894547C769.289383 116.096916 653.9801 0 511.9701 0 370.038864 0 254.572054 116.096916 254.572054 258.815783v170.522268H181.71612A63.798169 63.798169 0 0 0 118.15424 493.214983v466.671795c0 35.443427 28.433505 64.034459 63.56188 64.034459h660.586724a63.719406 63.719406 0 0 0 63.483117-64.034459V493.214983a63.798169 63.798169 0 0 0-63.56188-63.876932z m-294.574264 309.539266v101.840781a8.427659 8.427659 0 0 1-8.270133 8.42766h-54.661641a8.427659 8.427659 0 0 1-8.42766-8.42766V738.798554A79.393277 79.393277 0 0 1 511.9701 588.439658a79.393277 79.393277 0 0 1 35.75848 150.280133z m122.870549-309.539266H353.498598V263.068995c0-87.8997 71.201908-159.574187 158.629029-159.574187 87.427121 0 158.550265 71.674487 158.550265 159.574187v166.190293z" p-id="12558"></path></svg>
      </div>

    </div>
    <div class="vbc-text-wrapper vbc-flex-sb-column">
      <div class="vbc-text" id="vbcTextInput" :style="textStyle">
        <span :class="textClass">{{value}}</span>
      </div>
    </div>
    <div class="vbc-input-wrapper vbc-flex-sb vbc-input-wrapper-active" @click.stop>
      <div class="vbc-flex-sb">
        <input class="input-box" ref="inputBox" v-model="setValue" placeholder="请输入弹幕文字" @focus="handleFocus" @keypress.enter="handleInput" />
        <div class="vbc-close-box">
          <svg t="1612167727491" class="vbc-close-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1694" width="128" height="128"><path d="M512 64a448 448 0 1 1 0 896A448 448 0 0 1 512 64zM408.576 363.136a32 32 0 1 0-45.312 45.248l103.808 103.744-103.808 103.744a32 32 0 1 0 45.312 45.248l103.744-103.68 103.744 103.68a32 32 0 1 0 45.248-45.248l-103.744-103.68 103.744-103.808a32 32 0 0 0-45.248-45.248L512.32 466.88z" fill="#B8B8B8" p-id="1695"></path></svg>
        </div>
      </div>
      <div class="svg-box" @click="handlePopup">
        <svg t="1611985197202" class="vbc-icon-svg" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
             p-id="5543" width="128" height="128">
          <path d="M661.3 250.7H138.7c-41.2 0-74.7 33.4-74.7 74.7v560c0 41.2 33.4 74.7 74.7 74.7h522.7c41.2 0 74.7-33.4 74.7-74.7v-560c-0.1-41.3-33.5-74.7-74.8-74.7z m18.7 616c0 20.6-16.7 37.3-37.3 37.3H157.3c-20.6 0-37.3-16.7-37.3-37.3V344c0-20.6 16.7-37.3 37.3-37.3h485.3c20.6 0 37.3 16.7 37.3 37.3v522.7z"
                p-id="5544" fill="#ffffff"></path>
          <path d="M596 624h-37.3c-15.4 0-28 12.6-28 28s12.6 28 28 28H596c15.4 0 28-12.6 28-28s-12.6-28-28-28z m0-93.3h-93.3c-15.4 0-28 12.6-28 28s12.6 28 28 28H596c15.4 0 28-12.6 28-28s-12.6-28-28-28z m0-93.4H465.3c-15.4 0-28 12.6-28 28s12.6 28 28 28H596c15.4 0 28-12.6 28-28s-12.6-28-28-28z m-297 0l-123 336h77.7l24.7-77h122.1l25.2 77h78.1l-120.6-336H299zM294 641l44.3-145.8h2L383.6 641H294z"
                p-id="5545" fill="#ffffff"></path>
          <path d="M885.3 64H418.7c-41.1 0-74.7 33.6-74.7 74.7v46.7c0 15.5 12.5 28 28 28s28-12.5 28-28v-28c0-20.5 16.8-37.3 37.3-37.3h429.3c20.5 0 37.3 16.8 37.3 37.3V680c0 20.5-16.8 37.3-37.3 37.3h-65.3c-15.5 0-28 12.5-28 28s12.5 28 28 28h84c41.1 0 74.7-33.6 74.7-74.7v-560c0-41-33.6-74.6-74.7-74.6z"
                p-id="5546" fill="#ffffff"></path>
        </svg>
      </div>
    </div>
    <vue-bullet-chat-popup
            :vbc-popup-vis-able.sync="vbcPopupVisAble"
            :animation="animation"
            @effect="getEffect"
            @color="getColor"
            @speed="getSpeed"
            @fontSize="getFontSize"
    />
  </div>
</template>

<script>
  import VueBulletChatPopup from "./vue-bullet-chat-popup";
  export default {
    name: "vue-bullet-chat",
    components: { VueBulletChatPopup },
    props: {
      background: {
        type: Object,
        default: function () {
          return {
            backgroundColor: 'black'
          }
        }
      },
      textObj: {
        type: Object,
        default: function () {
          return {
            transform: 'rotate(90deg)',
            letterSpacing: '8px'
          }
        }
      }
    },
    data() {
      return {
        el: '',
        textWrapper: '',
        innerHeight: '', // 屏幕高度
        i: 0,
        timer: null,
        initBottom: '',
        initValue: '', // 初始距离底部的值
        initBottomCopy: '',
        value: '请输入文字显示文字弹幕', // 设置值
        clickFlag: false,
        inputWrapper: false,
        isLock: false,
        setValue: '',
        lockClose: '',
        lockOpen: '',
        lockWrapper: '',
        closeTimer: null,
        initTransform: '',
        reqAnFrame: '',
        inputBox: '',
        closeIcon: '',
        vbcPopupVisAble: false,
        animation: '',
        color: 'white',
        fontSize: '48px',
        textClass: '',
        speed: 2,
        requestAnimationFrame: '',
        cancelAnimationFrame: ''
      }
    },
    watch: {
      setValue() {
        if(!this.setValue) {
          this.utils.removeClass(this.closeIcon, 'vbc-close-active')
        }else {
          this.utils.addClass(this.closeIcon, 'vbc-close-active')
        }
      }
    },
    computed: {
      textStyle() {
        return {
          ...this.textObj,
          color: this.color,
          fontSize: this.fontSize,
          opacity: 0
        }
      }
    },
    mounted() {
      this.utils.fitIos()
      this.getEle()
      this.getText()
    },


    beforeDestroy() {
      window.cancelAnimationFrame(this.reqAnFrame)
    },

    methods: {
      getEle() {
        this.el = this.utils.classEle('vue-bullet-chat-wrapper')
        this.inputWrapper = this.el.getElementsByClassName('vbc-input-wrapper')[0]
        this.textWrapper = this.el.getElementsByClassName('vbc-text')[0]
        this.lockWrapper = this.el.getElementsByClassName('vbc-lock-wrapper')[0]
        this.lockClose = this.utils.classEle('vbc-icon-close')
        this.lockOpen = this.utils.classEle('vbc-icon-open')
        this.inputBox = this.utils.classEle('input-box')
        this.initTransform = this.utils.deepClone(this.textWrapper.style.transform)
        this.closeIcon = this.utils.classEle('vbc-close-box')

        // 设置锁
        this.lockWrapper.onclick = () => {
          if (!this.isLock) {
            this.isLock = true
            this.utils.hiddenClass('vbc-icon-open')
            this.utils.showClass('vbc-icon-close')
            this.utils.removeClass(this.inputWrapper, 'vbc-input-wrapper-active')
            this.utils.addClass(this.lockWrapper, 'lock-wrapper-active')

          }else {
            this.isLock = false
            this.utils.hiddenClass('vbc-icon-close')
            this.utils.showClass('vbc-icon-open')
          }
        }

        // 点击清空

        this.closeIcon.onclick = () => {
          this.setValue = ''
          this.$refs.inputBox.focus()
        }

        // this.init()
        // this.closeFun()
      },

      init() {
        this.textWrapper.style.opacity = '1'
        this.initBottom = Math.round(window.innerHeight / 2 + this.textWrapper.getBoundingClientRect().height / 2)
        this.initBottomCopy = this.utils.deepClone(this.initBottom)
        this.textWrapper.style.transform = 'translateY('+ this.initBottom + 'px)' + this.initTransform // 初始化文字位置
        this.move()
      },

      move() {
        this.initBottom -= this.speed
        this.textWrapper.style.transform = 'translateY('+ this.initBottom + 'px)' + this.initTransform // 初始化文字位置
        if ( this.initBottom <= -this.initBottomCopy ) {
          this.initBottom = this.initBottomCopy
          this.i = 0
        }
        this.reqAnFrame = window.requestAnimationFrame(this.move)
      },

      handleInput() {
        this.textWrapper.style.opacity = '0'
        window.cancelAnimationFrame(this.reqAnFrame)
        if(this.setValue) {
          this.value = this.setValue
        }else {
          this.value = '请输入文字显示文字弹幕'
          this.utils.removeClass(this.closeIcon, 'vbc-close-active')
        }

        this.$refs.inputBox.blur()
        this.bulletChatClick()
        setTimeout(() => {
          this.init()
        }, 100)
      },

      bulletChatClick() {
        if(!this.isLock) {
          if (!this.clickFlag) {
            this.utils.removeClass(this.closeIcon, 'vbc-close-active')
            this.utils.removeClass(this.inputWrapper, 'vbc-input-wrapper-active')
            this.utils.addClass(this.lockWrapper, 'lock-wrapper-active')
            this.clickFlag = true
          }else {
            this.utils.addClass(this.inputWrapper, 'vbc-input-wrapper-active')
            this.utils.removeClass(this.lockWrapper, 'lock-wrapper-active')
            this.clickFlag = false
          }
        }else {
          if (!this.clickFlag) {
            this.clickFlag = true
            this.utils.addClass(this.lockWrapper, 'lock-wrapper-active')
            this.utils.hiddenClass('vbc-icon-open')
          }else {
            this.clickFlag = false
            this.utils.removeClass(this.lockWrapper, 'lock-wrapper-active')
            this.utils.showClass('vbc-icon-close')
          }
        }
        this.closeFun()
      },
      closeFun() {
        if(!this.clickFlag) {
          clearTimeout(this.closeTimer)
          this.closeTimer = setTimeout( () => {
            this.utils.removeClass(this.inputWrapper, 'vbc-input-wrapper-active')
            this.utils.addClass(this.lockWrapper, 'lock-wrapper-active')
            this.clickFlag = true
          }, 3000)
        }
      },
      handleFocus() {
        this.utils.addClass(this.closeIcon, 'vbc-close-active')
        clearTimeout(this.closeTimer)
        // this.clickFlag = true
        this.utils.addClass(this.inputWrapper, 'vbc-input-wrapper-active')
        this.utils.removeClass(this.lockWrapper, 'lock-wrapper-active')
      },

      handlePopup() {
        this.vbcPopupVisAble = true
      },

      getColor(v) {
        this.color = v
      },

      getEffect(v) {
        this.textClass = v
      },

      getFontSize(v) {
        this.textWrapper.style.opacity = '0'
        this.fontSize = v
        window.cancelAnimationFrame(this.reqAnFrame)
        setTimeout(() => {
          this.init()
        }, 100)
      },

      getText() {
        // this.color = this.utils.get('vbcColor') || this.color
        // this.textClass = this.utils.get('vbcEffect') || this.fontSize
        // this.speed = +this.utils.get('vbcSpeed') || +this.speed
        // this.fontSize = this.utils.get('vbcFontSize') || this.fontSize
      },

      getSpeed(v) {
        this.textWrapper.style.opacity = '0'
        this.speed = +v
        if(v === '0') {
          window.cancelAnimationFrame(this.reqAnFrame)
          this.reqAnFrame = null
        }else {
          window.cancelAnimationFrame(this.reqAnFrame)
          setTimeout(() => {
            this.init()
          }, 100)
        }
      }
    }
  }
</script>
<style scoped>
  .input-box::placeholder {
    font-size: 24px;
    line-height: 100px;
    vertical-align: baseline;
    color: blue;
  }
</style>

<style scoped>
  .vue-bullet-chat-wrapper {
    width: 100%;
    height: 100vh;
    margin: 0;
    padding: 0;
    overflow: hidden;
    position: relative;
    user-select: none;
  }

  .vue-bullet-chat-wrapper .vbc-text {
    white-space: nowrap;
    letter-spacing: 8px;
  }
  .vue-bullet-chat-wrapper .vbc-input-wrapper {
    width: 100%;
    position: absolute;
    bottom: 0;
    margin: 0 auto;
    padding-bottom: 20px;
    transition: all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    transform: translate(0, 120px);
  }
  .vue-bullet-chat-wrapper .vbc-input-wrapper-active {
    transition: all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    transform: translate(0, 0px);
  }

  .vue-bullet-chat-wrapper .svg-box {
    position: relative;
    width: 80px;
    height: 80px;
    background: #222;
    border-radius: 14px;
  }

  .vue-bullet-chat-wrapper .vbc-icon-svg {
    position: absolute;
    left: 15px;
    top: 15px;
    width: 50px;
    height: 50px;
  }

  .vue-bullet-chat-wrapper .input-box {
    width: 570px;
    display: block;
    height: 80px;
    background: #222;
    border: none;
    outline: none;
    border-radius: 6px;
    text-indent: 1em;
    color: white;
    font-size: 30px;
  }

  /*顶部*/
  .vue-bullet-chat-wrapper .vbc-top {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 80px;
    fill: white;
    z-index: 10;
  }
  .vue-bullet-chat-wrapper .vbc-top .vbc-icon-svg {
    opacity: 0
  }
  .vue-bullet-chat-wrapper .vbc-top .vbc-icon-svg.vbc-show-active {
    opacity: 1;
  }

  .vbc-text-wrapper {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    width: 100%;
  }
  .vbc-lock-wrapper {
    height: 40px;
    transition: all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    transform: translate(0, 0px);
  }
  .vbc-lock-wrapper.lock-wrapper-active {
    transition: all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    transform: translate(0, -80px);
  }
  .vbc-close-box {
    position: relative;
  }
  .vbc-close-box .vbc-close-icon {
    position: absolute;
    top: -10px;
    right: 10px;
    width: 30px;
    height: 30px;
    opacity: 0;
    transition: all .3s;
  }
  .vbc-close-box.vbc-close-active .vbc-close-icon {
    opacity: 1;
  }
</style>





第二个弹框组件

vue-bullet-chat-popup.vue



<template>
  <transition name="bullet-chat-fade">
    <div v-show="vbcPopupVisAble" tabindex="-1" :style="style" class="bullet-chat-popup" @click.stop>
      <div class="bullet-chat-mask" @click="handleClick"></div>
      <transition name="bullet-chat-slide">
        <div class="bullet-chat-dialog" v-show="show" :style="dialogStyle">
          <div class="vbc-popup-top vbc-flex-sb">
            <div v-for="(item, index) in topTitle" :key="index" :class="{'vbc-popup-top-active': item === active}">
              <span @click="handleClickTop(item)">{{item}}</span>
            </div>
          </div>
          <div class="bullet-chat-content">
            <div v-for="(item, index) in textOpt" :key="index" class="content-item">
              <p class="bullet-chat-cont-title">{{item.header}}</p>
              <div class="cont-item">
                <span v-for="(spanItem, i) in item.selectOpt" :key="i" :class="{'one-span':item.type === 1, 'two-span': item.type === 2 || item.type === 3 || item.type === 4, active: spanItem.active, activeColor: spanItem.active }" :style="{background: spanItem.background}" @click="handleSpan(spanItem, index)">
                  {{spanItem.label}}
                </span>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </div>
  </transition>
</template>

<script>
  export default {
    name: "VueBulletChatPopup",
    props: {
      vbcPopupVisAble: {
        type: Boolean,
        default: false
      },
      duration: {
        type: Number,
        default: 300
      }
    },
    data() {
      return {
        topTitle: ['文字', '背景', '音乐'],
        active: '文字',
        show: false,
        textOpt: [{
          header: '效果(多选)',
          type: 1,
          selectOpt: [{
            label: '阴影',
            value: 'vbc-text-shadow',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '叠字',
            value: 'vbc-text-double',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '闪烁',
            value: 'vbc-text-twinkle',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '描边',
            value: 'vbc-text-stroke',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '酷炫',
            value: 'vbc-text-masked',
            active: false,
            background: '#3d3d3d'
          }]
        }, {
          header: '字体颜色',
          type: 2,
          selectOpt: [{
            label: '',
            value: '1',
            active: true,
            background: "white",
          }, {
            label: '',
            value: '2',
            active: false,
            background: 'red'
          }, {
            label: '',
            value: '3',
            active: false,
            background: '#FD2E74'
          }, {
            label: '',
            value: '4',
            active: false,
            background: '#FCDE46'
          }, {
            label: '',
            value: '5',
            active: false,
            background: '#FD2E74'
          }, {
            label: '',
            value: '6',
            active: false,
            background: '#61FE4B'
          }, {
            label: '',
            value: '7',
            active: false,
            background: '#41A0FE'
          }, {
            label: '',
            value: '8',
            active: false,
            background: '#FC5727'
          }, {
            label: '',
            value: '9',
            active: false,
            background: '#8911FE'
          }, {
            label: '',
            value: '10',
            active: false,
            background: '#65FFC9'
          }]
        }, {
          header: '速度',
          type: 3,
          selectOpt: [{
            label: '静止',
            value: '0',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '0.5x',
            value: '1',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '1x',
            value: '2',
            active: true,
            background: '#3d3d3d'
          }, {
            label: '1.5x',
            value: '3',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '2x',
            value: '4',
            active: false,
            background: '#3d3d3d'
          }]
        }, {
          header: '字号',
          type: 4,
          selectOpt: [{
            label: '24',
            value: '24',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '36',
            value: '36',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '48',
            value: '48',
            active: true,
            background: '#3d3d3d'
          }, {
            label: '64',
            value: '64',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '72',
            value: '72',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '120',
            value: '120',
            active: false,
            background: '#3d3d3d'
          }]
        }, {
          header: '字体',
          type: 5,
          selectOpt: [{
            label: '宋体',
            value: '宋体',
            active: false,
            background: '#3d3d3d'
          }, {
            label: '宋体',
            value: '宋体',
            active: false,
            background: '#3d3d3d'
          }]
        }]
      }
    },
    watch: {
      vbcPopupVisAble(v) {
        this.show = v
      }
    },
    computed: {
      style() {
        return {
          animationDuration: `${this.duration}ms`
        };
      },
      dialogStyle() {
        return {
          animationDuration: `${this.duration}ms`,
        }
      }
    },
    mounted() {
      this.textOpt = this.utils.get('textOpt', true) || this.textOpt
    },
    methods: {
      handleClickTop(v) {
        this.active = v
      },
      handleClick() {
        this.$emit('update:vbcPopupVisAble', false)
      },
      handleSpan(item, index) {
        this.textOpt[index].selectOpt.map(val => {
          if(val.active) {
            val.active = false
          }
        })
        if(!item.active) {
          item.active = true
        }
        if(index === 0) {
          this.$emit('effect', item.value)
          this.utils.set('vbcEffect', item.value)
        } else if(index === 1) {
          this.$emit('color', item.background)
          this.utils.set('vbcColor', item.background)
        }else if(index === 2) {
          this.$emit('speed', item.value)
          this.utils.set('vbcSpeed', item.value)
        }else if (index === 3) {
          this.$emit('fontSize', item.value + 'px')
          this.utils.set('vbcFontSize', item.value + 'px')
        }
        this.utils.set('textOpt', JSON.stringify(this.textOpt))
      },
    }
  }
</script>





还有一个util.js, 自己封装的工具方法




class Utils {
  constructor() {

  }
  hasClass(ele, cls) {
    return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
  }
  addClass(ele, cls) {
    if (!this.hasClass(ele, cls)) ele.className += ' ' + cls
  }
  removeClass(ele, cls) {
    if (this.hasClass(ele, cls)) {
      const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
      ele.className = ele.className.replace(reg, '')
    }
  }
  set(key, value) {
    localStorage.setItem(key, value)
  }

  get(key, isObj = false) {
    if(isObj) {
      return JSON.parse(localStorage.getItem(key))
    }else {
      return localStorage.getItem(key)
    }
  }
  showClass(cls) {
    cls ? document.getElementsByClassName(cls)[0].style.opacity = '1' : new Error('请输入类名')
  }
  hiddenClass(cls) {
    cls ? document.getElementsByClassName(cls)[0].style.opacity = '0' : new Error('请输入类名')
  }
  changeInnerText(cls, text) {
    document.getElementsByClassName(cls)[0].innerHTML = text
  }

  clickfu (to, cls) {
    //回调函数,to为点击对象
    to.setAttribute("class", cls);
    const siblings = to.parentNode.childNodes;
    for (let i = 0; i < siblings.length; i++)
      if (siblings[i].nodeType == 1 && siblings[i] != to) siblings[i].className = '';
  }

  formatSeconds(value) {
    if(!value) return '00:00'
    value = parseInt(value);
    let time;
    if (value > -1) {
      let hour = Math.floor(value / 3600);
      let min = Math.floor(value / 60) % 60;
      let sec = value % 60;
      let day = parseInt(hour / 24);
      if (day > 0) {
        hour = hour - 24 * day;
        time = day + "day " + hour + ":";
      } else if (hour > 0) {
        time = hour + ":";
      }else {
        time = "";
      }
      if (min < 10) {
        time += "0";
      }
      time += min + ":";
      if (sec < 10) {
        time += "0";
      }
      time += sec;
    }
    return time;
  }

  classEle(cls) {
    return  cls && document.getElementsByClassName(cls)[0]
  }

  deepClone(source) {
    if (source && typeof source !== 'object') {
      return JSON.parse(JSON.stringify(source))
    }
    const targetObj = source.constructor === Array ? [] : {}
    Object.keys(source).forEach(keys => {
      if (source[keys] && typeof source[keys] === 'object') {
        targetObj[keys] = deepClone(source[keys])
      } else {
        targetObj[keys] = source[keys]
      }
    })
    return targetObj
  }

  isIPHONE() {
    return navigator.userAgent.toUpperCase().indexOf('IPHONE') !== -1;
  }

  /**
   * 解决IOS:input框输入完成,键盘关闭后位置上移问题
   */

  fitIos() {
    const u = navigator.userAgent;
    let flag;
    let myFunction;
    let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
    if(isIOS){
      document.body.addEventListener('focusin', () => {  //软键盘弹起事件
        flag=true;
        clearTimeout(myFunction);
      })
      document.body.addEventListener('focusout', () => { //软键盘关闭事件
        flag=false;
        if(!flag){
          myFunction = setTimeout(function(){

            window.scrollTo({top:0,left:0,behavior:"smooth"})//重点  =======当键盘收起的时候让页面回到原始位置(这里的top可以根据你们个人的需求改变,并不一定要回到页面顶部)

          },200);
        }else{
          return
        }
      })
    }else{
      return
    }
  }


}

export default Utils

感兴趣的可以到github上下载源码,但是开源不易,请点个小星星支持一下,谢谢啦😀😀😀

源码

线上演示

最后祝大家,兔年大吉,事业牛气冲天

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容