JavaScript: Scroll to selection after using textarea.setSelectionRange in Chrome
Asked Answered
G

8

24

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.

Gooding answered 18/9, 2011 at 20:35 Comment(0)
C
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.

Canned answered 18/9, 2011 at 21:24 Comment(3)
The charsPerRow logic ignores line breaks, both soft (wrapping between words) and hard (actual newlines).Dorolice
You are perfectly right! I am currently checking your solutionTruda
This cannot work with CSS because textArea.rows is not the number of rows of the text contentWards
F
10

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);
Farmergeneral answered 31/10, 2018 at 11:20 Comment(2)
it's so important that all the clever operations to happend on a "focused" texarea. textArea.focus() should be on topRheotaxis
This the only solution that works for long and short text area content.Roundelay
D
7

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.

Dorolice answered 18/9, 2011 at 20:38 Comment(2)
Hi Matt, just suggesting, do put the codes in your answer so we could see them in SO instead of a link.Cowes
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)Cowes
F
4

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.

Frederick answered 11/3, 2019 at 22:17 Comment(0)
R
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);
Retread answered 13/10, 2016 at 9:33 Comment(1)
It doesn't make sense that the variable position is used both as a substring index, and for scrollTop which takes pixels.Rink
P
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);
Ploughshare answered 16/11, 2019 at 1:58 Comment(0)
B
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>
Betz answered 8/9, 2017 at 11:17 Comment(1)
works very f9 for me please check and revert solved issue for scrolling on textarea for the word searchBetz
B
-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!

Bushelman answered 9/8, 2012 at 21:59 Comment(2)
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.Toh
The link is broken: "404. Not Found. The requested URL was not found on this server."Exhibitor

© 2022 - 2024 — McMap. All rights reserved.