哇塞,有好吃的~

手撸一个简单的滑块验证码

使用过很多次滑块验证码的功能,偶然一次想起来,能不能简单的实现一个呢,于是就尝试了一下,然后记录下来了,图个乐子。

思路

  • 首先是绘制一张图片,自然而然,要用canvas了,就像下面这样,首先加载一张图片,然后去绘制到canvas中。
<div id="validate">
   <canvas id="canvas"></canvas>
</div>
<script>
   const canvas = document.getElementById('canvas');
   const ctx = canvas.getContext('2d');
   // 加载图片
   const loadImage = (url) => {
      return new Promise((resolve, reject) => {
        const image = new Image();
        image.src = url;
        image.crossOrigin = 'anonymous';
        image.onload = () => {
          resolve(image);
        };
        image.onerror = () => {
          reject();
        }
      });
  };
  const renderImage = (image) => {
      const w = image.width;
      const h = image.height;
      canvas.width = w;
      canvas.height = h;
      ctx.drawImage(image, 0, 0, w, h);
      return [w, h];
  };
  // 函数入口
  const start = () => {
      loadImage(url).then(renderImage).catch(() => {
        console.log('图片加载失败');
      });
  };

  start();
</script>
  • 接下来呢,肯定就是扣出一块区域了,把原图的区域清空,然后把该区域转化成一张新的可拖拽的图片。
const clipImage = () => {
    // 设置滑块的宽高
    const dw = 50;
    const dh = 50;
    // 随机生成空缺的x坐标
    const x = Math.floor(Math.random() * (w - 2 * dw) + dw);
    // y坐标就固定在中间
    const y = (h - dh) / 2;
    // 获取到这块区域的ImageData对象
    const imageData = ctx.getImageData(x, y, dw, dh);
    // 清空这块区域
    ctx.clearRect(x, y, dw, dh);
    // 使用一个临时的canvas承载这个ImageData去生成滑块图片
    let avatarCanvas = document.createElement("canvas");
    avatarCanvas.width = dw;
    avatarCanvas.height = dh;
    let avatarCtx = avatarCanvas.getContext("2d");
    avatarCtx.putImageData(imageData, 0, 0);
    let avatarDataUrl = avatarCanvas.toDataURL();
    const img = document.createElement('img');
    img.src = avatarDataUrl;
    img.style.top = `${y}px`;
    img.style.left = 0;
    // 图片可以拖拽
    img.draggable = true;
    // 挂载到页面上,样式使用绝对定位固定到最左侧的中间位置
    validate.appendChild(img);
    return [img, x];
}
  • 最后呢,就处理一下图片的滑动事件,每次拖动的时候去修改img的left值,然后结束的时候就判断一下距离之前的随机x是否在一个可接受范围内就ok了。这个地方有一个注意点,就是图片拖拽的时候,默认会出现一个阴影,和原图片一样,会感觉很丑,可以设置一个空标签来隐藏这个阴影。
const handlerDrag = (img, x) => {
    let startX = 0;
    img.ondragstart = (e) => {
        startX = e.pageX;
        // // create an empty element
        dragElement = document.createElement("span");
        dragElement.innerHTML = "&nbsp;";
        dragElement.style.position = "absolute";
        dragElement.style.left = "-1000%";

        // add the element to the dom
        document.body.appendChild(dragElement);

        //set it as the drag image
        event.dataTransfer.setDragImage(dragElement, 0, 0);
    }
    img.ondrag = (e) => {
        if (e.pageX === 0) {
          return;
        }
        const deltaX = e.pageX - startX;
        img.style.left = `${deltaX}px`;
    };
    img.ondragend = (e) => {
        const deltaX = e.pageX - startX;
        if(Math.abs(deltaX - x) < 50) {
          alert('验证成功');
        } else {
          alert('验证失败');
        }
        img.draggable = false;
    }
}
  • 最后把之前的代码整合一下,其实就是在初始方法中去以此调用就好了。
const start = () => {
  loadImage(url).then(image => {
      // 这个地方是不是很眼熟,上一个函数的执行结果,是下一个函数的参数,这个大家有兴趣的可以试试,实现一个compose函数了,类似compose: (fns: Array<(arg1, arg2...) => Array<any>) => any;
      const [w, h] = renderImage(image);
      const [img, x] = clipImage(w, h);
      handlerDrag(img, x);
  }).catch(() => {
    console.log('图片加载失败');
  });
};

效果图

start.png

end.jpg

小结

最后呢,我想说的是,这个肯定还有很多优化的地方,仅仅是提供一种实现思路,代码上也有更精简的写法,欢迎大家交流学习。

posted @ 2023-04-13 19:25  风行者夜色  阅读(88)  评论(0编辑  收藏  举报