Як перемістити курсор у кінець об’єкта, що задовольняє


84

Мені потрібно перемістити каретку до кінця contenteditableвузла, як у віджеті нотаток Gmail.

Я читав потоки на StackOverflow, але ці рішення засновані на використанні входів, і вони не працюють з contenteditableелементами.

Відповіді:


27

Також є ще одна проблема.

Ніко Бернс працює, якщо contenteditablediv не містить інших мультилінітованих елементів.

Наприклад, якщо div містить інші div, а ці інші div містять все інше всередині, можуть виникнути деякі проблеми.

Для їх вирішення я влаштував наступне рішення, це вдосконалення рішення Ніко :

//Namespace management idea from http://enterprisejquery.com/2010/10/how-good-c-habits-can-encourage-bad-javascript-habits-part-1/
(function( cursorManager ) {

    //From: http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
    var voidNodeTags = ['AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'MENUITEM', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR', 'BASEFONT', 'BGSOUND', 'FRAME', 'ISINDEX'];

    //From: /programming/237104/array-containsobj-in-javascript
    Array.prototype.contains = function(obj) {
        var i = this.length;
        while (i--) {
            if (this[i] === obj) {
                return true;
            }
        }
        return false;
    }

    //Basic idea from: /programming/19790442/test-if-an-element-can-contain-text
    function canContainText(node) {
        if(node.nodeType == 1) { //is an element node
            return !voidNodeTags.contains(node.nodeName);
        } else { //is not an element node
            return false;
        }
    };

    function getLastChildElement(el){
        var lc = el.lastChild;
        while(lc && lc.nodeType != 1) {
            if(lc.previousSibling)
                lc = lc.previousSibling;
            else
                break;
        }
        return lc;
    }

    //Based on Nico Burns's answer
    cursorManager.setEndOfContenteditable = function(contentEditableElement)
    {

        while(getLastChildElement(contentEditableElement) &&
              canContainText(getLastChildElement(contentEditableElement))) {
            contentEditableElement = getLastChildElement(contentEditableElement);
        }

        var range,selection;
        if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
        {    
            range = document.createRange();//Create a range (a range is a like the selection but invisible)
            range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
            range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
            selection = window.getSelection();//get the selection object (allows you to change selection)
            selection.removeAllRanges();//remove any selections already made
            selection.addRange(range);//make the range you have just created the visible selection
        }
        else if(document.selection)//IE 8 and lower
        { 
            range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
            range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
            range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
            range.select();//Select the range (make it the visible selection
        }
    }

}( window.cursorManager = window.cursorManager || {}));

Використання:

var editableDiv = document.getElementById("my_contentEditableDiv");
cursorManager.setEndOfContenteditable(editableDiv);

Таким чином, курсор, безсумнівно, розміщується в кінці останнього елемента, врешті-решт вкладеного.

EDIT # 1 : Для того, щоб бути більш загальним, оператор while повинен враховувати також усі інші теги, які не можуть містити текст. Ці елементи називаються порожніми елементами , і в цьому питанні є кілька методів, як перевірити, чи порожній елемент. Отже, якщо припустити, що існує функція, що викликається, canContainTextяка повертає, trueякщо аргумент не є порожнім елементом, наступний рядок коду:

contentEditableElement.lastChild.tagName.toLowerCase() != 'br'

слід замінити на:

canContainText(getLastChildElement(contentEditableElement))

EDIT # 2 : Наведений вище код повністю оновлений, з усіма описаними та обговореними змінами


Цікаво, я би очікував, що браузер автоматично подбає про цю справу (не те, що мене дивує, що ні, браузери ніколи, здається, не роблять інтуїтивно зрозумілих справ із задоволеним). У вас є приклад HTML, де ваше рішення працює, а моє - ні?
Ніко Бернс,

У моєму коді була ще одна помилка. Я виправив це. Тепер ви можете переконатися, що мій код працює на цій сторінці , а ваш - ні,
Віто Джентіле

Я отримую повідомлення про помилку з використанням вашої функції, говорить консоль, Uncaught TypeError: Cannot read property 'nodeType' of nullі це відбувається від функції getLastChildElement, яку викликають. Чи знаєте ви, що може спричинити цю проблему?
Дерек

@VitoGentile це трохи стара відповідь, але я хочу зауважити, що ваше рішення піклується лише про елементи блоку, якщо всередині є вбудовані елементи, тоді курсор буде розташовано після цього вбудованого елемента (наприклад, span, em ...) , просте виправлення - розглянути вбудовані елементи як теги void та додати їх до voidNodeTags, щоб вони були пропущені.
medBouzid

241

Рішення Geowa4 буде працювати для текстової області, але не для елемента, що задовольняє.

Це рішення призначене для переміщення каретки до кінця задоволеного елемента. Він повинен працювати у всіх браузерах, які підтримують вміст.

function setEndOfContenteditable(contentEditableElement)
{
    var range,selection;
    if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
    {
        range = document.createRange();//Create a range (a range is a like the selection but invisible)
        range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
        range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
        selection = window.getSelection();//get the selection object (allows you to change selection)
        selection.removeAllRanges();//remove any selections already made
        selection.addRange(range);//make the range you have just created the visible selection
    }
    else if(document.selection)//IE 8 and lower
    { 
        range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
        range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
        range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
        range.select();//Select the range (make it the visible selection
    }
}

Він може використовуватися кодом, подібним до:

elem = document.getElementById('txt1');//This is the element that you want to move the caret to the end of
setEndOfContenteditable(elem);

1
Рішення geowa4 буде працювати для текстових областей в хромі, воно не працюватиме для елементів, що задовольняють жодного браузера. шахта працює для задоволених елементів, але не для текстових областей.
Ніко Бернс,

4
Це правильна відповідь на це питання, ідеально, дякую Ніко.
Роб

7
selectNodeContentsЧастина Ніко давав помилки мені як в Chrome і FF (не перевіряв інші браузери) , поки я не дізнався, що я , мабуть , потрібно додати .get(0)до елементу , який я годував функцію. Я думаю, це пов’язано зі тим, що я використовую jQuery замість простого JS? Я дізнався про це від @jwarzech на запитання 4233265 . Дякую всім!
Макс Старкенбург,

5
Так, функція очікує елемент DOM, а не об’єкт jQuery. .get(0)отримує елемент dom, який jQuery зберігає всередині. Ви також можете додати [0], що еквівалентно .get(0)в цьому контексті.
Ніко Бернс,

1
@ Ніко Бернс: Я спробував ваш метод, і він не працював у FireFox.
Льюїс,

25

Якщо ви не дбаєте про старіші браузери, цей для мене зробив свою справу.

// [optional] make sure focus is on the element
yourContentEditableElement.focus();
// select all the content in the element
document.execCommand('selectAll', false, null);
// collapse selection to the end
document.getSelection().collapseToEnd();

це єдине, що в мене спрацювало у фоновому сценарії для розширення chrome
Роб

1
Це чудово працює. Тестується на chrome 71.0.3578.98 та WebView на Android 5.1.
maswerdna

3
document.execCommandзараз застарілий developer.mozilla.org/en-US/docs/Web/API/Document/execCommand .
програміст,

2020, і це все ще працює у версії chrome 83.0.4103.116 (Офіційна збірка) (64-розрядна
версія

це переможець!
MNN ТНК

7

Можна встановити курсор до кінця через діапазон:

setCaretToEnd(target/*: HTMLDivElement*/) {
  const range = document.createRange();
  const sel = window.getSelection();
  range.selectNodeContents(target);
  range.collapse(false);
  sel.removeAllRanges();
  sel.addRange(range);
  target.focus();
  range.detach(); // optimization

  // set scroll to the end if multiline
  target.scrollTop = target.scrollHeight; 
}

Використовуючи наведений вище код, це робить трюк - але я хочу мати можливість мати можливість переміщувати курсор у будь-яке місце в межах редагованого div вмісту та продовжувати друкувати з цієї точки - наприклад, користувач розпізнав друкарську помилку, наприклад ... Як би Я змінив ваш код вище до цього?
Забс

1
@Zabs це досить просто: не викликайте setCaretToEnd()кожного разу - викликайте його лише тоді, коли вам це потрібно: наприклад, після копіювання-вставки або після обмеження довжини повідомлення.
am0wa

Це спрацювало для мене. після того, як користувач вибере тег, я переміщую курсор у заданому div до кінця.
Аюд,

0

У мене була подібна проблема при спробі зробити елемент редагованим. Це було можливо в Chrome та FireFox, але у FireFox каретка або переходила на початок введення, або вона проходила один пробіл після закінчення введення. Думаю, це дуже бентежить кінцевого користувача, намагаючись відредагувати вміст.

Я не знайшов рішення, щоб спробувати кілька речей. Єдине, що мені вдалося, це "обійти проблему", помістивши звичайний старий введення тексту ВНУТРІ мого. Зараз це працює. Здається, "редагований вміст" все ще є сучасною технологією, яка може працювати, а може і не працювати, як ви хотіли б, щоб вона працювала, залежно від контексту.


0

Переміщення курсору до кінця редагованого діапазону у відповідь на подію фокусування:

  moveCursorToEnd(el){
    if(el.innerText && document.createRange)
    {
      window.setTimeout(() =>
        {
          let selection = document.getSelection();
          let range = document.createRange();

          range.setStart(el.childNodes[0],el.innerText.length);
          range.collapse(true);
          selection.removeAllRanges();
          selection.addRange(range);
        }
      ,1);
    }
  }

І викликаючи це в обробнику подій (React тут):

onFocus={(e) => this.moveCursorToEnd(e.target)}} 

0

Проблема з contenteditable <div>та <span>вирішується, коли ви починаєте вводити його спочатку. Одним з обхідних шляхів для цього може бути активація події фокусу на вашому елементі div та на цій функції, очищення та заповнення того, що вже було в елементі div. Таким чином проблема вирішується, і нарешті ви можете помістити курсор в кінець, використовуючи діапазон та виділення. Працював у мене.

  moveCursorToEnd(e : any) {
    let placeholderText = e.target.innerText;
    e.target.innerText = '';
    e.target.innerText = placeholderText;

    if(e.target.innerText && document.createRange)
    {
      let range = document.createRange();
      let selection = window.getSelection();
      range.selectNodeContents(e.target);
      range.setStart(e.target.firstChild,e.target.innerText.length);
      range.setEnd(e.target.firstChild,e.target.innerText.length);
      selection.removeAllRanges();
      selection.addRange(range);
    }
  }

У HTML-коді:

<div contentEditable="true" (focus)="moveCursorToEnd($event)"></div>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.