Cách di chuyển con trỏ đến cuối thực thể có thể nội dung


83

Tôi cần di chuyển dấu mũ đến cuối contenteditablenút như trên tiện ích ghi chú Gmail.

Tôi đọc các chủ đề trên StackOverflow, nhưng các giải pháp đó dựa trên việc sử dụng đầu vào và chúng không hoạt động với contenteditablecác phần tử.

Câu trả lời:


27

Ngoài ra còn có một vấn đề khác.

Giải pháp của Nico Burns hoạt động nếu contenteditablediv không chứa các phần tử nhiều dòng khác.

Ví dụ: nếu một div chứa các div khác và những div khác này chứa những thứ khác bên trong, có thể xảy ra một số vấn đề.

Để giải quyết chúng, tôi đã sắp xếp giải pháp sau, đó là một cải tiến của Nico 's:

//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 || {}));

Sử dụng:

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

Bằng cách này, con trỏ chắc chắn được định vị ở cuối phần tử cuối cùng, cuối cùng được lồng vào nhau.

CHỈNH SỬA # 1 : Để chung chung hơn, câu lệnh while cũng nên xem xét tất cả các thẻ khác không thể chứa văn bản. Các phần tử này được đặt tên là phần tử void , và trong câu hỏi này có một số phương pháp về cách kiểm tra xem một phần tử có trống hay không. Vì vậy, giả sử tồn tại một hàm được gọi là canContainTexttrả về truenếu đối số không phải là phần tử void, thì dòng mã sau:

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

nên được thay thế bằng:

canContainText(getLastChildElement(contentEditableElement))

CHỈNH SỬA # 2 : Đoạn mã trên được cập nhật đầy đủ, với mọi thay đổi được mô tả và thảo luận


Thật thú vị, tôi đã mong đợi trình duyệt sẽ tự động xử lý trường hợp này (không phải tôi ngạc nhiên là không, các trình duyệt dường như không bao giờ làm những việc trực quan với contenteditable). Bạn có một ví dụ về HTML mà giải pháp của bạn hoạt động nhưng giải pháp của tôi thì không?
Nico Burns

Trong mã của tôi có một lỗi khác. Tôi sửa nó rồi. Bây giờ, bạn có thể xác minh rằng mã của tôi làm việc trong trang này , trong khi bạn không làm
Vito Gentile

Tôi đang gặp lỗi khi sử dụng hàm của bạn, bảng điều khiển cho biết Uncaught TypeError: Cannot read property 'nodeType' of nullvà đây là do hàm getLastChildElement đang được gọi. Bạn có biết điều gì có thể gây ra vấn đề này không?
Derek

@VitoGentile đó là một câu trả lời hơi cũ nhưng tôi muốn lưu ý rằng giải pháp của bạn chỉ quan tâm đến các phần tử khối, nếu có các phần tử nội dòng bên trong, thì con trỏ sẽ được định vị sau phần tử nội tuyến đó (như span, em ...) , một cách khắc phục dễ dàng là coi các phần tử nội tuyến là thẻ void và thêm chúng vào voidNodeTags để chúng sẽ bị bỏ qua.
medBouzid

239

Giải pháp của Geowa4 sẽ hoạt động cho một vùng văn bản, nhưng không hoạt động cho một phần tử có thể nội dung.

Giải pháp này là để di chuyển dấu mũ đến cuối phần tử có thể nội dung. Nó sẽ hoạt động trong tất cả các trình duyệt hỗ trợ contentedable.

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
    }
}

Nó có thể được sử dụng bằng mã tương tự như:

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

1
Giải pháp của geowa4 sẽ hoạt động đối với textarea trong chrome, nó sẽ không hoạt động đối với các phần tử có thể nội dung trong bất kỳ trình duyệt nào. của tôi hoạt động cho các phần tử có thể nội dung, nhưng không hoạt động cho các vùng văn bản.
Nico Burns

4
Đây là câu trả lời chính xác cho câu hỏi này, hoàn hảo, cảm ơn Nico.
Rob

7
Một selectNodeContentsphần của Nico đã cho tôi lỗi trong cả Chrome và FF (không kiểm tra các trình duyệt khác) cho đến khi tôi phát hiện ra rằng tôi dường như cần thêm .get(0)vào phần tử mà tôi đang cung cấp chức năng. Tôi đoán điều này phải làm với tôi bằng cách sử dụng jQuery thay vì JS trần? Tôi đã học được điều này từ @jwarzech ở câu hỏi 4233265 . Cảm ơn tất cả!
Max Starkenburg

5
Có, hàm yêu cầu một phần tử DOM, không phải một đối tượng jQuery. .get(0)truy xuất phần tử dom mà jQuery lưu trữ bên trong. Bạn cũng có thể nối thêm [0], tương đương với .get(0)trong ngữ cảnh này.
Nico Burns

1
@Nico Burns: Tôi đã thử phương pháp của bạn và nó không hoạt động trên FireFox.
Lewis

25

Nếu bạn không quan tâm đến các trình duyệt cũ hơn, thì cái này đã giúp tôi.

// [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();

đây là điều duy nhất mà tôi từng làm việc cho bên trong một kịch bản nền cho một phần mở rộng chrome
Rob

1
Điều này hoạt động tốt. Đã thử nghiệm trên chrome 71.0.3578.98 và WebView trên Android 5.1.
maswerdna

3
document.execCommandhiện đã lỗi thời developer.mozilla.org/en-US/docs/Web/API/Document/execCommand .
webprogrammer

2020 và điều này vẫn hoạt động trong Phiên bản chrome 83.0.4103.116 (Bản dựng chính thức) (64-bit)
dùng2677034

đây là người chiến thắng!
MNN TNK

7

Có thể đặt con trỏ đến cuối trong phạm vi:

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; 
}

Sử dụng đoạn mã trên nó thực hiện được mẹo - nhưng tôi muốn sau đó có thể di chuyển con trỏ đến bất kỳ đâu trong div có thể chỉnh sửa nội dung và tiếp tục nhập từ thời điểm đó - ví dụ: người dùng đã nhận ra lỗi đánh máy chẳng hạn ... Tôi sửa đổi mã của bạn ở trên cho điều này?
Zabs

1
@Zabs khá dễ dàng: không gọi setCaretToEnd()mỗi lần - chỉ gọi khi bạn cần: ví dụ sau khi Copy-Paste hoặc sau khi giới hạn độ dài thư.
am0wa 21/02/18

Điều này đã làm việc cho tôi. sau khi người dùng chọn một thẻ, tôi di chuyển con trỏ trong div có thể nội dung đến cuối.
Ayudh

0

Tôi đã gặp sự cố tương tự khi cố gắng làm cho một phần tử có thể chỉnh sửa được. Điều này có thể xảy ra trong Chrome và FireFox nhưng trong FireFox, dấu mũ ở đầu phần nhập hoặc dấu cách nằm sau phần cuối của phần nhập. Tôi nghĩ rất khó hiểu đối với người dùng cuối, đang cố gắng chỉnh sửa nội dung.

Tôi không tìm thấy giải pháp nào khi thử một số thứ. Chỉ có điều hiệu quả với tôi là "giải quyết vấn đề" bằng cách đưa một kiểu nhập văn bản cũ đơn giản vào BÊN TRONG của tôi. Bây giờ nó hoạt động. Có vẻ như "nội dung có thể chỉnh sửa" vẫn còn là công nghệ tiên tiến, có thể có hoặc không hoạt động như bạn muốn, tùy thuộc vào ngữ cảnh.


0

Di chuyển con trỏ đến cuối khoảng có thể chỉnh sửa để phản hồi sự kiện tiêu điểm:

  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);
    }
  }

Và gọi nó trong trình xử lý sự kiện (Phản ứng tại đây):

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

0

Sự cố với contenteditable <div><span>được giải quyết khi bạn bắt đầu nhập vào nó ban đầu. Một giải pháp cho điều này có thể là kích hoạt sự kiện tiêu điểm trên phần tử div của bạn và trên chức năng đó, xóa và điền lại những gì đã có trong phần tử div. Bằng cách này, vấn đề được giải quyết và cuối cùng bạn có thể đặt con trỏ ở cuối bằng cách sử dụng phạm vi và lựa chọn. Đã làm cho tôi.

  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);
    }
  }

Trong mã HTML:

<div contentEditable="true" (focus)="moveCursorToEnd($event)"></div>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.