前些日子當前端面試官,問了一個問題:「你瞭解過canvas嗎?」javascript
「這個我知道,我有作過DEMO,這個不難吧,看着它的api接口就能實現!」css
看着他這麼(蜜汁)自信,我決定深刻了解(爲難)一下他!html
「電商中大轉盤,九宮格,刮刮樂,如何使用canvas實現,講講你的思路?」前端
「二維碼的生成和掃碼識別如何實現?」java
「圖片的粒子爆炸效果呢?」android
「......」webpack
所以,打算寫一系列關於 canvas 的文章,探索學習提高本身的同時順便分享給你們。ios
二維碼的生成需藉助第三方庫,利用其算法對文本轉化成二維碼,並用 canvas
繪畫出來。利用 canvas.toDataURL('image/png')
獲取二維碼轉 base64
值,再將其賦值給 img
標籤的 src
屬性git
這裏我使用了一個庫,qrcodejs.github
可點擊 《Demo》 查看效果
使用方法以下:
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Suporka Vue App</title>
<style> .container { padding: 60px; margin: 0 auto; line-height: 50px; } input { display: inline-block; width: 200px; height: 32px; line-height: 1.5; padding: 4px 7px; font-size: 12px; border: 1px solid #dcdee2; border-radius: 4px; color: #515a6e; background-color: #fff; background-image: none; position: relative; cursor: text; transition: border 0.2s ease-in-out, background 0.2s ease-in-out, box-shadow 0.2s ease-in-out; } button { color: #fff; background-color: #19be6b; border-color: #19be6b; outline: 0; vertical-align: middle; line-height: 1.5; display: inline-block; font-weight: 400; text-align: center; -ms-touch-action: manipulation; touch-action: manipulation; cursor: pointer; background-image: none; border: 1px solid transparent; white-space: nowrap; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; padding: 5px 15px 6px; font-size: 12px; border-radius: 4px; transition: color 0.2s linear, background-color 0.2s linear, border 0.2s linear, box-shadow 0.2s linear; } #qrcode { margin-top: 20px; } </style>
</head>
<body>
<div class="container">
<input type="text" placeholder="請輸入您想轉化成二維碼的字符串" id="input" />
<button onclick="creatQRcode();">一鍵生成</button>
<div id="qrcode"></div>
</div>
<script src="https://zxpsuper.github.io/Demo/qrcode/qrcode-dev.js"></script>
<script type="text/javascript"> var qrcode = null; function creatQRcode() { document.getElementById("qrcode").innerHTML = ""; qrcode = new QRCode(document.getElementById("qrcode"), { text: document.getElementById("input").value, width: 200, height: 200, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H }); } </script>
</body>
</html>
複製代碼
options
img
標籤的父元素Object
類型的 options
屬性 | 類型 | 說明 |
---|---|---|
text | String | 目標文本 |
width | Number | 圖片寬度 |
height | Number | 圖片高度 |
colorDark | String | 二維碼顏色 |
colorLight | 默認QRCode.CorrectLevel.L | 容錯級別,可設置爲:QRCode.CorrectLevel.L ,QRCode.CorrectLevel.M,QRCode.CorrectLevel.Q,QRCode.CorrectLevel.H |
這裏利用了一個庫 llqrcode.js, 使用 qrcode.decode()
對 id 爲 qr-canvas
的 canvas
進行解碼.
咱們須要作的就是,調用設備的攝像頭(後置攝像頭優先),得到的畫面用 video
標籤實時顯示出來,再定時取畫面生成 canvas ,調用 qrcode.decode()
解密。
// variable.js
var gCtx = null; //canvas.ctx
var gCanvas = null; // qr-canvas
// var c = 0;
var stype = 0; // 識別流程 0未開始 1進行中 2已結束
var gUM = false;
var webkit = false;
var moz = false;
var v = null; // 存放視頻的變量
var scanCodeStart = false; // 開始掃碼
var mediaStreamTrack = null; // mediaStreamTrack 實現關閉攝像頭功能 mediaStreamTrack.stop()
var imghtml =
'<div id="qrfile"><canvas id="out-canvas" width="320" height="240"></canvas>' +
'<div id="imghelp">drag and drop a QRCode here' +
"<br>or select a file" +
'<input type="file" onchange="handleFiles(this.files)" id="upload-img"/>' +
"</div>" +
"</div>";
var vidhtml = '<video id="v" autoplay muted></video>';
複製代碼
// methods.js
function qrcodeScanLoad(width, height) {
if (isCanvasSupported() && window.File && window.FileReader) {
initCanvas(width, height);
qrcode.callback = scanCodeCallback;
document.getElementById("mainbody").style.display = "inline";
setwebcam();
} else {
document.getElementById("mainbody").style.display = "inline";
document.getElementById("mainbody").innerHTML =
'<p id="mp1">QR code scanner for HTML5 capable browsers</p><br>' +
'<br><p id="mp2">sorry your browser is not supported</p><br><br>';
}
}
// 繪製遮罩層canvas
function setMask() {
var canvas = document.querySelector("#scancode-mask");
canvas.width =
document.body.clientWidth > 1024 ? 1024 : document.body.clientWidth;
canvas.height =
document.body.clientWidth > 1024 ? 1136 : document.body.clientHeight;
var ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "destination-out";
ctx.beginPath();
let x1,
y1,
width = canvas.width * 0.6;
x1 = (canvas.width - width) / 2;
y1 = (canvas.height - width) / 2;
ctx.fillRect(x1, y1, width, width);
ctx.fill();
ctx.save();
ctx.globalCompositeOperation = "source-over";
// 第二象限點
ctx.moveTo(x1, y1 + 2);
ctx.lineTo(x1 + 20, y1 + 2);
ctx.moveTo(x1 + 2, y1);
ctx.lineTo(x1 + 2, y1 + 20);
// 第一象限點
ctx.moveTo(x1 + width - 20, y1 + 2);
ctx.lineTo(x1 + width, y1 + 2);
ctx.moveTo(x1 + width - 2, y1 + 1);
ctx.lineTo(x1 + width - 2, y1 + 20);
// 第四象限點
ctx.moveTo(x1 + width - 20, y1 + width - 2);
ctx.lineTo(x1 + width, y1 + width - 2);
ctx.moveTo(x1 + width - 2, y1 + width - 1);
ctx.lineTo(x1 + width - 2, y1 + width - 20);
// 第三象限點
ctx.moveTo(x1 + 20, y1 + width - 2);
ctx.lineTo(x1, y1 + width - 2);
ctx.moveTo(x1 + 2, y1 + width - 2);
ctx.lineTo(x1 + 2, y1 + width - 20);
ctx.lineWidth = 4;
ctx.strokeStyle = "green";
ctx.stroke();
}
function setwebcam() {
var options = true;
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
try {
navigator.mediaDevices.enumerateDevices().then(function(devices) {
let video = [];
devices.forEach(function(device) {
if (device.kind === "videoinput") {
video.push(device);
}
});
// 調用設備的攝像頭,video[1] 爲後置攝像頭,或者label含有‘back’爲後置攝像頭
if (video.length >= 2) {
options = {
deviceId: { exact: video[1].deviceId },
facingMode: { exact: "environment" }
};
}
video.forEach(item => {
if (item.label.toLowerCase().search("back") > -1)
options = {
deviceId: { exact: device.deviceId },
facingMode: { exact: "environment" }
};
});
scanCodeStart = true;
setwebcam2(options);
});
} catch (e) {
console.error(e);
}
} else {
console.log("no navigator.mediaDevices.enumerateDevices");
setwebcam2(options);
}
}
function setwebcam2(options) {
if (stype == 1) {
setTimeout(captureToCanvas, 500);
return;
}
var n = navigator;
document.getElementById("outdiv").innerHTML = vidhtml;
v = document.getElementById("v");
try {
if (n.mediaDevices && n.mediaDevices.getUserMedia) {
n.mediaDevices
.getUserMedia({ video: options, audio: false })
.then(function(stream) {
success(stream);
})
.catch(function(error) {
error(error);
});
} else if (n.getUserMedia) {
webkit = true;
n.getUserMedia({ video: options, audio: false }, success, error);
} else if (n.webkitGetUserMedia) {
webkit = true;
n.webkitGetUserMedia({ video: options, audio: false }, success, error);
}
} catch (err) {
console.log(err);
}
stype = 1;
if (getSystem() === "ios") {
alert("您的設備暫不支持實時掃碼,請上傳圖片識別!");
return;
}
if (
(n.mediaDevices && n.mediaDevices.getUserMedia) ||
n.getUserMedia ||
n.webkitGetUserMedia
)
setTimeout(captureToCanvas, 500);
else {
alert("您的設備暫不支持實時掃碼,請上傳圖片識別!");
}
}
// 獲取操做系統
function getSystem() {
var u = navigator.userAgent;
var isAndroid = u.indexOf("Android") > -1 || u.indexOf("Linux") > -1; //g
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios終端
if (isAndroid) {
//這個是安卓操做系統
return "android";
} else if (isIOS) {
//這個是ios操做系統
return "ios";
} else {
return "other";
}
}
// 選擇圖片上傳
function setimg($event) {
qrcode.callback = scanCodeCallback;
$event && $event.preventDefault();
stype = 2;
let file = document.getElementById("upload-img");
file.click();
}
// 上傳成功的回調
function scanCodeCallback(a) {
var html = htmlEntities(a);
stype = 0;
alert(html);
}
// 處理上傳文件識別
function handleFiles(f) {
var o = [];
for (var i = 0; i < f.length; i++) {
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
gCtx.clearRect(0, 0, gCanvas.width, gCanvas.height);
qrcode.decode(e.target.result);
};
})(f[i]);
reader.readAsDataURL(f[i]);
}
}
function initCanvas(w, h) {
gCanvas = document.getElementById("qr-canvas");
gCanvas.style.width = w + "px";
gCanvas.style.height = h + "px";
gCanvas.width = w;
gCanvas.height = h;
gCtx = gCanvas.getContext("2d");
gCtx.clearRect(0, 0, w, h);
}
// 畫面轉 canvas
function captureToCanvas() {
if (stype != 1) return;
if (gUM && scanCodeStart) {
try {
gCtx.drawImage(v, 0, 0);
try {
qrcode.decode(); // 默認爲id=qr-canvas的canvas轉成圖片的base64
} catch (e) {
console.log(e);
setTimeout(captureToCanvas, 500);
}
} catch (e) {
console.log(e);
setTimeout(captureToCanvas, 500);
}
}
}
// 對特殊符號進行處理
function htmlEntities(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}
// 判斷是否支持canvas
function isCanvasSupported() {
var elem = document.createElement("canvas");
return !!(elem.getContext && elem.getContext("2d"));
}
function success(stream) {
// mediaStreamTrack 實現關閉攝像頭功能
if (stream)
mediaStreamTrack =
typeof stream.stop === "function" ? stream : stream.getTracks()[0];
v.srcObject = stream;
if (scanCodeStart) {
v.play();
gUM = true;
setTimeout(captureToCanvas, 500);
} else {
}
}
function error(error) {
gUM = false;
return;
}
複製代碼
其中 setMask 爲繪製遮罩層方法,qrcodeScanLoad 爲初始化加載方法。
<body>
<div class="body">
<div id="mainbody" style="display: inline;">
<div id="outdiv" autoplay muted></div>
</div>
<canvas id="qr-canvas" width="800" height="600"></canvas>
<canvas id="scancode-mask"></canvas>
<div class="scancode-tips-group" id="scancode-tips-group">
<span class="tips">將QR Code 放入框內,便可自動掃描</span>
<div class="upload-my-code" onClick="setimg()">個人QR Code</div>
</div>
<div id="img-upload-container" style="display: none">
<div id="qrfile">
<canvas id="out-canvas" width="320" height="240"></canvas>
<div id="imghelp">
drag and drop a QRCode here
<br />or select a file
<input type="file" onchange="handleFiles(this.files)" id="upload-img" />
</div>
</div>
</div>
</div>
<script src="./llqrcode.js"></script>
<script src="./variable.js"></script>
<script src="./methods.js"></script>
<script> qrcodeScanLoad(320, 400); setMask(); // 對個人 QR Code 進行定位顯示 if (document.body.clientWidth < 1025) { document.getElementById("scancode-tips-group").style.top = (document.body.clientHeight - document.body.clientWidth) / 2 + document.body.clientWidth * 0.9 - 10 + "px"; } else { document.getElementById("scancode-tips-group").style.top = "720px"; } </script>
</body>
複製代碼
這裏作了適配,當超過 1024 時,canvas 寬度就設爲 1024。詳細在代碼中已經作了註釋。