|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,20 @@ |
||||
import Vue from 'vue' |
||||
import Main from './main.vue' |
||||
import {PopupManager} from 'element-ui/src/utils/popup' |
||||
|
||||
let Constructor = Vue.extend(Main) |
||||
|
||||
const PuzzleVerify = function ({left = 0, top = 0} = {}) { |
||||
const instance = new Constructor({data: {positionLeft: left}}) |
||||
instance.positionTop = top - Math.round((instance.height + 130) / 2) |
||||
instance.$mount() |
||||
document.body.appendChild(instance.$el) |
||||
instance.$el.style.zIndex = PopupManager.nextZIndex() |
||||
instance.visible = true |
||||
return new Promise((resolve, reject) => { |
||||
instance.resolve = resolve |
||||
instance.reject = reject |
||||
}) |
||||
} |
||||
|
||||
export default PuzzleVerify |
||||
@ -1,370 +0,0 @@ |
||||
<template> |
||||
<div class="puzzle-container"> |
||||
<div class="puzzle-header"> |
||||
<span class="puzzle-header-title">拖动下方滑块完成拼图</span> |
||||
<div class="puzzle-header-tool"> |
||||
<el-button @click="refreshImg" icon="el-icon-refresh" size="small" type="text"/> |
||||
<el-button @click="$emit('close')" icon="el-icon-close" size="small" type="text"/> |
||||
</div> |
||||
</div> |
||||
|
||||
<div :style="'width:' + width + 'px'" class="puzzle-view"> |
||||
<img :src="imgRandom" |
||||
:style="'width:' + width + 'px;height:' + height + 'px;'" |
||||
id="scream" |
||||
ref="scream" |
||||
/> |
||||
<canvas :height="height" :width="width" id="puzzle-box" ref="puzzleBox"/> |
||||
<div :style="'left:' + left_Num+ 'px'" class="puzzle-lost-box"> |
||||
<canvas :height="height" :width="width" id="puzzle-shadow" ref="puzzleShadow"/> |
||||
<canvas :height="height" :width="width" id="puzzle-lost" ref="puzzleLost"/> |
||||
</div> |
||||
<span :class="{'hide':displayTips}" class="verify-result" ref="verTips"> |
||||
<span class="success" v-show="verification"> |
||||
<i class="el-icon-circle-check"/>验证通过 |
||||
</span> |
||||
<span class="error" v-show="!verification"> |
||||
<i class="el-icon-circle-close"/>验证失败 |
||||
</span> |
||||
</span> |
||||
</div> |
||||
|
||||
<div class="slider-container"> |
||||
<div class="slider-bar"/> |
||||
<el-button @mousedown.native.prevent.stop="startMove" |
||||
circle |
||||
class="slider-btn" |
||||
icon="el-icon-rank" |
||||
ref="sliderBtn" |
||||
type="success"/> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
/* |
||||
* https://github.com/Kevin-269581661/vue-puzzle-verification |
||||
* 网上搬的例子,实际开发建议用极验 |
||||
* */ |
||||
export default { |
||||
name: "PuzzleVerify", |
||||
props: { |
||||
// 画布图片的尺寸 |
||||
width: { |
||||
type: Number, |
||||
default: 260 |
||||
}, |
||||
height: { |
||||
type: Number, |
||||
default: 120 |
||||
}, |
||||
// 图集 |
||||
puzzleImgList: { |
||||
type: Array, |
||||
default: () => [] |
||||
}, |
||||
// 滑块的大小 |
||||
blockSize: { |
||||
type: Number, |
||||
default: 40 |
||||
}, |
||||
// 误差 |
||||
deviation: { |
||||
type: Number, |
||||
default: 4 |
||||
}, |
||||
// 滑块随机出现的范围 |
||||
padding: { |
||||
type: Number, |
||||
default: 20 |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
moveStart: null, |
||||
displayTips: false, |
||||
verification: false, |
||||
randomX: null, |
||||
randomY: null, |
||||
imgRandom: "", |
||||
left_Num: 0 |
||||
} |
||||
}, |
||||
methods: { |
||||
//刷新 |
||||
refreshImg() { |
||||
let imgRandomIndex = Math.round(Math.random() * (this.puzzleImgList.length - 1)) |
||||
this.imgRandom = this.puzzleImgList[imgRandomIndex] |
||||
this.initCanvas() |
||||
}, |
||||
//画布初始化 |
||||
initCanvas() { |
||||
this.clearCanvas() |
||||
let w = this.width |
||||
let h = this.height |
||||
let PL_Size = this.blockSize |
||||
let padding = this.padding |
||||
let MinN_X = padding + PL_Size |
||||
let MaxN_X = w - padding - PL_Size - PL_Size / 6 |
||||
let MaxN_Y = padding |
||||
let MinN_Y = h - padding - PL_Size - PL_Size / 6 |
||||
this.randomX = Math.round(Math.random() * (MaxN_X - PL_Size) + MinN_X) |
||||
this.randomY = Math.round(Math.random() * MaxN_Y + MinN_Y) |
||||
let X = this.randomX |
||||
let Y = this.randomY |
||||
this.left_Num = -X + 10 |
||||
let d = PL_Size / 3 |
||||
|
||||
let c = this.$refs.puzzleBox |
||||
let c_l = this.$refs.puzzleLost |
||||
let c_s = this.$refs.puzzleShadow |
||||
let ctx = c.getContext("2d") |
||||
let ctx_l = c_l.getContext("2d") |
||||
let ctx_s = c_s.getContext("2d") |
||||
ctx.globalCompositeOperation = "xor" |
||||
ctx.shadowBlur = 10 |
||||
ctx.shadowColor = "#fff" |
||||
ctx.shadowOffsetX = 3 |
||||
ctx.shadowOffsetY = 3 |
||||
ctx.fillStyle = "rgba(0,0,0,0.7)" |
||||
ctx.beginPath() |
||||
ctx.lineWidth = 1 |
||||
ctx.strokeStyle = "rgba(0,0,0,0)" |
||||
|
||||
ctx.moveTo(X, Y) |
||||
ctx.lineTo(X + d, Y) |
||||
ctx.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y) |
||||
ctx.lineTo(X + 3 * d, Y) |
||||
ctx.lineTo(X + 3 * d, Y + d) |
||||
ctx.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d) |
||||
ctx.lineTo(X + 3 * d, Y + 3 * d) |
||||
ctx.lineTo(X, Y + 3 * d) |
||||
|
||||
ctx.closePath() |
||||
ctx.stroke() |
||||
ctx.fill() |
||||
|
||||
let img = new Image() |
||||
img.src = this.imgRandom |
||||
|
||||
img.onload = function () { |
||||
ctx_l.drawImage(img, 0, 0, w, h) |
||||
} |
||||
ctx_l.beginPath() |
||||
ctx_l.strokeStyle = "rgba(0,0,0,0)" |
||||
|
||||
ctx_l.moveTo(X, Y) |
||||
ctx_l.lineTo(X + d, Y) |
||||
ctx_l.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y) |
||||
ctx_l.lineTo(X + 3 * d, Y) |
||||
ctx_l.lineTo(X + 3 * d, Y + d) |
||||
ctx_l.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d) |
||||
ctx_l.lineTo(X + 3 * d, Y + 3 * d) |
||||
ctx_l.lineTo(X, Y + 3 * d) |
||||
|
||||
ctx_l.closePath() |
||||
ctx_l.stroke() |
||||
ctx_l.shadowBlur = 10 |
||||
ctx_l.shadowColor = "black" |
||||
ctx_l.clip() |
||||
ctx_s.beginPath() |
||||
ctx_s.lineWidth = 1 |
||||
ctx_s.strokeStyle = "rgba(0,0,0,0)" |
||||
|
||||
ctx_s.moveTo(X, Y) |
||||
ctx_s.lineTo(X + d, Y) |
||||
ctx_s.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y) |
||||
ctx_s.lineTo(X + 3 * d, Y) |
||||
ctx_s.lineTo(X + 3 * d, Y + d) |
||||
ctx_s.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d) |
||||
ctx_s.lineTo(X + 3 * d, Y + 3 * d) |
||||
ctx_s.lineTo(X, Y + 3 * d) |
||||
|
||||
ctx_s.closePath() |
||||
ctx_s.stroke() |
||||
ctx_s.shadowBlur = 20 |
||||
ctx_s.shadowColor = "black" |
||||
ctx_s.fill() |
||||
}, |
||||
//通过重置画布尺寸清空画布,这种方式更彻底 |
||||
clearCanvas() { |
||||
let c = this.$refs.puzzleBox |
||||
let c_l = this.$refs.puzzleLost |
||||
let c_s = this.$refs.puzzleShadow |
||||
c.setAttribute("height", c.getAttribute("height")) |
||||
c_l.setAttribute("height", c.getAttribute("height")) |
||||
c_s.setAttribute("height", c.getAttribute("height")) |
||||
}, |
||||
//按住滑块后初始化移动监听,记录初始位置 |
||||
startMove(e) { |
||||
this.moveStart = e.pageX |
||||
this.addMouseMoveListener() |
||||
}, |
||||
//滑块移动 |
||||
moving(e) { |
||||
let moveX = e.pageX |
||||
let d = moveX - this.moveStart |
||||
if (this.moveStart === null |
||||
|| d < 0 |
||||
|| d > this.width - this.padding - this.blockSize) return |
||||
this.$refs.sliderBtn.$el.style.left = d + "px" |
||||
this.$refs.sliderBtn.$el.style.transition = "inherit" |
||||
this.$refs.puzzleLost.style.left = d + "px" |
||||
this.$refs.puzzleLost.style.transition = "inherit" |
||||
this.$refs.puzzleShadow.style.left = d + "px" |
||||
this.$refs.puzzleShadow.style.transition = "inherit" |
||||
}, |
||||
//移动结束,验证并回调 |
||||
moveEnd(e) { |
||||
let moveEnd_X = e.pageX - this.moveStart |
||||
let ver_Num = this.randomX - 10 |
||||
let Min_left = ver_Num - this.deviation |
||||
let Max_left = ver_Num + this.deviation |
||||
if (this.moveStart !== null) { |
||||
if (Max_left > moveEnd_X && moveEnd_X > Min_left) { |
||||
this.displayTips = true |
||||
this.verification = true |
||||
setTimeout(() => { |
||||
this.displayTips = false |
||||
this.initCanvas() |
||||
this.$emit('success') |
||||
}, 500) |
||||
} |
||||
else { |
||||
this.displayTips = true |
||||
this.verification = false |
||||
setTimeout(() => { |
||||
this.displayTips = false |
||||
this.initCanvas() |
||||
this.$emit('fail') |
||||
}, 800) |
||||
} |
||||
} |
||||
setTimeout(() => { |
||||
this.$refs.sliderBtn.$el.style.left = '0' |
||||
this.$refs.sliderBtn.$el.style.transition = "left 0.5s" |
||||
this.$refs.puzzleLost.style.left = '0' |
||||
this.$refs.puzzleLost.style.transition = "left 0.5s" |
||||
this.$refs.puzzleShadow.style.left = '0' |
||||
this.$refs.puzzleShadow.style.transition = "left 0.5s" |
||||
}, 400) |
||||
this.moveStart = null |
||||
this.removeMouseMoveListener() |
||||
}, |
||||
//全局绑定滑块移动与滑动结束,移动过程中鼠标可在页面任何位置 |
||||
addMouseMoveListener() { |
||||
document.addEventListener("mousemove", this.moving) |
||||
document.addEventListener("mouseup", this.moveEnd) |
||||
}, |
||||
removeMouseMoveListener() { |
||||
document.removeEventListener("mousemove", this.moving) |
||||
document.removeEventListener("mouseup", this.moveEnd) |
||||
} |
||||
}, |
||||
created() { |
||||
// 随机显示一张图片 |
||||
let imgRandomIndex = Math.round(Math.random() * (this.puzzleImgList.length - 1)) |
||||
this.imgRandom = this.puzzleImgList[imgRandomIndex] |
||||
}, |
||||
mounted() { |
||||
this.$nextTick(() => this.initCanvas()) |
||||
}, |
||||
|
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss"> |
||||
.puzzle-container { |
||||
position: relative; |
||||
display: inline-block; |
||||
padding: 0 15px 28px; |
||||
border: 1px solid #ddd; |
||||
background: #ffffff; |
||||
border-radius: 10px; |
||||
|
||||
.puzzle-header { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
margin: 5px 0; |
||||
|
||||
.puzzle-header-title { |
||||
font-size: 13px; |
||||
color: #909399 |
||||
} |
||||
} |
||||
|
||||
.puzzle-view { |
||||
position: relative; |
||||
overflow: hidden; |
||||
|
||||
.puzzle-lost-box { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
} |
||||
|
||||
canvas { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
z-index: 22; |
||||
} |
||||
|
||||
.verify-result { |
||||
position: absolute; |
||||
left: 0; |
||||
bottom: -22px; |
||||
background: rgba(255, 255, 255, 1); |
||||
height: 22px; |
||||
line-height: 22px; |
||||
font-size: 12px; |
||||
width: 100%; |
||||
transition: bottom 0.4s; |
||||
|
||||
.success { |
||||
color: #67C23A |
||||
} |
||||
|
||||
.error { |
||||
color: #F56C6C |
||||
} |
||||
|
||||
i { |
||||
margin: 0 10px; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
&.hide { |
||||
bottom: 0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.slider-container { |
||||
position: relative; |
||||
margin: 10px auto 0; |
||||
min-height: 15px; |
||||
|
||||
.slider-bar { |
||||
height: 10px; |
||||
border: 1px solid #c3c3c3; |
||||
border-radius: 5px; |
||||
background: #e4e4e4; |
||||
box-shadow: 0 1px 1px rgba(12, 10, 10, 0.2) inset; |
||||
position: relative; |
||||
top: 7px; |
||||
} |
||||
|
||||
.slider-btn { |
||||
position: absolute; |
||||
width: 44px; |
||||
height: 44px; |
||||
left: 0; |
||||
top: -10px; |
||||
z-index: 12; |
||||
transition: inherit; |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,519 @@ |
||||
<template> |
||||
<transition :name="transition" @after-leave="handleAfterLeave"> |
||||
<div |
||||
v-show="visible" |
||||
class="puzzle-container" |
||||
:class="{'puzzle-shake':stat==='fail'}" |
||||
:style="`left: ${positionLeft}px;top: ${positionTop}px;`" |
||||
> |
||||
<div class="puzzle-view" :style="`width:${width}px;height:${height}px;`"> |
||||
<div v-show="!image.loading" style="width: 100%;height: 100%"> |
||||
<img :src="image.src"/> |
||||
<canvas ref="puzzleBox" :height="height" :width="width"/> |
||||
<div class="puzzle-lost-box" :style="`left:${leftNum}px`"> |
||||
<canvas ref="puzzleShadow" :style="sliderStyle" :height="height" :width="width"/> |
||||
<canvas ref="puzzleLost" :style="sliderStyle" :height="height" :width="width"/> |
||||
</div> |
||||
</div> |
||||
<transition name="fade"> |
||||
<div v-show="image.loading" class="puzzle-image-loading"> |
||||
<div class="puzzle-image-loading__icon"/> |
||||
<div class="puzzle-image-loading__text">加载中...</div> |
||||
</div> |
||||
</transition> |
||||
<div :class="resultTipClass"> |
||||
{{stat==='success'?'验证通过':'请正确拼合图像'}} |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="slider-container"> |
||||
<div class="slider-track"> |
||||
<div class="slider-tip" :style="stat==='ready'?'opacity: 1':''">拖动滑块完成拼图</div> |
||||
</div> |
||||
<div class="slider-btn" :style="sliderBtnStyle" @mousedown.prevent.stop="moveStart"/> |
||||
</div> |
||||
|
||||
<div class="puzzle-footer"> |
||||
<div class="puzzle-small"> |
||||
<a class="puzzle-text-container puzzle-close" @click="close"> |
||||
<div class="puzzle-text-tip">关闭验证</div> |
||||
</a> |
||||
<a class="puzzle-text-container puzzle-refresh" @click="refresh"> |
||||
<div class="puzzle-text-tip">刷新验证</div> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</transition> |
||||
</template> |
||||
|
||||
<script> |
||||
/* |
||||
* https://github.com/Kevin-269581661/vue-puzzle-verification |
||||
* 网上搬的例子,实际开发建议用极验 |
||||
* */ |
||||
export default { |
||||
name: "PuzzleVerify", |
||||
props: { |
||||
//动画名称,具体请查看transition.scss |
||||
transition: { |
||||
type: String, |
||||
default: 'scale' |
||||
}, |
||||
// 画布图片的尺寸 |
||||
width: { |
||||
type: Number, |
||||
default: 260 |
||||
}, |
||||
height: { |
||||
type: Number, |
||||
default: 160 |
||||
}, |
||||
// 滑块的大小 |
||||
blockSize: { |
||||
type: Number, |
||||
default: 40 |
||||
}, |
||||
// 误差 |
||||
deviation: { |
||||
type: Number, |
||||
default: 4 |
||||
}, |
||||
// 滑块随机出现的范围 |
||||
padding: { |
||||
type: Number, |
||||
default: 80 |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
visible: false, |
||||
stat: 'ready',//ready,move,success,fail,error |
||||
positionLeft: 0, |
||||
positionTop: 0, |
||||
resolve: () => ({}), |
||||
reject: () => ({}), |
||||
moveStartAtX: null, |
||||
randomX: null, |
||||
randomY: null, |
||||
leftNum: 0, |
||||
image: { |
||||
src: '', |
||||
loading: true |
||||
}, |
||||
sliderStyle: { |
||||
left: '0', |
||||
transition: 'inherit' |
||||
} |
||||
} |
||||
}, |
||||
computed: { |
||||
resultTipClass() { |
||||
const showResultTip = ['success', 'fail'].includes(this.stat) |
||||
return ['verify-result', `verify-${this.stat}`, showResultTip ? 'show-result' : ''] |
||||
}, |
||||
sliderBtnStyle() { |
||||
return { |
||||
...this.sliderStyle, |
||||
'background-position': this.stat === 'move' ? '0 31.0992%' : '0 11.79625%' |
||||
} |
||||
} |
||||
}, |
||||
methods: { |
||||
close() { |
||||
this.visible = false |
||||
this.clear() |
||||
this.reject() |
||||
}, |
||||
refresh() { |
||||
this.initImage().then(() => this.initCanvas()) |
||||
}, |
||||
initCanvas() { |
||||
this.clear() |
||||
let MinX = this.padding + this.blockSize |
||||
let MaxX = this.width - this.padding - 2 * this.blockSize - this.blockSize / 6 |
||||
let MaxY = this.padding |
||||
let MinY = this.height - this.padding - this.blockSize - this.blockSize / 6 |
||||
this.randomX = Math.round(Math.random() * MaxX + MinX) |
||||
this.randomY = Math.round(Math.random() * MaxY + MinY) |
||||
let X = this.randomX |
||||
let Y = this.randomY |
||||
this.leftNum = -X + 10 |
||||
let d = this.blockSize / 3 |
||||
|
||||
let c = this.$refs.puzzleBox |
||||
let c_l = this.$refs.puzzleLost |
||||
let c_s = this.$refs.puzzleShadow |
||||
let ctx = c.getContext("2d") |
||||
let ctx_l = c_l.getContext("2d") |
||||
let ctx_s = c_s.getContext("2d") |
||||
ctx.globalCompositeOperation = "xor" |
||||
ctx.shadowBlur = 10 |
||||
ctx.shadowColor = "#fff" |
||||
ctx.shadowOffsetX = 3 |
||||
ctx.shadowOffsetY = 3 |
||||
ctx.fillStyle = "rgba(0,0,0,0.7)" |
||||
ctx.beginPath() |
||||
ctx.lineWidth = 1 |
||||
ctx.strokeStyle = "rgba(0,0,0,0)" |
||||
|
||||
ctx.moveTo(X, Y) |
||||
ctx.lineTo(X + d, Y) |
||||
ctx.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y) |
||||
ctx.lineTo(X + 3 * d, Y) |
||||
ctx.lineTo(X + 3 * d, Y + d) |
||||
ctx.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d) |
||||
ctx.lineTo(X + 3 * d, Y + 3 * d) |
||||
ctx.lineTo(X, Y + 3 * d) |
||||
|
||||
ctx.closePath() |
||||
ctx.stroke() |
||||
ctx.fill() |
||||
|
||||
this.drawImage(img => ctx_l.drawImage(img, 0, 0, this.width, this.height)) |
||||
|
||||
ctx_l.beginPath() |
||||
ctx_l.strokeStyle = "rgba(0,0,0,0)" |
||||
|
||||
ctx_l.moveTo(X, Y) |
||||
ctx_l.lineTo(X + d, Y) |
||||
ctx_l.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y) |
||||
ctx_l.lineTo(X + 3 * d, Y) |
||||
ctx_l.lineTo(X + 3 * d, Y + d) |
||||
ctx_l.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d) |
||||
ctx_l.lineTo(X + 3 * d, Y + 3 * d) |
||||
ctx_l.lineTo(X, Y + 3 * d) |
||||
|
||||
ctx_l.closePath() |
||||
ctx_l.stroke() |
||||
ctx_l.shadowBlur = 10 |
||||
ctx_l.shadowColor = "black" |
||||
ctx_l.clip() |
||||
ctx_s.beginPath() |
||||
ctx_s.lineWidth = 1 |
||||
ctx_s.strokeStyle = "rgba(0,0,0,0)" |
||||
|
||||
ctx_s.moveTo(X, Y) |
||||
ctx_s.lineTo(X + d, Y) |
||||
ctx_s.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y) |
||||
ctx_s.lineTo(X + 3 * d, Y) |
||||
ctx_s.lineTo(X + 3 * d, Y + d) |
||||
ctx_s.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d) |
||||
ctx_s.lineTo(X + 3 * d, Y + 3 * d) |
||||
ctx_s.lineTo(X, Y + 3 * d) |
||||
|
||||
ctx_s.closePath() |
||||
ctx_s.stroke() |
||||
ctx_s.shadowBlur = 20 |
||||
ctx_s.shadowColor = "black" |
||||
ctx_s.fill() |
||||
}, |
||||
initImage() { |
||||
const imageListTemp = [ |
||||
"https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4018557288,1217151095&fm=26&gp=0.jpg", |
||||
"https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3252521864,872614242&fm=26&gp=0.jpg", |
||||
"https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2534506313,1688529724&fm=26&gp=0.jpg" |
||||
] |
||||
this.image.loading = true |
||||
let imgRandomIndex = Math.round(Math.random() * (imageListTemp.length - 1)) |
||||
this.image.src = imageListTemp[imgRandomIndex] |
||||
return new Promise(resolve => resolve()) |
||||
}, |
||||
drawImage(fun) { |
||||
let img = new Image() |
||||
img.src = this.image.src |
||||
img.onload = () => { |
||||
fun(img) |
||||
this.image.loading = false |
||||
} |
||||
}, |
||||
clear() { |
||||
this.stat = 'ready' |
||||
let c = this.$refs.puzzleBox |
||||
let c_l = this.$refs.puzzleLost |
||||
let c_s = this.$refs.puzzleShadow |
||||
c.setAttribute("height", c.getAttribute("height")) |
||||
c_l.setAttribute("height", c.getAttribute("height")) |
||||
c_s.setAttribute("height", c.getAttribute("height")) |
||||
}, |
||||
moveStart(e) { |
||||
this.moveStartAtX = e.pageX |
||||
this.addListener() |
||||
}, |
||||
moving(e) { |
||||
this.stat = 'move' |
||||
const distance = e.pageX - this.moveStartAtX |
||||
if (this.moveStartAtX === null |
||||
|| distance < 0 |
||||
|| distance > this.width - this.blockSize) { |
||||
return |
||||
} |
||||
this.sliderStyle.left = distance + "px" |
||||
this.sliderStyle.transition = "inherit" |
||||
}, |
||||
moveEnd(e) { |
||||
const distance = e.pageX - this.moveStartAtX |
||||
const ver_Num = this.randomX - 10 |
||||
const minLeft = ver_Num - this.deviation |
||||
const maxLeft = ver_Num + this.deviation |
||||
if (this.moveStartAtX !== null) { |
||||
const success = maxLeft > distance && distance > minLeft |
||||
this.stat = success ? 'success' : 'fail' |
||||
setTimeout(() => { |
||||
this.stat = 'ready' |
||||
if (!success) return this.initCanvas() |
||||
this.resolve() |
||||
this.close() |
||||
}, success ? 500 : 1000) |
||||
} |
||||
setTimeout(() => { |
||||
this.sliderStyle.left = '0' |
||||
this.sliderStyle.transition = "left 0.5s" |
||||
}, 1000) |
||||
this.moveStartAtX = null |
||||
this.removeListener() |
||||
}, |
||||
addListener() { |
||||
if (this.image.loading) return |
||||
document.addEventListener("mousemove", this.moving) |
||||
document.addEventListener("mouseup", this.moveEnd) |
||||
}, |
||||
removeListener() { |
||||
document.removeEventListener("mousemove", this.moving) |
||||
document.removeEventListener("mouseup", this.moveEnd) |
||||
}, |
||||
handleAfterLeave() { |
||||
this.$destroy(true) |
||||
this.$el.parentNode.removeChild(this.$el) |
||||
} |
||||
}, |
||||
mounted() { |
||||
this.initImage().then(() => this.initCanvas()) |
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss"> |
||||
.puzzle-container { |
||||
position: absolute; |
||||
box-shadow: 0 0 10px #cccccc; |
||||
border: 1px solid #cccccc; |
||||
background: #ffffff; |
||||
|
||||
.puzzle-text-container { |
||||
&:hover { |
||||
overflow: visible !important; |
||||
} |
||||
|
||||
.puzzle-text-tip { |
||||
position: absolute; |
||||
top: -32px; |
||||
left: 10px; |
||||
border-radius: 2px; |
||||
padding: 0 4px; |
||||
height: 22px; |
||||
min-width: 50px; |
||||
line-height: 22px; |
||||
background-color: #5F5F5F; |
||||
white-space: nowrap; |
||||
font-size: 12px; |
||||
text-align: center; |
||||
color: white; |
||||
|
||||
&::before { |
||||
display: block; |
||||
position: absolute; |
||||
bottom: -6px; |
||||
left: 0; |
||||
content: ''; |
||||
border-style: solid; |
||||
border-width: 4px 6px; |
||||
border-color: #5F5F5F transparent transparent #5F5F5F; |
||||
width: 0; |
||||
height: 0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.puzzle-view { |
||||
position: relative; |
||||
overflow: hidden; |
||||
margin: 10px 10px 0; |
||||
|
||||
.puzzle-image-loading { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
background-color: #e5e5e5; |
||||
padding-top: 10%; |
||||
|
||||
&__icon { |
||||
background-size: 764.70588%; |
||||
background-position: 0 70.70218%; |
||||
margin: 11% auto 10px; |
||||
width: 34px; |
||||
height: 26px; |
||||
overflow: hidden; |
||||
background-repeat: no-repeat; |
||||
background-image: url(~@/assets/images/sprite.1.2.4.png); |
||||
} |
||||
|
||||
&__text { |
||||
text-align: center; |
||||
font-size: 14px; |
||||
margin-bottom: 1%; |
||||
color: #b2b2b2; |
||||
} |
||||
} |
||||
|
||||
img { |
||||
width: 100%; |
||||
height: 100%; |
||||
object-fit: cover; |
||||
} |
||||
|
||||
.puzzle-lost-box { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
} |
||||
|
||||
canvas { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
z-index: 22; |
||||
} |
||||
|
||||
.verify-result { |
||||
position: absolute; |
||||
z-index: 999; |
||||
color: white; |
||||
height: 24px; |
||||
line-height: 24px; |
||||
left: 0; |
||||
bottom: -25px; |
||||
font-size: 14px; |
||||
width: 100%; |
||||
transition: bottom 0.3s; |
||||
text-indent: 16px; |
||||
|
||||
&.verify-success { |
||||
background-color: $--color-success |
||||
} |
||||
|
||||
&.verify-fail { |
||||
background-color: $--color-danger |
||||
} |
||||
|
||||
&.show-result { |
||||
bottom: 0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.slider-container { |
||||
position: relative; |
||||
margin: 5.39% 3.24%; |
||||
width: 93.52%; |
||||
padding: 0 0 13.67% 0; |
||||
background-color: white; |
||||
background-size: 100%; |
||||
background-position: 0 0; |
||||
background-repeat: no-repeat; |
||||
background-image: url(~@/assets/images/sprite.1.2.4.png); |
||||
|
||||
.slider-track { |
||||
position: absolute; |
||||
top: 50%; |
||||
margin: -19px 0 0 0; |
||||
padding: 0 0 0 25%; |
||||
|
||||
.slider-tip { |
||||
position: relative; |
||||
width: 100%; |
||||
height: 100%; |
||||
opacity: 0; |
||||
transition: opacity .5s; |
||||
line-height: 38px; |
||||
font-size: 14px; |
||||
text-align: center; |
||||
white-space: nowrap; |
||||
color: #88949d; |
||||
} |
||||
} |
||||
|
||||
.slider-btn { |
||||
position: absolute; |
||||
margin: -4.62% 0 0 -2.31%; |
||||
width: 25.38%; |
||||
padding: 0 0 25.38% 0; |
||||
cursor: pointer; |
||||
background-size: 393.93939%; |
||||
background-repeat: no-repeat; |
||||
background-image: url(~@/assets/images/sprite.1.2.4.png); |
||||
} |
||||
} |
||||
|
||||
.puzzle-footer { |
||||
position: relative; |
||||
border-top: 1px solid #EEEEEE; |
||||
width: 100%; |
||||
margin: 0; |
||||
padding: 0 0 17.27% 0; |
||||
|
||||
.puzzle-small { |
||||
position: absolute; |
||||
left: 5.1%; |
||||
top: 50%; |
||||
margin-top: -4.13%; |
||||
padding: 0 0 8.27% 0; |
||||
width: 40.45%; |
||||
height: 0; |
||||
|
||||
.puzzle-close, .puzzle-refresh { |
||||
margin-left: 8.9%; |
||||
width: 17.8%; |
||||
cursor: pointer; |
||||
text-decoration: none; |
||||
vertical-align: top; |
||||
position: relative; |
||||
display: inline-block; |
||||
height: 0; |
||||
padding-bottom: 17.8%; |
||||
background-repeat: no-repeat; |
||||
background-image: url(~@/assets/images/sprite.1.2.4.png); |
||||
overflow: hidden; |
||||
background-size: 1300%; |
||||
} |
||||
|
||||
.puzzle-close { |
||||
margin-left: 0; |
||||
background-position: 0 44.86874%; |
||||
} |
||||
|
||||
.puzzle-refresh { |
||||
background-position: 0 81.38425%; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.puzzle-shake { |
||||
animation: shake 0.2s linear 3 both; |
||||
} |
||||
|
||||
@keyframes shake { |
||||
25% { |
||||
transform: translate3d(-6px, 0, 0) |
||||
} |
||||
75% { |
||||
transform: translate3d(6px, 0, 0) |
||||
} |
||||
100% { |
||||
transform: translate3d(0, 0, 0) |
||||
} |
||||
} |
||||
</style> |
||||
@ -1,20 +0,0 @@ |
||||
<template> |
||||
<div> |
||||
<el-row class="tip-row"> |
||||
<a href="https://github.com/Kevin-269581661/vue-puzzle-verification" target="_blank">网上搬的例子</a> |
||||
</el-row> |
||||
<puzzle-verification :puzzleImgList="puzzleImgList"/> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import PuzzleVerification from '@/components/PuzzleVerify' |
||||
|
||||
export default { |
||||
components: {PuzzleVerification}, |
||||
data() { |
||||
return { |
||||
puzzleImgList: ['http://q4r31dqam.bkt.clouddn.com/image/56.jpg'] |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||