(function(window) { const l = 42, // 滑块边长 r = 10, // 滑块半径 PI = Math.PI; let w = 344, // canvas宽度 h = 206; // canvas高度 const L = l + r * 2; // 滑块实际边长 // 验证设备 function ismobilesDevice() { return /androids|weboses|iphoness|ipads|ipods|BlackBerry|IEmobiles|Opera Mini/i.test(navigator.userAgent); } function getRandomNumberByRange(start, end) { return Math.round(Math.random() * (end - start) + start); //Math.round(x):与 x 最接近的整数 } //创建画布 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.onsuccess = 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() { let num = getRandomNumberByRange(1, 10); return '/image/jigsaw/' + num + '-300x150.jpg'; } //绘制裁剪画布区域 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 { //jigsaw:拼图 constructor(el, success, fail, close) { //constructor属性:返回对创建此对象的数组函数的引用;用于创建和初始化使用class创建的一个对象 this.el = el; this.success = success; this.fail = fail; this.close = close; } //初始化拼图执行函数 init(canvasW, canvasH) { w = canvasW; h = canvasH; this.initDOM(); this.initImg(); this.draw(); this.bindEvents(); } //初始化拼图对象函数 initDOM() { const silderTitle = createElement('div'); const silderTitleSm = createElement('div'); const silderRefresh = createElement('div'); const silderClose = createElement('div'); const captcha = createElement('div'); const canvas = createCanvas(w, h); // 画布 const block = canvas.cloneNode(true); // 滑块 const sliderContainer = createElement('div'); const sliderMask = createElement('div'); const slider = createElement('div'); const text = createElement('span'); silderTitle.className = 'silder-title'; silderTitleSm.className = 'silder-title-sm'; silderRefresh.className = 'silder-refresh'; silderClose.className = 'silder-close'; captcha.id = 'captcha'; silderTitle.innerHTML = '安全验证'; silderTitleSm.innerHTML = '拖动下方滑块完成拼图'; block.className = 'block'; sliderContainer.className = 'slider-container'; sliderMask.className = 'sliderMask'; slider.className = 'slider'; // text.innerHTML = '向右滑动滑块填充拼图'; text.innerHTML = ''; text.className = 'sliderText'; const el = this.el; el.appendChild(silderTitle); el.appendChild(silderTitleSm); el.appendChild(silderRefresh); el.appendChild(silderClose); el.appendChild(captcha); const old_el = document.getElementById('captcha'); old_el.appendChild(canvas); old_el.appendChild(block); sliderMask.appendChild(slider); sliderContainer.appendChild(sliderMask); sliderContainer.appendChild(text); el.appendChild(sliderContainer); /* //Object.assign()例子 var target = {a : 1}; //目标对象 var source1 = {b : 2}; //源对象1 var source2 = {c : 3}; //源对象2 var source3 = {c : 4}; //源对象3,和source2中的对象有同名属性c Object.assign(target,source1,source2,source3); //结果如下: //{a:1,b:2,c:4} */ Object.assign(this, { //Object.assign是ES6新添加的接口,主要的用途是用来合并多个JavaScript的对象 canvas, silderTitle, silderTitleSm, silderRefresh, silderClose, captcha, block, sliderContainer, slider, sliderMask, text, canvasCtx: canvas.getContext('2d'), blockCtx: block.getContext('2d') }) } //初始化拼图图片函数 initImg() { const img = createImg(() => { //设置截取画布 this.canvasCtx.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(ImageData, 0, y); }); this.img = img; } //拼图滑块函数 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); //clearRect() 方法清空给定矩形内的指定像素 this.blockCtx.clearRect(0, 0, w, h); this.block.width = w; } //事件绑定 bindEvents() { this.el.onselectstart = () => false; this.silderRefresh.onclick = () => { this.reset(); } this.silderClose.onclick = () => { document.getElementById('silderDialog').style.display = 'none'; this.close && this.close(); } let originX, originY, trail = [], isMouseDown = false; if (ismobilesDevice()) { this.slider.addEventListener('touchstart', function(e) { originX = e.touches[0].clientX, originY = e.touches[0].clientY; isMouseDown = true; }); this.slider.addEventListener('touchmove', (e) => { e.preventDefault(); if (!isMouseDown) return false; const moveX = e.touches[0].clientX - originX; const moveY = e.touches[0].clientY - 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, 'slider-container_active'); this.sliderMask.style.width = moveX + 'px'; trail.push(moveY); }) this.slider.addEventListener('touchend', (e) => { e.preventDefault(); if (!isMouseDown) return false; isMouseDown = false; if (e.changedTouches[0].clientX == originX) return false; removeClass(this.sliderContainer, 'slider-container_active'); this.trail = trail; const { spliced, TuringTest } = this.verify(); if (spliced) { if (TuringTest) { addClass(this.sliderContainer, 'slider-container_success'); document.getElementById('silderDialog').style.display = 'none'; this.success && this.success(); setTimeout(() => { this.reset() }, 1000) } else { addClass(this.sliderContainer, 'slider-container_fail') this.text.innerHTML = '再试一次'; this.reset(); } } else { addClass(this.sliderContainer, 'slider-container_fail') this.fail && this.fail() setTimeout(() => { this.reset() }, 1000) } }) } else { this.slider.addEventListener('mousedown', function(e) { originX = e.x, originY = e.y; isMouseDown = true; e.stopPropagation(); }); window.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, 'slider-container_active'); this.sliderMask.style.width = moveX + 'px'; trail.push(moveY); e.stopPropagation(); }) window.addEventListener('mouseup', (e) => { if (!isMouseDown) return false; isMouseDown = false; if (e.x == originX) return false; removeClass(this.sliderContainer, 'slider-container_active'); this.trail = trail; const { spliced, TuringTest } = this.verify(); if (spliced) { if (TuringTest) { addClass(this.sliderContainer, 'slider-container_success'); document.getElementById('silderDialog').style.display = 'none'; this.success && this.success(); setTimeout(() => { this.reset() }, 1000) } else { addClass(this.sliderContainer, 'slider-container_fail') this.text.innerHTML = '再试一次'; this.reset(); } } else { addClass(this.sliderContainer, 'slider-container_fail') this.fail && this.fail() setTimeout(() => { this.reset() }, 1000) } e.stopPropagation(); }) } } //验证 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 = 'slider-container'; 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(data) { new jigsaw(data.element, data.success, data.fail, data.close).init(data.canvasW || 344, data.canvasH || 206); } } }(window))