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

xiaoniao25 8年前
   <h2>需求</h2>    <p>要实现音频的播放如下图:</p>    <p><img src="https://simg.open-open.com/show/b294915802369181d41ad0b58c1ae103.png"></p>    <h2>html</h2>    <p>html代码如下:</p>    <pre>  <code class="language-javascript"><audio src="" preload="metadata" controls />  </code></pre>    <p>本来我以为在css3这么强大的年代,自定义一个audio的皮肤应该是完全没问题的,后来的事实证明too young too simple。</p>    <p>看了下audio的shadow dom结构,然后试了试用css去自定义,于是发现两个问题:</p>    <p><img src="https://simg.open-open.com/show/0fbc25614ac6f992196b09ebd0ecda14.png"></p>    <ul>     <li>第一个为播放暂停按钮,就是一个标签没有状态,默认的css定义是为 -webkit-appearance: media-play-button; ,一个样式控制两种状态,没招。</li>     <li>第二个为中间的进度条,自身是个shadow dom,于是构成了两层shadow dom(audio本身是一层),这也没招。</li>    </ul>    <p>于是只好转向js来控制了,html修改如下:</p>    <pre>  <code class="language-javascript"><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>  </code></pre>    <h2>事件</h2>    <ul>     <li>audio的 loadedmetadata 事件,读取音频的总时长</li>     <li>audio的 timeupdate 事件,用于更新播放进度</li>     <li>audio的 canplaythrough 事件,当播放结束,判断是否可以重播</li>     <li>icon-play 的点击事件,暂停或播放</li>     <li>timeline 的点击事件,用于跳跃播放</li>    </ul>    <h2>react 组件</h2>    <p>目前采用的es5,audio地址通过props传入,判断播放还是暂停采用state切换,进度条更新用了reactDOM操作。</p>    <pre>  <code class="language-javascript">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      }  })  </code></pre>    <p> </p>    <p>来自: <a href="/misc/goto?guid=4959674625975538524" rel="nofollow">http://imweb.io/topic/5763849f551731da289cba21</a></p>    <p> </p>