移動端使用原生audio標簽制作react 音頻組件

xiaoniao25 9年前發布 | 19K 次閱讀 React HTML 前端技術

需求

要實現音頻的播放如下圖:

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

 

 本文由用戶 xiaoniao25 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!