时候玩的拼图游戏,大家好记得吗?有没有想过自己做一个这样的游戏呢,特别一些特殊的日子,送给他(她)呢。为了实现大家的想法,小猿圈web前端讲师就讲讲利用HTML5拖拽功能实现的拼图游戏
具体代码如下所示:
<!--代码如下,最下面给出了我测试用的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事件的默认行为,该行为默认无法将数据或者元素放置到其他元素
}
box[i].ondrop=function(event){
thenImage=box[i].childNodes[0];
box[i].appendChild(nowImage);
nowImageBox.appendChild(thenImage);
}
}
</script>
</body>
</html>
以上就是小猿圈web前端讲师针对HTML5拖拽功能实现的拼图游戏的讲解,你有想过学习前端开发嘛,想学习前端可以到小猿圈去直接观看,这里面从基础到实战的所有学习资料,可以满足你提升自己,为你实现编程梦想的起点。
文收录本头条号已发表的经典文章,并持续更新中,请大家关注。
ES6/JavaScript的箭头函数是怎样将this与作用域联系起来的,及什么是this词法
你所不知道的JavaScript动态作用域,它与词法作用域有什么区别和关联?
你写过JavaScript的类吗?你知道JavaScript的类的机制是什么样的吗?
JavaScript中this是一种什么机制?又该如何的避免的二个坑?
高级JavaScript程序员必须掌握的一个问题:如何解决回调的信任问题
JavaScript异步:请先理解JavaScript的事件循环
使用JavaScript+HTML5 Canvas实现的界面元素截屏功能
区分初级和高级JavaScript程序员:是否理解JavaScript闭包(Closure)
在JavaScript中使用LINQ:linq.js插件简介
Processing.js和P5.js的功能简介和区别
前端开发框架选型:Angular2/React/jQuery
JavaScript语言的三种编程范式比较
JavaScript的那些坑,你遇到过吗?又该如何避免?
常见面试题JavaScript语言中四种函数调用方式实例分析
Ionic2、React、NativeScript比较
JavaScript Promise实现任务的灵活调度
一个支持PDF/文本/二进制文件的实用的客户端数字签名框架
jQuery实现纯HTML/CSS的拼图游戏
阻止广告:JavaScript实现Chrome插件实例分析
JavaScript实现三维轨道灯动画
使用 Angular 构建 JavaScript 应用
利用jQuery实现多个ajax请求等待
JavaScript代码重构
SQL注入和跨站点脚本实例分析
仿生智能算法之路径智能寻找的HTML及JS动画实现代码
HTML5动画背后的数学-粒子群仿生算法简介
HTML5及JS实现粒子群仿生动画代码
NodeJS、Java和PHP性能考量和若干参考结论
WebGL、Asm.js和WebAssembly概念简介
WebAssembly工作原理和JavaScript性能对比
Async之流程控制
由:之前看哔哩哔哩官网登录的时候有一个拼图验证码,很好奇怎么去实现。然后就想着自己弄一个。先给大家看我的最终效果。后面再一点点拆解代码。
为什么想着写这个功能呢,主要在于拼图验证码在前端这里会比较复杂并且深入。相比文字拼写,12306的图片验证码都没有拼图验证码对前端的要求来的复杂,和难。
我总结下知识点:
1、弹窗功能
2、弹窗基于元素定位
3、元素拖动
4、canvas绘图
5、基础逻辑
一、弹窗和弹窗组件
抱歉,这里我偷懒了直接用了elementUI的el-popover组件,所以小伙伴不懂的直接看elementUI官网的说明。
我个人也研究和编写了这块的组件功能(基于popper.js)
二、编写基础结构
这块属于html的基础内容,也就标题党了
三、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进行重新绘制
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);
};
复制代码
2、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是裁剪出绘制的部分,利用这个就可以出现一个扣掉的图片和一个裁剪出来的图片。
完成之后就是我的那个函数了。大家可以直接拿去用。
3、让元素跟随鼠标点击之后滑动
这里其实原理非常简单,就是有一个注意点。
原理:
鼠标点击之后记录当前坐标,然后随着(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);
}复制代码
4、总结
核心点比较多,写过之后发现不难,关键在于去写
个人该页面git地址:https://github.com/ht-sauce/dream
该页面处于项目的
路由访问为: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>
<!--验证码弹窗-->
<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">
#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>
最后
喜欢的可以点个关注吗,小可乐也不容易呢
*请认真填写需求信息,我们会在24小时内与您取得联系。