移动端使用原生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>