22

A JavaScript function selects a certain word in a textarea using .setSelectionRange().

In Firefox, the textarea automatically scrolls down to show the selected text. In Chrome (v14), it does not. Is there a way to get Chrome to scroll the textarea down to the newly selected text?

jQuery solutions are welcome.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
hamboy
  • 727
  • 2
  • 8
  • 21

8 Answers8

15

Here is a simple and efficient solution in pure JavaScript:

// Get the textarea
var textArea = document.getElementById('myTextArea');

// Define your selection
var selectionStart = 50;
var selectionEnd = 60;
textArea.setSelectionRange(selectionStart, selectionEnd);

// Mow let’s do some math.
// We need the number of characters in a row
var charsPerRow = textArea.cols;

// We need to know at which row our selection starts
var selectionRow = (selectionStart - (selectionStart % charsPerRow)) / charsPerRow;

// We need to scroll to this row but scrolls are in pixels,
// so we need to know a row's height, in pixels
var lineHeight = textArea.clientHeight / textArea.rows;

// Scroll!!
textArea.scrollTop = lineHeight * selectionRow;

Put this in a function, extend the prototype of JavaScript's Element object with it, and you're good.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Frederik Eychenié
  • 1,253
  • 7
  • 8
8

A lot of answers, but the accepted one doesn't consider line breaks, Matthew Flaschen didn't add the solution code, and naXa answer has a mistake. The simplest solution code is:

textArea.focus();

const fullText = textArea.value;
textArea.value = fullText.substring(0, selectionEnd);
textArea.scrollTop = textArea.scrollHeight;
textArea.value = fullText;

textArea.setSelectionRange(selectionStart, selectionEnd);
Valeriy Katkov
  • 33,616
  • 20
  • 100
  • 123
6

You can see how we solved the problem in ProveIt (see highlightLengthAtIndex). Basically, the trick is to truncate the textarea, scroll to the end, then restore the second part of the text. We also used the textSelection plugin for consistent cross-browser behavior.

Matthew Flaschen
  • 278,309
  • 50
  • 514
  • 539
  • 12
    Hi Matt, just suggesting, do put the codes in your answer so we could see them in SO instead of a link. – fedmich Sep 20 '13 at 00:23
  • Just some comments to your code, in case you haven't encountered it yet. instead of document.getElementById("my_textarea") , you could do jQuery('#my_textarea').get(0) – fedmich Sep 20 '13 at 00:23
3

Valeriy Katkov's elegant solution works great but has two problems:

  1. It does not work for long strings
  2. Selected contents are scrolled to the bottom of the textarea, making it hard to see the context which surrounds the selection

Here's my improved version that works for long strings (tested with at least 50,000 words) and scroll selection to the center of the textarea:

function setSelectionRange(textarea, selectionStart, selectionEnd) {
    // First scroll selection region to view
    const fullText = textarea.value;
    textarea.value = fullText.substring(0, selectionEnd);
    // For some unknown reason, you must store the scollHeight to a variable
    // before setting the textarea value. Otherwise it won't work for long strings
    const scrollHeight = textarea.scrollHeight
    textarea.value = fullText;
    let scrollTop = scrollHeight;
    const textareaHeight = textarea.clientHeight;
    if (scrollTop > textareaHeight){
        // scroll selection to center of textarea
        scrollTop -= textareaHeight / 2;
    } else{
        scrollTop = 0;
    }
    textarea.scrollTop = scrollTop;

    // Continue to set selection range
    textarea.setSelectionRange(selectionStart, selectionEnd);
}

It works in Chrome 72, Firefox 65, Opera 58, and Edge 42.

For an example of using this function, see my GitHub project SmartTextarea.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
AlienKevin
  • 2,691
  • 2
  • 17
  • 19
2

This is a code inspired by the Matthew Flaschen's answer.

/**
 * Scroll textarea to position.
 *
 * @param {HTMLInputElement} textarea
 * @param {Number} position
 */
function scrollTo(textarea, position) {
    if (!textarea) { return; }
    if (position < 0) { return; }

    var body = textarea.value;
    if (body) {
        textarea.value = body.substring(0, position);
        textarea.scrollTop = position;
        textarea.value = body;
    }
}

Basically, the trick is to truncate the textarea, scroll to the end, then restore the second part of the text.

Use it as follows

var textarea, start, end;
/* ... */

scrollTo(textarea, end);
textarea.focus();
textarea.setSelectionRange(start, end);
Community
  • 1
  • 1
naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
  • 3
    It doesn't make sense that the variable `position` is used both as a substring index, and for scrollTop which takes pixels. – Michael Franzl Mar 09 '17 at 21:03
2

Based on the idea from naXa and Valeriy Katkov, I refined the function with fewer bugs. It should work out of the box (It's written with TypeScript. For JavaScript, just remove the type declaration):

function scrollTo(textarea: HTMLTextAreaElement, offset: number) {
    const txt = textarea.value;
    if (offset >= txt.length || offset < 0)
      return;

    // Important, so that scrollHeight will be adjusted
    textarea.scrollTop = 0;

    textarea.value = txt.substring(0, offset);
    const height = textarea.scrollHeight;
    textarea.value = txt;

    // Margin between selection and top of viewport
    textarea.scrollTop = height - 40;
}

Usage:

let textarea, start, end;
/* ... */

scrollTo(textarea, start);
textarea.focus();
textarea.setSelectionRange(start, end);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jianwu Chen
  • 5,336
  • 3
  • 30
  • 35
0

Complete code for Chrome:

<script type="text/javascript">
    var SAR = {};

    SAR.find = function () {
        debugger;
        var parola_cercata = $("#text_box_1").val(); // The searched word

        // Make text lowercase if search is
        // supposed to be case insensitive
        var txt = $('#remarks').val().toLowerCase();
        parola_cercata = parola_cercata.toLowerCase();

        // Take the position of the word in the text
        var posi = jQuery('#remarks').getCursorPosEnd();

        var termPos = txt.indexOf(parola_cercata, posi);

        if (termPos !== -1) {
            debugger;
            var target = document.getElementById("remarks");
            var parola_cercata2 = $("#text_box_1").val();

            // Select the textarea and the word
            if (target.setSelectionRange) {

                if ('selectionStart' in target) {
                    target.selectionStart = termPos;
                    target.selectionEnd = termPos;
                    this.selectionStart = this.selectionEnd = target.value.indexOf(parola_cercata2);
                    target.blur();
                    target.focus();
                    target.setSelectionRange(termPos, termPos + parola_cercata.length);
                }
            } else {
                var r = target.createTextRange();
                r.collapse(true);
                r.moveEnd('character', termPos + parola_cercata);
                r.moveStart('character', termPos);
                r.select();
            }
        } else {
            // Not found from cursor pos, so start from beginning
            termPos = txt.indexOf(parola_cercata);
            if (termPos !== -1) {
                var target = document.getElementById("remarks");
                var parola_cercata2 = $("#text_box_1").val();

                // Select the textarea and the word
                if (target.setSelectionRange) {

                    if ('selectionStart' in target) {
                        target.selectionStart = termPos;
                        target.selectionEnd = termPos;
                        this.selectionStart = this.selectionEnd = target.value.indexOf(parola_cercata2);
                        target.blur();
                        target.focus();
                        target.setSelectionRange(termPos, termPos + parola_cercata.length);
                    }
                } else {
                    var r = target.createTextRange();
                    r.collapse(true);
                    r.moveEnd('character', termPos + parola_cercata);
                    r.moveStart('character', termPos);
                    r.select();
                }
            } else {
                alert("not found");
            }
        }
    };


    $.fn.getCursorPosEnd = function () {
        var pos = 0;
        var input = this.get(0);
        // IE support
        if (document.selection) {
            input.focus();
            var sel = document.selection.createRange();
            pos = sel.text.length;
        }
        // Firefox support
        else if (input.selectionStart || input.selectionStart === '0')
            pos = input.selectionEnd;
        return pos;
    };
</script>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
-1

I published an answer here:

http://blog.blupixelit.eu/scroll-textarea-to-selected-word-using-javascript-jquery/

It works perfectly with just one needed rule: Set a line-height in the CSS content of the textarea!

It calculate the position of the word to scroll to just by doing some simple mathematical calculation and it worked perfectly in all my experiments!

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 7
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – Mihai Iorga Sep 27 '12 at 14:41
  • The link is broken: *"404. Not Found. The requested URL was not found on this server."* – Peter Mortensen May 09 '22 at 13:44