Creating a Spell Check Feature with Underline and Popup in Textarea with pure HTML, CSS, JS

54 Views Asked by At

I want to create a textarea with a spell check feature similar to Grammarly. When a misspelled word is typed, it should be underlined. Clicking on the underline should trigger a popup like this.

Upon inspecting the website's structure, I found that the content within the textarea remains unchanged. The underlines are achieved using div elements as overlays.

I've attempted this approach, but it's challenging to determine the position of each word. For example, the textarea's behavior includes early line breaks to avoid cutting off words. Here's my current code:

function updateOverlay() {
        const textarea = document.getElementById('textarea');
        const overlay = document.getElementById('overlay');

        overlay.innerHTML = '';

        const text = textarea.value;

        const wordsAndSpaces = text.split(/(\s+)/) //.filter(e => e.length > 0)//.map(e => e.replace(" ", " "));
        console.log(wordsAndSpaces)

        let offsetLeft = 0;
        let offsetTop = 0;
        let lineHeight = parseFloat(window.getComputedStyle(textarea).lineHeight);

        for (let item of wordsAndSpaces) {
            const span = document.createElement('span');
            if (item.includes(" ")) {
                span.innerHTML = item.replace(" ", " ");
            } else {
                span.textContent = item;
            }
            span.style.display = 'inline-block';

            overlay.appendChild(span);

            const rect = span.getBoundingClientRect();

            const width = rect.width;
            const height = rect.height;
            const left = offsetLeft + rect.left - 10;
            const top = offsetTop + rect.top;

            const underline = document.createElement('div');
            underline.classList.add('underline');
            underline.style.width = width + 'px';
            underline.style.height = '1px';
            underline.style.left = left + 'px';
            underline.style.top = top + height - 10 + 'px'; 
            overlay.appendChild(underline);

            offsetLeft += span.offsetWidth;
            if (offsetLeft >= textarea.clientWidth) {
                offsetLeft = 0;
                offsetTop += lineHeight;
            }

            overlay.removeChild(span);
        }
    }

    // Initial call to update overlay
    updateOverlay();
#textarea-container {
    position: relative;
    width: 400px;
    font-size: medium;
    font-weight: 500;
    font-family: 'Courier New', Courier, monospace;
    /* margin-bottom: 20px; */
}

#textarea, #overlay-container {
    width: 100%;
    height: 200px;
    box-sizing: border-box;
    padding: 0px;
    font-size: 16px;
    resize: none;
    border: 0px solid #ccc;
    margin: 0;
}

#overlay-container {
    position: absolute;
    top: 0;
    left: 0;
    pointer-events: none; 
    will-change: transform; 
}

#overlay {
    position: relative;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    padding: 0px;
    margin: 0px;
}

.underline {
    position: absolute;
    border-bottom: 1px solid blue;
}
<div id="textarea-container">
        <textarea id="textarea" oninput="updateOverlay()">a b</textarea>
        <div id="overlay-container">
            <div id="overlay"></div>
        </div>
    </div>

How can I solve this issue? Is there a way to calculate the position of each word within the textarea? Or is there another approach I should consider?

Thank you.

1

There are 1 best solutions below

0
ranga durai On
<!DOCTYPE html>
<html>

<head>
    <title>Grammarly Style Underline</title>
    <style>
        #editor {
            width: 80%;
            margin: 20px auto;
            padding: 10px;
            font-size: 16px;
            line-height: 1.5;
            border: 1px solid #ccc;
        }

        .grammarly-spelling-error {
            border-bottom: 2px dotted red;
        }

        .popup {
            display: none;
            position: absolute;
            background: #fff;
            border: 1px solid #ccc;
            padding: 10px;
        }

        #popupContent {
            margin: 0;
        }

        .textarea {
            width: 300px;
            height: 200px;
            padding: 10px;
            font-size: 16px;
            border: 1px solid #ccc;
        }

        .error {
            background-color: #ffcfcf;
            border-bottom: 2px solid red;
        }
    </style>
</head>

<body>
    <div class="popup" id="popup">
        <p id="popupContent">Popup Content Here</p>
    </div>
    <div id="editor" contenteditable="true">
        The red underline in a text editor or word processor typically indicates a spelling error. Most modern word
        processors or text editing software, like Microsoft Word or Google Docs, have built-in spell checkers. When you
        type a word that isn't recognized by the spell checker's dictionary, it underlines it with a red squiggly line
        to alert you to a potential spelling mistake.
    </div>
    

    <script>
        document.addEventListener('DOMContentLoaded', function () {
            const editor = document.getElementById('editor');
            const misspelledWords = new Set();
            
            
            editor.addEventListener('keyup', function (e) {
               
                const text = editor.innerText; // Get plain text content
                const words = text.split(' '); // Split text into words


                let newContent = []; // Store the corrected content here

                words.forEach(word => {
                    if (!spellCheck(word) && !misspelledWords.has(word)) {
                        newContent.push(addErrorStyle(word));
                        misspelledWords.add(word);
                    } else {
                        newContent.push(removeErrorStyle(word));
                        misspelledWords.delete(word);
                    }
                });


                editor.innerHTML = newContent.join(" ");
            });


            // Event listener for double-click on textarea
            editor.addEventListener('dblclick', function (event) {
                const selection = window.getSelection();
                const selectedWord = selection.toString().trim();

                if (selectedWord !== '') {
                    const range = selection.getRangeAt(0);
                    const rect = range.getBoundingClientRect();

                    const x = rect.x + window.scrollX;
                    const y = rect.y + window.scrollY + rect.height;

                    showPopup(selectedWord, x, y);
                }
            });

            // Function to show popup with selected word
            function showPopup(word, x, y) {
                const popup = document.getElementById('popup');
                const popupContent = document.getElementById('popupContent');
                popupContent.innerText = `Selected Word: ${word}`;

                popup.style.left = `${x}px`;
                popup.style.top = `${y}px`;
                popup.style.display = 'block';

                // Hide popup after 3 seconds
                setTimeout(function () {
                    popup.style.display = 'none';
                }, 3000);
            }

            function spellCheck(word) {
                const dictionary = ["red", "underline", "text", "editor", "word", "processor", "spelling", "error", 'Moest', 'dsasassthe'];
                return dictionary.includes(word.toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "")); // Removing punctuation
            }

            // Function to add error style
            function addErrorStyle(word) {
                return `<span class="grammarly-spelling-error">${word}</span>`;
            }

            // Function to remove error style
            function removeErrorStyle(word) {
                const regex = new RegExp(`<span class="grammarly-spelling-error">${word}</span>`, 'gi');
                return word.replace(regex, "");
            }
        });
    </script>
</body>

</html>`

Check whether this code will help you.