前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >移动端使用原生audio标签制作react 音频组件

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

作者头像
IMWeb前端团队
发布2019-12-04 14:56:37
3.7K0
发布2019-12-04 14:56:37
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队

本文作者:IMWeb 结一 原文出处:IMWeb社区 未经同意,禁止转载

需求

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

html

html代码如下:

代码语言:javascript
复制
<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修改如下:

代码语言: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>

事件

  • audio的loadedmetadata事件,读取音频的总时长
  • audio的timeupdate事件,用于更新播放进度
  • audio的canplaythrough事件,是否能够不停下来进行缓冲的情况下持续播放指定的音频/视频
  • icon-play的点击事件,暂停或播放
  • timeline的点击事件,用于跳跃播放

react 组件

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

代码语言: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
    }
})
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-06-17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需求
  • html
  • 事件
  • react 组件
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档