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


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.