功能
用 navigator.mediaDevices.getUserMedia
取得使用者的鏡頭影像,并有拍照與濾鏡效果
播放
step 1 : 取得頁面 HTML 元素
const video = document.querySelector('.player');// 使用者鏡頭顯示的影像
const canvas = document.querySelector('.photo'); // 鏡頭影像渲染以及產生特效的區塊
const ctx = canvas.getContext('2d'); // canvas 的内容
const strip = document.querySelector('.strip'); // 拍照後顯示照片的區塊
const snap = document.querySelector('.snap'); // 拍照時產生的音效
step 2 : 獲取影像
MediaDevices: getUserMedia() method:在安全連線下使用(HTTPS、Localhost),並且瀏覽器會向使用者取得攝影鏡頭的權限
function getVideo() {
// navigator.mediaDevices.getUserMedia 拿到使用者的鏡頭影像,並回傳 Promise
navigator.mediaDevices.getUserMedia({
// 要影像,不要聲音
video: true,
audio: false
})
// 將回傳的 MediaStream 帶入 video 的src 中
.then(localMediaStream => {
/* console.log(localMediaStream); */
video.srcObject = localMediaStream
// 使用 play() 方法拿到連續的影格
video.play();
})
.catch(err => {
console.error(`OH NO~: `, err);
})
}
getVideo()
step 3 : 將取得的影像資料畫在 canvas 元素中
MDN-CanvasRenderingContext2D.drawImage()
function paintToCanavas() {
// 設置 canvas 區塊的寬高為 video 的寬高
const width = video.videoWidth;
const height = video.videoHeight;
canvas.width = width;
canvas.height = height;
// 用setInterval來持續取得目前的影像資訊
return setInterval(() => {
// 將畫面擷取:drawImage(img, x, y, width, heigth)
ctx.drawImage(video, 0, 0, width, height);
// 每幀數 60 FPS,每個影格約為 16 毫秒(1 / 60 )
}, 16)
}
step 4 : 拍照功能
HTMLCanvasElement: toDataURL() method
insertBefore()
function takePhoto() {
// 將拍照音效 snap 跳到第 0 秒
snap.currentTime = 0;
// 播放音效
snap.play();
// toDataURL() 可以把 canvas 轉為 base64 編碼的圖檔
const data = canvas.toDataURL('image/jpeg');
// 變數 link 建立 a 元素
const link = document.createElement('a');
// link 的位置設爲 base64 的圖檔 URL
link.href = data;
// 設置 link 具有 download 屬性,點擊後即可下載檔案,檔案名稱 handsomeMan
link.setAttribute('download', 'handsomeMan');
// 將圖片顯示在 link 中
link.innerHTML = `<img src="${data}" alt="photo" />`;
// 在 strip 元素内放入 link(在第一筆的位置)
strip.insertBefore(link, strip.firstChild);
}
step 5 : 紅色濾鏡功能
CanvasRenderingContext2D: getImageData() method
CanvasRenderingContext2D: putImageData() method
function paintToCanavas() {
// ...
return setInterval(() => {
ctx.drawImage(video, 0, 0, width, height);
// 拿到 canvans 中所有像素的 r,g,b,alpha資訊
let pixels = ctx.getImageData(0, 0, width, height);
// 帶入紅色濾鏡效果
pixels = redEffect(pixels);
// 色相分離
pixels = rgbSplit(pixels);
ctx.globalAlpha = 0.8;
// 綠屏效果,需搭配 slide bar
pixels = greenScreen(pixels);
// 將特效處理後 data 再丟回繪製成圖像:putImageData(img, x, y)
ctx.putImageData(pixels, 0, 0);
}, 16)
}
getVideo();
// 監聽 canplay 事件,鏡頭準備使用時觸發,執行 paintToCanvas function
video.addEventListener('canplay', paintToCanvas);
// 紅色濾鏡效果
function redEffect(pixels) {
// for 迴圈獲得所有像素資料,
// i +=4 是由於每個像素有四筆資料(r,g,b,alpha)
for (let i = 0; i < pixels.data.length; i += 4) {
pixels.data[i + 0] = pixels.data[i + 0] + 100; // red: 增强
pixels.data[i + 1] = pixels.data[i + 1] - 50; // green:減少
pixels.data[i + 2] = pixels.data[i + 2] * 0.5; // blue:減少
}
return pixels;
}
// rgb 色相分離
function rgbSplit(pixels) {
for(let i = 0; i < pixels.data.length; i+=4) {
pixels.data[i - 150] = pixels.data[i + 0]; // RED
pixels.data[i + 500] = pixels.data[i + 1]; // GREEN
pixels.data[i - 550] = pixels.data[i + 2]; // Blue
}
return pixels;
}
// 綠屏效果:搭配 html 中的 range bar 調整
function greenScreen(pixels) {
const levels = {};
document.querySelectorAll('.rgb input').forEach((input) => {
levels[input.name] = input.value;
});
for (i = 0; i < pixels.data.length; i = i + 4) {
red = pixels.data[i + 0];
green = pixels.data[i + 1];
blue = pixels.data[i + 2];
alpha = pixels.data[i + 3];
if (red >= levels.rmin
&& green >= levels.gmin
&& blue >= levels.bmin) {
// 、將 alpha 值設爲 0
pixels.data[i + 3] = 0;
}
}
return pixels;
}
參考資料:
- MediaDevices: getUserMedia() method@MDN
- Unreal Webcam Fun with getUserMedia() and HTML5 Canvas - #JavaScript30 19/30 @Wes Bos
- [Alex 宅幹嘛 ] 👨💻 深入淺出 Javascript30 快速導覽 | Day 19:Webcam Fun @Alex 宅幹嘛
- 19 - Webcam Fun @guahsu
- 19 Webcam Fun 中文指南@soyaine
- WebCam Fun@dustin
- JS30-Day19-Webcam Fun@王郁翔
- JS30 自學筆記 Day19_Unreal Webcam Fun@jen147774ny