如何使用HTML+JavaScript實現滑動驗證碼_前端滑動驗證碼

以下是使用 HTML + JavaScript(Canvas) 實現滑動拼圖驗證碼的完整示例。該示例模擬了主流滑動驗證碼的核心流程:隨機生成缺口、拖拽滑塊、驗證對齊。

 1. 原理說明

如何使用HTML+JavaScript實現滑動驗證碼_前端滑動驗證碼

- 背景底板:Canvas 繪制純色背景或圖片,並“摳出”一個缺口(矩形或異形)。  

- 滑塊:可拖動的拼圖塊,初始位於左側起始區域。  

- 交互:監聽滑鼠/觸摸事件,實時更新滑塊的水平位置。  

- 驗證:松開滑鼠時,計算滑塊當前位置與缺口位置的差值,若在容差範圍內則通過。

> 生產環境還需增加行為軌跡驗證、加密參數、服務端二次校驗等安全措施,本例僅演示前端交互邏輯。

 2. 完整示例代碼

將以下代碼保存為 `.html` 文件,用瀏覽器打開即可體驗。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>滑動拼圖驗證碼(Canvas實現)</title>
    <style>
        body {
            font-family: sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: #f0f2f5;
            margin: 0;
        }
        .captcha-container {
            text-align: center;
            background: white;
            padding: 30px;
            border-radius: 12px;
            box-shadow: 0 5px 20px rgba(0,0,0,0.1);
        }
        canvas {
            display: block;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            margin-bottom: 15px;
            cursor: pointer;
        }
        .slider-track {
            position: relative;
            width: 300px;
            height: 40px;
            background: #e9ecef;
            border-radius: 20px;
            margin: 0 auto;
            user-select: none;
        }
        .slider-button {
            position: absolute;
            width: 40px;
            height: 40px;
            background: #4a90e2;
            border-radius: 50%;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            cursor: grab;
            transition: box-shadow 0.1s;
            left: 0;
            top: 0;
        }
        .slider-button:active {
            cursor: grabbing;
            box-shadow: 0 4px 8px rgba(0,0,0,0.3);
        }
        .result {
            margin-top: 20px;
            font-size: 16px;
            color: #333;
            min-height: 24px;
        }
        .btn-reset {
            margin-top: 15px;
            padding: 8px 20px;
            background: #6c757d;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
        }
        .btn-reset:hover {
            background: #5a6268;
        }
    </style>
</head>
<body>
<div>
    <h3>滑動拼圖驗證碼</h3>
    <!-- 拼圖底板 Canvas -->
    <canvas id="puzzleCanvas" width="300" height="150"></canvas>
    <!-- 滑動軌道與滑塊(獨立於 Canvas,便於拖拽) -->
    <div id="sliderTrack">
        <div id="sliderButton"></div>
    </div>
    <div id="resultMessage"></div>
    <button id="resetBtn">刷新驗證</button>
</div>
<script>
    (function() {
        // -- 配置參數 --
        const canvas = document.getElementById('puzzleCanvas');
        const ctx = canvas.getContext('2d');
        const track = document.getElementById('sliderTrack');
        const button = document.getElementById('sliderButton');
        const resultMsg = document.getElementById('resultMessage');
        // 缺口參數
        const GAP_WIDTH = 40;          // 缺口寬度
        const GAP_HEIGHT = 40;         // 缺口高度
        const GAP_Y = 55;             // 缺口垂直位置(固定,更真實可隨機)
        const TOLERANCE = 5;          // 允許的誤差像素
        // 滑塊參數(與缺口大小一致)
        const SLIDER_SIZE = 40;        // 滑塊寬高
        // 狀態變量
        let gapX = 0;                // 缺口左上角的X坐標(隨機生成)
        let isDragging = false;      // 是否正在拖拽
        let startMouseX = 0;         // 拖拽起始滑鼠X
        let startButtonLeft = 0;     // 拖拽起始按鈕left值
        let verified = false;       // 是否已驗證成功
        // 軌道相關尺寸
        const trackWidth = track.clientWidth;
        const buttonWidth = button.clientWidth;
        const maxLeft = trackWidth - buttonWidth; // 滑塊最大可移動距離
        // -- 初始化 / 重置 --
        function resetCaptcha() {
            // 隨機生成缺口X坐標(範圍:左邊距10px ~ 右邊距-缺口寬度-10px)
            const minGapX = 10;
            const maxGapX = canvas.width - GAP_WIDTH - 10;
            gapX = Math.floor(Math.random() * (maxGapX - minGapX + 1) + minGapX);
            // 重置滑塊位置
            button.style.left = '0px';
            // 重置驗證狀態
            verified = false;
            resultMsg.innerHTML = '';
            resultMsg.style.color = '#333';
            // 繪制底板
            drawPuzzle();
        }
        // -- 繪制拼圖底板(包含缺口)--
        function drawPuzzle() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            // 繪制背景(模擬圖片,此處用漸變色)
            const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
            gradient.addColorStop(0, '#9fa8da');
            gradient.addColorStop(0.5, '#80cbc4');
            gradient.addColorStop(1, '#ffcc80');
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            // 增加一些噪點/紋理,更逼真
            ctx.fillStyle = 'rgba(255,255,255,0.3)';
            for (let i = 0; i < 50; i++) {
                ctx.beginPath();
                ctx.arc(Math.random() * canvas.width, Math.random() * canvas.height, 1, 0, 2 * Math.PI);
                ctx.fill();
            }
            // -- 繪制缺口(鏤空效果)--
            ctx.save();
            // 缺口區域用深色陰影表示凹陷
            ctx.shadowColor = 'rgba(0,0,0,0.5)';
            ctx.shadowBlur = 4;
            ctx.shadowOffsetX = 2;
            ctx.shadowOffsetY = 2;
            ctx.fillStyle = '#2c3e50';   // 深色填充缺口
            ctx.fillRect(gapX, GAP_Y, GAP_WIDTH, GAP_HEIGHT);
            // 邊緣高光,增加立體感
            ctx.shadowBlur = 0;
            ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = 0;
            ctx.strokeStyle = 'rgba(255,255,255,0.6)';
            ctx.lineWidth = 1;
            ctx.strokeRect(gapX + 0.5, GAP_Y + 0.5, GAP_WIDTH - 1, GAP_HEIGHT - 1);
            ctx.restore();
            // 在缺口位置上方寫個“缺口”提示(僅演示,實際不需要)
            ctx.font = '12px Arial';
            ctx.fillStyle = 'white';
            ctx.shadowColor = 'black';
            ctx.shadowBlur = 2;
            ctx.fillText('⬅ 拖滑塊對齊這裏', gapX - 70, GAP_Y - 8);
            ctx.shadowBlur = 0;
            // 繪制起始標記(滑塊初始位置)
            ctx.fillStyle = 'rgba(74,144,226,0.3)';
            ctx.fillRect(2, GAP_Y, SLIDER_SIZE, SLIDER_SIZE);
            ctx.strokeStyle = '#4a90e2';
            ctx.lineWidth = 1.5;
            ctx.strokeRect(2, GAP_Y, SLIDER_SIZE, SLIDER_SIZE);
        }
        // -- 拖拽滑塊邏輯 --
        function onDragStart(e) {
            if (verified) return; // 已驗證成功,不可再拖
            e.preventDefault();
            isDragging = true;
            // 記錄開始時的滑鼠X位置(兼容觸摸)
            const clientX = e.type.startsWith('touch') ? e.touches[0].clientX : e.clientX;
            startMouseX = clientX;
            // 獲取當前按鈕的 left 值(可能帶px)
            const leftStr = window.getComputedStyle(button).left;
            startButtonLeft = parseFloat(leftStr) || 0;
            // 綁定全局事件
            document.addEventListener('mousemove', onDragMove);
            document.addEventListener('mouseup', onDragEnd);
            document.addEventListener('touchmove', onDragMove, { passive: false });
            document.addEventListener('touchend', onDragEnd);
        }
        function onDragMove(e) {
            if (!isDragging) return;
            e.preventDefault();
            const clientX = e.type.startsWith('touch') ? e.touches[0].clientX : e.clientX;
            const deltaX = clientX - startMouseX; // 滑鼠移動差值
            let newLeft = startButtonLeft + deltaX;
            // 限制範圍 0 ~ maxLeft
            newLeft = Math.max(0, Math.min(newLeft, maxLeft));
            // 更新滑塊位置
            button.style.left = newLeft + 'px';
            // 實時在Canvas上繪制滑塊跟隨位置(增強反饋)
            drawSliderOnCanvas(newLeft);
        }
        // 在Canvas上繪制滑塊(拼圖塊)實時位置
        function drawSliderOnCanvas(buttonLeft) {
            // 重繪底板
            drawPuzzle();
            // 將按鈕的left值映射到Canvas上的X坐標
            // 滑塊在Canvas上的X範圍:左側起始2px,右側截止 canvas.width - SLIDER_SIZE - 2
            const minSliderX = 2;
            const maxSliderX = canvas.width - SLIDER_SIZE - 2;
            // 按鈕left: 0 ~ maxLeft,映射到 sliderX
            const sliderX = minSliderX + (buttonLeft / maxLeft) * (maxSliderX - minSliderX);
            // 繪制滑塊(帶陰影和邊框)
            ctx.save();
            ctx.shadowColor = 'rgba(0,0,0,0.3)';
            ctx.shadowBlur = 5;
            ctx.shadowOffsetX = 2;
            ctx.shadowOffsetY = 2;
            ctx.fillStyle = '#4a90e2';
            ctx.fillRect(sliderX, GAP_Y, SLIDER_SIZE, SLIDER_SIZE);
            // 增加內發光效果
            ctx.shadowBlur = 0;
            ctx.strokeStyle = '#ffffff';
            ctx.lineWidth = 2;
            ctx.strokeRect(sliderX + 1, GAP_Y + 1, SLIDER_SIZE - 2, SLIDER_SIZE - 2);
            // 在滑塊上繪制箭頭圖案(提示可拖動)
            ctx.font = '18px Arial';
            ctx.fillStyle = 'white';
            ctx.shadowColor = 'rgba(0,0,0,0.5)';
            ctx.shadowBlur = 3;
            ctx.fillText('⇨', sliderX + 10, GAP_Y + 28);
            ctx.restore();
        }
        function onDragEnd(e) {
            if (isDragging) {
                // 驗證對齊
                verifyMatch();
            }
            isDragging = false;
            // 移除全局事件
            document.removeEventListener('mousemove', onDragMove);
            document.removeEventListener('mouseup', onDragEnd);
            document.removeEventListener('touchmove', onDragMove);
            document.removeEventListener('touchend', onDragEnd);
        }
        // 驗證滑塊是否與缺口對齊
        function verifyMatch() {
            // 獲取當前按鈕left值
            const leftStr = window.getComputedStyle(button).left;
            const currentLeft = parseFloat(leftStr) || 0;
            // 將按鈕left映射到Canvas上的滑塊X坐標
            const minSliderX = 2;
            const maxSliderX = canvas.width - SLIDER_SIZE - 2;
            const sliderX = minSliderX + (currentLeft / maxLeft) * (maxSliderX - minSliderX);
            // 計算滑塊與缺口的水平距離
            const diff = Math.abs(sliderX - gapX);
            if (diff <= TOLERANCE) {
                verified = true;
                resultMsg.innerHTML = '✅ 驗證通過!';
                resultMsg.style.color = 'green';
                // 驗證成功後,將滑塊固定在缺口位置(視覺上)
                drawSuccessState();
            } else {
                resultMsg.innerHTML = `❌ 驗證失敗,請再試一次 (誤差${Math.round(diff)}px)`;
                resultMsg.style.color = 'red';
                // 失敗後將滑塊彈回原位
                button.style.left = '0px';
                drawPuzzle(); // 重繪底板,清除滑塊繪制
            }
        }
        // 驗證成功後的展示:滑塊嵌合到缺口
        function drawSuccessState() {
            drawPuzzle(); // 重繪底板
            ctx.save();
            // 在缺口位置繪制藍色滑塊,表示已對齊
            ctx.fillStyle = '#4a90e2';
            ctx.shadowColor = 'rgba(0,0,0,0.3)';
            ctx.shadowBlur = 5;
            ctx.fillRect(gapX, GAP_Y, SLIDER_SIZE, SLIDER_SIZE);
            ctx.shadowBlur = 0;
            ctx.strokeStyle = '#fff';
            ctx.lineWidth = 2;
            ctx.strokeRect(gapX + 1, GAP_Y + 1, SLIDER_SIZE - 2, SLIDER_SIZE - 2);
            ctx.restore();
            // 滑塊按鈕變綠色,並鎖定
            button.style.background = '#28a745';
            button.style.cursor = 'default';
        }
        // -- 重置 --
        function resetAll() {
            // 重置滑塊樣式
            button.style.left = '0px';
            button.style.background = '#4a90e2';
            button.style.cursor = 'grab';
            resetCaptcha();
        }
        // -- 事件綁定 --
        function bindEvents() {
            // 拖拽開始:滑鼠/觸摸
            button.addEventListener('mousedown', onDragStart);
            button.addEventListener('touchstart', onDragStart, { passive: false });
            // 防止拖拽時選中頁面其他文字
            button.addEventListener('dragstart', (e) => e.preventDefault());
            // 重置按鈕
            document.getElementById('resetBtn').addEventListener('click', resetAll);
        }
        // 初始化
        resetCaptcha();
        bindEvents();
        // 窗口大小變化時重新計算軌道寬度(但本例尺寸固定,可不處理)
        // 如果父容器寬度變化,需動態更新maxLeft,此處略
    })();
</script>
</body>
</html>

3. 代碼關鍵點解析

模塊                說明                                                                                                                                 

缺口生成         `gapX` 隨機生成,限制在畫布左右邊距內,避免缺口溢出。                                                                                

滑塊拖拽          使用獨立 `div` 模擬滑塊按鈕,監聽 `mousedown`/`touchstart` 開始拖拽,全局監聽移動事件。                                              

滑塊位置映射  將按鈕在軌道上的 `left` 值(0~`maxLeft`)線性映射到 Canvas 上的 X 坐標(2~`canvas.width-SLIDER_SIZE-2`),保證滑塊繪制位置與按鈕同步。 

實時繪制        拖拽過程中不斷調用 `drawSliderOnCanvas()`,在底板缺口上方繪制移動的滑塊,視覺反饋更真實。                                            

驗證邏輯       松開滑鼠時計算滑塊與缺口水平距離,若小於 `TOLERANCE` 則通過。驗證通過後滑塊變色鎖定,不可再拖。                                     

重置功能      重新生成缺口位置,滑塊歸位,恢復樣式。                                                                                               

 4. 常見問題與註意事項

(1). 移動端適配  

   - 已添加 `touchstart`、`touchmove`、`touchend` 事件,並設置 `passive: false` 以允許 `preventDefault()` 阻止滚動。  

   - Canvas 尺寸建議固定,避免高分屏模糊(可考慮 `window.devicePixelRatio` 適配,本例略)。

(2). 安全性缺陷  

   - 純前端驗證極易被繞過。真實場景必須將拖拽軌跡、最終位置等參數加密發送至服務端校驗,並增加時間戳、流水號等防重放機制。

(3). 用戶體驗  

   - 驗證失敗時滑塊歸位並提示誤差,可增加緩動動畫,提升友好度。  

   - 缺口形狀可設計為異形(如凸起/凹陷),增加破解難度。

(4). 性能優化  

   - 拖拽期間頻繁繪制 Canvas,低端設備可能卡頓。可考慮使用 `requestAnimationFrame` 節流。

(5). 擴展性  

   - 可替換背景為真實圖片,缺口位置自適應。  

   - 增加滑塊陰影、透明效果,使其更像“拼圖塊”。

 5. 總結

上述示例完整實現了基於 Canvas + 獨立滑塊按鈕的滑動拼圖驗證碼,涵蓋了隨機缺口、拖拽跟隨、對齊驗證、狀態重置等核心功能。開發者可直接使用此代碼作為原型,並在此基礎上增加服務端校驗、行為分析等安全加固,以滿足生產環境需求。

分享給朋友:

“如何使用HTML+JavaScript實現滑動驗證碼_前端滑動驗證碼” 的相關文章

meter元素顏色,可以使用CSS樣式來設置顏色

meter元素顏色,可以使用CSS樣式來設置顏色

meter元素可以用於表示已知範圍內的度量值,可以使用CSS樣式來設置顏色。具體來說,可以使用 <code>::-webkit-meter-optimum-value, ::-moz-meter-bar, ::-webkit-meter-bar</code> 偽元素來設置顏色…

css高亮效果,CSS 創建不同高亮效果具體方法及例子

css高亮效果,CSS 創建不同高亮效果具體方法及例子

CSS 可以為  元素創建各種不同的高亮效果,具體方法如下:1. 使用默認的高亮樣式。可以通過樣式修改  元素的背景色和文本顏色,實現自定義高亮效果,例如:```html這是一段需要高亮的文本,這裏面的文本被自定義高亮了,這裏又是正常的文本。.highlight {  …

一個簡單的html大海日出特效的代碼

一個簡單的html大海日出特效的代碼

以下是一個簡單的html大海日出特效的代碼:1. 使用html和css定義了一個大海和太陽的基本樣式;2. 使用animation讓太陽從初始位置向上升起,並且設置好其動畫屬性;3. 設置大海背景漸變和水面的動畫效果。…

星空特效的HTML代碼示例

星空特效的HTML代碼示例

以下是一個星空特效的HTML代碼示例:這個代碼會在頁面背景中生成100個閃爍的星星,使得整個頁面看起來像是夜空中的星空。可以在瀏覽器中運行查看效果。這些代碼可以在瀏覽器中運行並產生相應的星空特效。…

用html和CSS3制作酷炫的導航欄代碼及例子

用html和CSS3制作酷炫的導航欄代碼及例子

使用HTML5和CSS3的新特性可以制作出很多酷炫的導航欄效果,例如下拉菜單、響應式導航欄、帶有動態效果的導航欄等等。下面以下拉菜單為例,具體步驟如下:1. 創建 HTML 結構;2. 設置基本樣式;3. 添加動態效果。這樣就可以制作出下拉菜單效果,當滑鼠懸停在菜單項上時,菜單項下面的下拉菜單顯示出…

HTML標簽屬性大全及代碼例子

HTML標簽屬性大全及代碼例子

在HTML中,屬性是在標簽中使用的特殊命令,它們提供了額外的信息以更好地描述標簽的內容和行為。屬性名表示該屬性的名稱,而屬性值表示該屬性要設置的值。HTML標簽屬性有很多種類和用途,它們可以影響標簽的內容、顏色、尺寸、超鏈接、樣式、表單等方面。一些常見的HTML標簽屬性包括:class、id、sty…