阅读视图

发现新文章,点击刷新页面。

实现CSS滚动视差效果的几种方式

前言

视差滚动效果是一种视觉设计。这种效果通过让页面中不同元素创造出层次感,让网页看起来更加生动。

本文将详细介绍如何使用纯CSS实现滚动视差效果,重点分析三种不同的实现方式。

1. 实现思路

在CSS中实现视差效果主要有以下几种思路:

  1. 固定背景法:使用background-attachment: fixed属性将背景图片固定在视口中,当内容滚动时背景保持不动,从而产生视差效果。
  2. 粘性定位法:使用position: sticky属性让特定元素在滚动到特定位置时"粘"在视口中,而其他内容继续滚动,产生视差感。
  3. 基于CSS 3D变换 使用 transform-style: preserve-3d 和 perspective 的3D视差滚动效果

在实际项目中,可以根据自身具体需求选择最适合的方法。

2. 具体实现

下面详细介绍三种具体的CSS滚动视差实现方法:

  1. 基于粘性定位的视差效果
  2. 基于固定背景的视差效果
  3. 基于CSS 3D变换

2.1 基于粘性定位的视差效果

第一种实现方式,使用了position: sticky属性来创建视差效果。

具体代码如下:


    <style>
        .container {
            position: relative;
            width: 100%;
        }
        .container section {
            position: sticky;
            top: 0;
            height: 100vh;
            background: #333;
            display: flex;
        }
        .container section img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
    </style>

    <div class="container">
        <section>
            <img src="https://img1.baidu.com/it/u=2172818577,3783888802&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422">
        </section>
        <section>
            <img src="https://img2.baidu.com/it/u=3018303209,1765139986&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=722">
        </section>
        <section>
            <img src="https://img1.baidu.com/it/u=2376489989,3127732063&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=657">
        </section>
        <section>
            <img src="https://img0.baidu.com/it/u=3739429570,2783758709&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750">
        </section>
        <section>
            <img src="https://img2.baidu.com/it/u=1533484450,2625314894&fm=253&fmt=auto&app=138&f=JPEG?w=664&h=500">
        </section>
    </div>

代码为每个section都使用了position: sticky属性,设置为top: 0,高度为100vh(视口高度)。当滚动页面时,每个section会在到达视口顶部时粘住,保持在视口中可见。

2.2 基于固定背景的视差效果

第二种实现方式,使用了background-attachment: fixed属性来创建视差效果。

具体代码如下:

section {
            width: 100%;
            height: 100vh;
            position: relative;
            overflow: hidden;
            background-repeat: no-repeat;
            background-size: cover;
            background-position: top center;
            background-attachment: fixed;
        }

        section:nth-child(1) {
            background-image: url("https://img1.baidu.com/it/u=2172818577,3783888802&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422");
        }

        section:nth-child(2) {
            background-image: url("https://img2.baidu.com/it/u=3018303209,1765139986&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=722");
        }

        section:nth-child(3) {
            background-image: url("https://img1.baidu.com/it/u=2376489989,3127732063&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=657");
        }

        section:nth-child(4) {
            background-image: url("https://img1.baidu.com/it/u=2172818577,3783888802&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422");
        }

<section></section>
<section></section>
<section></section>
<section></section>

代码通过background-attachment: fixed属性将背景图片固定在视口中。当用户滚动页面时,背景图片保持不动,而内容区域(包含文字的div)正常滚动,从而产生视差效果。

这种方法实现简单,兼容性好,适合创建全屏背景视差效果。

2.3 基于CSS 3D变换

通过CSS 3D变换实现了一个简单的视差滚动效果,主要利用transform-style: preserve-3d以及perspective属性实现。

具体代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        html {
            height: 100vh;
            overflow: hidden;
        }

        body {
            width: 100vw;
            height: 100vh;
            margin: 0;
            background: #222;

            /* 设置景深,并且开启3D效果 */
            perspective: 1px;
            transform-style: preserve-3d;
            overflow-x: hidden;
            overflow-y: scroll;
        }

        .section1::before {
            content: "";
            width: 100%;
            height: 100%;
            position: absolute;
            background: url("https://img1.baidu.com/it/u=2172818577,3783888802&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422") top center;
            background-size: cover;

            /* 将图片图层换到-1图层,并放大图片 */
            transform: translateZ(-1px) scale(2.2);
        }

        .section1,
        .section2 {
            width: 100%;
            min-height: 100vh;
            position: relative;

            /* 保留字类的3D属性 */
            transform-style: preserve-3d;

        }

        .section2 {
            background: rgb(68, 35, 19);
        }

    </style>
</head>

<body>
    <div class="section1">
    </div>

    <div class="section2">
    </div>

</body>

</html>

  • perspective :perspective 定义我们眼睛看到的 3d 立体效果,即空间感。所以perspective: 1px 时,我们是在告诉浏览器,以一个非常近的的距离(1 像素)来观察元素的 3D 变换。

  • transform-style: preserve-3d 开启3D空间。

  • transform: translateZ(-1px) scale(2.2); translateZ(-1px)把元素放在-1图层,距离越远物体越小,所以我们要设置scale将其放大。

  • transform-style: preserve-3d;这是为了让它有自己的3D样式,避免父元素的3D效果对其造成影响。

  • .section1::before: 通过 ::before 伪元素,可以将其视为一个实际的元素,具有自己的 positiontransformopacity 等属性,使得我们可以对背景图像进行 3D 变换、动画效果等操作。而这些操作使用 background-image 可能无法轻松实现。

利用透视(perspective)Z轴位移(translateZ)控制不同层级的滚动速度差异,形成近快远慢的视差效果。

3. 总结

最后总结一下,通过本文的介绍,我们三种方式的纯CSS实现滚动视差效果的方法:基于粘性定位的方法和基于固定背景的方法和基于3D方式。

希望能对你有所帮助,如有错误,请指正O^O!

前端实现验证码输入框

前言

web端验证码输入框常用于用户注册、登录、支付确认场景。与传统的单输入框验证码相比,多输入框分离能够有效防止自动填充和机器人攻击,同时提供更直观的用户体验。

本文将详细介绍如何使用纯前端技术实现一个验证码输入框组件,包含:

  1. 支持自动验证、回车处理
  2. 支持一次性粘贴6位验证码
  3. 输入完成后自动提交验证
  4. 成功/失败提示和动画效果

1. 实现思路和效果

1.1 实现思路

1. 初始化阶段


flowchart TD

    A[加载DOM元素] --> B[设置初始状态]

    B --> C[绑定事件监听器]

    C --> D[禁用所有输入框]

  • 获取所有输入框DOM元素
  • 初始化状态变量(isActive、countdown等)
  • 绑定所有必要的事件监听器
  • 设置输入框为禁用状态

2. 验证码发送流程


flowchart TD

    A[点击发送按钮] --> B[启动60秒倒计时]

    B --> C[激活输入框]

    C --> D[自动聚焦首个输入框]

    D --> E[模拟发送验证码]

  • 按钮点击触发倒计时
  • 激活所有输入框的交互状态
  • 自动聚焦到第一个输入框
  • 显示模拟的验证码发送提示
  1. 输入处理流程

flowchart TD

    A[输入/粘贴事件] --> B{验证输入内容}

    B -->|有效数字| C[自动跳转下一输入框]

    B -->|无效输入| D[清空当前输入]

    C --> E{是否填满6位}

    E -->|是| F[触发自动验证]

    E -->|否| G[等待继续输入]

  • 实时验证输入内容是否为数字
  • 自动处理输入框之间的跳转逻辑
  • 支持退格键回退功能
  • 完整输入后自动触发验证

4. 验证处理流程


flowchart TD

    A[收集输入内容] --> B{验证逻辑}

    B -->|验证成功| C[显示成功状态]

    B -->|验证失败| D[显示错误状态]

    C --> E[准备后续操作]

    D --> F[保留输入框激活状态]

    F --> G[允许重新输入]

  • 验证过程
  • 根据结果显示不同的UI状态
  • 成功时执行跳转逻辑
  • 失败时保留输入状态允许重试

1.2 实现效果

  1. 成功效果

image.png

  1. 失败效果

image.png

2. 代码实现

2.1 HTML结构

<div class="otp-container">
        <h2>请输入6位验证码</h2>
        <div class="otp-field">
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
        </div>
        <div class="message error-message"></div>
        <div class="message success-message"></div>
        <button class="resend-btn">发送验证码</button>
    </div>

关键点:

- 使用readonly属性初始禁用输入框 - inputmode="numeric"确保移动设备显示数字键盘 - pattern="[0-9]*"限制只允许数字输入

2.2 CSS样式


.otp-field input {
    width40px;
    height40px;
    text-align: center;
    border1px solid #ddd;
    border-radius5px;
    margin-right10px;
    font-size18px;
    transition: all 0.3s ease;
    background-color#f9f9f9;
    color#999;
}

.otp-field input.active {
    background-color: white;
    color#333;
}

.otp-field input:focus {
    border-color#4CAF50;
    box-shadow0 0 5px rgba(76175800.5);
    transformscale(1.05);
}

@keyframes shake {
    0%100% { transformtranslateX(0); }
    20%60% { transformtranslateX(-5px); }
    40%80% { transformtranslateX(5px); }
}

.otp-field input.error {
    border-color#f44336;
    animation: shake 0.5s;
}

关键点:

- 使用CSS过渡实现平滑的聚焦效果 - 抖动动画增强错误反馈 - 通过.active类控制输入框视觉状态

2.3 js核心逻辑

2.3.1 初始化阶段变量声明

const inputs = document.querySelectorAll('.otp-field input');
const errorMessage = document.querySelector('.error-message');
const successMessage = document.querySelector('.success-message');
const resendBtn = document.querySelector('.resend-btn');
let otpCode = '';
let countdown = 60;
let countdownInterval;
let isActive = false;
  • inputs:获取所有验证码输入框元素
  • errorMessage/successMessage:获取错误和成功提示元素
  • resendBtn:获取发送验证码按钮
  • otpCode:存储当前输入的验证码
  • countdown:倒计时秒数(初始60秒)
  • countdownInterval:存储倒计时定时器
  • isActive:控制输入框是否可用的标志

2.3.2 初始化函数

function initInputs() {
    // 粘贴事件监听
    document.addEventListener('paste', handlePaste);
    
    // 为每个输入框添加事件
    inputs.forEach((input, index) => {
        input.addEventListener('input', handleInput.bind(null, index));
        input.addEventListener('keydown', handleKeydown.bind(null, index));
    });
}

// 初始调用
initInputs();
deactivateInputs(); // 初始禁用输入框
  • 在document级别监听粘贴事件
  • 为每个输入框绑定input和keydown事件
  • 初始状态下禁用所有输入框

2.3.3 输入框状态管理

  1. 激活输入框:
function activateInputs() {
    isActive = true;
    inputs.forEach(input => {
        input.readOnly = false;
        input.classList.add('active');
    });
    inputs[0].focus(); // 自动聚焦第一个输入框
}
  • 设置isActive为true
  • 移除所有输入框的readOnly属性
  • 添加active类改变状态
  • 自动聚焦到第一个输入框
  1. 禁用输入框:
function deactivateInputs() {
    isActive = false;
    inputs.forEach(input => {
        input.readOnly = true;
        input.classList.remove('active');
        input.value = ''; // 清空所有输入
    });
}
  • 设置isActive为false
  • 添加readOnly属性禁用输入
  • 移除active类恢复默认样式
  • 清空所有输入框内容

2.3.4 事件处理逻辑

  1. 粘贴事件处理
function handlePaste(e) {
    if (!isActive) return; // 检查是否激活状态
    
    // 检查是否粘贴到验证码输入框中
    const activeElement = document.activeElement;
    if (!Array.from(inputs).includes(activeElement)) return;
    
    e.preventDefault();
    const pasteData = e.clipboardData.getData('text/plain').trim();
    
    // 验证是否为6位数字
    if (/^\d{6}$/.test(pasteData)) {
        // 填充到各个输入框
        pasteData.split('').forEach((char, i) => {
            if (inputs[i]) inputs[i].value = char;
        });
        
        inputs[5].focus(); // 聚焦最后一个
        checkCompletion(); // 检查是否完成
    } else {
        showError("请粘贴6位数字验证码");
        shakeInputs(); // 错误抖动效果
    }
}

2. 输入事件处理

function handleInput(index) {
    if (!isActive) return;
    
    const value = this.value;
    
    // 只允许数字输入
    if (!/^\d*$/.test(value)) {
        this.value = '';
        return;
    }
    
    if (value.length === 1) {
        // 自动跳转下一个输入框
        const nextIndex = index + 1;
        if (nextIndex < inputs.length) inputs[nextIndex].focus();
        
        checkCompletion(); // 检查是否完成
    }
}

3. 键盘事件处理

function handleKeydown(index, e) {
    if (!isActive) return;
    
    // 处理退格键
    if (e.key === 'Backspace' && this.value.length === 0) {
        const prevIndex = index - 1;
        if (prevIndex >= 0) {
            inputs[prevIndex].focus();
            inputs[prevIndex].select(); // 选中上一个输入框内容
        }
    }
}

2.3.5 验证逻辑

  1. 检查输入完成
function checkCompletion() {
    otpCode = Array.from(inputs).map(input => input.value).join('');
    
    if (otpCode.length === 6) {
        // 延迟300ms后验证
        setTimeout(() => verifyOtp(otpCode), 300);
    }
}

2. 验证处理

function verifyOtp(code) {
    // 模拟验证(实际项目替换为API调用)
    const isValid = code === '123456';
    
    if (isValid) {
        showSuccess("验证成功!正在跳转...");
        setTimeout(() => {
            alert("验证成功!");
            resetForm();
            deactivateInputs();
        }, 1500);
    } else {
        showError("验证码错误,请重新输入");
        shakeInputs();
    }
}

2.3.6 反馈效果

  1. 成功失败消息提示
function showError(message) {
    errorMessage.textContent = message;
    errorMessage.style.display = 'block';
    errorMessage.classList.add('fade-in');
    successMessage.style.display = 'none';
}

function showSuccess(message) {
    successMessage.textContent = message;
    successMessage.style.display = 'block';
    successMessage.classList.add('fade-in');
    errorMessage.style.display = 'none';
}

2. 错误抖动效果

function shakeInputs() {
    inputs.forEach(input => input.classList.add('error'));
    setTimeout(() => {
        inputs.forEach(input => input.classList.remove('error'));
    }, 500);
}

2.3.7 验证码发送逻辑

  1. 倒计时管理
function initResendButton() {
    updateResendButton();
    countdownInterval = setInterval(() => {
        countdown--;
        updateResendButton();
        if (countdown <= 0) clearInterval(countdownInterval);
    }, 1000);
}

function updateResendButton() {
    resendBtn.textContent = countdown > 0 
        ? `${countdown}秒后重新发送` 
        : "重新发送验证码";
    resendBtn.disabled = countdown > 0;
}

2. 发送验证码

resendBtn.addEventListener('click', function() {
    alert("验证码已发送到您的手机/邮箱");
    countdown = 60; // 重置倒计时
    initResendButton();
    activateInputs(); // 激活输入框
});

3. 完整代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>修复粘贴功能的验证码输入框</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f5f5f5;
        }
        
        .otp-container {
            background-color: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
            text-align: center;
            max-width: 400px;
            width: 100%;
        }
        
        h2 {
            color: #333;
            font-weight: normal;
            font-size: 18px;
            margin-bottom: 20px;
        }
        
        .otp-field {
            display: flex;
            justify-content: center;
            margin-bottom: 20px;
        }
        
        .otp-field input {
            width: 40px;
            height: 40px;
            text-align: center;
            border: 1px solid #ddd;
            border-radius: 5px;
            margin-right: 10px;
            font-size: 18px;
            transition: all 0.3s ease;
            background-color: #f9f9f9;
            color: #999;
        }
        
        .otp-field input.active {
            background-color: white;
            color: #333;
        }
        
        .otp-field input:focus {
            border-color: #4CAF50;
            box-shadow: 0 0 5px rgba(76, 175, 80, 0.5);
            outline: none;
            transform: scale(1.05);
        }
        
        .otp-field input.error {
            border-color: #f44336;
            animation: shake 0.5s;
        }
        
        .message {
            margin-top: 15px;
            padding: 10px;
            border-radius: 5px;
            display: none;
        }
        
        .error-message {
            background-color: #ffebee;
            color: #f44336;
        }
        
        .success-message {
            background-color: #e8f5e9;
            color: #4CAF50;
        }
        
        .resend-btn {
            margin-top: 20px;
            padding: 8px 16px;
            background-color: #2196F3;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        
        .resend-btn:hover {
            background-color: #0b7dda;
        }
        
        .resend-btn:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }
        
        @keyframes shake {
            0%, 100% { transform: translateX(0); }
            20%, 60% { transform: translateX(-5px); }
            40%, 80% { transform: translateX(5px); }
        }
        
        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }
        
        .fade-in {
            animation: fadeIn 0.5s ease-in-out;
        }
    </style>
</head>

<body>
    <div class="otp-container">
        <h2>请输入6位验证码</h2>
        <div class="otp-field">
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
            <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
        </div>
        <div class="message error-message"></div>
        <div class="message success-message"></div>
        <button class="resend-btn">发送验证码</button>
    </div>

    <script>
        const inputs = document.querySelectorAll('.otp-field input');
        const errorMessage = document.querySelector('.error-message');
        const successMessage = document.querySelector('.success-message');
        const resendBtn = document.querySelector('.resend-btn');
        let otpCode = '';
        let countdown = 60;
        let countdownInterval;
        let isActive = false;

        // 初始化输入框事件
        function initInputs() {
            // 在document上监听粘贴事件
            document.addEventListener('paste', function(e) {
                if (!isActive) return;
                
                // 检查是否粘贴到验证码输入框中
                const activeElement = document.activeElement;
                if (!Array.from(inputs).includes(activeElement)) return;
                
                e.preventDefault();
                const pasteData = e.clipboardData.getData('text/plain').trim();
                
                // 验证粘贴内容是否为6位数字
                if (/^\d{6}$/.test(pasteData)) {
                    // 将粘贴内容分割并填充到各个输入框
                    pasteData.split('').forEach((char, i) => {
                        if (inputs[i]) {
                            inputs[i].value = char;
                        }
                    });
                    
                    // 自动聚焦最后一个输入框
                    inputs[5].focus();
                    
                    // 检查是否完成输入
                    checkCompletion();
                } else {
                    // 粘贴内容不符合要求,显示错误
                    showError("请粘贴6位数字验证码");
                    shakeInputs();
                }
            });

            inputs.forEach((input, index) => {
                // 输入事件处理
                input.addEventListener('input', function() {
                    if (!isActive) return;
                    
                    const value = this.value;
                    
                    // 只允许数字输入
                    if (!/^\d*$/.test(value)) {
                        this.value = '';
                        return;
                    }
                    
                    if (value.length === 1) {
                        // 自动跳转下一个输入框
                        const nextIndex = index + 1;
                        if (nextIndex < inputs.length) {
                            inputs[nextIndex].focus();
                        }
                        
                        // 检查是否所有输入框都已填写
                        checkCompletion();
                    }
                });
                
                // 键盘事件处理
                input.addEventListener('keydown', function(e) {
                    if (!isActive) return;
                    
                    if (e.key === 'Backspace') {
                        const value = this.value;
                        if (value.length === 0) {
                            // 退格键处理空输入框
                            const prevIndex = index - 1;
                            if (prevIndex >= 0) {
                                inputs[prevIndex].focus();
                                inputs[prevIndex].select();
                            }
                        }
                    }
                });
            });
        }

        // 激活输入框
        function activateInputs() {
            isActive = true;
            inputs.forEach(input => {
                input.readOnly = false;
                input.classList.add('active');
            });
            inputs[0].focus();
        }

        // 禁用输入框
        function deactivateInputs() {
            isActive = false;
            inputs.forEach(input => {
                input.readOnly = true;
                input.classList.remove('active');
                input.value = '';
            });
        }

        // 检查是否所有输入框都已填写
        function checkCompletion() {
            otpCode = Array.from(inputs).map(input => input.value).join('');
            
            if (otpCode.length === 6) {
                // 所有输入框都已填写,触发自动提交
                setTimeout(() => {
                    verifyOtp(otpCode);
                }, 300);
            }
        }

        // 验证验证码
        function verifyOtp(code) {
            // 这里应该是实际的验证逻辑,这里用模拟验证
            const isValid = code === '123456'; // 固定验证码123456
            
            if (isValid) {
                // 验证成功
                showSuccess("验证成功!正在跳转...");
                
                // 模拟跳转
                setTimeout(() => {
                    alert("验证成功!在实际应用中这里会进行页面跳转或执行其他操作。");
                    resetForm();
                    deactivateInputs();
                }, 1500);
            } else {
                // 验证失败
                showError("验证码错误,请重新输入");
                shakeInputs();
            }
        }

        // 显示错误信息
        function showError(message) {
            errorMessage.textContent = message;
            errorMessage.style.display = 'block';
            errorMessage.classList.add('fade-in');
            successMessage.style.display = 'none';
        }

        // 显示成功信息
        function showSuccess(message) {
            successMessage.textContent = message;
            successMessage.style.display = 'block';
            successMessage.classList.add('fade-in');
            errorMessage.style.display = 'none';
        }

        // 重置表单
        function resetForm() {
            inputs.forEach(input => {
                input.classList.remove('error');
            });
            errorMessage.style.display = 'none';
            successMessage.style.display = 'none';
        }

        // 输入框抖动效果
        function shakeInputs() {
            inputs.forEach(input => {
                input.classList.add('error');
            });
            
            setTimeout(() => {
                inputs.forEach(input => {
                    input.classList.remove('error');
                });
            }, 500);
        }

        // 初始化重新发送按钮倒计时
        function initResendButton() {
            updateResendButton();
            countdownInterval = setInterval(() => {
                countdown--;
                updateResendButton();
                
                if (countdown <= 0) {
                    clearInterval(countdownInterval);
                    resendBtn.textContent = "重新发送验证码";
                    resendBtn.disabled = false;
                }
            }, 1000);
        }

        // 更新重新发送按钮状态
        function updateResendButton() {
            if (countdown > 0) {
                resendBtn.textContent = `${countdown}秒后重新发送`;
                resendBtn.disabled = true;
            }
        }

        // 重新发送验证码
        resendBtn.addEventListener('click', function() {
            // 模拟发送验证码
            alert("验证码已发送到您的手机/邮箱");
            
            // 重置倒计时
            countdown = 60;
            initResendButton();
            
            // 激活输入框
            activateInputs();
        });

        // 初始化
        initInputs();
        deactivateInputs(); // 初始状态下禁用输入框
    </script>
</body>
</html>

4. 总结

最后总结一下,前端验证码输入框核心逻辑就是监听input输入,进行聚焦输入框,并检查是否全部输入;监听回车,可进行删除,聚焦前一个。

如有错误,请指正O^O!

纯前端实现滑动拼图验证码

前言

在日常应用中,验证码是防止恶意攻击的重要手段。其中滑动拼图验证码的用户体验更好。

本文将详细介绍如何仅使用HTML、CSS和JavaScript实现一个纯前端的滑动拼图验证码功能,无需后端支持即可完成验证。

1. 实现效果和思路

1.1 效果展示

ezgif.com-video-to-gif-converter.gif

1.2 实现思路

我们实现的滑动拼图验证码包含两个主要部分:

  1. 验证区域:显示一张背景图片,其中有一个半透明的方形缺口(目标位置)和一个可以滑动的拼图块
  2. 滑动条区域:包含一个可拖动的滑块和操作提示

用户需要通过拖动滑块将拼图块移动到正确位置(与缺口重合)来完成验证。如果位置准确(误差在2像素内),则验证成功;否则滑块和拼图块会复位,用户可以重新尝试。

具体流程如下:

flowchart LR
    A[初始化] --> B[生成随机缺口]
    B --> C[渲染拼图UI]
    C --> D[拖拽事件处理]
    D --> E[精度校验]
    E --> F[结果回调]

2. 代码实现

下面具体实现,代码实现主要分为三个部分:

  1. 布局结构:使用HTML创建验证区域和滑动条区域的基本结构
  2. 样式设计:使用CSS定位验证缺口和拼图块,确保拼图块能正确显示背景图片的对应部分
  3. 交互逻辑:通过JavaScript实现:
    • 随机生成验证缺口位置
    • 滑块拖拽功能
    • 验证位置判断
    • 成功/失败反馈

2.1 HTML结构

<div class="check">
    <div class="check-content"></div>
    <div class="check-block"></div>
</div>
<div class="drag">
    <div class="drag-block"></div>
    <div class="drag-tips">
        按住左边按钮向右拖动完成上方图像验证
    </div>
</div>
  • .check:验证区域容器,包含背景图片
  • .check-content:验证缺口(目标位置)
  • .check-block:可移动的拼图块
  • .drag:滑动条区域
  • .drag-block:可拖动的滑块
  • .drag-tips:操作提示文字

2.2 CSS样式

.check {
    width: 375px;
    height: 250px;
    background-repeat: no-repeat;
    background-size: 100% 100%;
    background-image: url(背景图片URL);
}

验证区域设置固定宽高和背景图片,背景图片会自适应填充整个区域。

.check-content {
    width: 50px;
    height: 50px;
    background: rgba(0, 0, 0, 0.5);
    border: 1px solid #fff;
    position: absolute;
    top: 100px;
    left: 280px;
}

验证缺口使用绝对定位,半透明黑色背景(rgba(0,0,0,0.5))和白色边框使其在背景图上可见。

.check-block {
    width: 50px;
    height: 50px;
    border: 1px solid #fff;
    background-image: inherit;
    background-repeat: inherit;
    background-size: 400px 300px;
    background-position: -280px -100px;
    position: absolute;
    top: 100px;
    left: 10px;
}

拼图块的关键在于background-image: inherit继承父级的背景图片,然后通过background-position调整显示位置,使其显示与缺口对应的背景部分。background-size设置为原图尺寸确保图片定位准确。

.drag {
    width: 375px;
    height: 50px;
    background-color: #e3e3e3;
    margin-top: 10px;
    position: relative;
}
.drag-block {
    width: 50px;
    height: 50px;
    background-color: yellowgreen;
    z-index: 10;
    position: absolute;
    top: 0;
    left: 0;
}
.drag-tips {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    text-align: center;
    line-height: 50px;
    font-size: 12px;
    color: #8a8a8a;
}

滑动条区域使用相对简单的样式,滑块使用亮色突出显示,提示文字居中。

2.3 JavaScript交互逻辑

2.3.1 随机生成验证位置

let x = random(0, 325)
let y = random(0, 200)

function random(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min) + min)
}

使用random()函数生成缺口和拼图块的随机位置,x范围0-325(考虑滑块宽度),y范围0-200。

2.3.2 设置初始位置

const checkContent = document.querySelector('.check-content')
const checkBlock = document.querySelector('.check-block')
checkContent.style.cssText = `left:${x}px;top:${y}px`
checkBlock.style.cssText = `background-position: -${x}px -${y}px;top: ${y}px`

将验证缺口定位到随机位置,同时设置拼图块的背景位置与之对应,确保拼图块显示的是缺口位置的背景内容。

2.3.3 滑块拖拽功能

let animating = false
let startX = 0
const dragBlock = document.querySelector('.drag-block')

dragBlock.addEventListener('mousedown', (e) => {
    animating = true
    startX = e.pageX
})

document.addEventListener('mouseup', () => {
    animating = false
})
  • animating标记拖动状态
  • startX记录鼠标按下时的初始位置
  • 鼠标按下时开始拖动,鼠标释放时结束拖动

2.3.4 拖动处理

let offsetX = 0
const drag = document.querySelector('.drag')

drag.addEventListener('mousemove', (event) => {
    if (!animating) return 
    offsetX = event.pageX - startX
    if (offsetX < 0 || offsetX > 350) { return }
    dragBlock.style.transform = `translateX(${offsetX}px`
    checkBlock.style.left = `${offsetX}px`
})

计算鼠标移动距离并限制范围(0-350px),同时移动滑块和拼图块。

2.3.5 验证结果判断

drag.addEventListener('mouseup', () => {
    animating = false
    if (offsetX >= x-2 && offsetX <= x+2) {
        alert('成功')
    } else {
        dragBlock.style.transform = `translateX(0px)`
        checkBlock.style.left = `0px`
    }
})

松开鼠标时判断滑块位置是否在目标位置的±2像素范围内,成功则提示,失败则复位。

3. 完整代码

完整代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        .check {
            width: 375px;
            height: 250px;
            background-repeat: no-repeat;
            background-size: 100% 100%;
            background-image: url(https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimage109.360doc.com%2FDownloadImg%2F2025%2F04%2F0321%2F296122601_4_20250403090445718&refer=http%3A%2F%2Fimage109.360doc.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1753661288&t=a771e85cfd66e6e402deb166953f266e&w=750&h=500);
        }

        .check-content {
            width: 50px;
            height: 50px;
            background: rgba(0, 0, 0, 0.5);
            border: 1px solid #fff;
            position: absolute;
            top: 100px;
            left: 280px;
        }


        .check-block {
            width: 50px;
            height: 50px;
            border: 1px solid #fff;
            /* 直接继承父级 */
            background-image: inherit;
            background-repeat: inherit;
            /* 图片的大小*/
            background-size: 400px 300px;
            /* 设置为校验区域的坐标位置 check-content的left和top */
            background-position: -280px -100px;
            position: absolute;
            top: 100px;
            left: 10px;
        }

        .drag {
            width: 375px;
            height: 50px;
            background-color: #e3e3e3;
            margin-top: 10px;
            position: relative;
        }

        .drag-block {
            width: 50px;
            height: 50px;
            background-color: yellowgreen;
            z-index: 10;
            position: absolute;
            top: 0;
            left: 0;
        }

        .drag-tips {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            text-align: center;
            line-height: 50px;
            font-size: 12px;
            color: #8a8a8a;
        }
    </style>
</head>

<body>
    <div class="check">
        <div class="check-content"></div>
        <div class="check-block"></div>
    </div>
    <div class="drag">
        <div class="drag-block">
            
        </div>
        <div class="drag-tips">
           按住左边按钮向右拖动完成上方图像验证
        </div>
    </div>
</body>

<script>
    //随机生成一个x , Y坐标 用于设置校验块的位置 因为滑块有50px的宽度 所以x的范围为0-325 y的范围为0-200
    let x = random(0, 325)
    let y = random(0, 200)
    //随机函数
    //@ param min 最小值
    //@ param max 最大值
    function random(min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min) + min)
    }

    // 设置校验块的位置
    const checkContent = document.querySelector('.check-content')
    const checkBlock = document.querySelector('.check-block')
    checkContent.style.cssText = `left:${x}px;top:${y}px`
    checkBlock.style.cssText = `background-position: -${x}px -${y}px;top: ${y}px`


    // 定义个运动状态 如果为true代表鼠标已经按下
    let animating = false
    // 定义存储鼠标按下的x坐标
    let startX = 0
    // 获取拖拽的滑块
    const dragBlock = document.querySelector('.drag-block')
    // 添加鼠标按下事件
    dragBlock.addEventListener('mousedown', (e) => {
        animating = true
        startX = e.pageX
    })
    // 添加鼠标弹起事件
    document.addEventListener('mouseup', () => {
        animating = false
    })

    // 存储移动的距离
    let offsetX = 0
    // 获取校验区域
    const drag = document.querySelector('.drag')
    // 添加鼠标移动事件
    drag.addEventListener('mousemove', (event) => {
        // 没有按下鼠标 不执行
        if (!animating) return 
        // 存储鼠标移动的距离
        offsetX = event.pageX - startX
        // 限制移动的范围 0-350
        if (offsetX < 0 || offsetX > 350) { return }
        // 修改可移动盒子的 x 轴坐标
        dragBlock.style.transform = `translateX(${offsetX}px`
        // 设置被验证滑块left值
        checkBlock.style.left = `${offsetX}px`
    })


    drag.addEventListener('mouseup', () => {
        animating = false
        // 根据移动的位置判断是否成功
        // 移动的距离和校验块的x坐标的差值小于2 代表成功
        if (offsetX >= x-2 && offsetX <= x+2) {
            alert('成功')
        } else {
            //  验证失败 滑块和被验证块恢复坐标
            dragBlock.style.transform = `translateX(0px)`
            checkBlock.style.left = `0px`
        }
    })


</script>

</html>

3. 总结

最后总结一下,滑动拼图验证码的核心原理是在背景中生成随机的拼图块,进行拖拽匹配,如果匹配在误差合理范围内则成功,否则失败。

希望本文能对你有所帮助,如果有错误,请指正O^O!

❌