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之間關係