JS 30 是由加拿大的全端工程师 Wes Bos 免费提供的 JavaScript 简单应用课程,课程主打 No Frameworks
、No Compilers
、No Libraries
、No Boilerplate
在30天的30部教学影片里,建立30个JavaScript的有趣小东西。
另外,Wes Bos 也很无私地在 Github 上公开了所有 JS 30 课程的程序码,有兴趣的话可以去 fork 或下载。
灵活运用 video
元素的相关属性、方法,实作出一个拥有快进快退
、播放速度倍率
、控制音量大小
、拖拉时间轴
功能的简易影片播放器。
.player
代表整个影片播放器,包含影片(.player__video)
、播放控制列(.player__controls)
两部分。
播放控制列
内部又可细分为四个部分:
.progress
、.progress__filled
.toggle
.player__slider
.player__button
<div class="player">
<video class="player__video viewer" src="652333414.mp4"></video>
<div class="player__controls">
<div class="progress">
<div class="progress__filled"></div>
</div>
<button class="player__button toggle" title="Toggle Play">►</button>
<input type="range" name="volume" class="player__slider" min="0" max="1" step="0.05" value="1">
<input type="range" name="playbackRate" class="player__slider" min="0.5" max="2" step="0.1" value="1">
<button data-skip="-10" class="player__button">« 10s</button>
<button data-skip="25" class="player__button">25s »</button>
</div>
</div>
首先,取得所有要用到 HTML 标签并放到对应宣告的常数中。
/*get element we need*/
const player = document.querySelector('.player');
const video = player.querySelector('.viewer');
const progress = player.querySelector('.progress');
const progressBar = player.querySelector('.progress__filled');
const toggle = player.querySelector('.toggle');
const skipButtons = player.querySelectorAll('[data-skip]');
const ranges = player.querySelectorAll('.player__slider');
下面我们将各个播放器的功能一个个拆出来做 :
功能目的 : 我们希望在点击影片或播放/暂停钮时,播放或暂停影片。
在video
(影片)、toggle
(播放/暂停钮)上都注册click 事件
监听器,触发事件後用togglePlay()
进行处理。
在togglePlay()
里,我们宣告一个常数method
,经过条件判断,当video.paused
回传 true 则 method = play
;当video.paused
回传 false 则method = pause
。
这边有一个特殊的写法video[method]();
。举例来说,当method = play
则实际效果相当於video.play();
。
function togglePlay(){
const method = video.paused ? 'play' : 'pause';
video[method]();
}
/*控制影片的播放*/
video.addEventListener('click',togglePlay);
toggle.addEventListener('click',togglePlay);
功能目的 : 我们希望在影片播放/暂停的状态下,同步更新图示。
在video
(影片)上注册play 事件
和pause 事件
两个监听器并都以updateButton()
进行事件处理。
在updateButton()
里,我们宣告一个常数icon
指定当影片处於暂停状态则icon = '►'
,接着利用toggle.textContent = icon
修改按钮的图示,当影片处於播放状态的处理也是用一样的方式。
function updateButton(){
const icon = this.paused ? '►' : '❚ ❚';
toggle.textContent = icon;
}
/*让播放键的图示改变*/
video.addEventListener('play',updateButton);
video.addEventListener('pause',updateButton);
功能目的 : 我们希望在点击快进/快退按钮时,同步调整影片的时间轴。
在skipButtons
(快进、快退按钮)里的所有button
都注册click 事件
并以skip()
进行事件处理。
在skip()
里,我们将video.currentTime
(影片现在播放的时间点)加上我们要快进或快退的秒数。
由於video.currentTime
本身是 float
型别,因此需要将this.dataset.skip
(快进/快退的秒数)用parseFloat()
转换成float
型别之後再进行运算。
function skip(){
video.currentTime += parseFloat(this.dataset.skip);
}
/*调整影片的快进和倒退*/
skipButtons.forEach(button => button.addEventListener('click',skip));
功能目的 : 我们希望在滑鼠在倍率或音量条上移动改变数值时,同步反映到video
(影片)上。
在速度倍率和音量条上都注册change 事件
和mousemove 事件
,分别在数值改变和滑鼠拖曳时触发事件,之後用handleRangeUpdate()
进行事件处理。
在handleRangeUpdate()
里,我们使用和之前一样的特殊语法video[this.name] = this.value;
对video
的属性值进行调整。举例来说,如果this.name = volume
、this.value = 0
,则video[this.name] = this.value;
的效果和video.volume = 0;
一样。
function handleRangeUpdate(){
video[this.name] = this.value;
}
/*调整影片的播放速度、音量*/
ranges.forEach(range => range.addEventListener('change',handleRangeUpdate));
ranges.forEach(range => range.addEventListener('mousemove',handleRangeUpdate));
功能目的 : 我们希望在影片播放的过程中,不断地更新时间轴。
在video
(影片)上注册timeupdate 事件
的监听器,当影片的播放时间(currentTime
)有变动就触发事件,之後用handleProgress()
进行事件处理。
在handleProgress()
里,我们宣告常数percent
并放入video.currentTime
(影片现在时间)除以video.duration
(影片的总长度)再乘以100得到的比例值。
接着用progressBar.style.flexBasis = `${percent}%`;
,用percent
指定时间轴的长度占比。
function handleProgress(){
const percent = (video.currentTime / video.duration) * 100
progressBar.style.flexBasis = `${percent}%`;
}
/*持续更新时间轴*/
video.addEventListener('timeupdate',handleProgress);
功能目的 : 我们希望按住滑鼠拖或点击时间轴的同时,更新video
(影片)现在播放的时间。
宣告mousedown
作为 flag 判断现在是否有按住滑鼠。
我们在progress
注册click 事件
、mousemove 事件
、mousedown 事件
、mouseup 事件
监听器。
触发click 事件
时,我们可以直接就以scrub(e)
进行事件的处理。
但在触发mousemove 事件
时,我们需要先判断是否有按住滑鼠,所以要借助mousedown 事件
和mouseup 事件
的帮忙,在mousedown 事件
触发地当下将 flag(mousedown
) 设为 true,反之触发mouseup 事件
则将 flag(mousedown
) 设为 false。最後用mousedown && scrub(e)
判断是否执行scrub(e)
,只有当flag(mousedown
) 是 true 的时候,才接着执行scrub(e)
完成事件处理。
在scrub(e)
里,我们宣告常数scrubTime
放入将滑鼠在元素内部的X座标(e.offsetX
)除以时间轴的长度(progress.offsetWidth
)再乘以影片长度(video.duration
)所得到要前往的时间点。最後将影片现在的时间(video.currentTime
)指定为要前往的时间点(scrubTime
)。
function scrub(e){
const scrubTime = (e.offsetX / progress.offsetWidth) * video.duration;
video.currentTime = scrubTime;
}
/*拖拉时间轴*/
let mousedown = false;
progress.addEventListener('click',scrub);
progress.addEventListener('mousemove',(e)=> mousedown && scrub(e));
progress.addEventListener('mousedown',() => mousedown = true);
progress.addEventListener('mouseup',() => mousedown = false);
HTMLVideoElement
继承自HTMLMediaElement
所以一些video
元素的属性都可以到HTMLMediaElement
查询。
使用HTMLElement.offsetWidth
所取得的元素(element
)宽度包括透过 CSS 设定的width、border、padding
等等...。
HTMLMediaElement
HTMLElement.dataset
HTMLElement.offsetWidth
JS一秒区分clientX,offsetX,screenX,pageX之间关系
<<: Laravel Queue Job:深入理解 timeout 的运作
资料型别:字串 JavaScript的字串(string)以单引号(‘ ’)或双引号(“ ”)包住,...
今天大概会聊到的范围 Testing Compose 的 Test 属於 UI Test ,在执行...
T0888 Remote System Information Discovery 攻击者透过後门收...
本系列文章范例专案 本系列文章中所有的设定与范例程序码都可以在 Github 上的范例专案 zkqu...
在一般专案里,一定会有很多个 .java 跟 .class 档案,那我们要怎麽去管理这两种档案呢? ...