(function(window) {
|
const l = 42, // 滑块边长
|
r = 10, // 滑块半径
|
w = 260, // canvas宽度
|
h = 155, // canvas高度
|
PI = Math.PI
|
const L = l + r * 2 // 滑块实际边长
|
function getRandomNumberByRange(start, end) {
|
return Math.round(Math.random() * (end - start) + start)
|
}
|
|
function createCanvas(width, height) {
|
const canvas = createElement('canvas')
|
canvas.width = width
|
canvas.height = height
|
return canvas
|
}
|
|
function createImg(onload) {
|
const img = createElement('img')
|
img.crossOrigin = "Anonymous"
|
img.onload = onload
|
img.onerror = () => {
|
img.src = getRandomImg()
|
}
|
img.src = getRandomImg()
|
return img
|
}
|
|
function createElement(tagName) {
|
return document.createElement(tagName)
|
}
|
|
function addClass(tag, className) {
|
tag.classList.add(className)
|
}
|
|
function removeClass(tag, className) {
|
tag.classList.remove(className)
|
}
|
|
function getRandomImg() {
|
return "./img/login/" + getRandomNumberByRange(1, 50) + ".jpg";
|
//return 'https://picsum.photos/300/150/?image=' + getRandomNumberByRange(0, 100)
|
}
|
|
function draw(ctx, operation, x, y) {
|
ctx.beginPath()
|
ctx.moveTo(x, y)
|
ctx.lineTo(x + l / 2, y)
|
ctx.arc(x + l / 2, y - r + 2, r, 0, 2 * PI)
|
ctx.lineTo(x + l / 2, y)
|
ctx.lineTo(x + l, y)
|
ctx.lineTo(x + l, y + l / 2)
|
ctx.arc(x + l + r - 2, y + l / 2, r, 0, 2 * PI)
|
ctx.lineTo(x + l, y + l / 2)
|
ctx.lineTo(x + l, y + l)
|
ctx.lineTo(x, y + l)
|
ctx.lineTo(x, y)
|
ctx.fillStyle = '#fff'
|
|
ctx[operation]()
|
ctx.beginPath()
|
ctx.arc(x, y + l / 2, r, 1.5 * PI, 0.5 * PI)
|
|
ctx.globalCompositeOperation = "xor"
|
ctx.fill()
|
|
}
|
|
function sum(x, y) {
|
return x + y
|
}
|
|
function square(x) {
|
return x * x
|
}
|
class jigsaw {
|
constructor(el, success, fail) {
|
this.el = el
|
this.success = success
|
this.fail = fail
|
}
|
init() {
|
this.initDOM()
|
this.initImg()
|
this.draw()
|
this.bindEvents()
|
}
|
initDOM() {
|
const canvas = createCanvas(w, h) // 画布
|
const block = canvas.cloneNode(true) // 滑块
|
const sliderContainer = createElement('div')
|
const refreshIcon = createElement('div')
|
const sliderMask = createElement('div')
|
const slider = createElement('div')
|
const sliderIcon = createElement('span')
|
const text = createElement('span')
|
block.className = 'block'
|
sliderContainer.className = 'sliderContainer'
|
refreshIcon.className = 'refreshIcon'
|
sliderMask.className = 'sliderMask'
|
slider.className = 'slider'
|
sliderIcon.className = 'sliderIcon'
|
text.innerHTML = '向右滑动滑块填充拼图'
|
text.className = 'sliderText'
|
const el = this.el
|
el.appendChild(canvas)
|
el.appendChild(refreshIcon)
|
el.appendChild(block)
|
slider.appendChild(sliderIcon)
|
sliderMask.appendChild(slider)
|
sliderContainer.appendChild(sliderMask)
|
sliderContainer.appendChild(text)
|
el.appendChild(sliderContainer)
|
Object.assign(this, {
|
canvas,
|
block,
|
sliderContainer,
|
refreshIcon,
|
slider,
|
sliderMask,
|
sliderIcon,
|
text,
|
canvasCtx: canvas.getContext('2d'),
|
blockCtx: block.getContext('2d')
|
})
|
}
|
initImg() {
|
const img = createImg(() => {
|
// this.canvasCtx.linewidth = 10;
|
// this.canvasCtx.fillstyle = "rgba(88, 204, 245, 0.4)"; //设置背景颜色
|
// this.canvasCtx.stroke();
|
|
// this.canvasCtx.strokeStyle = 'red';
|
// this.canvasCtx.lineWidth = 2;
|
|
// this.canvasCtx.strokeRect(0, 0, w, h);
|
|
|
this.canvasCtx.drawImage(img, 0, 0, w, h)
|
this.blockCtx.drawImage(img, 0, 0, w, h)
|
// this.blockCtx.strokeRect(1, 1, w - 2, h - 2);
|
|
// this.blockCtx.shadowColor = 'rgba(255,0,0,1)';
|
// this.blockCtx.shadowBlur = 10;
|
// this.blockCtx.shadowOffsetX = 10;
|
// this.blockCtx.shadowOffsetY = 10;
|
|
// this.blockCtx.fillStyle = 'rgba(0,0,255,1.0)';
|
// this.blockCtx.fillRect(100,100,200,100);
|
// this.blockCtx.globalCompositeOperation = 'source-in';
|
// this.blockCtx.globalCompositeOperation="source-atop";
|
// this.blockCtx.drawImage(img, 0, 0, w + 12, h + 12);
|
// this.blockCtx.fillRect(0, 0, w + 12, h + 12);
|
// this.blockCtx.fillStyle = "#FF0000";
|
// this.blockCtx.drawImage(img, 0, 0, w + 12, h + 12);
|
|
// this.blockCtx.globalCompositeOperation = "source-in";
|
// this.blockCtx.fillRect(0, 0, w + 12, h + 12);
|
// this.blockCtx.globalCompositeOperation = "source-over";
|
|
// this.blockCtx.globalCompositeOperation="source-over";
|
// this.blockCtx.drawImage(img, 0, 0, w, h);
|
// this.blockCtx.drawImage(img, 0, 0, w, h );
|
|
|
const y = this.y - r * 2 + 2
|
const ImageData = this.blockCtx.getImageData(this.x, y, L, L)
|
this.block.width = L
|
|
this.blockCtx.putImageData(this.halveAlpha(ImageData), 0, y)
|
})
|
this.img = img
|
}
|
|
drawblock(ctx, width, height, type, img) { //这里是二合一函数,可以画出阴影部分也切割出拼图形状的函数
|
let {
|
w,
|
r,
|
sliderleft
|
} = this; //w宽度,r圆的半径sliderleft是滑块的初始位置
|
//这地方用随机数每次显示的位置都不同
|
var x = this.random(30, width - w - r - 1); //这里最大值为了不让滑块进入隐藏所以要减去滑块的宽度 有个半圆所以要减去半圆位置
|
var y = this.random(10, height - w - r - 1);
|
if (type == "clip") { //这里要保证在两个东西要在同一个y值上
|
x = sliderleft;
|
y = this.y;
|
} else {
|
this.x = x;
|
this.y = y;
|
}
|
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);
|
if (type == "clip") {
|
ctx.shadowblur = 10;
|
ctx.shadowcolor = "black";
|
}
|
ctx.linewidth = 1;
|
ctx.fillstyle = "rgba(0, 0, 0, 0.4)"; //设置背景颜色
|
ctx.stroke();
|
ctx[type]();
|
if (img) { //这里为什么要在这里加载图片呢,因为这个高度是动态的必须计算完之后在放进去
|
//还有个原因是你要先剪裁在加载图片
|
ctx.drawimage(img, -this.x, 0, width, height);
|
}
|
}
|
|
|
halveAlpha(ImageData) {
|
var alpha = 0.5
|
// var alpha = 1
|
for (var i = 0, len = ImageData.data.length; i < len; i += 4) {
|
// 改变每个像素的透明度
|
ImageData.data[i + 1] = ImageData.data[i + 1] * alpha;
|
ImageData.data[i + 2] = ImageData.data[i + 2] * alpha;
|
}
|
return ImageData;
|
}
|
|
draw() {
|
// 随机创建滑块的位置
|
this.x = getRandomNumberByRange(L + 10, w - (L + 10))
|
this.y = getRandomNumberByRange(10 + r * 2, h - (L + 10))
|
draw(this.canvasCtx, 'fill', this.x, this.y)
|
draw(this.blockCtx, 'clip', this.x, this.y)
|
}
|
clean() {
|
this.canvasCtx.clearRect(0, 0, w, h)
|
this.blockCtx.clearRect(0, 0, w, h)
|
this.block.width = w
|
}
|
bindEvents() {
|
this.el.onselectstart = () => false
|
this.refreshIcon.onclick = () => {
|
this.reset()
|
}
|
let originX, originY, trail = [],
|
isMouseDown = false
|
|
this.block.addEventListener('mousedown', function(e) {
|
originX = e.x, originY = e.y
|
isMouseDown = true
|
})
|
|
this.slider.addEventListener('mousedown', function(e) {
|
originX = e.x, originY = e.y
|
isMouseDown = true
|
})
|
document.addEventListener('mousemove', (e) => {
|
if (!isMouseDown) return false
|
const moveX = e.x - originX
|
const moveY = e.y - originY
|
if (moveX < 0 || moveX + 38 >= w) return false
|
this.slider.style.left = moveX + 'px'
|
var blockLeft = (w - 40 - 20) / (w - 40) * moveX
|
this.block.style.left = blockLeft + 'px'
|
addClass(this.sliderContainer, 'sliderContainer_active')
|
this.sliderMask.style.width = moveX + 'px'
|
trail.push(moveY)
|
})
|
document.addEventListener('mouseup', (e) => {
|
if (!isMouseDown) return false
|
isMouseDown = false
|
if (e.x == originX) return false
|
removeClass(this.sliderContainer, 'sliderContainer_active')
|
this.trail = trail
|
const {
|
spliced,
|
TuringTest
|
} = this.verify()
|
if (spliced) {
|
if (TuringTest) {
|
addClass(this.sliderContainer, 'sliderContainer_success')
|
this.success && this.success()
|
} else {
|
addClass(this.sliderContainer, 'sliderContainer_fail')
|
this.text.innerHTML = '再试一次'
|
this.reset()
|
}
|
} else {
|
//alert("验证失败");
|
addClass(this.sliderContainer, 'sliderContainer_fail')
|
this.fail && this.fail();
|
//验证失败后,1秒后重新加载图片
|
setTimeout(() => {
|
this.reset()
|
}, 1000)
|
}
|
})
|
}
|
verify() {
|
const arr = this.trail // 拖动时y轴的移动距离
|
const average = arr.reduce(sum) / arr.length // 平均值
|
const deviations = arr.map(x => x - average) // 偏差数组
|
const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length) // 标准差
|
const left = parseInt(this.block.style.left)
|
return {
|
spliced: Math.abs(left - this.x) < 10,
|
TuringTest: average !== stddev, // 只是简单的验证拖动轨迹,相等时一般为0,表示可能非人为操作
|
}
|
}
|
reset() {
|
this.sliderContainer.className = 'sliderContainer'
|
this.slider.style.left = 0
|
this.block.style.left = 0
|
this.sliderMask.style.width = 0
|
this.clean()
|
this.img.src = getRandomImg()
|
this.draw()
|
}
|
}
|
window.jigsaw = {
|
init: function(element, success, fail) {
|
new jigsaw(element, success, fail).init()
|
}
|
}
|
}(window))
|