• 前置
  • 对于 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 外面来的(不然不难看

  1. 筹备一个,最简略的带有 audio 的 html,并把安排好布局
  2. 要害 JavaScript 代码

    • 2.1 筹备参数
    • 2.2 播放 && 暂停 && 进度更新
    • 2.3 拖动进度条
  3. 配合 css 食用
  4. 筹备一个,最简略的带有 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