滑块验证码,给图就行

news2024/11/26 2:54:56

在这里插入图片描述
效果如上~

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>滑块拼图验证码 - 拖动条 Loading 效果和验证后禁用</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
  <style>
    #app {
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    .slider-container {
      width: 300px;
      height: 40px;
      margin: 0 auto;
      background-color: #f0f0f0;
      position: relative;
      user-select: none;
    }
    .slider-mask {
      position: absolute;
      left: 0;
      top: 0;
      height: 40px;
      border: 0;
      background: #D1E9FE;
    }
    .slider-button {
      position: absolute;
      left: 0;
      top: 0;
      width: 40px;
      height: 38px;
      background: #fff;
      box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
      cursor: pointer;
      transition: background .2s linear;
    }
    .slider-text {
      position: absolute;
      text-align: center;
      width: 100%;
      height: 40px;
      line-height: 40px;
      font-size: 14px;
      color: #45494c;
      z-index: 2;
    }
    .puzzle-container {
      position: relative;
      width: 300px;
      height: 150px;
      margin: 0 auto 20px;
      overflow: hidden;
    }
    .puzzle-image, .puzzle-piece {
      position: absolute;
      top: 0;
      left: 0;
    }
    .refresh-button {
      margin-top: 10px;
      padding: 5px 10px;
      background-color: #4CAF50;
      color: white;
      border: none;
      cursor: pointer;
    }
    .debug-info {
      margin-top: 20px;
      font-size: 14px;
      color: #666;
    }
    .loading-slider {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0, 0, 0, 0.5);
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 10;
    }
    .loading-spinner {
      width: 20px;
      height: 20px;
      border: 3px solid #f3f3f3;
      border-top: 3px solid #3498db;
      border-radius: 50%;
      animation: spin 1s linear infinite;
    }
    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
    .verified .slider-button {
      background-color: #4CAF50;
    }
    .verified .slider-mask {
      background-color: #a5d6a7;
    }
  </style>
</head>
<body>
<div id="app">
  <h1>滑块拼图验证码 - 拖动条 Loading 效果和验证后禁用</h1>
  <div class="puzzle-container">
    <canvas ref="bgCanvas" width="300" height="150"></canvas>
    <canvas ref="pieceCanvas" width="300" height="150" class="puzzle-piece" :style="puzzlePieceStyle"></canvas>
  </div>
  <div class="slider-container"
       :class="{ 'verified': isVerified }"
       @mousedown="startSlide"
       @mousemove="moveSlide"
       @mouseup="endSlide"
       @mouseleave="endSlide">
    <div class="slider-mask" :style="{ width: sliderLeft + 'px' }"></div>
    <div class="slider-button" :style="{ left: sliderLeft + 'px' }"></div>
    <div class="slider-text">{{ sliderText }}</div>
    <div v-if="isLoading" class="loading-slider">
      <div class="loading-spinner"></div>
    </div>
  </div>
  <button @click="refreshPuzzle" class="refresh-button">刷新验证码</button>
  <div class="debug-info">
    <p>目标位置: {{ targetLeft }}</p>
    <p>当前位置: {{ puzzlePieceLeft }}</p>
    <p>误差: {{ Math.abs(targetLeft - puzzlePieceLeft) }}</p>
    <p>容差: {{ tolerance }}</p>
  </div>
</div>

<script>
  // 模拟后端API
  const mockBackendAPI = {
    getPuzzleData() {
      const images = [
        '1 (1).jpg',
        '1 (2).png',
        '1 (3).png'
      ];
      const randomImage = images[Math.floor(Math.random() * images.length)];
      return {
        image: randomImage,
        pieceX: Math.floor(Math.random() * 150) + 50,  // 50 to 200
        pieceY: Math.floor(Math.random() * 50) + 20,   // 20 to 70
      };
    },
    verifyPuzzle(submittedX, actualX, tolerance) {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(Math.abs(submittedX - actualX) <= tolerance);
        }, 1000);  // 模拟网络延迟
      });
    }
  };

  new Vue({
    el: '#app',
    data: {
      puzzleImage: null,
      sliderLeft: 0,
      isSliding: false,
      startX: 0,
      puzzlePieceLeft: 0,
      targetLeft: 0,
      targetTop: 0,
      sliderText: '向右滑动完成拼图',
      pieceWidth: 50,
      pieceHeight: 50,
      pieceShape: null,
      tolerance: 10,
      isLoading: false,
      isVerified: false
    },
    computed: {
      puzzlePieceStyle() {
        return {
          transform: `translateX(${this.puzzlePieceLeft}px)`,
        }
      }
    },
    mounted() {
      this.refreshPuzzle();
    },
    methods: {
      refreshPuzzle() {
        this.isVerified = false;
        const puzzleData = mockBackendAPI.getPuzzleData();
        this.loadImage(puzzleData.image).then(img => {
          this.puzzleImage = img;
          this.targetLeft = puzzleData.pieceX;
          this.targetTop = puzzleData.pieceY;
          this.sliderLeft = 0;
          this.puzzlePieceLeft = 0;
          this.sliderText = '向右滑动完成拼图';
          this.generatePieceShape();
          this.drawPuzzle();
        });
      },
      loadImage(src) {
        return new Promise((resolve, reject) => {
          const img = new Image();
          img.onload = () => resolve(img);
          img.onerror = reject;
          img.src = src;
        });
      },
      generatePieceShape() {
        const canvas = document.createElement('canvas');
        canvas.width = this.pieceWidth;
        canvas.height = this.pieceHeight;
        const ctx = canvas.getContext('2d');

        ctx.beginPath();
        ctx.moveTo(0, 0);
        ctx.lineTo(this.pieceWidth, 0);
        ctx.lineTo(this.pieceWidth, this.pieceHeight);
        ctx.lineTo(0, this.pieceHeight);
        ctx.closePath();
        ctx.fill();

        this.pieceShape = canvas;
      },
      drawPuzzle() {
        const bgCtx = this.$refs.bgCanvas.getContext('2d');
        const pieceCtx = this.$refs.pieceCanvas.getContext('2d');

        // 清空画布
        bgCtx.clearRect(0, 0, 300, 150);
        pieceCtx.clearRect(0, 0, 300, 150);

        // 绘制背景图
        bgCtx.drawImage(this.puzzleImage, 0, 0, 300, 150);

        // 在背景图上绘制黑色缺口
        bgCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
        bgCtx.fillRect(this.targetLeft, this.targetTop, this.pieceWidth, this.pieceHeight);

        // 绘制拼图块
        pieceCtx.drawImage(
                this.puzzleImage,
                this.targetLeft, this.targetTop, this.pieceWidth, this.pieceHeight,
                0, this.targetTop, this.pieceWidth, this.pieceHeight
        );

        // 绘制拼图块边框
        pieceCtx.strokeStyle = 'white';
        pieceCtx.lineWidth = 2;
        pieceCtx.strokeRect(0, this.targetTop, this.pieceWidth, this.pieceHeight);
      },
      startSlide(e) {
        if (this.isVerified) return;
        this.isSliding = true;
        this.startX = e.clientX - this.sliderLeft;
      },
      moveSlide(e) {
        if (!this.isSliding || this.isVerified) return;
        let moveX = e.clientX - this.startX;
        if (moveX < 0) moveX = 0;
        if (moveX > 250) moveX = 250;  // 限制最大滑动距离
        this.sliderLeft = moveX;
        this.puzzlePieceLeft = moveX;
      },
      async endSlide() {
        if (!this.isSliding || this.isVerified) return;
        this.isSliding = false;
        this.isLoading = true;  // 显示 loading 效果
        const isValid = await mockBackendAPI.verifyPuzzle(this.puzzlePieceLeft, this.targetLeft, this.tolerance);
        this.isLoading = false;  // 隐藏 loading 效果
        if (isValid) {
          this.sliderText = '验证成功!';
          this.sliderLeft = 250;  // 滑块移动到最右侧
          this.isVerified = true;
        } else {
          this.sliderLeft = 0;
          this.puzzlePieceLeft = 0;
          this.sliderText = '验证失败,请重试';
        }
        this.drawPuzzle();
      }
    }
  });
</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2210607.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

K8s环境下使用sidecar模式对EMQX的exhook.proto 进行流量代理

背景 在使用emqx作为mqtt时需要我们需要拦截client的各种行为&#xff0c;如连接&#xff0c;发送消息&#xff0c;认证等。除了使用emqx自带的插件机制。我们也可以用多语言-钩子扩展来实现这个功能&#xff0c;但是目前emqx仅仅支持单个grpc服务端的设置&#xff0c;所以会有…

视频格式转换

格式转换 1️⃣Convertio &#x1f3c6;优点 速度快、格式多、免费免登录 缺点 超过100m的文件就要登录付费了 点击进入 2️⃣123APPs &#x1f3c6;优点 这个免费的文件上限是10G&#xff0c;完完全全够用功能多、除了转换器格式还有视频、音频、PDF处理工具 缺点 广…

无人机飞手执照培训,三类、四类傻傻分不清楚

无人机飞手执照培训中的三类和四类&#xff0c;主要依据无人机的空机重量进行区分&#xff0c;并对应不同的飞行权限和应用场景。以下是对这两类执照的详细解析&#xff1a; 一、无人机飞手执照的三类与四类定义 1. 三类执照&#xff1a; 定义&#xff1a;三类执照是指允许操…

架构设计笔记-9-软件可靠性

目录 知识要点 综合知识 案例分析 1.可靠性特性&#xff0c;软硬件可靠性对比 论文 1.论软件可靠性设计技术的应用 知识要点 软件架构需求过程主要是获取用户需求&#xff0c;标识系统中所要用到的构件&#xff0c;并进行架构需求评审。其中&#xff0c;标识构件又详细地…

Redis——持久化

文章目录 Redis持久化Redis的两种持久化的策略定期备份&#xff1a;RDB触发机制rdb的触发时机&#xff1a;手动执行save&bgsave保存测试不手动执行bgsave测试bgsave操作流程测试通过配置&#xff0c;自动生成rdb快照RDB的优缺点 实时备份&#xff1a;AOFAOF是否会影响到red…

去耦电容的“滤波半径”

1、简介 去耦电容的滤波半径通常指的是在电路板上&#xff0c;去耦电容能够对其周围电源线路或信号线路产生有效去耦作用的范围。这个范围是以去耦电容为中心&#xff0c;向周围扩展的一个特定距离。 想象你有一个水桶&#xff0c;里面装满了混浊的水&#xff08;含有噪声的信…

基于ESP32的厨房计时器

基于ESP32的厨房计时器 一、项目说明二、项目材料三、OLED显示屏四、外壳设计五、外壳打印六、电路和外壳的集成七、编程八、成品展示 一、项目说明 厨房计时器很有用&#xff0c;但现在没有多少人使用实体厨房计时器了。我个人还是喜欢使用它们&#xff0c;因为拥有一个可以按…

CGAL 带约束的Delaunay三角剖分

CGAL 带约束的Delaunay三角剖分 本文使用CGAL进行简单的2D Delaunay 三角剖分,添加内外边界及点作为约束剖分。 Code #include <CGAL/Exact_predicates_inexact_constructions_kernel.h> #include <CGAL/Constrained_Delaunay_triangulation_2.h> #include <…

问题杂录-NVIDIA Bluefield DPU bfb-build编译报错记录与处理办法?(无数坑)

文章目录 背景bfb-build之后直接退出docker.io无法访问报错 ERROR: failed to solve: processkubernotes 下载失败报错mlnx-fw-updater-signed-24.07-0.6.1.1.aarch64: Cannot download报错 No match for argument: bf-release报错 放弃编译anolis&#xff0c;直接编译老版ubun…

RabbitMQ 入门(二)基本结构和消息模型

一、RabbitMQ的基本结构、角色和消息模型 MQ的基本结构&#xff1a; RabbitMQ中的一些角色&#xff1a; - publisher&#xff1a;生产者 - consumer&#xff1a;消费者 - exchange个&#xff1a;交换机&#xff0c;负责消息路由 - queue&#xff1a;队列&#xff0c;存储消息…

初步认识torch自定义算子

此篇为PyTorch 自定义算子&#xff1a;复现CPU和CUDA版的二维卷积的代码详解 这篇是为了展示setup在构建简单的cpp算子的使用 1.环境配置 整体结构如下图所示 pytorch_cpp_helper.hpp中准备了CPU版卷积需要的头文件 pytorch_cuda_helper.hpp和common_cuda_helper.hpp是cuda…

板级支持包构建1

开发板&#xff1a;STM32h743xi 编程软件&#xff1a;Keil 项目&#xff1a;GPIO外设操作&#xff08;彩色LED灯&#xff09; 学习打卡&#xff1a;Day2 学习地址&#xff1a;【野火】STM32 HAL库开发实战指南 教学视频 手把手教学STM32全系列 零基础入门CubeMXHAL库&#xff0…

jQuery——自定义jQuery插件

1、扩展jQuery&#xff08;将$看成对象&#xff09;的工具方法 $.extend&#xff08;object&#xff09; min&#xff08;a&#xff0c;b&#xff09; 返回较小的值 max&#xff08;c&#xff0c;d&#xff09; 返回较大的值 leftTrim&#xff08;&#xff09; 去掉字符串…

9.4 栅格图层符号化山体阴影渲染

9.4 栅格图层符号化山体阴影渲染-CSDN博客 目录 前言 山体阴影渲染 QGis设置为山体阴影 二次开发代码实现山体阴影 总结 前言 介绍栅格图层数据渲染之山体阴影渲染说明&#xff1a;文章中的示例代码均来自开源项目qgis_cpp_api_apps 山体阴影渲染 以“3420C_2010_327_…

leetcode二叉树(一)-理论基础

本节主要参考代码随想录&#xff1a;代码随想录 题目分类 二叉树的种类 满二叉树 满二叉树&#xff1a;如果一棵二叉树只有度为0的结点和度为2的结点&#xff0c;并且度为0的结点在同一层上&#xff0c;则这棵二叉树为满二叉树。 这棵二叉树为满二叉树&#xff0c;也可以说深…

产品设计——应用架构

我的理解应用架构是业务架构的落地&#xff0c;微服务架构下平台的应用架构设计&#xff0c;实质是根据业务来明确应用微服务的边界。因此业务不同&#xff0c;应用架构图也不同。但是基本框架应该相差不大。 其划分原则莫过于高内聚、低耦合。这个跟接口设计是一致的。我们总是…

MySQL数据库安装手册

MySQL数据库安装手册 MySQL数据库的下载与安装 在上一小节我们已经简单了解了数据库与一些相关概念 没看上一节的同学戳这里 接下来我们首先需要学会如何下载和安装数据库 1 MySQL数据库的版本 目前MySQL官网为我们提供了两个版本&#xff0c;一个是社区版本&#xff0c;…

<<迷雾>> 第11章 全自动加法计算机(4)--带地址译码器的内存模型 示例电路

地址译码器 info::操作说明 书中没有给出具体的实现电路. 这里根据电路特性采用自定义逻辑实现. 关于 “自定义逻辑”, 可参考此链接的说明: https://book.xiaogd.net/usage-of-circuitjs/circuitjs-custom-logic.html 这里本质上就是利用两个 2-4 译码器分别控制读和写. 注: 在…

Linux进程控制(3)(进程程序替换2 -- 微型shell)

目录 补充 一.引入 二.自助微型shell 1&#xff1a;输出一个命令行 2&#xff1a;获取用户命令字符串 3&#xff1a;命令行字符串的分割 4&#xff1a;先试着执行一下命令 5&#xff1a;关键点&#xff1a;需要在执行命令前检查是否为内建命令 其他&#xff1a; 三&…

JSONAJAX

JSON&&AJAX 一、JSON 1.1 json简介 JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON采用完全独立于语言的文本格式&#xff0c;而且很多语言都提供了对 json 的支持&#xff08;包括 C, C, C#, J…