在上一个环节《花潮经典版播放器开发环节之界面设计》中,我们完成了界面设计与实现,接下来要做的是播放器的功能实现机制,包括人机交互操作(比如点击按钮、进度条,拖曳进度条上的滑块)、音频播放与暂停所引发的联动动作(比如音频暂停与播放状态按钮的样式、进度条的着色和滑块的运行位置)等等,都需要逐一使用JS来一一实现。在具体操作之前,我们先得获取播放器、按钮、进度条、滑块等元素的操作入口标识,这些标识指向对应的HTML标签,需要和上一环节的HTML代码进行关联,并且,得额外在HTML代码中加入audio标签。以下是五个元素操作标识,最后一个指向 audio 标签:
const mplayer = document.querySelector('.mplayer');
const btnPlay = document.querySelector('.btnPlay');
const progress = document.querySelector('.progress');
const thumb = document.querySelector('.thumb');
const audio = document.querySelector('audio');
这样,我们要处理按钮的点击操作,直接使用 btnPlay 作为操作依据,像这样:
btnPlay.onclick = () => audio.paused ? audio.play() : audio.pause();
btnPlay 按钮单击事件(onclick)执行一个操作,判断音频标签 audio 是否处于暂停中(audio.paused ?),若是,则令其播放(audio.play()),反之(:),若它处在播放状态中,则令其暂停(audio.pause())。按钮的工作就这么简单,只管控音频标签的播放、暂停。
那按钮的样式谁来管?没错,按钮的样式,在音频播放的时候它应该变成一个暂停样式,用以指示或暗示点击它可令音乐暂停,音频暂停的时候它应该是一个播放形状,用以引导用户点击它来让音乐暂停。这个工作由一个函数把关,然后利用音频标签的播放事件(playing)、暂停事件(pause)运行这个函数,从而达到智能动态管理按钮状态的目的:
// 联动函数 mState
const mState = () => {
btnPlay.className = `btnPlay ${['pause', 'play'][+audio.paused]}`;
// 其它需要联动管控的代码
};
// 音频标签开始播放和暂停事件
audio.onplaying = audio.onpause = () => mState();
audio 标签的开始播放、暂停这两个事件在多种情形下必会触发其一:每次点击 btnPlay 按钮之时都会可能触发,页面刚打开时、播放结束时也可能会触发。因此,监听它们并以此运行 mState() 函数非常牢靠。mState 函数要做的工作之一是管理按钮形状,将 CSS 的两个选择器 .play {...} 和 .pause {...} 所描述的 CSS 剪裁变量 --clip 数据以 className 的赋值方式交给 btnPlay,这样按钮的形状就会自动改变。之前,btnPlay 标签代码中有两个class名称,className="btnPlay play",其中的 play 是临时绑定的,我们现在就是动态改变它是 play 还是 pause,所有用一个数组来包含它们俩,['pause', 'play'],这个数组的下标(即序号)由 audio 标签的 paused 暂停属性来决定,audio.paused 返回 true(真,即音乐暂停中的状态)或 false(假,即音乐暂停状态为假),true 和 false 变量前加 + 号可以令其变为 1(真)或 0(假),如此使用 +audio.paused 作为下标读取记录暂停和播放选择器的数组 ['pause', 'play'] 的至就能对应当前的状态、给出正确的形状描述。
按钮形状的事情解决了,下来时进度条的着色、滑块的运行问题,两者共享一个 CSS 变量 --prg,我们只需计算出音频当前的播放进度与音频总时长的关系就行,具体说即为,--prg 变量是用于驱动进度指示色、滑块运行相对与进度条长度的百分比,这个百分比其实对等于音乐的播放进度,即它等于音频当前播放时间和音频总时长的比例。代码放在 audio.ontimeupdate 事件中,因为该事件是音乐播放时的音频播放时间更新事件:
audio.ontimeupdate = () => {
// ... 这里是其它相关
// 下面是利用 audio 的当前播放时间 currentTime 和 音频总时长
// duration 二者的关系来计算进度百分比并赋值给 progress 标签
progress.style.setProperty('--prg', `${audio.currentTime / audio.duration * 100}%`);
};
本节的最后一个要实现的功能是数字时间信息问题。我们在 CSS 代码中设置了一个 mplayer 标签的伪元素,它的内容属性 content 是 attr() 函数,函数值为一个特定变量 data-time,用来接收 HTML 相应标签代码中同名属性 data-time 传递的数据,JS 可以动态变更 HTML 相应标签所需的数据,用 dataset.* 指代 data.*。当然,音频标签提供的播放时间、音频时长是浮点数形式的秒数,我们需要把它们换算成便于阅读的 mm:ss 格式,这需要一个换算函数来处理,然后也是在 audio 的 timupdate 监听事件中获取时间信息时调用该换算函数:
// 秒数换算为 mm:ss 格式
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
// 音频 timeupdate 监听事件
audio.ontimeupdate = () => {
// ... 这里是其它相关
progress.style.setProperty('--prg', `${audio.currentTime / audio.duration * 100}%`);
mplayer.dataset.time = `${formatTime(audio.currentTime)} ${formatTime(audio.duration)}`;
};
两个时间信息都由 formatTime() 函数进行格式化输出,彼此间相隔一个空格,这样他们就会在 mplayer 伪元素中两端对齐。
至此,播放器已经具备了一些初始功能,我们结合上一环节的代码把播放器至今为止的 CSS、HTML、JS 代码整理一下:
<style>
.mplayer { position: relative; width: 300px; height: fit-content; display: flex; flex-direction: column; align-items: center; gap: 10px; margin: auto; margin-top: 100px; }
.mplayer::before { position: absolute; content: attr(data-time); width: 100%; text-align-last: justify; pointer-events: none; }
.btnPlay { width: 20px; height: 20px; cursor: pointer; position: relative; }
.btnPlay::after { position: absolute; content: ''; width: 100%; height: 100%; background: red; clip-path: var(--clip); }
.progress { --prg: 0%; position: relative; width: 100%; height: 20px; display: grid; place-items: center start; background: linear-gradient(90deg, red var(--prg), gray var(--prg), gray 0) no-repeat center/100% 2px; padding: 0; margin: 0; }
.thumb { position: absolute; left: calc(var(--prg) - 10px); width: 20px; height: 20px; background: red; border: 8px solid green; border-radius: 50%; cursor: pointer; box-sizing: border-box; }
.play { --clip: polygon(10% 0,100% 50%,10% 100%); }
.pause { --clip: polygon(35% 0,15% 0,15% 100%, 35% 100%,35% 0,75% 0,75% 100%,55% 100%,55% 0); }
</style>
<div class="mplayer" data-time="00:00 00:00">
<div class="btnPlay play"></div>
<div class="progress">
<div class="thumb"></div>
</div>
</div>
<audio src="https://music.163.com/song/media/outer/url?id=30031117" autoplay loop></audio>
<script>
//获取需要操作的元素标识
const mplayer = document.querySelector('.mplayer');
const btnPlay = document.querySelector('.btnPlay');
const progress = document.querySelector('.progress');
const thumb = document.querySelector('.thumb');
const audio = document.querySelector('audio');
//时间格式化工具函数 :秒转分秒 mm:ss 格式
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
// 联动函数 mState :处理按钮形状
const mState = () => {
btnPlay.className = `btnPlay ${['pause', 'play'][+audio.paused]}`;
};
// 音频标签开始播放和暂停时执行联动函数
audio.onplaying = audio.onpause = () => mState();
// 音频时间更新事件 :驱动文本时间信息及进度条进度变更
audio.ontimeupdate = () => {
// 其它代码
mplayer.dataset.time = `${formatTime(audio.currentTime)} ${formatTime(audio.duration)}`;
progress.style.setProperty('--prg', `${audio.currentTime / audio.duration * 100}%`);
};
// 按钮单击 :播放、暂停状态切换
btnPlay.onclick = () => audio.paused ? audio.play() : audio.pause();
</script>
上面的代码可以 点击这里 运行,也可以将其复制到 简易编辑器 或将其存为本地 .html 文档进行修改。
下一环节:花潮经典版播放器开发环节之功能实现机制(二)