原因:以前看嗶哩嗶哩官網登陸的時候有一個拼圖驗證碼,很好奇怎麼去實現。而後就想着本身弄一個。先給你們看個人最終效果。後面再一點點拆解代碼。css
爲何想着寫這個功能呢,主要在於拼圖驗證碼在前端這裏會比較複雜而且深刻。相比文字拼寫,12306的圖片驗證碼都沒有拼圖驗證碼對前端的要求來的複雜,和難。html
我總結下知識點:前端
一、彈窗功能git
二、彈窗基於元素定位github
三、元素拖動canvas
四、canvas繪圖vim
五、基礎邏輯dom
1、彈窗和彈窗組件ide
抱歉,這裏我偷懶了直接用了elementUI的el-popover組件,因此小夥伴不懂的直接看elementUI官網的說明。函數
我我的也研究和編寫了這塊的組件功能(基於popper.js)
2、編寫基礎結構
這塊屬於html的基礎內容,也就標題黨了
3、canvas繪製圖片
一、canvas繪製外部img圖片
代碼:
let mainDom = document.querySelector("#codeImg");let bg = mainDom.getContext("2d");let width = mainDom.width;let height = mainDom.height;let blockDom = document.querySelector("#sliderBlock");let block = blockDom.getContext("2d"); //從新賦值,讓canvas進行從新繪製 blockDom.height = height; mainDom.height = height;let imgsrc = require("../assets/images/back.jpg");let img = document.createElement("img"); img.style.objectFit = "scale-down"; img.src = imgsrc; img.onload = function() { bg.drawImage(img, 0, 0, width, height); block.drawImage(img, 0, 0, width, height); };複製代碼
這裏我繪製了兩個canvas,由於一個是背景一個是滑塊
核心在於
let mainDom = document.querySelector("#codeImg");let imgsrc = require("../assets/images/back.jpg");let bg = mainDom.getContext("2d");let img = document.createElement("img"); img.onload = function() { bg.drawImage(img, 0, 0, width, height); }; 複製代碼
二、canvas繪製滑塊部分
就是這個圖,這個有一些知識點,不難,可是很複雜。
代碼部分:
drawBlock(ctx, xy = { x: 254, y: 109, r: 9 }, type) { let x = xy.x, y = xy.y, r = xy.r, w = 40; let PI = Math.PI; //繪製 ctx.beginPath(); //left ctx.moveTo(x, y); //top ctx.arc(x + (w + 5) / 2, y, r, -PI, 0, true); ctx.lineTo(x + w + 5, y); //right ctx.arc(x + w + 5, y + w / 2, r, 1.5 * PI, 0.5 * PI, false); ctx.lineTo(x + w + 5, y + w); //bottom ctx.arc(x + (w + 5) / 2, y + w, r, 0, PI, false); ctx.lineTo(x, y + w); ctx.arc(x, y + w / 2, r, 0.5 * PI, 1.5 * PI, true); ctx.lineTo(x, y); //修飾,沒有會看不出效果 ctx.lineWidth = 1; ctx.fillStyle = "rgba(255, 255, 255, 0.5)"; ctx.strokeStyle = "rgba(255, 255, 255, 0.5)"; ctx.stroke(); ctx[type](); ctx.globalCompositeOperation = "xor"; }複製代碼
解釋下:
參數是傳入canvas對象
x,y軸數據
剪切仍是填充的canvas函數(fill,clip)
繪製難點:(很重要,否則你無法理解它怎麼繪製的)
繪製主要是須要理解這裏的繪製是根據你設置一個起始點座標,而後你繪製第二次的時候線就會鏈接到第二個點,依次鏈接最後回到原點就造成一個完整的圖形。
用的是arc參數,主要是看這個圖
fill是用於填充繪製的部分,clip是裁剪出繪製的部分,利用這個就能夠出現一個扣掉的圖片和一個裁剪出來的圖片。
完成以後就是個人那個函數了。你們能夠直接拿去用。
三、讓元素跟隨鼠標點擊以後滑動
這裏其實原理很是簡單,就是有一個注意點。
原理:
鼠標點擊以後記錄當前座標,而後隨着(mousemove)滾動的時候修改元素的left和top值就好了。
還有一點就是鼠標快速滑動會致使丟失滑動效果,這裏須要用document,不能是元素級別的監聽。
元素上面我只須要鑑定按下mousedown
代碼:
//鼠標按下drag(e) { console.log("鼠標按下", e); let dom = e.target; //dom元素 let slider = document.querySelector("#sliderBlock"); //滑塊dom const downCoordinate = { x: e.x, y: e.y }; //正確的滑塊數據 let checkx = Number(this.slider.mx) - Number(this.slider.bx); //x軸數據 let x = 0; const move = moveEV => { x = moveEV.x - downCoordinate.x; //y = moveEV.y - downCoordinate.y; if (x >= 251 || x <= 0) return false; dom.style.left = x + "px"; //dom.style.top = y + "px"; slider.style.left = x + "px"; }; const up = () => { document.removeEventListener("mousemove", move); document.removeEventListener("mouseup", up); dom.style.left = ""; console.log(x, checkx); let max = checkx - 5; let min = checkx - 10; //容許正負偏差1 if ((max >= x && x >= min) || x === checkx) { console.log("滑動解鎖成功"); this.puzzle = true; this.tips = "驗證成功"; setTimeout(() => { this.visible = false; }, 500); } else { console.log("拼圖位置不正確"); this.tips = "驗證失敗,請重試"; this.puzzle = false; this.canvasInit(); } }; document.addEventListener("mousemove", move); document.addEventListener("mouseup", up); }複製代碼
四、總結
核心點比較多,寫過以後發現不難,關鍵在於去寫
我的該頁面git地址:https://github.com/ht-sauce/dream
該頁面處於項目的
路由訪問爲:http://localhost:8080/consumer
五、完整的頁面代碼
<template> <div id="login"> <el-form class="loginFrom" :model="logindata" :rules="rules" ref="ruleForm"> <el-form-item class="login-item"> <h1 class="login-title">海天醬油登陸中心</h1> </el-form-item> <el-form-item prop="userName"> <el-input class="login-inputorbuttom" prefix-icon="el-icon-user" placeholder="登陸名" v-model="logindata.userName" ></el-input> </el-form-item> <el-form-item prop="password"> <el-input class="login-inputorbuttom" prefix-icon="el-icon-lock" placeholder="密碼" v-model="logindata.password" ></el-input> </el-form-item> <!--<el-form-item prop="verificationCode"> <el-input class="login-inputorbuttom" v-model="logindata.verificationCode" ></el-input> </el-form-item>--> <el-form-item class="login-item"> <el-button class="login-inputorbuttom login-bottom" type="primary" v-popover:popover @click="visible = !visible" >登 錄</el-button > </el-form-item> </el-form> <!--驗證碼彈窗--> <el-popover popper-class="slidingPictures" ref="popover" trigger="manual" v-model="visible" > <div class="sliding-pictures"> <div class="vimg"> <canvas id="sliderBlock"></canvas> <canvas id="codeImg"></canvas> </div> <div class="slider"> <div class="track" :class="{ pintuTrue: puzzle }"> {{ tips }} </div> <div class="button el-icon-s-grid" @mousedown.prevent="drag"></div> </div> <div class="operation"> <span title="關閉驗證碼" @click="visible = false" class="el-icon-circle-close" ></span> <span title="刷新驗證碼" @click="canvasInit" class="el-icon-refresh-left" ></span> </div> </div> </el-popover> </div></template><script>export default { name: "login", data() { return { tips: "拖動左邊滑塊完成上方拼圖", logindata: { userName: "", password: "", verificationCode: "" }, rules: {}, visible: false, //滑塊x軸數據 slider: { mx: 0, bx: 0 }, //拼圖是否正確 puzzle: false }; }, watch: { visible(e) { if (e === true) { this.canvasInit(); this.puzzle = false; } } }, beforeCreate() {}, created() {}, beforeMount() {}, mounted() {}, methods: { //拼圖驗證碼初始化 canvasInit() { //生成指定區間的隨機數 const random = (min, max) => { return Math.floor(Math.random() * (max - min + 1) + min); }; //x: 254, y: 109 let mx = random(127, 244), bx = random(10, 128), y = random(10, 99); this.slider = { mx, bx }; this.draw(mx, bx, y); }, //鼠標按下 drag(e) { console.log("鼠標按下", e); let dom = e.target; //dom元素 let slider = document.querySelector("#sliderBlock"); //滑塊dom const downCoordinate = { x: e.x, y: e.y }; //正確的滑塊數據 let checkx = Number(this.slider.mx) - Number(this.slider.bx); //x軸數據 let x = 0; const move = moveEV => { x = moveEV.x - downCoordinate.x; //y = moveEV.y - downCoordinate.y; if (x >= 251 || x <= 0) return false; dom.style.left = x + "px"; //dom.style.top = y + "px"; slider.style.left = x + "px"; }; const up = () => { document.removeEventListener("mousemove", move); document.removeEventListener("mouseup", up); dom.style.left = ""; console.log(x, checkx); let max = checkx - 5; let min = checkx - 10; //容許正負偏差1 if ((max >= x && x >= min) || x === checkx) { console.log("滑動解鎖成功"); this.puzzle = true; this.tips = "驗證成功"; setTimeout(() => { this.visible = false; }, 500); } else { console.log("拼圖位置不正確"); this.tips = "驗證失敗,請重試"; this.puzzle = false; this.canvasInit(); } }; document.addEventListener("mousemove", move); document.addEventListener("mouseup", up); }, draw(mx = 200, bx = 20, y = 50) { let mainDom = document.querySelector("#codeImg"); let bg = mainDom.getContext("2d"); let width = mainDom.width; let height = mainDom.height; let blockDom = document.querySelector("#sliderBlock"); let block = blockDom.getContext("2d"); //從新賦值,讓canvas進行從新繪製 blockDom.height = height; mainDom.height = height; let imgsrc = require("../assets/images/back.jpg"); let img = document.createElement("img"); img.style.objectFit = "scale-down"; img.src = imgsrc; img.onload = function() { bg.drawImage(img, 0, 0, width, height); block.drawImage(img, 0, 0, width, height); }; let mainxy = { x: mx, y: y, r: 9 }; let blockxy = { x: bx, y: y, r: 9 }; this.drawBlock(bg, mainxy, "fill"); this.drawBlock(block, blockxy, "clip"); }, //繪製拼圖 drawBlock(ctx, xy = { x: 254, y: 109, r: 9 }, type) { let x = xy.x, y = xy.y, r = xy.r, w = 40; let PI = Math.PI; //繪製 ctx.beginPath(); //left ctx.moveTo(x, y); //top ctx.arc(x + (w + 5) / 2, y, r, -PI, 0, true); ctx.lineTo(x + w + 5, y); //right ctx.arc(x + w + 5, y + w / 2, r, 1.5 * PI, 0.5 * PI, false); ctx.lineTo(x + w + 5, y + w); //bottom ctx.arc(x + (w + 5) / 2, y + w, r, 0, PI, false); ctx.lineTo(x, y + w); ctx.arc(x, y + w / 2, r, 0.5 * PI, 1.5 * PI, true); ctx.lineTo(x, y); //修飾,沒有會看不出效果 ctx.lineWidth = 1; ctx.fillStyle = "rgba(255, 255, 255, 0.5)"; ctx.strokeStyle = "rgba(255, 255, 255, 0.5)"; ctx.stroke(); ctx[type](); ctx.globalCompositeOperation = "xor"; } } };</script><style>.slidingPictures { padding: 0; width: 300px; border-radius: 2px; }</style><style scoped lang="scss"></style>
最後
喜歡的能夠點個關注嗎,小可樂也不容易呢