使用canvas實(shí)現(xiàn)魔法攝像頭的示例代碼
背景
我們用手機(jī)的攝像頭自拍,很容易實(shí)現(xiàn)簡(jiǎn)單的自拍效果,如復(fù)古、黑白等等。其實(shí)我們使用web端的JavaScript也是可以實(shí)現(xiàn)的。接下來(lái)就帶領(lǐng)小伙伴實(shí)現(xiàn)一個(gè)魔法攝像頭。并且提供了截圖下載功能。
魔鬼風(fēng)格
復(fù)古風(fēng)格
關(guān)鍵技術(shù)
- canvas 它可以用于動(dòng)畫、游戲畫面、數(shù)據(jù)可視化、圖片編輯以及實(shí)時(shí)視頻處理等方面。
- video 用于在 HTML 或者 XHTML 文檔中嵌入媒體播放器
- navigator.mediaDevices.getUserMedia 用來(lái)將攝像頭視頻轉(zhuǎn)成文件流
- requestAnimationFrame 你希望執(zhí)行一個(gè)動(dòng)畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動(dòng)畫
主要業(yè)務(wù)流程
- 調(diào)用攝像頭加載畫面到video上
- 使用canvas將video視頻逐幀畫到canvas上
- 實(shí)現(xiàn)canvas濾鏡效果
- 點(diǎn)擊截圖
調(diào)用攝像頭加載畫面到video上
<!DOCTYPE html> <html> <head> <title>Canvas Demo</title> </head> <body> <video id="videoElement" autoplay></video> <canvas id="canvasElement"></canvas> <script> // 獲取視頻元素和畫布元素 const video = document.getElementById('videoElement'); // 檢查瀏覽器是否支持 getUserMedia API if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // 請(qǐng)求訪問(wèn)攝像頭 navigator.mediaDevices.getUserMedia({ video: true }) .then(function (stream) { // 將視頻流綁定到視頻元素上 video.srcObject = stream; // 開始繪制視頻畫面到畫布上 requestAnimationFrame(drawFrame); }) .catch(function (error) { console.error('無(wú)法訪問(wèn)攝像頭:', error); }); } else { console.error('瀏覽器不支持 getUserMedia API'); } </script> </body> </html>
將video視頻逐幀畫到canvas上
<!DOCTYPE html> <html> <head> <title>Canvas Demo</title> </head> <body> <video id="videoElement" autoplay></video> <canvas id="canvasElement"></canvas> <script> // 獲取視頻元素和畫布元素 const video = document.getElementById('videoElement'); const canvas = document.getElementById('canvasElement'); const ctx = canvas.getContext('2d'); // 當(dāng)視頻元素加載完成后執(zhí)行 video.addEventListener('loadedmetadata', function () { // 設(shè)置畫布大小與視頻尺寸相同 canvas.width = video.videoWidth; canvas.height = video.videoHeight; }); // 在每一幀繪制視頻畫面到畫布上 一秒描繪60次 function drawFrame() { ctx.drawImage(video, 0, 0, canvas.width, canvas.height);// 將視頻畫在畫布上 requestAnimationFrame(drawFrame); } // 檢查瀏覽器是否支持 getUserMedia API if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // 請(qǐng)求訪問(wèn)攝像頭 navigator.mediaDevices.getUserMedia({ video: true }) .then(function (stream) { // 將視頻流綁定到視頻元素上 video.srcObject = stream; // 開始繪制視頻畫面到畫布上 requestAnimationFrame(drawFrame); }) .catch(function (error) { console.error('無(wú)法訪問(wèn)攝像頭:', error); }); } else { console.error('瀏覽器不支持 getUserMedia API'); } </script> </body> </html>
實(shí)現(xiàn)canvas濾鏡效果
以下代碼沒(méi)有進(jìn)行過(guò)多封裝,后續(xù)會(huì)出一篇使用面向?qū)ο蠛驮O(shè)計(jì)模式的續(xù)集來(lái)優(yōu)化代碼
本次案例實(shí)現(xiàn)的濾鏡效果主要有 反轉(zhuǎn) 黑白 亮度 復(fù)古 紅色 綠色 藍(lán)色 透明 馬賽克 漸變
在canvas中,可以通過(guò) getImageData 獲取到當(dāng)前畫布上所有的像素點(diǎn),它以4個(gè)點(diǎn)為一組,表示畫布上當(dāng)前坐標(biāo)點(diǎn)的 R G B A (紅、綠、藍(lán)、透明度)。我們要實(shí)現(xiàn)的濾鏡效果,幾乎都是直接對(duì)該像素點(diǎn)進(jìn)行操作。如 黑白效果 將每個(gè)像素的RGB值轉(zhuǎn)換為灰度值(R、G、B三個(gè)分量取平均值)
<!DOCTYPE html> <html> <head> <title>Canvas Demo</title> </head> <body> <video id="videoElement" autoplay></video> <canvas id="canvasElement"></canvas> <script> // 獲取視頻元素和畫布元素 const video = document.getElementById('videoElement'); const canvas = document.getElementById('canvasElement'); const ctx = canvas.getContext('2d'); const buttons = document.querySelectorAll("button[data-type]"); const takePhoto = document.querySelector("#takePhoto") let drawType = "" // 當(dāng)視頻元素加載完成后執(zhí)行 video.addEventListener('loadedmetadata', function () { // 設(shè)置畫布大小與視頻尺寸相同 canvas.width = video.videoWidth; canvas.height = video.videoHeight; }); // 在每一幀繪制視頻畫面到畫布上 function drawFrame() { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); let imageObj = ctx.getImageData(0, 0, canvas.width, canvas.height); // 黑白效果 for (let i = 0; i < imageObj.data.length; i += 4) { const average = (imageObj.data[i + 0] + imageObj.data[i + 1] + imageObj.data[i + 2] + imageObj.data[i + 3]) / 3; imageObj.data[i + 0] = average;//紅 imageObj.data[i + 1] = average; //綠 imageObj.data[i + 2] = average; //藍(lán) } ctx.putImageData(imageObj, 0, 0) requestAnimationFrame(drawFrame); } // 檢查瀏覽器是否支持 getUserMedia API if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // 請(qǐng)求訪問(wèn)攝像頭 navigator.mediaDevices.getUserMedia({ video: true }) .then(function (stream) { // 將視頻流綁定到視頻元素上 video.srcObject = stream; // 開始繪制視頻畫面到畫布上 requestAnimationFrame(drawFrame); }) .catch(function (error) { console.error('無(wú)法訪問(wèn)攝像頭:', error); }); } else { console.error('瀏覽器不支持 getUserMedia API'); } </script> </body> </html>
所有濾鏡效果總結(jié)如下
- 反轉(zhuǎn)效果:
- 原理:通過(guò)將每個(gè)像素的RGB值取反來(lái)實(shí)現(xiàn)反轉(zhuǎn)效果。
- 實(shí)現(xiàn)方式:使用
getImageData
獲取圖像數(shù)據(jù),然后遍歷每個(gè)像素,將每個(gè)像素的RGB值取反,再使用putImageData
將修改后的數(shù)據(jù)繪制回Canvas。
- 黑白效果:
- 原理:將每個(gè)像素的RGB值轉(zhuǎn)換為灰度值,使圖像變?yōu)楹诎住?/li>
- 實(shí)現(xiàn)方式:使用
getImageData
獲取圖像數(shù)據(jù),然后遍歷每個(gè)像素,將每個(gè)像素的RGB值轉(zhuǎn)換為灰度值(R、G、B三個(gè)分量取平均值),再使用putImageData
將修改后的數(shù)據(jù)繪制回Canvas。
- 亮度效果:
- 原理:調(diào)整每個(gè)像素的亮度值,使圖像變亮或變暗。
- 實(shí)現(xiàn)方式:使用
getImageData
獲取圖像數(shù)據(jù),然后遍歷每個(gè)像素,調(diào)整每個(gè)像素的亮度值,再使用putImageData
將修改后的數(shù)據(jù)繪制回Canvas。
- 復(fù)古效果:
- 原理:通過(guò)調(diào)整每個(gè)像素的色調(diào)、飽和度和亮度,使圖像呈現(xiàn)復(fù)古效果。
- 實(shí)現(xiàn)方式:使用
getImageData
獲取圖像數(shù)據(jù),然后遍歷每個(gè)像素,調(diào)整每個(gè)像素的色調(diào)、飽和度和亮度,再使用putImageData
將修改后的數(shù)據(jù)繪制回Canvas。
- 紅色、綠色、藍(lán)色效果:
- 原理:增加或減少每個(gè)像素的紅色、綠色、藍(lán)色分量的值,使圖像呈現(xiàn)相應(yīng)顏色的效果。
- 實(shí)現(xiàn)方式:使用
getImageData
獲取圖像數(shù)據(jù),然后遍歷每個(gè)像素,增加或減少每個(gè)像素的紅色、綠色、藍(lán)色分量的值,再使用putImageData
將修改后的數(shù)據(jù)繪制回Canvas。
- 透明效果:
- 原理:調(diào)整每個(gè)像素的透明度值,使圖像呈現(xiàn)透明效果。
- 實(shí)現(xiàn)方式:使用
getImageData
獲取圖像數(shù)據(jù),然后遍歷每個(gè)像素,調(diào)整每個(gè)像素的透明度值,再使用putImageData
將修改后的數(shù)據(jù)繪制回Canvas。
- 馬賽克效果:
- 原理:將圖像分割為小塊,每個(gè)小塊的像素值設(shè)置為該小塊內(nèi)像素的平均值,從而實(shí)現(xiàn)馬賽克效果。
- 實(shí)現(xiàn)方式:使用
getImageData
獲取圖像數(shù)據(jù),然后將圖像分割為小塊,計(jì)算每個(gè)小塊內(nèi)像素的平均值,再將該小塊內(nèi)所有像素的值設(shè)置為該平均值,最后使用putImageData
將修改后的數(shù)據(jù)繪制回Canvas。
- 馬賽克效果
- 由于實(shí)際操作過(guò)程中,上述馬賽克效果處理性能比較底下,這里用來(lái)一個(gè)取巧的效果來(lái)實(shí)現(xiàn)。就是先用canvas將畫面畫小,然后再將畫面縮放來(lái)實(shí)現(xiàn)一個(gè)模糊效果,間接實(shí)現(xiàn)馬賽克效果
- 漸變?yōu)V鏡效果:
- 原理:通過(guò)在圖像上應(yīng)用漸變效果,使圖像呈現(xiàn)漸變色的效果。
- 實(shí)現(xiàn)方式:使用
createLinearGradient
或createRadialGradient
創(chuàng)建漸變對(duì)象,然后使用漸變對(duì)象作為填充樣式,繪制圖像到Canvas上。
<!DOCTYPE html> <html> <head> <title>Canvas Demo</title> <style> button { border-radius: 10px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; overflow: hidden; user-select: none; outline: none; border: none; padding: 16px; background-color: #1d93ab; color: #fff; } button:focus { background-color: #e88f21 } </style> </head> <body> <div> <button data-type="gray">反轉(zhuǎn)</button> <button data-type="blackwhite">黑白</button> <button data-type="brightness">亮度</button> <button data-type="sepia">復(fù)古</button> <button data-type="redMask">紅色</button> <button data-type="greenMask">綠色</button> <button data-type="blueMask">藍(lán)色</button> <button data-type="opacity">透明</button> <button data-type="mosaic">馬賽克</button> <button data-type="linearGradient">漸變</button> </div> <video id="videoElement" autoplay></video> <canvas id="canvasElement"></canvas> <script> // 獲取視頻元素和畫布元素 const video = document.getElementById('videoElement'); const canvas = document.getElementById('canvasElement'); const ctx = canvas.getContext('2d'); const buttons = document.querySelectorAll("button[data-type]"); let drawType = "" // 當(dāng)視頻元素加載完成后執(zhí)行 video.addEventListener('loadedmetadata', function () { // 設(shè)置畫布大小與視頻尺寸相同 canvas.width = video.videoWidth; canvas.height = video.videoHeight; }); // 在每一幀繪制視頻畫面到畫布上 function drawFrame() { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); let imageObj = ctx.getImageData(0, 0, canvas.width, canvas.height); if (drawType === "gray") { // 反轉(zhuǎn) for (let i = 0; i < imageObj.data.length; i += 4) { imageObj.data[i + 0] = 255 - imageObj.data[i + 0]; imageObj.data[i + 1] = 255 - imageObj.data[i + 1]; imageObj.data[i + 2] = 255 - imageObj.data[i + 2]; } ctx.putImageData(imageObj, 0, 0) } if (drawType === "blackwhite") { // 黑白 for (let i = 0; i < imageObj.data.length; i += 4) { const average = (imageObj.data[i + 0] + imageObj.data[i + 1] + imageObj.data[i + 2] + imageObj.data[i + 3]) / 3; imageObj.data[i + 0] = average;//紅 imageObj.data[i + 1] = average; //綠 imageObj.data[i + 2] = average; //藍(lán) } ctx.putImageData(imageObj, 0, 0) } if (drawType === "brightness") { // 亮度 for (let i = 0; i < imageObj.data.length; i += 4) { const a = 50; imageObj.data[i + 0] += a; imageObj.data[i + 1] += a; imageObj.data[i + 2] += a; } ctx.putImageData(imageObj, 0, 0) } if (drawType === "sepia") { // 復(fù)古 for (let i = 0; i < imageObj.data.length; i += 4) { const r = imageObj.data[i + 0]; const g = imageObj.data[i + 1]; const b = imageObj.data[i + 2]; imageObj.data[i + 0] = r * 0.39 + g * 0.76 + b * 0.18; imageObj.data[i + 1] = r * 0.35 + g * 0.68 + b * 0.16; imageObj.data[i + 2] = r * 0.27 + g * 0.53 + b * 0.13; } ctx.putImageData(imageObj, 0, 0) } if (drawType === "redMask") { // 紅色 for (let i = 0; i < imageObj.data.length; i += 4) { const r = imageObj.data[i + 0] const g = imageObj.data[i + 1] const b = imageObj.data[i + 2] const average = (r + g + b) / 3 imageObj.data[i + 0] = average imageObj.data[i + 1] = 0 imageObj.data[i + 2] = 0 } ctx.putImageData(imageObj, 0, 0) } if (drawType === "greenMask") { // 綠色 for (let i = 0; i < imageObj.data.length; i += 4) { const r = imageObj.data[i + 0] const g = imageObj.data[i + 1] const b = imageObj.data[i + 2] const average = (r + g + b) / 3 imageObj.data[i + 0] = 0 imageObj.data[i + 1] = average imageObj.data[i + 2] = 0 } ctx.putImageData(imageObj, 0, 0) } if (drawType === "blueMask") { // 藍(lán)色 for (let i = 0; i < imageObj.data.length; i += 4) { const r = imageObj.data[i + 0] const g = imageObj.data[i + 1] const b = imageObj.data[i + 2] const average = (r + g + b) / 3 imageObj.data[i + 0] = 0 imageObj.data[i + 1] = 0 imageObj.data[i + 2] = average } ctx.putImageData(imageObj, 0, 0) } if (drawType === "opacity") { // 透明 for (let i = 0; i < imageObj.data.length; i += 4) { imageObj.data[i + 3] = imageObj.data[i + 3] * 0.3; } ctx.putImageData(imageObj, 0, 0) } if (drawType === "linearGradient") { // 漸變 const data = imageObj.data; // 遍歷每個(gè)像素 for (let i = 0; i < data.length; i += 4) { const x = (i / 4) % canvas.width; // 當(dāng)前像素的 x 坐標(biāo) const y = Math.floor(i / (4 * canvas.width)); // 當(dāng)前像素的 y 坐標(biāo) // 計(jì)算當(dāng)前像素的顏色值 const r = x / canvas.width * 255; // 紅色分量 const g = y / canvas.height * 255; // 綠色分量 const b = 128; // 藍(lán)色分量 const a = 100; // 不透明度 // 設(shè)置當(dāng)前像素的顏色值 data[i] = r; // 紅色分量 data[i + 1] = g; // 綠色分量 data[i + 2] = b; // 藍(lán)色分量 data[i + 3] = a; // 不透明度 } ctx.putImageData(imageObj, 0, 0) } if (drawType === "mosaic") { // 馬賽克 ctx.imageSmoothingEnabled = false; // 禁用圖像平滑處理 const tileSize = 10; // 馬賽克塊的大小 // 縮小馬賽克塊 ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width / tileSize, canvas.height / tileSize); // 放大回原來(lái)的大小 ctx.drawImage(canvas, 0, 0, canvas.width / tileSize, canvas.height / tileSize, 0, 0, canvas.width, canvas.height); } requestAnimationFrame(drawFrame); // setTimeout(drawFrame, 1000); } // 檢查瀏覽器是否支持 getUserMedia API if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // 請(qǐng)求訪問(wèn)攝像頭 navigator.mediaDevices.getUserMedia({ video: true }) .then(function (stream) { // 將視頻流綁定到視頻元素上 video.srcObject = stream; // 開始繪制視頻畫面到畫布上 requestAnimationFrame(drawFrame); }) .catch(function (error) { console.error('無(wú)法訪問(wèn)攝像頭:', error); }); } else { console.error('瀏覽器不支持 getUserMedia API'); } buttons.forEach(button => { button.addEventListener("click", function (e) { drawType = e.target.dataset.type; }) }) </script> </body> </html>
點(diǎn)擊截圖
流程
- 將Canvas 的
toDataURL
方法將內(nèi)容轉(zhuǎn)換為數(shù)據(jù) URL。 - 創(chuàng)建一個(gè)
<a>
元素,并將數(shù)據(jù) URL 賦值給其href
屬性。 - 設(shè)置
<a>
元素的download
屬性為要保存的文件名。 - 使用 JavaScript 模擬點(diǎn)擊
<a>
元素來(lái)觸發(fā)下載。
const takePhoto = document.querySelector("#takePhoto")// 截圖 按鈕 takePhoto.addEventListener('click', function (e) { // 繪制原始 Canvas 的內(nèi)容到新的 Canvas 上 ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height); // 將內(nèi)容轉(zhuǎn)換為數(shù)據(jù) URL const dataURL = canvas.toDataURL(); // 創(chuàng)建一個(gè) <a> 元素并設(shè)置屬性 const link = document.createElement('a'); link.href = dataURL; link.download = 'screenshot.png'; // 設(shè)置要保存的文件名 // 模擬點(diǎn)擊 <a> 元素來(lái)觸發(fā)下載 link.click(); })
完整代碼
<!DOCTYPE html> <html> <head> <title>Canvas Demo</title> <style> button { border-radius: 10px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; overflow: hidden; user-select: none; outline: none; border: none; padding: 16px; background-color: #1d93ab; color: #fff; } button:focus { background-color: #e88f21 } </style> </head> <body> <div> <button data-type="gray">反轉(zhuǎn)</button> <button data-type="blackwhite">黑白</button> <button data-type="brightness">亮度</button> <button data-type="sepia">復(fù)古</button> <button data-type="redMask">紅色</button> <button data-type="greenMask">綠色</button> <button data-type="blueMask">藍(lán)色</button> <button data-type="opacity">透明</button> <button data-type="mosaic">馬賽克</button> <button data-type="linearGradient">漸變</button> <button id="takePhoto">拍攝</button> </div> <video id="videoElement" autoplay></video> <canvas id="canvasElement"></canvas> <script> // 獲取視頻元素和畫布元素 const video = document.getElementById('videoElement'); const canvas = document.getElementById('canvasElement'); const ctx = canvas.getContext('2d'); const buttons = document.querySelectorAll("button[data-type]"); const takePhoto = document.querySelector("#takePhoto")// 截圖 按鈕 let drawType = "" // 當(dāng)視頻元素加載完成后執(zhí)行 video.addEventListener('loadedmetadata', function () { // 設(shè)置畫布大小與視頻尺寸相同 canvas.width = video.videoWidth; canvas.height = video.videoHeight; }); // 在每一幀繪制視頻畫面到畫布上 function drawFrame() { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); let imageObj = ctx.getImageData(0, 0, canvas.width, canvas.height); if (drawType === "gray") { // 反轉(zhuǎn) for (let i = 0; i < imageObj.data.length; i += 4) { imageObj.data[i + 0] = 255 - imageObj.data[i + 0]; imageObj.data[i + 1] = 255 - imageObj.data[i + 1]; imageObj.data[i + 2] = 255 - imageObj.data[i + 2]; } ctx.putImageData(imageObj, 0, 0) } if (drawType === "blackwhite") { // 黑白 for (let i = 0; i < imageObj.data.length; i += 4) { const average = (imageObj.data[i + 0] + imageObj.data[i + 1] + imageObj.data[i + 2] + imageObj.data[i + 3]) / 3; imageObj.data[i + 0] = average;//紅 imageObj.data[i + 1] = average; //綠 imageObj.data[i + 2] = average; //藍(lán) } ctx.putImageData(imageObj, 0, 0) } if (drawType === "brightness") { // 亮度 for (let i = 0; i < imageObj.data.length; i += 4) { const a = 50; imageObj.data[i + 0] += a; imageObj.data[i + 1] += a; imageObj.data[i + 2] += a; } ctx.putImageData(imageObj, 0, 0) } if (drawType === "sepia") { // 復(fù)古 for (let i = 0; i < imageObj.data.length; i += 4) { const r = imageObj.data[i + 0]; const g = imageObj.data[i + 1]; const b = imageObj.data[i + 2]; imageObj.data[i + 0] = r * 0.39 + g * 0.76 + b * 0.18; imageObj.data[i + 1] = r * 0.35 + g * 0.68 + b * 0.16; imageObj.data[i + 2] = r * 0.27 + g * 0.53 + b * 0.13; } ctx.putImageData(imageObj, 0, 0) } if (drawType === "redMask") { // 紅色 for (let i = 0; i < imageObj.data.length; i += 4) { const r = imageObj.data[i + 0] const g = imageObj.data[i + 1] const b = imageObj.data[i + 2] const average = (r + g + b) / 3 imageObj.data[i + 0] = average imageObj.data[i + 1] = 0 imageObj.data[i + 2] = 0 } ctx.putImageData(imageObj, 0, 0) } if (drawType === "greenMask") { // 綠色 for (let i = 0; i < imageObj.data.length; i += 4) { const r = imageObj.data[i + 0] const g = imageObj.data[i + 1] const b = imageObj.data[i + 2] const average = (r + g + b) / 3 imageObj.data[i + 0] = 0 imageObj.data[i + 1] = average imageObj.data[i + 2] = 0 } ctx.putImageData(imageObj, 0, 0) } if (drawType === "blueMask") { // 藍(lán)色 for (let i = 0; i < imageObj.data.length; i += 4) { const r = imageObj.data[i + 0] const g = imageObj.data[i + 1] const b = imageObj.data[i + 2] const average = (r + g + b) / 3 imageObj.data[i + 0] = 0 imageObj.data[i + 1] = 0 imageObj.data[i + 2] = average } ctx.putImageData(imageObj, 0, 0) } if (drawType === "opacity") { // 透明 for (let i = 0; i < imageObj.data.length; i += 4) { imageObj.data[i + 3] = imageObj.data[i + 3] * 0.3; } ctx.putImageData(imageObj, 0, 0) } if (drawType === "linearGradient") { // 漸變 const data = imageObj.data; // 遍歷每個(gè)像素 for (let i = 0; i < data.length; i += 4) { const x = (i / 4) % canvas.width; // 當(dāng)前像素的 x 坐標(biāo) const y = Math.floor(i / (4 * canvas.width)); // 當(dāng)前像素的 y 坐標(biāo) // 計(jì)算當(dāng)前像素的顏色值 const r = x / canvas.width * 255; // 紅色分量 const g = y / canvas.height * 255; // 綠色分量 const b = 128; // 藍(lán)色分量 const a = 100; // 不透明度 // 設(shè)置當(dāng)前像素的顏色值 data[i] = r; // 紅色分量 data[i + 1] = g; // 綠色分量 data[i + 2] = b; // 藍(lán)色分量 data[i + 3] = a; // 不透明度 } ctx.putImageData(imageObj, 0, 0) } if (drawType === "mosaic") { // 馬賽克 ctx.imageSmoothingEnabled = false; // 禁用圖像平滑處理 const tileSize = 10; // 馬賽克塊的大小 // 縮小馬賽克塊 ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width / tileSize, canvas.height / tileSize); // 放大回原來(lái)的大小 ctx.drawImage(canvas, 0, 0, canvas.width / tileSize, canvas.height / tileSize, 0, 0, canvas.width, canvas.height); } requestAnimationFrame(drawFrame); // setTimeout(drawFrame, 1000); } // 檢查瀏覽器是否支持 getUserMedia API if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // 請(qǐng)求訪問(wèn)攝像頭 navigator.mediaDevices.getUserMedia({ video: true }) .then(function (stream) { // 將視頻流綁定到視頻元素上 video.srcObject = stream; // 開始繪制視頻畫面到畫布上 requestAnimationFrame(drawFrame); }) .catch(function (error) { console.error('無(wú)法訪問(wèn)攝像頭:', error); }); } else { console.error('瀏覽器不支持 getUserMedia API'); } buttons.forEach(button => { button.addEventListener("click", function (e) { drawType = e.target.dataset.type; }) }) takePhoto.addEventListener('click', function (e) { // 繪制原始 Canvas 的內(nèi)容到新的 Canvas 上 ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height); // 將內(nèi)容轉(zhuǎn)換為數(shù)據(jù) URL const dataURL = canvas.toDataURL(); // 創(chuàng)建一個(gè) <a> 元素并設(shè)置屬性 const link = document.createElement('a'); link.href = dataURL; link.download = 'screenshot.png'; // 設(shè)置要保存的文件名 // 模擬點(diǎn)擊 <a> 元素來(lái)觸發(fā)下載 link.click(); }) </script> </body> </html>
以上就是使用canvas實(shí)現(xiàn)魔法攝像頭的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于canvas 魔法攝像頭的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript計(jì)算值然后把值嵌入到html中的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇JavaScript計(jì)算值然后把值嵌入到html中的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10基于cornerstone.js的dicom醫(yī)學(xué)影像查看瀏覽功能
這篇文章主要介紹了基于cornerstone.js的dicom醫(yī)學(xué)影像查看瀏覽功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07ajax前臺(tái)后臺(tái)跨域請(qǐng)求處理方式
本篇文章通過(guò)前臺(tái)跨域請(qǐng)求處理以及后臺(tái)跨域的數(shù)據(jù)處理方式介紹,詳細(xì)分析了ajax跨域的問(wèn)題,對(duì)此有需要的朋友學(xué)習(xí)下。2018-02-02JavaScript使用ul中l(wèi)i標(biāo)簽實(shí)現(xiàn)刪除效果
這篇文章主要為大家詳細(xì)介紹了JavaScript使用ul中l(wèi)i標(biāo)簽實(shí)現(xiàn)刪除效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04非jQuery實(shí)現(xiàn)照片散落桌子上,單擊放大的LightBox效果
本文給大家介紹一款js實(shí)現(xiàn)的照片散落桌子上點(diǎn)擊放大圖片的LightBox效果,非常的炫酷,而且是非jQuery實(shí)現(xiàn)的,有需要的小伙伴可以參考下2014-11-11Javascript延遲執(zhí)行實(shí)現(xiàn)方法(setTimeout)
延遲執(zhí)行,其實(shí)就是用到了setTimeout這個(gè)函數(shù)。善于利用這個(gè)函數(shù),可以減少很多ajax的請(qǐng)求,以及dom操作。2010-12-12利用JS實(shí)現(xiàn)頁(yè)面刪除并重新排序功能
這篇文章主要介紹了利用JS實(shí)現(xiàn)頁(yè)面刪除并重新排序功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-12-12