Building an Image CAPTCHA Checker with JavaScript
February 17, 20255 min read

Building an Image CAPTCHA Checker with JavaScript

Web developmentJavaScriptCSS3Programming
share this article on
CAPTCHAs (Completely Automated Public Turing tests to tell Computers and Humans Apart) help distinguish between human users and bots. In this tutorial, we’ll create an image CAPTCHA that challenges users to select all images containing a specific category, such as "planes" or "buses."

This article will guide you through building a dynamic CAPTCHA with JavaScript, HTML, and CSS. We’ll also explain how to handle user feedback and refresh images upon incorrect attempts.


Features of the CAPTCHA

  1. Displays a grid of 9 images, where 4 belong to a specified category (e.g., planes).
  2. Users must select all 4 correct images to pass the CAPTCHA.
  3. If incorrect, a new set of images is loaded automatically.
  4. Includes visual and auditory feedback to enhance accessibility.
  5. Results are displayed dynamically within the CAPTCHA interface.

Step 1: Setting Up the HTML Structure

The HTML defines the basic structure of the CAPTCHA interface. Here’s the code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Image CAPTCHA</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>

<div class="captcha-card">
    <div class="captcha-header">
        <h2>Select all images containing a <span id="category-name">plane</span></h2>
        <img id="category-image" src="./images/plane1.jpg" alt="Category example">
    </div>
    <div id="captcha-container" class="captcha-grid"></div>
    <div class="captcha-footer">
        <div>
            <span class="icon" id="refresh-btn">🔄</span>
            <span class="icon" id="audio-btn">🔊</span>
        </div>
        <button id="verify-btn">Verify</button>
    </div>
</div>

<script src="script.js"></script>

</body>
</html>

Step 2: Styling with CSS

The CSS styles the timer, input fields, and buttons, adding visual appeal and an animation for the timer display.

body {
    font-family: Arial, sans-serif;
    background-color: #161a20;
    color: #fff;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
}
.captcha-card {
    background-color: #fff;
    color: #000;
    width: 400px;
    border-radius: 10px;
    overflow: hidden;
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}
.captcha-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background-color: #007c7e;
    padding: 10px 20px;
}
.captcha-header h2 {
    margin: 0;
    font-size: 16px;
    font-weight: normal;
    color: #fff;
}
.captcha-header img {
    width: 50px;
    height: 50px;
    object-fit: cover;
    border-radius: 5px;
}

#category-name {
    font-weight: bold;
}

.captcha-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    padding: 20px;
    background-color: #fff;
}
.captcha-image {
    width: 100%;
    height: 100px;
    object-fit: cover;
    border: 2px solid transparent;
    cursor: pointer;
    transition: 0.3s;
}
.captcha-image.selected {
    border-color: #00ff00;
}
.captcha-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 20px;
    background-color: #f8f8f8;
}
.captcha-footer button {
    padding: 10px 15px;
    font-size: 14px;
    background-color: #007c7e;
    border: none;
    cursor: pointer;
    color: #fff;
    border-radius: 5px;
}
.captcha-footer button:hover {
    background-color: #00cc00;
}
.captcha-footer .icon {
    font-size: 18px;
    cursor: pointer;
    margin-right: 10px;
}
.captcha-footer .icon:hover {
    color: #00cc00;
}

Explanation

  1. Card Layout: The entire CAPTCHA is wrapped in a .captcha-card for styling.
  2. Header: Displays the instruction text and an example image for the target category.
  3. Grid Section: Contains a dynamic 3x3 grid of images.
  4. Footer: Includes buttons for refreshing the images, playing audio instructions, and verifying the selection. A dynamic message is displayed between the voice icon and the Verify button.

Step 3: Adding Functionality with JavaScript

The JavaScript dynamically generates the CAPTCHA, handles user selections, and provides feedback.

const captchaContainer = document.getElementById('captcha-container');
const categoryImage = document.getElementById('category-image');
const messageElement = document.createElement('span'); // For the result message
const verifyButton = document.getElementById('verify-btn');
const refreshButton = document.getElementById('refresh-btn');
const audioButton = document.getElementById('audio-btn');
const categoryNameElement = document.getElementById('category-name');

const categories = ['bus', 'car', 'bike', 'road', 'plane', 'boat', 'cat', 'dog'];
let targetCategory = '';
let correctImageIndexes = [];

// Example image URL set (replace these with your own images)
const imagePool = {
    bus: ['bus1.jpg', 'bus2.png', 'bus3.png', 'bus4.png'],
    car: ['car1.png', 'car2.png', 'car3.png', 'car4.png'],
    bike: ['bike1.png', 'bike2.png', 'bike3.png', 'bike4.png'],
    road: ['road1.png', 'road2.png', 'road3.png', 'road4.png'],
    plane: ['plane1.png', 'plane2.png', 'plane3.png', 'plane4.png'],
    boat: ['boat1.png', 'boat2.png', 'boat3.png', 'boat4.png'],
    cat: ['cat1.jpg', 'cat2.png', 'cat3.png', 'cat4.png'],
    dog: ['dog1.jpg', 'dog2.jpg', 'dog3.jpg', 'dog4.jpg']
};

// Function to shuffle an array
function shuffle(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
}

// Function to load a new CAPTCHA challenge
function loadCaptcha() {
    captchaContainer.innerHTML = '';
    messageElement.textContent = ''; // Clear previous messages

    // Randomly pick a target category
    const allCategories = Object.keys(imagePool);
    targetCategory = allCategories[Math.floor(Math.random() * allCategories.length)];
    categoryNameElement.textContent = targetCategory;

    // Update example image
    categoryImage.src = `./images/${imagePool[targetCategory][0]}`;

    // Get 4 images from the target category
    const targetImages = shuffle([...imagePool[targetCategory]]).slice(0, 4);

    // Get 5 random images from other categories
    const otherCategories = allCategories.filter(cat => cat !== targetCategory);
    let otherImages = [];
    while (otherImages.length < 5) {
        const randomCategory = otherCategories[Math.floor(Math.random() * otherCategories.length)];
        const randomImage = shuffle([...imagePool[randomCategory]])[0];
        if (!otherImages.includes(randomImage)) {
            otherImages.push(randomImage);
        }
    }

    // Merge and shuffle the 9 images
    const allImages = shuffle([...targetImages, ...otherImages]);

    // Render the images in the captcha container
    allImages.forEach((imgSrc, index) => {
        const imgElement = document.createElement('img');
        imgElement.src = `./images/${imgSrc}`;
        imgElement.classList.add('captcha-image');
        imgElement.dataset.correct = targetImages.includes(imgSrc) ? 'true' : 'false';
        imgElement.addEventListener('click', () => toggleImageSelection(imgElement));
        captchaContainer.appendChild(imgElement);
    });

    // Store correct image indexes
    correctImageIndexes = allImages
        .map((img, index) => ({ img, index }))
        .filter(item => targetImages.includes(item.img))
        .map(item => item.index);
}

// Function to toggle selection
function toggleImageSelection(imgElement) {
    imgElement.classList.toggle('selected');
}

// Function to verify the CAPTCHA
function verifyCaptcha() {
    const selectedImages = Array.from(document.querySelectorAll('.captcha-image.selected'));
    const selectedIndexes = selectedImages.map(img => Array.from(captchaContainer.children).indexOf(img));
    const isCorrect = selectedIndexes.length === 4 && correctImageIndexes.every(index => selectedIndexes.includes(index));

    if (isCorrect) {
        messageElement.textContent = '✅ CAPTCHA Passed!';
        messageElement.style.color = 'green';
    } else {
        messageElement.textContent = '❌ Incorrect! Refreshing...';
        messageElement.style.color = 'red';
        setTimeout(loadCaptcha, 1000); // Refresh the CAPTCHA after a brief delay
    }
}

// Function to play audio
function playAudio() {
    const utterance = new SpeechSynthesisUtterance(`Select all images containing a ${targetCategory}`);
    speechSynthesis.speak(utterance);
}

// Add the message element to the footer dynamically
const captchaFooter = document.querySelector('.captcha-footer');
captchaFooter.insertBefore(messageElement, verifyButton);

// Event listeners
verifyButton.addEventListener('click', verifyCaptcha);
refreshButton.addEventListener('click', loadCaptcha);
audioButton.addEventListener('click', playAudio);

// Load the initial CAPTCHA
loadCaptcha();

How It Works

  1. Dynamic Grid Creation:
  1. User Interaction:
  1. Verification: The verifyCaptcha function checks if exactly 4 correct images are selected. If correct, it shows a success message. Otherwise, it refreshes the CAPTCHA.

  2. Accessibility:

Final Thoughts

This interactive CAPTCHA provides a user-friendly, accessible, and secure method for verifying human users. You can customize it further by using different categories, images, or layouts. Let us know if you have any questions or improvements!

Back to Articles