React 极简响应式滑块验证组件实现,随机滑块位置

发布于:2025-07-07 ⋅ 阅读:(18) ⋅ 点赞:(0)

🎯 滑块验证组件 (Slider Captcha)

一个现代化、响应式的滑块验证组件,专为 React 应用设计,提供流畅的用户体验和强大的安全验证功能。

✨ 功能特性

🎮 核心功能

  • 智能滑块拖拽 – 支持鼠标和触摸屏操作,响应灵敏
  • 随机目标位置 – 每次验证生成不同的目标位置,提高安全性
  • 实时位置验证 – 精确的位置检测,支持容错范围设置
  • 状态反馈 – 清晰的视觉反馈,包括成功、错误和待验证状态

🎨 用户体验

  • 流畅动画 – 平滑的过渡动画和微交互效果
  • 响应式设计 – 完美适配桌面端和移动端
  • 直观界面 – 简洁美观的 UI 设计,操作简单明了
  • 即时反馈 – 实时显示验证状态和结果

🔧 技术特性

  • React Hooks – 使用最新的 React Hooks API
  • TypeScript 支持 – 完整的类型定义(可扩展)
  • Tailwind CSS – 现代化的样式框架
  • 事件处理 – 完善的鼠标和触摸事件处理
  • 性能优化 – 使用 useCallback 优化事件处理函数

🌟 产品亮点

  • 拖拽体验 – 滑块跟随鼠标/手指移动,提供真实的拖拽感
  • 视觉引导 – 绿色指示器显示目标位置,用户一目了然
  • 状态动画 – 成功时的庆祝动画,错误时的震动反馈
  • 触摸支持 – 完美支持移动端触摸操作
  • 响应式布局 – 自适应不同屏幕尺寸
  • 浏览器兼容 – 支持主流浏览器

🎯 使用场景

1. 用户注册/登录

  • 防止机器人注册 – 在用户注册时验证人类用户
  • 登录安全 – 在敏感操作前进行身份验证
  • 密码重置 – 重置密码流程中的安全验证

2. 表单提交

  • 评论系统 – 防止垃圾评论和恶意提交
  • 联系表单 – 保护网站免受垃圾邮件攻击
  • 调查问卷 – 确保问卷数据的真实性

3. 内容保护

  • 下载验证 – 下载敏感文件前的身份验证
  • 内容访问 – 访问付费或限制内容前的验证
  • API 调用 – 防止 API 接口被恶意调用

4. 电商应用

  • 下单验证 – 防止恶意下单和刷单行为
  • 优惠券领取 – 限制优惠券的领取频率
  • 评价系统 – 确保评价的真实性

5. 管理后台

  • 敏感操作 – 管理员执行危险操作前的确认
  • 数据导出 – 导出大量数据前的安全验证
  • 系统设置 – 修改关键系统设置时的验证

🚀 快速开始

使用组件

import SliderCaptcha from "./components/SliderCaptcha";

function App() {
  return (
    <div className="App">
      <SliderCaptcha />
    </div>
  );
}

自定义配置

// 可以轻松扩展组件以支持自定义配置
const captchaConfig = {
  tolerance: 15, // 容错范围
  sliderWidth: 40, // 滑块宽度
  trackHeight: 48, // 轨道高度
  theme: "dark", // 主题样式
};

源码

import { useState, useRef, useEffect, useCallback } from "react";

export default function SliderCaptcha() {
  const [isVerified, setIsVerified] = useState(false);
  const [isDragging, setIsDragging] = useState(false);
  const [sliderPosition, setSliderPosition] = useState(0);
  const [startX, setStartX] = useState(0);
  const [targetPosition, setTargetPosition] = useState(0);
  const [showSuccess, setShowSuccess] = useState(false);
  const [showError, setShowError] = useState(false);

  const sliderRef = useRef(null);
  const trackRef = useRef(null);
  const containerRef = useRef(null);

  // 生成随机目标位置
  useEffect(() => {
    const container = containerRef.current;
    if (container) {
      const containerWidth = container.offsetWidth;
      const sliderWidth = 32; // 滑块宽度 (w-8 = 32px)
      const maxPosition = containerWidth - sliderWidth;
      const randomPosition = Math.random() * (maxPosition - 50) + 25; // 避免太靠近边缘
      setTargetPosition(randomPosition);
    }
  }, []);

  // 获取客户端坐标
  const getClientX = useCallback((e) => {
    return e.touches ? e.touches[0].clientX : e.clientX;
  }, []);

  // 鼠标/触摸事件处理
  const handleStart = useCallback(
    (e) => {
      if (isVerified) return;

      e.preventDefault();
      setIsDragging(true);
      setStartX(getClientX(e) - sliderPosition);
      setShowError(false);
    },
    [isVerified, sliderPosition, getClientX]
  );

  const handleMove = useCallback(
    (e) => {
      if (!isDragging || isVerified) return;

      e.preventDefault();
      const container = containerRef.current;
      if (!container) return;

      const containerWidth = container.offsetWidth;
      const sliderWidth = 32;
      const maxPosition = containerWidth - sliderWidth;

      let newPosition = getClientX(e) - startX;
      newPosition = Math.max(0, Math.min(newPosition, maxPosition));

      setSliderPosition(newPosition);
    },
    [isDragging, isVerified, startX, getClientX]
  );

  const handleEnd = useCallback(() => {
    if (!isDragging || isVerified) return;

    setIsDragging(false);

    // 验证滑块位置
    const tolerance = 10; // 允许的误差范围
    const isCorrect = Math.abs(sliderPosition - targetPosition) <= tolerance;

    if (isCorrect) {
      setIsVerified(true);
      setShowSuccess(true);
      setTimeout(() => setShowSuccess(false), 2000);
    } else {
      setShowError(true);
      // 重置滑块位置
      setTimeout(() => {
        setSliderPosition(0);
        setShowError(false);
      }, 1000);
    }
  }, [isDragging, isVerified, sliderPosition, targetPosition]);

  // 重置验证
  const handleReset = () => {
    setIsVerified(false);
    setIsDragging(false);
    setSliderPosition(0);
    setShowSuccess(false);
    setShowError(false);

    // 重新生成目标位置
    const container = containerRef.current;
    if (container) {
      const containerWidth = container.offsetWidth;
      const sliderWidth = 32;
      const maxPosition = containerWidth - sliderWidth;
      const randomPosition = Math.random() * (maxPosition - 50) + 25;
      setTargetPosition(randomPosition);
    }
  };

  // 添加全局鼠标/触摸事件监听
  useEffect(() => {
    if (isDragging) {
      document.addEventListener("mousemove", handleMove);
      document.addEventListener("mouseup", handleEnd);
      document.addEventListener("touchmove", handleMove, { passive: false });
      document.addEventListener("touchend", handleEnd);

      return () => {
        document.removeEventListener("mousemove", handleMove);
        document.removeEventListener("mouseup", handleEnd);
        document.removeEventListener("touchmove", handleMove);
        document.removeEventListener("touchend", handleEnd);
      };
    }
  }, [isDragging, handleMove, handleEnd]);

  return (
    <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
      <div className="bg-white rounded-2xl shadow-xl p-8 max-w-md w-full">
        <h2 className="text-2xl font-bold text-gray-800 mb-6 text-center">
          滑块验证
        </h2>

        <div className="mb-6">
          <p className="text-gray-600 text-center mb-4">
            {isVerified ? "✅ 验证成功!" : "请将滑块拖拽到正确位置完成验证"}
          </p>
        </div>

        {/* 验证区域 */}
        <div
          ref={containerRef}
          className="relative bg-gray-100 rounded-lg h-12 mb-6 overflow-hidden"
        >
          {/* 背景图片或图案 */}
          <div className="absolute inset-0 bg-gradient-to-r from-blue-200 to-purple-200 opacity-30"></div>

          {/* 目标位置指示器 */}
          <div
            className="absolute top-1 bottom-1 w-2 bg-green-600 rounded-lg shadow ring-2 ring-green-300 transition-opacity duration-300"
            style={{
              left: `${targetPosition}px`,
              opacity: isVerified ? 0 : 1,
            }}
          ></div>

          {/* 滑块轨道 */}
          <div
            ref={trackRef}
            className="absolute top-0 bottom-0 left-0 bg-blue-500 transition-all duration-300 ease-out"
            style={{
              width: `${sliderPosition}px`,
              opacity: isVerified ? 0.8 : 0.6,
            }}
          ></div>

          {/* 滑块 */}
          {!isVerified && (
            <div
              ref={sliderRef}
              className={`absolute top-1 bottom-1 w-8 bg-white rounded-md shadow-lg cursor-pointer transition-all duration-200 ease-out flex items-center justify-center
                ${isDragging ? "shadow-xl scale-105" : ""}
                hover:shadow-lg
                ${showError ? "animate-shake" : ""}
              `}
              style={{ left: `${sliderPosition}px` }}
              onMouseDown={handleStart}
              onTouchStart={handleStart}
            >
              <svg
                className="w-4 h-4 text-gray-500"
                fill="currentColor"
                viewBox="0 0 20 20"
              >
                <path
                  fillRule="evenodd"
                  d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
                  clipRule="evenodd"
                />
              </svg>
            </div>
          )}

          {/* 成功提示 */}
          {showSuccess && (
            <div className="absolute inset-0 bg-green-500 bg-opacity-20 flex items-center justify-center">
              <div className="bg-white rounded-lg px-4 py-2 shadow-lg">
                <span className="text-green-600 font-semibold">验证成功!</span>
              </div>
            </div>
          )}

          {/* 错误提示 */}
          {showError && (
            <div className="absolute inset-0 bg-red-500 bg-opacity-20 flex items-center justify-center">
              <div className="bg-white rounded-lg px-4 py-2 shadow-lg">
                <span className="text-red-600 font-semibold">
                  位置错误,请重试
                </span>
              </div>
            </div>
          )}
        </div>

        {/* 操作按钮 */}
        <div className="flex gap-4">
          <button
            onClick={handleReset}
            className="flex-1 bg-gray-500 hover:bg-gray-600 text-white font-semibold py-3 px-6 rounded-lg transition-colors duration-200"
          >
            重置验证
          </button>

          {isVerified && (
            <button
              onClick={() => alert("验证通过,可以继续操作!")}
              className="flex-1 bg-green-500 hover:bg-green-600 text-white font-semibold py-3 px-6 rounded-lg transition-colors duration-200"
            >
              继续
            </button>
          )}
        </div>

        {/* 状态指示器 */}
        <div className="mt-4 text-center">
          <div className="inline-flex items-center gap-2 px-3 py-1 rounded-full text-sm">
            {isVerified ? (
              <>
                <div className="w-2 h-2 bg-green-500 rounded-full"></div>
                <span className="text-green-600">已验证</span>
              </>
            ) : (
              <>
                <div className="w-2 h-2 bg-yellow-500 rounded-full animate-pulse"></div>
                <span className="text-yellow-600">待验证</span>
              </>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

 React 极简响应式滑块验证组件实现,随机滑块位置 - 高质量源码分享平台-免费下载各类网站源码与模板及前沿动态资讯


网站公告

今日签到

点亮在社区的每一天
去签到