转载

移动端使用原生audio标签制作react 音频组件

需求

要实现音频的播放如下图:

移动端使用原生audio标签制作react 音频组件

html

html代码如下:

<audio src="" preload="metadata" controls /> 

本来我以为在css3这么强大的年代,自定义一个audio的皮肤应该是完全没问题的,后来的事实证明too young too simple。

看了下audio的shadow dom结构,然后试了试用css去自定义,于是发现两个问题:

移动端使用原生audio标签制作react 音频组件

  • 第一个为播放暂停按钮,就是一个标签没有状态,默认的css定义是为 -webkit-appearance: media-play-button; ,一个样式控制两种状态,没招。
  • 第二个为中间的进度条,自身是个shadow dom,于是构成了两层shadow dom(audio本身是一层),这也没招。

于是只好转向js来控制了,html修改如下:

<div class="audio-wrap">      <audio  src="" preload="metadata" controls />     <i  class="icon-play"></i> <!-- 播放/暂停按钮 通过js切换class -->     <div clas="timeline"> <!-- 进度条 -->         <div class="playhead"></div>     </div>     <div class="time-num">  <!-- 时间 -->         <span class="num-current">00:00</span> / <span class="num-duration">00:00</span>     </div> </div> 

事件

  • audio的 loadedmetadata 事件,读取音频的总时长
  • audio的 timeupdate 事件,用于更新播放进度
  • audio的 canplaythrough 事件,当播放结束,判断是否可以重播
  • icon-play 的点击事件,暂停或播放
  • timeline 的点击事件,用于跳跃播放

react 组件

目前采用的es5,audio地址通过props传入,判断播放还是暂停采用state切换,进度条更新用了reactDOM操作。

var React = require('react'); var ReactDOM = React.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;  // 简单格式化时间,小于9的数字前面添加0 function formatTime(num) {     var num = parseInt(num);     if(num <= 60) {         if(num < 10) {             num = '0' + num;         }         return num;     } }  module.exports = React.createClass({     getInitialState: function() {         return {             isPlay: false // 默认暂停         }     },     componentDidMount: function() {         var audioNode = ReactDOM.findDOMNode(this.refs.audio),             playNode = ReactDOM.findDOMNode(this.refs.play),             timeline = ReactDOM.findDOMNode(this.refs.timeline),             playhead = ReactDOM.findDOMNode(this.refs.playhead),             timeCurrent = ReactDOM.findDOMNode(this.refs.timeCurrent),             timeDuration = ReactDOM.findDOMNode(this.refs.timeDuration),             timelineWidth = timeline.offsetWidth - playhead.offsetWidth,             that = this,             duration;          // 得到初始数据         function loadedmetadata() {             timeDuration = '00:'+ formatTime(audioNode.duration);             timeCurrent = '00:00';         }         this.loadedmetadata = loadedmetadata;          // 播放进度         function timeUpdate() {             var playPercent = timelineWidth * (audioNode.currentTime / duration);             playhead.style.webkitTransform  = "translateX("+playPercent + "px)";             playhead.style.transform = "translateX("+playPercent + "px)";             if (audioNode.currentTime == duration) {                 that.setState({                     isPlay: false                 })             }             timeCurrent = '00:'+ formatTime(audioNode.currentTime);         }         this.timeUpdate = timeUpdate;          // 是否可以重新播放         function canplaythrough() {             duration = audioNode.duration;         }         this.canplaythrough = canplaythrough;          // 进度条点击         function timelineClick(e) {             // 更新坐标位置             var newLeft = e.pageX - timeline.offsetLeft;             if (newLeft >= 0 && newLeft <= timelineWidth) {                 playhead.style.transform = "translateX("+ newLeft +"px)";             }             if (newLeft < 0) {                 playhead.style.transform = "translateX(0)";             }             if (newLeft > timelineWidth) {                 playhead.style.transform = "translateX("+ timelineWidth + "px)";             }             // 更新时间             audioNode.currentTime = duration * (e.pageX - timeline.offsetLeft) / timelineWidth;         }         this.timelineClick = timelineClick;          // 监听事件         audioNode.addEventListener("loadedmetadata", that.loadedmetadata);         audioNode.addEventListener("timeupdate", that.timeUpdate);         audioNode.addEventListener("canplaythrough", that.canplaythrough);         timeline.addEventListener("click", that.timelineClick);     },     componentWillUnmount: function() {         var audioNode = ReactDOM.findDOMNode(this.refs.audio),             timeline = ReactDOM.findDOMNode(this.refs.timeline);          // 注销事件         audioNode.removeEventListener("loadedmetadata", this.loadedmetadata);         audioNode.removeEventListener("timeupdate", this.timeUpdate);         audioNode.removeEventListener("canplaythrough", this.canplaythrough);         timeline.removeEventListener("click", this.timelineClick);     },     play: function(){         var audioNode = ReactDOM.findDOMNode(this.refs.audio);          this.setState({             isPlay: !this.state.isPlay         })          if (!this.state.isPlay) {             audioNode.play();         } else { // pause music             audioNode.pause();         }     },     render: function() {         return (             <div className="audio-wrap">                  <audio ref="audio" src={this.props.audioUrl} preload="metadata" controls />                 <i ref="play" className={"icon-play" + (this.state.isPlay ? " pause" : "")} onClick={this.play}></i>                 <div ref="timeline" className="timeline">                       <div ref="playhead" className="playhead"></div>                 </div>                 <div className="time-num">                     <span ref="timeCurrent" className="num-current">00:00</span> / <span ref="timeDuration" className="num-duration">00:00</span>                 </div>             </div>         )     },     propTypes: {         audioUrl: React.PropTypes.string.isRequired     } }) 
原文  http://imweb.io/topic/5763849f551731da289cba21
正文到此结束
Loading...