移動端使用原生audio標簽制作react 音頻組件
需求
要實現音頻的播放如下圖:

html
html代碼如下:
<audio src="" preload="metadata" controls />
本來我以為在css3這么強大的年代,自定義一個audio的皮膚應該是完全沒問題的,后來的事實證明too young too simple。
看了下audio的shadow dom結構,然后試了試用css去自定義,于是發現兩個問題:

- 第一個為播放暫停按鈕,就是一個標簽沒有狀態,默認的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
}
})
</code></pre>
來自: http://imweb.io/topic/5763849f551731da289cba21