html5 Audio 标签自定义样式 手把手教你用react封装一个audio组件

4,075 阅读2分钟

1 audio简介

  • audio是html5新标签
  • audio用来定义声音,比如音乐或其他音频流

2 此篇文章背景

业务中经常遇到使用audio场景,比如音乐播放,比如即时聊天语音,最近在实现智能问答机器人,其中涉及到音频这块内容。 大部分情况下,设计稿不会使用浏览器默认的audio样式,而audio样式又不能直接通过css样式改变成我们想要的效果

3 看看一般浏览器默认样式和设计稿的差别之处

浏览器默认(谷歌) 一般设计稿

4 开始需求分析

  • 播放器有播放暂停功能
  • 开始时间和结束时间
  • 播放进度(随着播放时间变化)
  • 音量调节 (大部分用到音频的场景也就这些需求)

5实现UI界面

  <div className="audioComponent">
        <audio 
          id={`myAudio${id}`}
          src={src}
          onCanPlay={() => this.changeAudio('allTime')}
          onTimeUpdate={(e) => this.changeAudio('getCurrentTime')}
        >
              您的浏览器不支持 audio 标签。
        </audio>  
        {
          isPlay? 
          <svg onClick={() => this.changeAudio('pause')} t="1588991144735" 
          className="icon" viewBox="0 0 1024 1024" version="1.1" 
          xmlns="http://www.w3.org/2000/svg" p-id="3155" width="32" height="32">
            <path d="M304 176h80v672h-80z m408 0h-64c-4.4 0-8 3.6-8 8v656c0 
            4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V184c0-4.4-3.6-8-8-8z" p-id="3156" 
            fill="#ffffff">
            </path>
          </svg>
          :
          <svg onClick={() => this.changeAudio('play')} t="1588991060671" 
          className="icon" viewBox="0 0 1024 1024" version="1.1" 
          xmlns="http://www.w3.org/2000/svg" p-id="2228" width="32" height="32">
            <path d="M275.2 185.6v656L812.8 512z" fill="#ffffff" p-id="2229">
            </path>
          </svg>
        }
        <span className="current">
          {this.millisecondToDate(currentTime)+' / '+this.millisecondToDate(allTime)}
        </span>
        <input 
          type="range" 
          className="time" 
          ref={this.playInput}
          step="0.01" 
          max={allTime}     
          value={currentTime}  
          onChange={(value) => this.changeAudio('changeCurrentTime',value)} 
        />
           <div className="volume-box">
                {
                    isMuted ? <svg  onClick={() => this.changeAudio('muted')} t="1589002703280" 
                    className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3429" width="32" height="32"><path d="M469.726 139.266v0l-217.47 163.104h-135.985c-37.804 0-67.889 30.141-67.889 67.327v273.082c0 36.683 30.397 67.327 67.889 67.327h135.985l217.47 163.104c30.237 22.675 54.353 10.267 54.353-27.359v-679.232c0-37.152-24.338-49.873-54.353-27.359z" p-id="3430" fill="#ffffff"></path><path d="M887.187 538.378l69.126-69.126c22.907-22.907 22.907-60.044 0-82.958v0c-22.907-22.907-60.044-22.907-82.958 0l-69.126 69.126-69.126-69.126c-22.907-22.907-60.044-22.907-82.958 0s-22.907 60.044 0 82.958l69.126 69.126-69.126 69.126c-22.907 22.907-22.907 60.044 0 82.958v0c22.907 22.907 60.044 22.907 82.958 0l69.126-69.126 69.126 69.126c22.907 22.907 60.044 22.907 82.958 0v0c22.907-22.907 22.907-60.044 0-82.958l-69.126-69.126z" p-id="3431" fill="#ffffff"></path></svg> :
      <svg onClick={this.showVioce.bind(this)} t="1589001111572" 
      className="icon" viewBox="0 0 1024 1024" version="1.1" 
      xmlns="http://www.w3.org/2000/svg" p-id="2482" width="32" height="32"><path d="M689.085 335.699c-18.785-18.785-49.239-18.785-68.023 0s-18.785 49.239 0 68.023c56.355 56.355 56.355 147.717 0 204.069-18.785 18.785-18.785 49.239 0 68.023 18.785 18.785 49.239 18.785 68.023 0 93.92-93.92 93.92-246.199 0-340.125z" p-id="2483" fill="#ffffff"></path><path d="M463.835 159.435v0l-205.238 153.931h-128.336c-35.674 0-64.067 28.451-64.067 63.543v257.721c0 34.619 28.684 63.543 64.067 63.543h128.336l205.238 153.931c28.537 21.398 51.299 9.69 51.299-25.82v-641.027c0-35.062-22.971-47.065-51.299-25.82z" p-id="2484" fill="#ffffff"></path><path d="M825.132 199.656c-18.785-18.785-49.239-18.785-68.023 0s-18.785 49.239 0 68.023c131.492 131.492 131.492 344.682 0 476.174-18.785 18.785-18.785 49.239 0 68.023 18.785 18.785 49.239 18.785 68.023 0v-0.001c169.060-169.060 169.060-443.158 0-612.221z" p-id="2485" fill="#ffffff"></path></svg>
                }
               <div className="pannel">
                <input 
                    type="range" 
                    style={{backgroundSize: '100%',display: showVioce ? 'inline-block': 'none'}}
                    ref={this.vioceInput}
                    className="volume"
                    onChange={(value) => this.changeAudio('changeVolume',value)} 
                    value={isMuted ? 0 : volume} 
                    />
               </div>
           </div>
          </div>

svg是引入的小图标 css部分

.audioComponent {
    display: flex;
    align-items: center;
    background-color: $mainColor;
    padding: 5px;
    svg {
        width: 24px;
        height: 24px;
    }
    border-radius: 24px;
    input[type=range]{ 
            outline: none; 
            margin-left: 10px;
            -webkit-appearance: none;/*清除系统默认样式*/  
            // width:56% !important;  
            background: -webkit-linear-gradient(#61bd12, #61bd12) no-repeat, #ddd;  
            background-size: 0% 100%;/*设置左右宽度比例*/  
            height: 3px;/*横条的高度*/  
    } 
    /*拖动块的样式*/  
    input[type=range]::-webkit-slider-thumb {  
        -webkit-appearance: none;/*清除系统默认样式*/  
        height:16px;/*拖动块高度*/  
        width: 16px;/*拖动块宽度*/  
        background: #fff;/*拖动块背景*/  
        border-radius: 50%; /*外观设置为圆形*/  
        border: solid 1px #ddd; /*设置边框*/  
    }
    .volume-box {
        display: flex;
        position: relative;
        margin-left: 10px;
    }
    .pannel {
        background: $areaBgColor;
        position: absolute;
        top: -26px;
        left: -38px;
        display: flex;
        align-items: center;
        height: 20px;
    }    
    .volume {
        width: 80px;
        margin-left: 0;
    }
}

audio标签有几个属性值得关注

  • id:多数情况需要循环展示audio,每个audio要有自己的id
  • src: 音频路径或资源路径
  • onCanPlay: 当资源可以正常播放的时候
  • onTimeUpdate: 当时间更新

6 js实现

在state中定义一下变量

isPlay: false, // 是否播放 默认不播放
isMuted: false, // 是否静音
volume: 100, // 默认音量值
allTime: 0, // 音频总时长
currentTime: 0, // 当前播放时间
showVioce: false // 是否展示静音图标

用两个input表示播放进度条和音量进度条,在js中使用ref进行标识控制

this.playInput = React.createRef();
this.vioceInput = React.createRef();

音频时间计算

millisecondToDate(time) {
      const second = Math.floor(time % 60)
      let minite = Math.floor(time / 60)
      return `${minite}:${second >=10? second: `0${second}`}`
}

改变音频

changeAudio(type,value) {
    const { id,src } = this.props;
    const { allTime } = this.state;
    const audio = document.getElementById(`myAudio${id}`)
    switch(type) {
      case 'allTime':
        this.setState({
          allTime: audio.duration
        })
        break
      case 'play':
        audio.play()
        this.setState({
          isPlay: true
        })
        break
      case 'pause':
        audio.pause()
        this.setState({
          isPlay: false
        })
        break
      case 'muted':
        this.setState({
          isMuted: !audio.muted
        })
        audio.muted = !audio.muted
        break
      case 'changeCurrentTime':
        let str = (value.target.value)/allTime * 100 + "% 100%";
        this.playInput.current.style.backgroundSize = str;
        this.setState({
          currentTime: value.target.value
        })
        audio.currentTime = value.target.value
        if(value.target.value == audio.duration) {
          this.setState({
            isPlay: false
          })
        }
        break
      case 'getCurrentTime':
        let str1 = (audio.currentTime)/allTime * 100 + "% 100%";
        this.playInput.current.style.backgroundSize = str1;
        this.vioceInput.current.style.backgroundSize = this.state.volume + '% 100%';
        this.setState({
          currentTime: audio.currentTime
        })
        if(audio.currentTime == audio.duration) {
          this.setState({
            isPlay: false
          })
        }
        break
      case 'changeVolume':
        audio.volume = value.target.value / 100;
        let str3 = value.target.value + '% 100%';
        this.vioceInput.current.style.backgroundSize = str3;
        console.log(value.target.value)
        this.setState({
          volume: value.target.value,
          isMuted: !parseFloat(value.target.value)
        },() => {
            console.log(this.state.isMuted)
        })
        break  
    }
}

是否展示静音图标(在音量为0时切换成静音图标)

showVioce () {
    let { showVioce } = this.state;
    if(showVioce) {
        this.setState({
            showVioce: false
        })
    } else {
        this.setState({
            showVioce: true
        })
    }
}

根据不同状态通过changeAudio函数对音频播放进行控制

最终奉上动态图片效果

本文使用 mdnice 排版