Vue实现悬浮框自由移动+录音功能的示例代码

2022-07-13,,,,

效果如下

主要功能

1.一个漂浮的球,在全屏幕中自由移动遇到边边角角自动改变方向 ,自带动画效果

2.录音功能,可以录制用户的声音,可以下载为任意格式的音频文件到本地,也可以通过二进制流发给后端

由于后端要声音文件格式wav或者pcm,采样率16000,所以我改了配置文件,稍后我会介绍在哪里改,改什么什么样都是可以的。

注:代码我已经封装成组件了,下方的代码可以直接稍作修改后拿去用,需要修改的地方我以截图的形式贴出来了。

实现

1.封装第一个漂浮组件floatball.vue

<template>
  <!--悬浮小广告样式的提示信息-->
  <div
    id="thediv"
    ref="thediv"
    style="position: absolute; z-index: 111; left: 0; top: 0"
    @mouseover="clearfdad"
    @mouseout="starfdad"
  >
    <div
      style="
        overflow: hidden;
        cursor: pointer;
        text-align: right;
        font-size: 0.0625rem;
        color: #999999;
      "
    >
      <div @click="demo" style="position: relative">
        <!-- 录音组件 -->
        <div v-show="isaudio">
          <audio style="position: absolute; top: 10%; right: 20%"></audio>
        </div>
        <div class="loader">
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
        </div>
        <!-- <img src="@/assets/common/loginlogo.png" alt="" srcset="" /> -->
      </div>
    </div>
    <!-- <a
      href="http://xxxxxx" target="_blank""
    ><img src="../../assets/images/tips.png" width="320" border="0" /></a> -->
  </div>
</template>
<script>
 
var interval
export default {
  data () {
    return {
      isaudio: false,
      xpos: 0,
      ypos: 0,
      xin: true,
      yin: true,
      step: 1,
      delay: 18,
      height: 0,
      hoffset: 0,
      woffset: 0,
      yon: 0,
      xon: 0,
      pause: true,
      thedivshow: true,
    }
  },
 
  mounted () {
    interval = setinterval(this.changepos, this.delay)
  },
 
  methods: {
    demo () {
      this.isaudio = !this.isaudio
    },
    changepos () {
      let width = document.documentelement.clientwidth
      let height = document.documentelement.clientheight
      this.hoffset = this.$refs.thediv.clientheight//获取元素高度
      this.woffset = this.$refs.thediv.offsetwidth
 
      // 滚动部分跟随屏幕滚动
      // this.$refs.thediv.style.left = (this.xpos + document.body.scrollleft + document.documentelement.scrollleft) + "px";
      // this.$refs.thediv.style.top = (this.ypos + document.body.scrolltop + document.documentelement.scrolltop) + "px";
 
      // 滚动部分不随屏幕滚动
      this.$refs.thediv.style.left =
        this.xpos + document.body.scrollleft - 400 + "px"
      this.$refs.thediv.style.top = this.ypos + document.body.scrolltop + "px"
 
      if (this.yon) {
        this.ypos = this.ypos + this.step
      } else {
        this.ypos = this.ypos - this.step
      }
      if (this.ypos < 0) {
        this.yon = 1
        this.ypos = 0
      }
      if (this.ypos >= height - this.hoffset) {
        this.yon = 0
        this.ypos = height - this.hoffset
      }
 
      if (this.xon) {
        this.xpos = this.xpos + this.step
      } else {
        this.xpos = this.xpos - this.step
      }
      if (this.xpos < 0) {
        this.xon = 1
        this.xpos = 0
      }
      if (this.xpos >= width - this.woffset) {
        this.xon = 0
        this.xpos = width - this.woffset
      }
    },
    clearfdad () {
      clearinterval(interval)
    },
    starfdad () {
      interval = setinterval(this.changepos, this.delay)
    },
  },
};
</script>
 
<style lang="scss" scoped>
#thediv {
  z-index: 100;
  position: absolute;
  top: 0.224rem;
  left: 0.0104rem;
  height: 0.9583rem;
  width: 1.4583rem;
  overflow: hidden;
  img {
    width: 100%;
    height: 100%;
  }
}
// 以下是css图标
.loader {
  width: 100px;
  height: 100px;
  padding: 30px;
  font-size: 10px;
  background-color: #91cc75;
  border-radius: 50%;
  border: 8px solid #20a088;
  display: flex;
  align-items: center;
  justify-content: space-between;
  animation: loader-animate 1.5s infinite ease-in-out;
}
 
@keyframes loader-animate {
  45%,
  55% {
    transform: scale(1);
  }
}
 
.loader > span {
  width: 5px;
  height: 50%;
  background-color: #fff;
  transform: scaley(0.05) translatex(-5px);
  animation: span-animate 1.5s infinite ease-in-out;
  animation-delay: calc(var(--n) * 0.05s);
}
 
@keyframes span-animate {
  0%,
  100% {
    transform: scaley(0.05) translatex(-5px);
  }
  15% {
    transform: scaley(1.2) translatex(10px);
  }
  90%,
  100% {
    background-color: hotpink;
  }
}
 
.loader > span:nth-child(1) {
  --n: 1;
}
.loader > span:nth-child(2) {
  --n: 2;
}
.loader > span:nth-child(3) {
  --n: 3;
}
.loader > span:nth-child(4) {
  --n: 4;
}
.loader > span:nth-child(5) {
  --n: 5;
}
.loader > span:nth-child(6) {
  --n: 6;
}
.loader > span:nth-child(7) {
  --n: 7;
}
.loader > span:nth-child(8) {
  --n: 8;
}
.loader > span:nth-child(9) {
  --n: 9;
}
.loader > span:nth-child(10) {
  --n: 10;
}
</style>

2.封装第二个组件录音组件audio.vue

这个组件依赖另一个文件recorder.js,在最下方贴出来了。

<template>
  <div>
    <el-button @click="myrecording" style="margin-left: 1rem">{{
      time
    }}</el-button>
    <el-button @click="startplay" style="margin-left: 1rem">{{
      playing ? "播放" : "暂停"
    }}</el-button>
    <el-button @click="delvioce" style="margin-left: 1rem; color: black"
      >删除</el-button
    >
    <audio
      v-if="fileurl"
      :src="fileurl"
      controls="controls"
      style="display: none"
      ref="audio"
      id="myaudio"
    ></audio>
  </div>
</template>
<script>
// 引入recorder.js
import recording from "@/utils/recorder"
export default {
  data () {
    return {
      recordingswitch: true, //录音开关
      files: "", //语音文件
      num: 60, // 按住说话时间
      recorder: null,
      fileurl: "", //语音url
      interval: "", //定时器
      time: "点击说话(60秒)",
      playing: true,
    }
  },
 
  methods: {
    // 点击录制
    myrecording () {
      if (this.files === "") {
        if (this.recordingswitch) {
          this.start()
        } else {
          this.end()
        }
      } else if (this.time === "点击重录(60秒)") {
        this.files = ""
        this.start()
      }
      this.recordingswitch = !this.recordingswitch
    },
    // 点击播放
    startplay () {
      console.dir(this.$refs.audio, '--------------------')
      if (this.playing) {
        this.$refs.audio.play()
      } else {
        this.$refs.audio.pause()
      }
      this.playing = !this.playing
    },
    // 删除语音
    delvioce () {
      this.fileurl = ""
      this.files = ""
      this.num = 60
      this.time = "点击说话(60秒)"
    },
    // 清除定时器
    cleartimer () {
      if (this.interval) {
        this.num = 60
        clearinterval(this.interval)
      }
    },
    // 开始录制
    start () {
      this.cleartimer()
      recording.get((rec) => {
        // 当首次按下时,要获取浏览器的麦克风权限,所以这时要做一个判断处理
        if (rec) {
          this.recorder = rec
          this.interval = setinterval(() => {
            if (this.num <= 0) {
              this.recorder.stop()
              this.num = 60
              this.end()
            } else {
              this.time = "点击结束(" + this.num + "秒)"
              this.recorder.start()
              this.num--
            }
          }, 1000)
        }
      })
    },
    // 停止录制
    end () {
      this.cleartimer()
      if (this.recorder) {
        this.recorder.stop() // 重置说话时间
        this.num = 60
        this.time = "点击重录(60秒)" // 获取语音二进制文件
        let bold = this.recorder.getblob() // 将获取的二进制对象转为二进制文件流
        let files = new file([bold], "test.wav", {
          type: "audio/wav",
          lastmodified: date.now(),
        })
        this.files = files
        console.log(this.files, '----------', bold, '---------------', this.recorder)
        //获取音频时长
        let fileurl = url.createobjecturl(files)
        this.fileurl = fileurl
        let audioelement = new audio(fileurl)
        let duration
        audioelement.addeventlistener("loadedmetadata", function (_event) {
          duration = audioelement.duration
          console.log("视频时长:" + duration, files)
        })
        var downloadanchornode = document.createelement('a')
        downloadanchornode.setattribute("href", fileurl)
        downloadanchornode.setattribute("download", 'ssss')
        downloadanchornode.click()
        downloadanchornode.remove()
        this.$message.success("正在下载中,请稍后...")
      }
    },
  },
};
</script>
<style lang="less" >
</style>

3.recorder.js

// 兼容
window.url = window.url || window.webkiturl
navigator.getusermedia = navigator.getusermedia || navigator.webkitgetusermedia || navigator.mozgetusermedia || navigator.msgetusermedia
let hzrecorder = function (stream, config) {
  config = config || {}
  config.samplebits = config.samplebits || 8 // 采样数位 8, 16
  config.samplerate = config.samplerate || (16000) // 采样率(1/6 44100)
  let context = new (window.webkitaudiocontext || window.audiocontext)()
  let audioinput = context.createmediastreamsource(stream)
  let createscript = context.createscriptprocessor || context.createjavascriptnode
  let recorder = createscript.apply(context, [4096, 1, 1])
  let audiodata = {
    size: 0, // 录音文件长度
    buffer: [], // 录音缓存
    inputsamplerate: context.samplerate, // 输入采样率
    inputsamplebits: 16, // 输入采样数位 8, 16
    outputsamplerate: config.samplerate, // 输出采样率
    oututsamplebits: config.samplebits, // 输出采样数位 8, 16
    input: function (data) {
      this.buffer.push(new float32array(data))
      this.size += data.length
    },
    compress: function () { // 合并压缩
      // 合并
      let data = new float32array(this.size)
      let offset = 0
      for (let i = 0; i < this.buffer.length; i++) {
        data.set(this.buffer[i], offset)
        offset += this.buffer[i].length
      }
      // 压缩
      let compression = parseint(this.inputsamplerate / this.outputsamplerate)
      let length = data.length / compression
      let result = new float32array(length)
      let index = 0; let j = 0
      while (index < length) {
        result[index] = data[j]
        j += compression
        index++
      }
      return result
    },
    encodewav: function () {
      let samplerate = math.min(this.inputsamplerate, this.outputsamplerate)
      let samplebits = math.min(this.inputsamplebits, this.oututsamplebits)
      let bytes = this.compress()
      let datalength = bytes.length * (samplebits / 8)
      let buffer = new arraybuffer(44 + datalength)
      let data = new dataview(buffer)
      let channelcount = 1// 单声道
      let offset = 0
      let writestring = function (str) {
        for (let i = 0; i < str.length; i++) {
          data.setuint8(offset + i, str.charcodeat(i))
        }
      }
      // 资源交换文件标识符
      writestring('riff'); offset += 4
      // 下个地址开始到文件尾总字节数,即文件大小-8
      data.setuint32(offset, 36 + datalength, true); offset += 4
      // wav文件标志
      writestring('wave'); offset += 4
      // 波形格式标志
      writestring('fmt '); offset += 4
      // 过滤字节,一般为 0x10 = 16
      data.setuint32(offset, 16, true); offset += 4
      // 格式类别 (pcm形式采样数据)
      data.setuint16(offset, 1, true); offset += 2
      // 通道数
      data.setuint16(offset, channelcount, true); offset += 2
      // 采样率,每秒样本数,表示每个通道的播放速度
      data.setuint32(offset, samplerate, true); offset += 4
      // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
      data.setuint32(offset, channelcount * samplerate * (samplebits / 8), true); offset += 4
      // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
      data.setuint16(offset, channelcount * (samplebits / 8), true); offset += 2
      // 每样本数据位数
      data.setuint16(offset, samplebits, true); offset += 2
      // 数据标识符
      writestring('data'); offset += 4
      // 采样数据总数,即数据总大小-44
      data.setuint32(offset, datalength, true); offset += 4
      // 写入采样数据
      if (samplebits === 8) {
        for (let i = 0; i < bytes.length; i++, offset++) {
          let s = math.max(-1, math.min(1, bytes[i]))
          let val = s < 0 ? s * 0x8000 : s * 0x7fff
          val = parseint(255 / (65535 / (val + 32768)))
          data.setint8(offset, val, true)
        }
      } else {
        for (let i = 0; i < bytes.length; i++, offset += 2) {
          let s = math.max(-1, math.min(1, bytes[i]))
          data.setint16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
        }
      }
      return new blob([data], { type: 'audio/mp3' })
    }
  }
  // 开始录音
  this.start = function () {
    audioinput.connect(recorder)
    recorder.connect(context.destination)
  }
  // 停止
  this.stop = function () {
    recorder.disconnect()
  }
  // 获取音频文件
  this.getblob = function () {
    this.stop()
    return audiodata.encodewav()
  }
  // 回放
  this.play = function (audio) {
    let downrec = document.getelementbyid('downloadrec')
    downrec.href = window.url.createobjecturl(this.getblob())
    downrec.download = new date().tolocalestring() + '.mp3'
    audio.src = window.url.createobjecturl(this.getblob())
  }
  // 上传
  this.upload = function (url, callback) {
    let fd = new formdata()
    fd.append('audiodata', this.getblob())
    let xhr = new xmlhttprequest()
    /* eslint-disable */
    if (callback) {
      xhr.upload.addeventlistener('progress', function (e) {
        callback('uploading', e)
      }, false)
      xhr.addeventlistener('load', function (e) {
        callback('ok', e)
      }, false)
      xhr.addeventlistener('error', function (e) {
        callback('error', e)
      }, false)
      xhr.addeventlistener('abort', function (e) {
        callback('cancel', e)
      }, false)
    }
    /* eslint-disable */
    xhr.open('post', url)
    xhr.send(fd)
  }
  // 音频采集
  recorder.onaudioprocess = function (e) {
    audiodata.input(e.inputbuffer.getchanneldata(0))
    // record(e.inputbuffer.getchanneldata(0));
  }
}
// 抛出异常
hzrecorder.throwerror = function (message) {
  alert(message)
  throw new function () { this.tostring = function () { return message } }()
}
// 是否支持录音
hzrecorder.canrecording = (navigator.getusermedia != null)
// 获取录音机
hzrecorder.get = function (callback, config) {
  if (callback) {
    if (navigator.getusermedia) {
      console.log(navigator)
      navigator.getusermedia(
        { audio: true } // 只启用音频
        , function (stream) {
          let rec = new hzrecorder(stream, config)
          callback(rec)
        }
        , function (error) {
          console.log(error)
          switch (error.code || error.name) {
            case 'permission_denied':
            case 'permissiondeniederror':
              hzrecorder.throwerror('用户拒绝提供信息。')
              break
            case 'not_supported_error':
            case 'notsupportederror':
              hzrecorder.throwerror('浏览器不支持硬件设备。')
              break
            case 'mandatory_unsatisfied_error':
            case 'mandatoryunsatisfiederror':
              hzrecorder.throwerror('无法发现指定的硬件设备。')
              break
            default:
              hzrecorder.throwerror('无法打开麦克风。异常信息:' + (error.code || error.name))
              break
          }
        })
    } else {
      hzrecorder.throwerr('当前浏览器不支持录音功能。'); return
    }
  }
}
export default hzrecorder

以上就是vue实现悬浮框自由移动+录音功能的示例代码的详细内容,更多关于vue悬浮框移动 录音的资料请关注其它相关文章!

《Vue实现悬浮框自由移动+录音功能的示例代码.doc》

下载本文的Word格式文档,以方便收藏与打印。