- 前置
- 对于 css 来设置 audio 款式
- 对于 JavaScript 来设置款式
- 对于 React 写一个自定义的 Audio 组件
- 总结
前置
- 大抵理解 audio 属性
- 懂一点点 CSS
- 懂一点点 JS 与 audio 事件
- 懂一点点 React
对于 css 来设置 audio 款式
援用 MDN 中的话 应用 CSS 设置款式
- 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,并把安排好布局
<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <title>Audio DIY</title> <link rel="stylesheet" type="text/css" href="./style.css" /> </head> <body> <audio id='audio' src='https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3'' preload='metadata' ></audio> <div class="audio-container"> <div class="audio-toggle" id="control"> 播放 </div> <div class="audio-progress-box" id='progress'> <span class="progressDot" id="control-dot"></span> <div class="audio-progress-bar" id="control-progress"></div> </div> <div class="audio-time" id="‘time’"> <span id='current'>00:00</span> / <span id='duration'>00:00</span> </div> </div> </body> <script src="./audio.js"></script></html>
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 组件
框架加持会让组件更加简略,chch
import 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<any> = props => { const { src, width = '80%', height = '30px' } = props; const audioRef = useRef<HTMLAudioElement>(null); const barBgRef = useRef<HTMLDivElement>(null); const barRef = useRef<HTMLDivElement>(null); const dotRef = useRef<HTMLSpanElement>(null); const uidRef = useRef<string>(uniqueId()); const [toggle, setToggle] = useState<boolean>(true); const [progress, setProgress] = useState<number>(0); const [duration, setDuration] = useState<string>('00 : 00'); const [currentTime, setCurrentTime] = useState<string>('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 ( <> <audio // @ts-ignore pid={uidRef.current} controls={false} src={src} preload='metadata' ref={audioRef}> 您的浏览器不反对 audio 标签 </audio> <div className='audio-container' style={{ width, height }}> <div className='audio-toggle' onClick={handlePaly}> {toggle && src ? '>' : '||'} </div> <div className='audio-progress-bar-bg' ref={barBgRef}> <span ref={dotRef} className='progressDot' style={{ left: `${progress - 2}%` }}></span> <div ref={barRef} className='audio-progress-bar' style={{ width: `${progress}%`, }}></div> </div> <div className='audio-time'> {currentTime}/{duration} </div> </div> </> );};export default Audio;
总结
PS:原生内有个性能是下载,这里并没有实现
对于 JS 代码局部,大部分参考至这里 H5 audio 音频标签自定义款式批改以及增加播放管制事件
需要起因,原生款式仿佛并不能满足产品,就会呈现须要 DIY 的状况,集体参照了很多网上相干的 Blog,如有谬误,敬请指教
感激浏览
参考文章
- H5 audio 音频标签自定义款式批改以及增加播放管制事件
- HTML audio根底API齐全使用指南
- MDN_audio