時(shí)候玩的拼圖游戲,大家好記得嗎?有沒有想過自己做一個(gè)這樣的游戲呢,特別一些特殊的日子,送給他(她)呢。為了實(shí)現(xiàn)大家的想法,小猿圈web前端講師就講講利用HTML5拖拽功能實(shí)現(xiàn)的拼圖游戲
具體代碼如下所示:
<!--代碼如下,最下面給出了我測(cè)試用的9張250*250的圖片切片-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>drag拖拽</title>
<style>
.box{
float: left;
}
img{
width: 150px;
height:150px;
}
#puzzle{
font-size: 0;
margin:80px auto;
padding: 5px;
width: 460px;
}
</style>
</head>
<body>
<div id="puzzle">
<div class="box"><img alt="1"></div>
<div class="box"><img alt="2"></div>
<div class="box"><img alt="3"></div>
<div class="box"><img alt="4"></div>
<div class="box"><img alt="5"></div>
<div class="box"><img alt="6"></div>
<div class="box"><img alt="7"></div>
<div class="box"><img alt="8"></div>
<div class="box"><img alt="9"></div>
</div>
<script>
var image=document.getElementsByTagName("img");
var box=document.getElementsByClassName("box");
image.draggable=true;
var source="";
var nowImage;
var nowImageBox;
var thenImage;
for(let i=0;i<image.length;i++){
source="picture"+i+".jpg";
image[i].setAttribute("src",source);
image[i].onmousedown=function(){
nowImage=this;
nowImageBox=this.parentNode;
}
box[i].ondragover=function(event){
event.preventDefault(); //去除ondragover事件的默認(rèn)行為,該行為默認(rèn)無法將數(shù)據(jù)或者元素放置到其他元素
}
box[i].ondrop=function(event){
thenImage=box[i].childNodes[0];
box[i].appendChild(nowImage);
nowImageBox.appendChild(thenImage);
}
}
</script>
</body>
</html>
以上就是小猿圈web前端講師針對(duì)HTML5拖拽功能實(shí)現(xiàn)的拼圖游戲的講解,你有想過學(xué)習(xí)前端開發(fā)嘛,想學(xué)習(xí)前端可以到小猿圈去直接觀看,這里面從基礎(chǔ)到實(shí)戰(zhàn)的所有學(xué)習(xí)資料,可以滿足你提升自己,為你實(shí)現(xiàn)編程夢(mèng)想的起點(diǎn)。
文收錄本頭條號(hào)已發(fā)表的經(jīng)典文章,并持續(xù)更新中,請(qǐng)大家關(guān)注。
ES6/JavaScript的箭頭函數(shù)是怎樣將this與作用域聯(lián)系起來的,及什么是this詞法
你所不知道的JavaScript動(dòng)態(tài)作用域,它與詞法作用域有什么區(qū)別和關(guān)聯(lián)?
你寫過JavaScript的類嗎?你知道JavaScript的類的機(jī)制是什么樣的嗎?
JavaScript中this是一種什么機(jī)制?又該如何的避免的二個(gè)坑?
高級(jí)JavaScript程序員必須掌握的一個(gè)問題:如何解決回調(diào)的信任問題
JavaScript異步:請(qǐng)先理解JavaScript的事件循環(huán)
使用JavaScript+HTML5 Canvas實(shí)現(xiàn)的界面元素截屏功能
區(qū)分初級(jí)和高級(jí)JavaScript程序員:是否理解JavaScript閉包(Closure)
在JavaScript中使用LINQ:linq.js插件簡(jiǎn)介
Processing.js和P5.js的功能簡(jiǎn)介和區(qū)別
前端開發(fā)框架選型:Angular2/React/jQuery
JavaScript語言的三種編程范式比較
JavaScript的那些坑,你遇到過嗎?又該如何避免?
常見面試題JavaScript語言中四種函數(shù)調(diào)用方式實(shí)例分析
Ionic2、React、NativeScript比較
JavaScript Promise實(shí)現(xiàn)任務(wù)的靈活調(diào)度
一個(gè)支持PDF/文本/二進(jìn)制文件的實(shí)用的客戶端數(shù)字簽名框架
jQuery實(shí)現(xiàn)純HTML/CSS的拼圖游戲
阻止廣告:JavaScript實(shí)現(xiàn)Chrome插件實(shí)例分析
JavaScript實(shí)現(xiàn)三維軌道燈動(dòng)畫
使用 Angular 構(gòu)建 JavaScript 應(yīng)用
利用jQuery實(shí)現(xiàn)多個(gè)ajax請(qǐng)求等待
JavaScript代碼重構(gòu)
SQL注入和跨站點(diǎn)腳本實(shí)例分析
仿生智能算法之路徑智能尋找的HTML及JS動(dòng)畫實(shí)現(xiàn)代碼
HTML5動(dòng)畫背后的數(shù)學(xué)-粒子群仿生算法簡(jiǎn)介
HTML5及JS實(shí)現(xiàn)粒子群仿生動(dòng)畫代碼
NodeJS、Java和PHP性能考量和若干參考結(jié)論
WebGL、Asm.js和WebAssembly概念簡(jiǎn)介
WebAssembly工作原理和JavaScript性能對(duì)比
Async之流程控制
由:之前看嗶哩嗶哩官網(wǎng)登錄的時(shí)候有一個(gè)拼圖驗(yàn)證碼,很好奇怎么去實(shí)現(xiàn)。然后就想著自己弄一個(gè)。先給大家看我的最終效果。后面再一點(diǎn)點(diǎn)拆解代碼。
為什么想著寫這個(gè)功能呢,主要在于拼圖驗(yàn)證碼在前端這里會(huì)比較復(fù)雜并且深入。相比文字拼寫,12306的圖片驗(yàn)證碼都沒有拼圖驗(yàn)證碼對(duì)前端的要求來的復(fù)雜,和難。
我總結(jié)下知識(shí)點(diǎn):
1、彈窗功能
2、彈窗基于元素定位
3、元素拖動(dòng)
4、canvas繪圖
5、基礎(chǔ)邏輯
一、彈窗和彈窗組件
抱歉,這里我偷懶了直接用了elementUI的el-popover組件,所以小伙伴不懂的直接看elementUI官網(wǎng)的說明。
我個(gè)人也研究和編寫了這塊的組件功能(基于popper.js)
二、編寫基礎(chǔ)結(jié)構(gòu)
這塊屬于html的基礎(chǔ)內(nèi)容,也就標(biāo)題黨了
三、canvas繪制圖片
1、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進(jìn)行重新繪制 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); };復(fù)制代碼
這里我繪制了兩個(gè)canvas,因?yàn)橐粋€(gè)是背景一個(gè)是滑塊
核心在于
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); }; 復(fù)制代碼
2、canvas繪制滑塊部分
就是這個(gè)圖,這個(gè)有一些知識(shí)點(diǎn),不難,但是很復(fù)雜。
代碼部分:
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); //修飾,沒有會(huì)看不出效果 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"; }復(fù)制代碼
解釋下:
參數(shù)是傳入canvas對(duì)象
x,y軸數(shù)據(jù)
剪切還是填充的canvas函數(shù)(fill,clip)
繪制難點(diǎn):(很重要,不然你沒法理解它怎么繪制的)
繪制主要是需要理解這里的繪制是根據(jù)你設(shè)置一個(gè)起始點(diǎn)坐標(biāo),然后你繪制第二次的時(shí)候線就會(huì)連接到第二個(gè)點(diǎn),依次連接最后回到原點(diǎn)就形成一個(gè)完整的圖形。
用的是arc參數(shù),主要是看這個(gè)圖
fill是用于填充繪制的部分,clip是裁剪出繪制的部分,利用這個(gè)就可以出現(xiàn)一個(gè)扣掉的圖片和一個(gè)裁剪出來的圖片。
完成之后就是我的那個(gè)函數(shù)了。大家可以直接拿去用。
3、讓元素跟隨鼠標(biāo)點(diǎn)擊之后滑動(dòng)
這里其實(shí)原理非常簡(jiǎn)單,就是有一個(gè)注意點(diǎn)。
原理:
鼠標(biāo)點(diǎn)擊之后記錄當(dāng)前坐標(biāo),然后隨著(mousemove)滾動(dòng)的時(shí)候修改元素的left和top值就行了。
還有一點(diǎn)就是鼠標(biāo)快速滑動(dòng)會(huì)導(dǎo)致丟失滑動(dòng)效果,這里需要用document,不能是元素級(jí)別的監(jiān)聽。
元素上面我只需要鑒定按下mousedown
代碼:
//鼠標(biāo)按下 drag(e) { console.log("鼠標(biāo)按下", e); let dom=e.target; //dom元素 let slider=document.querySelector("#sliderBlock"); //滑塊dom const downCoordinate={ x: e.x, y: e.y }; //正確的滑塊數(shù)據(jù) let checkx=Number(this.slider.mx) - Number(this.slider.bx); //x軸數(shù)據(jù) 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; //允許正負(fù)誤差1 if ((max >=x && x >=min) || x===checkx) { console.log("滑動(dòng)解鎖成功"); this.puzzle=true; this.tips="驗(yàn)證成功"; setTimeout(()=> { this.visible=false; }, 500); } else { console.log("拼圖位置不正確"); this.tips="驗(yàn)證失敗,請(qǐng)重試"; this.puzzle=false; this.canvasInit(); } }; document.addEventListener("mousemove", move); document.addEventListener("mouseup", up); }復(fù)制代碼
4、總結(jié)
核心點(diǎn)比較多,寫過之后發(fā)現(xiàn)不難,關(guān)鍵在于去寫
個(gè)人該頁面git地址:https://github.com/ht-sauce/dream
該頁面處于項(xiàng)目的
路由訪問為:http://localhost:8080/consumer
5、完整的頁面代碼
<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> <!--驗(yàn)證碼彈窗--> <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="關(guān)閉驗(yàn)證碼" @click="visible=false" class="el-icon-circle-close" ></span> <span title="刷新驗(yàn)證碼" @click="canvasInit" class="el-icon-refresh-left" ></span> </div> </div> </el-popover> </div> </template> <script> export default { name: "login", data() { return { tips: "拖動(dòng)左邊滑塊完成上方拼圖", logindata: { userName: "", password: "", verificationCode: "" }, rules: {}, visible: false, //滑塊x軸數(shù)據(jù) slider: { mx: 0, bx: 0 }, //拼圖是否正確 puzzle: false }; }, watch: { visible(e) { if (e===true) { this.canvasInit(); this.puzzle=false; } } }, beforeCreate() {}, created() {}, beforeMount() {}, mounted() {}, methods: { //拼圖驗(yàn)證碼初始化 canvasInit() { //生成指定區(qū)間的隨機(jī)數(shù) 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); }, //鼠標(biāo)按下 drag(e) { console.log("鼠標(biāo)按下", e); let dom=e.target; //dom元素 let slider=document.querySelector("#sliderBlock"); //滑塊dom const downCoordinate={ x: e.x, y: e.y }; //正確的滑塊數(shù)據(jù) let checkx=Number(this.slider.mx) - Number(this.slider.bx); //x軸數(shù)據(jù) 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; //允許正負(fù)誤差1 if ((max >=x && x >=min) || x===checkx) { console.log("滑動(dòng)解鎖成功"); this.puzzle=true; this.tips="驗(yàn)證成功"; setTimeout(()=> { this.visible=false; }, 500); } else { console.log("拼圖位置不正確"); this.tips="驗(yàn)證失敗,請(qǐng)重試"; 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進(jìn)行重新繪制 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); //修飾,沒有會(huì)看不出效果 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"> #login { display: flex; flex-flow: row; justify-content: flex-end; align-items: center; width: 100%; height: 100%; background-image: url("../assets/images/back.jpg"); background-size: 100% 100%; .loginFrom { width: 300px; margin-top: -10vw; margin-right: 10vw; .login-item { display: flex; justify-content: center; align-items: center; } .login-title { color: #ffffff; font-size: 16px; margin-bottom: 10px; } .login-bottom { margin-top: 15px; } .login-bottom:hover { background: rgba(28, 136, 188, 0.5); } .login-bottom:active { background: rgba(228, 199, 200, 0.5); } /deep/.login-inputorbuttom { height: 40px; width: 300px; background: rgba(57, 108, 158, 0.5); border-radius: 20px; border: #396c9e 1px solid; font-size: 14px; color: #ffffff; .el-input--small, .el-input__inner { line-height: 43px; border: none; color: #ffffff; font-size: 14px; height: 40px; border-radius: 20px; background: transparent; text-align: center; } .el-input__icon { line-height: 40px; font-size: 16px; } } } } /*該樣式最終是以彈窗插入*/ .sliding-pictures { width: 100%; .vimg { width: 100%; height: 170px; #codeImg, #sliderBlock { padding: 7px 7px 0 7px; width: inherit; height: inherit; } #codeImg { //display: none; } #sliderBlock { position: absolute; z-index: 4000; } } .slider { width: 100%; height: 65px; border-bottom: #c7c9d0 1px solid; display: flex; align-items: center; justify-content: flex-start; .track { margin-left: 7px; width: 286px; height: 38px; background: rgba(28, 136, 188, 0.5); border-radius: 25px; font-size: 14px; line-height: 38px; padding-right: 15px; padding-left: 70px; } .pintuTrue { background: #67c23a; color: #ffffff; } .button { position: absolute; width: 50px; height: 50px; line-height: 48px; background: #ffffff; box-shadow: #b9bdc8 0 0 3px; border-radius: 50%; left: 7px; text-align: center; font-size: 28px; color: #3e5d8b; &:hover { color: #2181bd; } } } .operation { width: 100%; height: 40px; > span { color: #9fa3ac; display: inline-block; width: 40px; font-size: 25px; line-height: 40px; text-align: center; &:hover { background: #e2e8f5; } } } } </style>
最后
喜歡的可以點(diǎn)個(gè)關(guān)注嗎,小可樂也不容易呢
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。