Search code examples
htmljavascriptcss

How to add a cursor/text position indicator to a <textarea> controlled by javascript?


I am making a typewriter like website (here) that uses a textarea and javascript (from button clicks) to add/remove from the textarea.

The next thing I want to add is an indicator to show where the next update (insert/remove) will be so I guess its always at the of the textarea value.

The key details that clash with other solutions are

  • The textarea is disabled (+ its a textarea and not a p tag)
  • Users add characters by using the buttons and not by typing with their keyboards
  • The user isn't selecting or has a selection while typing
  • Some solutions involve css that break the website

This is how I insert characters

function insert(char) {
    document.getElementById("textarea").value += char
    update()
}

to the textarea + a button

<textarea id="textarea" class="textarea" disabled>
</textarea>

<a onclick="insert('1')"> 
    <div class="letterbox">
        <p class="typewritter">1</p>
    </div> 
</a>

(btw I know I spelled typewriter wrong on the website and will fix that later)


Solution

  • If you want to make a custom insertion point (the blinking line) you can do that by adding and removing a chosen symbol ('|' for example) from the text with an interval. Perhaps something like this would do:

    const insertionSymbol = '|';
    const blinkInterval = 1000; // time in ms between insertion point updates
    let blinking = true; // whether the insertion line is hidden
    let textarea = document.getElementById('textarea');
    setInterval(function() {
        if(blinking) {
            textarea.value += insertionSymbol;
        } else {
            textarea.value = textarea.value.slice(0,-1);
        }
        blinking = !blinking;
    }, blinkInterval);
    

    that would require you to change the insert and delete functions:

    function insert(char) {
        if(blinking) {
            textarea.value += char;
        } else {
            // inserting the character before the insertion point by removing the insertion point temporarily 
            textarea.value = textarea.value.slice(0,-1) + char + insertionSymbol; 
        }
    }
    function delete() {
        if(blinking) {
            textarea.value = textarea.slice(0,-1);
        } else {
            // deleting the character before the insertion point by removing the insertion point temporarily 
            textarea.value = textarea.value.slice(0,-2) + insertionSymbol; 
        }
    }
    

    This idea can be expanded. for example, if you want to add a insertion point cool-down (i.e. change the insertion point to stay visible for some time after an update, like you see in most text editors), you can change the interval to run every millisecond, and add a timer for the next update. like this:

    // ... all the variable definitions like before
    let timer = blinkInterval;
    setInterval(function() {
        timer --;
        if(timer == 0) {
            if(blinking) {
                textarea.value += insertionSymbol;
            } else {
                textarea.value = textarea.value.slice(0,-1);
            }
            blinking = !blinking;
            timer = blinkInterval;
        }
    }, 1);
    function insert(char) {
        if(blinking) {
            blinking = false;
            textarea.value += char + insertionSymbol;
        } else {
            // inserting the character before the insertion point by removing the insertion point temporarily 
            textarea.value = textarea.value.slice(0,-1) + char + insertionSymbol; 
        }
        timer = blinkInterval;
    }
    function delete() {
        if(blinking) {
            blinking = false;
            textarea.value = textarea.slice(0,-1) + insertionSymbol;
        } else {
            // deleting the character before the insertion point by removing the insertion point temporarily 
            textarea.value = textarea.value.slice(0,-2) + insertionSymbol; 
        }
        timer = blinkInterval;
    }
    

    Note: I'm writing the code blindly (I didn't run it to see if it works) so I'm sorry in advance for any mistakes.