前置

关于 css 来设置 audio 样式

关于 JavaScript 来设置样式

关于 React 写一个自定义的 Audio 组件

总结

前置大致了解 audio 属性

懂一点点 CSS

懂一点点 JS 与 audio 事件

懂一点点 React

关于 css 来设置 audio 样式audio 元素没有自带的固有视觉样式,除非如果声明了 controls 属性,则会显示浏览器的默认控件。

默认控件的 display 的默认值为 inline。将该值设为 block 通常会对定位和布局有好处,除非你想将控件放在文本块或类似元素中。

你可以使用作用于整个控件的属性来为其设置样式。例如可用 border、border-radius、padding, margin 等等。但你不能设置音频播放器中的单个组件(如改变按钮大小、改变图标或字体等)。控件在不同的浏览器中也有所不同。

如果在跨浏览器中得到一致的外观和体验,你需要创建自定义控件;自定义控件可以根据你的需求任意设置样式,还可以使用 JavaScript 和 HTMLMediaElement API 来设置更多功能。

视频播放器样式基础 提供了一些有用的样式技术,这篇文章围绕 video 而写,但大部分都可以用于 audio。

总上所述,关键就是在于 css 可操作性相对少,且会产生关于兼容性的问题

关于 JavaScript 来设置 audio 样式

说是 JavaScript ,依然也需要配合到 css 里面来的(不然不好看准备一个,最简单的带有 audio 的 html,并把布置好布局

关键 JavaScript 代码2.1 准备参数

2.2 播放 && 暂停 && 进度更新

2.3 拖动进度条

配合 css 食用

准备一个,最简单的带有 audio 的 html,并把布置好布局

Audio DIY

id='audio'

src='https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3''

preload='metadata' >

播放

00:00 / 00:00

2.1 准备参数const Audio = document.querySelector('#audio');

const contorl = document.querySelector('#control');

const contorlDot = document.querySelector('#control-dot');

const contorlProgress = document.querySelector('#control-progress');

const contorlProgressBox = document.querySelector('#progress');

const current = document.querySelector('#current');

const duration = document.querySelector('#duration');

// 工具函数

// 时分秒转换

function transTime(value) {

var time = '';

var h = parseInt(`${value / 3600}`);

value %= 3600;

var m = parseInt(`${value / 60}`);

var s = parseInt(`${value % 60}`);

if (h > 0) {

time = formatTime(h + ':' + m + ':' + s);

} else {

time = formatTime(m + ':' + s);

}

return time;

}

// 补零

function formatTime(value) {

var time = '';

var s = value.split(':');

var i = 0;

for (; i < s.length - 1; i++) {

time += s[i].length === 1 ? '0' + s[i] : s[i];

time += ':';

}

time += s[i].length === 1 ? '0' + s[i] : s[i];

return time;

}

2.2 播放 && 暂停 && 进度更新contorl.addEventListener('click', e => {

if (e.target.innerText === '播放') {

e.target.innerText = '暂停';

Audio.play();

} else {

e.target.innerText = '播放';

Audio.pause();

}

});

Audio.addEventListener('loadedmetadata', e => {

duration.innerText = transTime(e.target.duration);

});

Audio.addEventListener('timeupdate', e => {

let value = e.target.currentTime / Audio.duration;

current.innerText = transTime(e.target.currentTime);

contorlProgress.style.width = `${value * 100}%`;

contorlDot.style.left = `${value * 100}%`;

});

Audio.addEventListener('ended', e => {

contorlProgress.style.width = '0%';

contorlDot.style.left = '0%';

contorl.innerText = '播放';

});

2.3 拖动进度条// 鼠标按下

contorlDot.addEventListener('mousedown', down, false);

contorlDot.addEventListener('touchstart', down, false);

// 开始拖动

document.addEventListener('mousemove', move, false);

document.addEventListener('touchmove', move, false);

// 拖动结束

document.addEventListener('mouseup', end, false);

document.addEventListener('touchend', end, false);

const position = {

oriOffestLeft : 0, // 移动开始时进度条的点距离进度条的偏移值

oriX : 0, // 移动开始时的x坐标

maxLeft : 0, // 向左最大可拖动距离

maxRight : 0, // 向右最大可拖动距离

};

let flag = false; // 标记是否拖动开始

const down = event => {

if (!Audio.pause || Audio.currentTime !== 0) {

flag = true;

position.oriOffestLeft = contorlDot.offsetLeft;

position.oriX = event.touches ?

event.touches[0].clientX :

event.clientX;

// 要同时适配mousedown和touchstart事件

position.maxLeft = position.oriOffestLeft;

// 向左最大可拖动距离

position.maxRight = contorlProgressBox.offsetWidth -

position.oriOffestLeft; // 向右边最大可拖动距离

if (event && event.preventDefault) event.preventDefault();

else event.returnValue = false;

if (event && event.stopPropagation) event.stopPropagation();

else window.event.cancelBubble = true;

}

};

const move = event => {

if (flag) {

let clientX = event.touches ? event.touches[0].clientX : event.clientX;

let length = clientX - position.oriX;

if (length > position.maxRight) {

length = position.maxRight;

} else if (length < -position.maxLeft) {

length = -position.maxLeft;

}

let pgsWidth = parseFloat(

window.getComputedStyle(contorlProgressBox, null).width.replace('px'),

);

let rate = (position.oriOffestLeft + length) / pgsWidth;

Audio.currentTime = Audio.duration * rate;

}

};

const end = () => {

flag = false;

};

关于 React 写一个自定义的 Audio 组件

框架加持会让组件更加简单,这里加入了 TSimport React, {

useRef,

useLayoutEffect,

useState,

useEffect,

MouseEvent,

} from 'react';

import './Audio.css';

function transTime(value: number) {

var time = '';

var h = parseInt(`${value / 3600}`);

value %= 3600;

var m = parseInt(`${value / 60}`);

var s = parseInt(`${value % 60}`);

if (h > 0) {

time = formatTime(h + ':' + m + ':' + s);

} else {

time = formatTime(m + ':' + s);

}

return time;

}

function formatTime(value: string) {

var time = '';

var s = value.split(':');

var i = 0;

for (; i < s.length - 1; i++) {

time += s[i].length === 1 ? '0' + s[i] : s[i];

time += ':';

}

time += s[i].length === 1 ? '0' + s[i] : s[i];

return time;

}

export const Audio: React.FC = props => {

const { src, width = '80%', height = '30px' } = props;

const audioRef = useRef(null);

const barBgRef = useRef(null);

const barRef = useRef(null);

const dotRef = useRef(null);

const uidRef = useRef(uniqueId());

const [toggle, setToggle] = useState(true);

const [progress, setProgress] = useState(0);

const [duration, setDuration] = useState('00 : 00');

const [currentTime, setCurrentTime] = useState('00:00');

useLayoutEffect(() => {

if (audioRef.current && src) {

audioRef.current.addEventListener('play', (e: Event) => {

const pid = (e.target as HTMLAudioElement).getAttribute('pid');

const audios = document.querySelectorAll('audio');

console.log('pid', pid);

audios.forEach((element, index) => {

if (element.getAttribute('pid') === pid) return;

element.pause();

});

});

audioRef.current.addEventListener('loadedmetadata', e => {

const duration = transTime(

(e.target as HTMLAudioElement).duration as number,

);

setDuration(duration);

});

audioRef.current.addEventListener('play', _res => {

setToggle(false);

});

audioRef.current.addEventListener('pause', () => {

setToggle(true);

});

audioRef.current.addEventListener('timeupdate', e => {

let value =

(e.target as HTMLAudioElement).currentTime /

(audioRef.current as HTMLAudioElement).duration;

setProgress(value * 100);

setCurrentTime(transTime((e.target as HTMLAudioElement).currentTime));

// console.log('timeupdate res', res.target.currentTime);

});

}

return () => {};

}, [src]);

useEffect(() => {

if (dotRef.current && src) {

const position = {

oriOffestLeft : 0, // 移动开始时进度条的点距离进度条的偏移值

oriX : 0, // 移动开始时的x坐标

maxLeft : 0, // 向左最大可拖动距离

maxRight : 0, // 向右最大可拖动距离

};

let flag = false; // 标记是否拖动开始

// 按下

const down = (event: TouchEvent | MouseEvent) => {

if (!audioRef.current?.paused || audioRef.current.currentTime !== 0) {

flag = true;

position.oriOffestLeft = dotRef.current?.offsetLeft ?? 0; // 初始位置

position.oriX = position.oriX =

event instanceof TouchEvent

? event.touches[0].clientX

: event.clientX; // 要同时适配mousedown和touchstart事件

position.maxLeft = position.oriOffestLeft; // 向左最大可拖动距离

position.maxRight =

barBgRef.current?.offsetWidth ?? 0 - position.oriOffestLeft; // 向右边最大可拖动距离

if (event && event.preventDefault) {

event.preventDefault();

} else {

(event as TouchEvent).returnValue = false;

}

// 禁止事件冒泡

if (event && event.stopPropagation) {

event.stopPropagation();

}

}

};

// 移动

const move = (event: TouchEvent | MouseEvent) => {

if (flag && barBgRef.current) {

let clientX =

event instanceof TouchEvent

? event.touches[0].clientX

: event.clientX; // 要同时适配mousemove和touchmove事件

let length = clientX - position.oriX;

if (length > position.maxRight) {

length = position.maxRight;

} else if (length < -position.maxLeft) {

length = -position.maxLeft;

}

// let pgsWidth = barBgRef.current?.offsetWidth;

let pgsWidth = parseFloat(

window.getComputedStyle(barBgRef.current).width.replace('px', ''),

);

let rate = (position.oriOffestLeft + length) / pgsWidth;

console.log('===', position.oriOffestLeft, length);

console.log(

'偏移总长比例',

(audioRef.current as HTMLAudioElement).duration * rate,

rate,

);

(audioRef.current as HTMLAudioElement).currentTime =

(audioRef.current as HTMLAudioElement).duration * rate;

}

};

// 结束

const end = () => {

flag = false;

};

// 鼠标按下时

dotRef.current.addEventListener('mousedown', down as any, false);

dotRef.current.addEventListener('touchstart', down, false);

// 开始拖动

document.addEventListener('mousemove', move as any, false);

document.addEventListener('touchmove', move, false);

// 拖动结束

document.addEventListener('mouseup', end, false);

barBgRef.current?.addEventListener('touchend', end, false);

}

}, [src]);

const handlePaly = () => {

if (toggle && src) {

audioRef.current?.play();

return;

}

audioRef.current?.pause();

return;

};

return (

<>

// @ts-ignore

pid={uidRef.current}

controls={false}

src={src}

preload='metadata'

ref={audioRef}>

您的浏览器不支持 audio 标签

{toggle && src ? '>' : '||'}

ref={dotRef}

className='progressDot'

style={{ left: `${progress - 2}%` }}>

ref={barRef}

className='audio-progress-bar'

style={{

width: `${progress}%`,

}}>

{currentTime}/{duration}

>

);

};

export default Audio;

总结

PS:原生内有个功能是下载,这里并没有实现

需求原因,原生样式似乎并不能满足产品,就会出现需要 DIY 的情况,个人参照了很多网上相关的 Blog,如有错误,敬请指教

感谢阅读

参考文章

css audio 碟片样式,关于 Audio 自定义样式相关推荐

  1. html自定义序号样式,word设置自定义样式

    如何使用Word自定义样式生成目录 步骤/方法 1 首先搞清楚论文格式要CSS布局HTML小编今天和大家分享,比如格式要CSS布局HTML小编今天和大家分享如下: 论文统一用微软Word软件排版,16 ...

  2. android自定义通知栏样式,Android 通知栏自定义样式

    8.0系统的通知栏适配 在8.0之前我们通知栏的使用:notification = new NotificationCompat.Builder(mContext) .setWhen(System.c ...

  3. html5 input file样式修改,css为input type=file设置自定义样式

    input file上传按钮的美化思路是,先把之前的按钮透明度opacity设置为0,然后,外层用div包裹,就实现了美化功能. 主要有两点需要做: 将input type=file控件透明 将inp ...

  4. 普通html和vue单选框的样式,vue2实现自定义样式radio单选框

    先上效果 主编已回复: {{item.label}} js: data() { return { radio: '1', radios:[ { label: '是', value:'1', isChe ...

  5. Antd组件中单选框、复选框自定义样式的优雅实现

    表单组件的样式控制算是antd组件使用的一大坑了. .以antd-mobile为例,checkbox选中与非选中状态是由Input包裹的父元素的'.am-checkbox-checked'来控制的 未 ...

  6. 滚动条css圆角兼容ie,ie浏览器滚动条样式修改

    用css怎样定义IE滚动条的样式 .testDiv{ border-style:solid; border-width:50px; border-color:pink; position:absolu ...

  7. 微信小程序实现多选框+自定义样式(checkbox)

    1.实现效果 2.实现原理 小程序多选框 微信小程序为我们提供了checkbox-group,多项选择器,内部由多个checkbox组成. checkbox: 3.实现代码 <!--pages/ ...

  8. 如何一键制作世界顶级音质的DVD Audio碟片?

    目前,DVD Audio.SACD是世界上音质最好.应用最广的两种音乐碟片,但二者在激励的市场竞争中,最后DVD Audio完全占据了车载音响市场.实际上,很多中高端车都是支持DVD Audio碟片的 ...

  9. html控制多个音频audio css,vue中audio自定义样式(页面中包含多个audio)

    前言 一开始看到UI设计稿,我内心是十分抗拒的.觉得用原生audio的样式就可以了,也不是特别丑,毕竟时间给的不多,自定义样式还要改逻辑啥的.在网上搜索了一番有没有合适的插件,没有看到心动的.最后还是 ...

  10. H5 audio 音频标签自定义样式修改以及添加播放控制事件

    20181023 更新 原来的代码拖动进度点只写了mouse事件,手机端的话应该是touch事件.所以出现了有朋友说的移动端无法拖动进度条的问题.现在更新的代码已经加上touch事件,即无论是手机模式 ...

最新文章

  1. Linux下getopt_long函数的使用
  2. 配置高可用的Hadoop平台
  3. JavaScript语言基础6
  4. mysql设计经纬度表_MySQL经纬度表设置
  5. boost::make_iterator_range用法的测试程序
  6. 关于 Laravel Redis 多个进程同时取队列问题详解
  7. 微型计算机硬盘为什么要分区,为什么懂电脑的人,都说硬盘不需要分区?看完你就知道了...
  8. laravel-admin grid中使用switch操作一对一关联属性(源码探究到功能实现)
  9. svn回退后如何再还原_设计师如何管理自己的文档
  10. could not open extension control file “/usr/share/postgresql/9.1/extension/dblink.control“
  11. UDID被禁用后的集中替代品
  12. java JVM剖析
  13. MongoDB的日志系统
  14. 小程序 ---- (引入iconfont,自定义组件内不显示iconfont)
  15. 怎样在 Ubuntu Unity Dash 添加关机、重启选项
  16. 【BUAA_CO_LAB】p5p6碎碎念
  17. 友盟分享Title设置
  18. 【TensorFlow基础】加载和预处理数据
  19. ANSI字符集和Unicode字符集
  20. 计算机固态硬盘与机械硬盘的区别是什么,电脑固态硬盘与机械硬盘的区别是什么,应该如何选择...

热门文章

  1. Vue实现媒体文件下载
  2. 跟着团子学SAP PS:BOM和项目结构的联动-BOM Transfer CN33
  3. 谢希仁计算机网络第7版考研真题章节题库下载
  4. 如何用word制作英语答题卡_考研英语答题卡模板(word打印版).doc
  5. java的强项_JAVA的三个开发方向
  6. Linux内核进程,线程,进程组,会话组织模型以及进程管理
  7. 进制转换器java程序_java编写简易的进制转换器
  8. 高等数学 画图软件Mathematica
  9. 动画程序时长缩放是什么意思_Pr预设:2000种文字标题排版指示线图形动画无缝转场剪辑工具包...
  10. erp服务器安装虚拟打印机,远程打印软件