使用node.js生成后端可以拖动的拼图验证码

本文介绍如何利用npm包canvas在Node.js后端生成一张包含拼图的背景图片,用户需要将拼图拖动到正确位置。前端加载图片后处理拖动事件,判断拼图位置是否正确。该方法适用于PC和移动端,代码可复用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用npm包canvas在后端绘制一张背景图片,然后在这张背景图片中截取一部分作为拼图,让用户移动拼图到正确的位置,同时将此拼图所截取的区域用一个空白的区域覆盖。

import express from 'express';

const Canvas = require('canvas');
const path = require('path');

const router = express.Router();

// 背景图片的宽可以传参设置,默认值是320 * 180,小拼图默认是60 * 45
router.get('/drag_captcha', (req, res) => {
  const { bgWidth: width } = req.query;
  const bgWidth = parseInt(width) || 320;
  const bgHeight = (width && parseInt(width * 180 / 320)) || 180;
  const dragPicWidth = 60;
  const dragPicHeight = 45;
  const index = Math.floor(Math.random() * 13);
  const positionX = Math.floor(Math.random() * (bgWidth - dragPicWidth - 10) + 11);  // 空白拼图的定位X
  const positionY = Math.floor(Math.random() * (bgHeight - dragPicHeight - 10) + 11);
  const Image = Canvas.Image;
  const bgCanvas = new Canvas(bgWidth, bgHeight);
  const dragCanvas = new Canvas(dragPicWidth, dragPicHeight);
  const background = bgCanvas.getContext('2d');
  const dragPic = dragCanvas.getContext('2d');

  const image = new Image();
  image.onload = () => {
    background.drawImage(image, 0, 0, 320, 180, 0, 0, bgWidth, bgHeight);
    dragPic.drawImage(bgCanvas, positionX, positionY, dragPicWidth, dragPicHeight, 0, 0,
    dragPicWidth, dragPicHeight);
    background.clearRect(positionX, positionY, dragPicWidth, dragPicHeight);
  };
  image.src = path.join(__dirname, `../app/assets/images/validate/bg-${index}.png`);
  if (req.session) {
    req.session.dragCaptcha = {
      positionX,
      positionY
    };
  }

  res.send({ bgCanvas: bgCanvas.toDataURL(), dragCanvas: dragCanvas.toDataURL() });
});

router.post('/check_captcha_position', (req, res) => {
  const { offsetLeft, offsetTop } = req.body;
  const { dragCaptcha = {} } = req.session;
  const range = 5;  // 误差范围
  if (!offsetLeft || !offsetTop) return;
  console.log(dragCaptcha.positionX, dragCaptcha.positionY);

  const deviationX = Math.abs(offsetLeft - dragCaptcha.positionX);
  const deviationY = Math.abs(offsetTop - dragCaptcha.positionY);
  if (deviationX < range && deviationY < range) {
    res.send({ error: false });
  } else {
    res.send({ error: true });
  }
});

export default router;

在前端页面加载后生成背景图片和拼图,当移动拼图时判断拼图的正确位置。在这里,把它封装为在普通JS或者node.js都可以加载实现的文件。注意,在PC端鼠标按下拖动事件是mousedown,mousemove,mouseup,对应的移动端事件是touchstart,touchmove,touchend

(function(factory) {
    if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = factory();
    } else {
        window.DragCaptcha = factory();
    }
})(function () {
    var extend = {
        addEventListener: function(target, type, handler) {
            var obj = target === 'document' ? document : target;
            try {
                obj.addEventListener(type, handler, false);
            } catch (error) {
                obj.attachEvent('on' + type, handler);
            }
        },
        removeEventListener: function(target, type, handler) {
            var obj = target === 'document' ? document : target;
            try {
                obj.removeEventListener(type, handler, false);
            } catch (error) {
                obj.detachEvent('on' + type, handler);
            }
        }
    };

    /**
     * DragCaptcha
     * @param targetId  添加到指定id的节点中
     * @param callback  拖动时松开鼠标后的回调函数
     * @param bgWidth  背景图片的宽度(不加单位px)
     */
    function DragCaptcha(targetId, callback, bgWidth) {
        this.targetId = targetId;
        this.callback = callback;
        this.bgWidth = bgWidth;
        this.bgHeight = bgWidth * 180 / 320;
        this.hasAddedEvent = false;
        this.defaultValue = {
            bgWidth: '320px',
            bgHeight: '180px',
            dragWidth: '60px',
            dragHeight: '45px'
        };

        this._init();
    }

    DragCaptcha.prototype = {
      constructor: DragCaptcha,
      _init: function () {
        this.createElement();
        this.getImage(this.bgWidth);
      },

      appendHtml: function(targetId, child) {
        var target = document.getElementById(targetId);
        target.appendChild(child);
      },

      createElement: function() {
        var canvasWrapper = document.createElement('div');
        canvasWrapper.style.position = 'relative';
        canvasWrapper.style.width = this.bgWidth ? this.bgWidth + 'px' : this.defaultValue.bgWidth;
        canvasWrapper.style.margin = '5px auto';

        var bgImage = document.createElement('img');
        bgImage.setAttribute('id', 'bg-canvas');
        bgImage.style.width = this.bgWidth ? this.bgWidth + 'px' : this.defaultValue.bgWidth;
        bgImage.style.height = this.bgHeight ? this.bgHeight + 'px' : this.defaultValue.bgHeight;

        var dragImage = document.createElement('img');
        dragImage.setAttribute('id', 'drag-canvas');
        dragImage.style.position = 'absolute';
        dragImage.style.top = 0;
        dragImage.style.left = 0;
        dragImage.style.width = this.defaultValue.dragWidth;
        dragImage.style.height = this.defaultValue.dragHeight;

        var tip = document.createElement('div');
        tip.innerHTML = '请拖动拼图填充完整图片';

        canvasWrapper.appendChild(bgImage);
        canvasWrapper.appendChild(dragImage);
        this.appendHtml(this.targetId, canvasWrapper);
        this.appendHtml(this.targetId, tip);
      },

      getImage: function(bgWidth) {
        var that = this;
        var xhr = new XMLHttpRequest();
        var response;
        var bgImage = document.querySelector('img#bg-canvas');
        var dragImage = document.querySelector('img#drag-canvas');

        xhr.open('get', '/drag_captcha?bgWidth=' + bgWidth);
        xhr.send();
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {     
                response = JSON.parse(xhr.responseText);
                dragImage.style.left = 0;
                dragImage.style.top = 0;
                bgImage.src = response.bgCanvas;
                dragImage.src = response.dragCanvas;

                if (that.hasAddedEvent) return;
                that.hasAddedEvent = true;
                that.drag();
            }
        }
      },

      drag: function() {
        var that = this;

        var bgImage = document.querySelector('img#bg-canvas');
        var dragImage = document.querySelector('img#drag-canvas');
        var moveMaxValueX = bgImage.width - dragImage.width;  // 水平方向最大移动距离
        var moveMaxValueY = bgImage.height - dragImage.height;  // 垂直方向最大移动距离
        var dragStartX = dragImage.offsetLeft;  // 拼图初始水平位置
        var dragStartY = dragImage.offsetTop;  // 拼图初始垂直位置

        if (/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
            extend.addEventListener(dragImage, 'touchstart', function(e) {
                if (e.preventDefault) {
                    e.preventDefault();
                } else {
                    window.event.returnValue = false;
                }

                var startClientX = e.targetTouches[0].pageX;
                var startClientY = e.targetTouches[0].pageY;
                var dragMoveLeft = dragImage.offsetLeft;
                var dragMoveTop = dragImage.offsetTop;
                var fnMouseMove = function(e) {
                    var mouseMoveX = e.targetTouches[0].pageX - startClientX;
                    var mouseMoveY = e.targetTouches[0].pageY - startClientY;
                    var toX = dragMoveLeft + mouseMoveX;
                    var toY = dragMoveTop + mouseMoveY;
                    if (toX > moveMaxValueX) {
                        toX = moveMaxValueX;
                    } else if (toX < dragStartX) {
                        toX = dragStartX;
                    }
                    if (toY > moveMaxValueY) {
                        toY = moveMaxValueY;
                    } else if (toY < dragStartY) {
                        toY = dragStartY;
                    }
                    dragImage.style.left = toX + 'px';
                    dragImage.style.top = toY + 'px';
                };
                extend.addEventListener(document, 'touchmove', fnMouseMove);
                var fnMouseUp = function() {
                    extend.removeEventListener(document, 'touchmove', fnMouseMove);
                    extend.removeEventListener(document, 'touchend', fnMouseUp);
                    that.callback && that.callback();
                }
                extend.addEventListener(document, 'touchend', fnMouseUp);
            });
            return;
        }

        extend.addEventListener(dragImage, 'mousedown', function(e) {
            if (e.preventDefault) {
                e.preventDefault();
            } else {
                window.event.returnValue = false;
            }

            var startClientX = e.clientX;
            var startClientY = e.clientY;
            var dragMoveLeft = dragImage.offsetLeft;
            var dragMoveTop = dragImage.offsetTop;
            var fnMouseMove = function(e) {
                var mouseMoveX = e.clientX - startClientX;
                var mouseMoveY = e.clientY - startClientY;
                var toX = dragMoveLeft + mouseMoveX;
                var toY = dragMoveTop + mouseMoveY;
                if (toX > moveMaxValueX) {
                    toX = moveMaxValueX;
                } else if (toX < dragStartX) {
                    toX = dragStartX;
                }
                if (toY > moveMaxValueY) {
                    toY = moveMaxValueY;
                } else if (toY < dragStartY) {
                    toY = dragStartY;
                }
                dragImage.style.left = toX + 'px';
                dragImage.style.top = toY + 'px';
            };
            extend.addEventListener(document, 'mousemove', fnMouseMove);
            var fnMouseUp = function() {
                extend.removeEventListener(document, 'mousemove', fnMouseMove);
                extend.removeEventListener(document, 'mouseup', fnMouseUp);
                that.callback && that.callback();
            }
            extend.addEventListener(document, 'mouseup', fnMouseUp);
        });
      }
    };

    return DragCaptcha;
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值