Nhận nội dung Vị trí chỉ mục dấu mũ có thể sửa đổi


119

Tôi đang tìm rất nhiều điều hay, trình duyệt chéo về cách ĐẶT vị trí chỉ mục con trỏ hoặc dấu mũ trong một contentEditablephần tử, nhưng không có gì về cách TẢI hoặc tìm chỉ mục của nó ...

Những gì tôi muốn làm là biết chỉ số của dấu mũ trong div này, trên keyup.

Vì vậy, khi người dùng đang nhập văn bản, tôi có thể biết chỉ số con trỏ của nó trong contentEditablephần tử bất kỳ lúc nào .

CHỈNH SỬA: Tôi đang tìm INDEX trong nội dung div (văn bản), không phải tọa độ con trỏ.

<div id="contentBox" contentEditable="true"></div>

$('#contentbox').keyup(function() { 
    // ... ? 
});

Nhìn vào vị trí của nó trong văn bản. Sau đó, tìm kiếm lần xuất hiện cuối cùng của '@' trước vị trí đó. Vì vậy, chỉ là một số logic văn bản.
Bertvan

Ngoài ra, tôi không có kế hoạch cho phép các thẻ khác trong <div>, chỉ có văn bản
Bertvan

ok, vâng tôi đang sẽ cần các thẻ khác trong <div>. Sẽ có <a> thẻ, nhưng sẽ không có làm tổ ...
Bertvan

@Bertvan: nếu dấu mũ nằm bên trong một <a>phần tử bên trong <div>, thì bạn muốn bù đắp bao nhiêu? Phần bù bên trong văn bản bên trong dấu <a>?
Tim Down

Nó không bao giờ được ở bên trong phần tử <a>. Phần tử <a> phải được hiển thị html, vì vậy người dùng không thể thực sự đặt dấu mũ vào đó.
Bertvan 21/10/10

Câu trả lời:


121

Đoạn mã sau giả định:

  • Luôn có một nút văn bản duy nhất trong phần có thể chỉnh sửa <div>và không có nút nào khác
  • Div có thể chỉnh sửa không có thuộc tính CSS white-spaceđược đặt thànhpre

Nếu bạn cần một cách tiếp cận tổng quát hơn sẽ phù hợp với nội dung với các phần tử lồng nhau, hãy thử câu trả lời sau:

https://stackoverflow.com/a/4812022/96100

Mã:

function getCaretPosition(editableDiv) {
  var caretPos = 0,
    sel, range;
  if (window.getSelection) {
    sel = window.getSelection();
    if (sel.rangeCount) {
      range = sel.getRangeAt(0);
      if (range.commonAncestorContainer.parentNode == editableDiv) {
        caretPos = range.endOffset;
      }
    }
  } else if (document.selection && document.selection.createRange) {
    range = document.selection.createRange();
    if (range.parentElement() == editableDiv) {
      var tempEl = document.createElement("span");
      editableDiv.insertBefore(tempEl, editableDiv.firstChild);
      var tempRange = range.duplicate();
      tempRange.moveToElementText(tempEl);
      tempRange.setEndPoint("EndToEnd", range);
      caretPos = tempRange.text.length;
    }
  }
  return caretPos;
}
#caretposition {
  font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="contentbox" contenteditable="true">Click me and move cursor with keys or mouse</div>
<div id="caretposition">0</div>
<script>
  var update = function() {
    $('#caretposition').html(getCaretPosition(this));
  };
  $('#contentbox').on("mousedown mouseup keydown keyup", update);
</script>


9
Điều này sẽ không hoạt động nếu có bất kỳ thẻ nào khác trong đó. Câu hỏi: nếu dấu mũ nằm bên trong một <a>phần tử bên trong <div>, thì bạn muốn bù đắp bao nhiêu? Phần bù bên trong văn bản bên trong dấu <a>?
Tim Down

3
@Richard: Chà, keyupcó thể là sự kiện sai cho điều này nhưng là những gì đã được sử dụng trong câu hỏi ban đầu. getCaretPosition()bản thân nó là tốt trong giới hạn của riêng nó.
Tim Down

3
Bản demo JSFIDDLE đó không thành công nếu tôi nhấn enter và chuyển sang dòng mới. Vị trí sẽ hiển thị 0.
giorgio79

5
@ giorgio79: Có, vì ngắt dòng tạo ra một <br>hoặc <div>phần tử, điều này vi phạm giả thiết đầu tiên được đề cập trong câu trả lời. Nếu bạn cần một giải pháp tổng quát hơn một chút, bạn có thể thử stackoverflow.com/a/4812022/96100
Tim Down

2
Có cách nào để làm điều này để nó bao gồm số dòng không?
Điều chỉnh

28

Một vài nếp nhăn mà tôi không thấy được giải quyết trong các câu trả lời khác:

  1. phần tử có thể chứa nhiều mức nút con (ví dụ: các nút con có các nút con có các nút con ...)
  2. một lựa chọn có thể bao gồm các vị trí bắt đầu và kết thúc khác nhau (ví dụ: nhiều ký tự được chọn)
  3. nút có chứa đầu / cuối Dấu mũ có thể không phải là phần tử hoặc con trực tiếp của nó

Đây là một cách để lấy các vị trí bắt đầu và kết thúc làm phần bù cho giá trị textContent của phần tử:

// node_walk: walk the element tree, stop when func(node) returns false
function node_walk(node, func) {
  var result = func(node);
  for(node = node.firstChild; result !== false && node; node = node.nextSibling)
    result = node_walk(node, func);
  return result;
};

// getCaretPosition: return [start, end] as offsets to elem.textContent that
//   correspond to the selected portion of text
//   (if start == end, caret is at given position and no text is selected)
function getCaretPosition(elem) {
  var sel = window.getSelection();
  var cum_length = [0, 0];

  if(sel.anchorNode == elem)
    cum_length = [sel.anchorOffset, sel.extentOffset];
  else {
    var nodes_to_find = [sel.anchorNode, sel.extentNode];
    if(!elem.contains(sel.anchorNode) || !elem.contains(sel.extentNode))
      return undefined;
    else {
      var found = [0,0];
      var i;
      node_walk(elem, function(node) {
        for(i = 0; i < 2; i++) {
          if(node == nodes_to_find[i]) {
            found[i] = true;
            if(found[i == 0 ? 1 : 0])
              return false; // all done
          }
        }

        if(node.textContent && !node.firstChild) {
          for(i = 0; i < 2; i++) {
            if(!found[i])
              cum_length[i] += node.textContent.length;
          }
        }
      });
      cum_length[0] += sel.anchorOffset;
      cum_length[1] += sel.extentOffset;
    }
  }
  if(cum_length[0] <= cum_length[1])
    return cum_length;
  return [cum_length[1], cum_length[0]];
}

3
Đây phải được chọn là câu trả lời đúng. Nó hoạt động với các thẻ bên trong văn bản (phản ứng được chấp nhận không)
hamboy75

17

$("#editable").on('keydown keyup mousedown mouseup',function(e){
		   
       if($(window.getSelection().anchorNode).is($(this))){
    	  $('#position').html('0')
       }else{
         $('#position').html(window.getSelection().anchorOffset);
       }
 });
body{
  padding:40px;
}
#editable{
  height:50px;
  width:400px;
  border:1px solid #000;
}
#editable p{
  margin:0;
  padding:0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
<div contenteditable="true" id="editable">move the cursor to see position</div>
<div>
position : <span id="position"></span>
</div>


3
Rất tiếc, điều này sẽ ngừng hoạt động ngay khi bạn nhấn enter và bắt đầu trên một dòng khác (nó lại bắt đầu từ 0 - có thể tính từ CR / LF).
Ian

Nó không hoạt động bình thường nếu bạn có một số từ In đậm và / hoặc Nghiêng.
user2824371

14

Thử cái này:

Caret.js Nhận vị trí dấu mũ và bù đắp từ trường văn bản

https://github.com/ichord/Caret.js

demo: http://ichord.github.com/Caret.js


Nó thật ngọt ngào. Tôi cần hành vi này để đặt dấu mũ ở cuối dấu contenteditable likhi được nhấp vào nút để đổi tên linội dung của.
akinuri

@AndroidDev Tôi không phải là tác giả của Caret.js nhưng bạn có nghĩ rằng việc nhận vị trí dấu mũ cho tất cả các trình duyệt chính phức tạp hơn một vài dòng không? Bạn có biết hoặc đã tạo một giải pháp thay thế không cồng kềnh mà bạn có thể chia sẻ với chúng tôi không?
adelriosantiago

8

Kinda đến bữa tiệc muộn, nhưng trong trường hợp có ai khác đang gặp khó khăn. Không có tìm kiếm nào trên Google mà tôi tìm thấy trong hai ngày qua không đưa ra được bất kỳ điều gì hiệu quả, nhưng tôi đã đưa ra một giải pháp ngắn gọn và thanh lịch sẽ luôn hoạt động cho dù bạn có bao nhiêu thẻ lồng nhau:

function cursor_position() {
    var sel = document.getSelection();
    sel.modify("extend", "backward", "paragraphboundary");
    var pos = sel.toString().length;
    if(sel.anchorNode != undefined) sel.collapseToEnd();

    return pos;
}

// Demo:
var elm = document.querySelector('[contenteditable]');
elm.addEventListener('click', printCaretPosition)
elm.addEventListener('keydown', printCaretPosition)

function printCaretPosition(){
  console.log( cursor_position(), 'length:', this.textContent.trim().length )
}
<div contenteditable>some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text</div>

Nó chọn tất cả các cách trở lại đầu đoạn và sau đó đếm độ dài của chuỗi để có được vị trí hiện tại và sau đó hoàn tác lựa chọn để đưa con trỏ trở lại vị trí hiện tại. Nếu bạn muốn thực hiện việc này cho toàn bộ tài liệu (nhiều hơn một đoạn văn), hãy thay đổi paragraphboundarythành documentboundaryhoặc bất kỳ mức độ chi tiết nào cho trường hợp của bạn. Kiểm tra API để biết thêm chi tiết . Chúc mừng! :)


1
Nếu tôi có <div contenteditable> some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text </div> Mỗi lần tôi đặt con trỏ trước ithẻ hoặc bất kỳ phần tử html con nào bên trong div, vị trí con trỏ đang bắt đầu từ 0. Có cách nào để thoát khỏi số lần khởi động lại này không?
vam

Kỳ quặc. Tôi không nhận được hành vi đó trong Chrome. Bạn đang dùng trình duyệt nào?
Soubriquet

2
Có vẻ như select.modify có thể được hỗ trợ hoặc không trên tất cả các trình duyệt. developer.mozilla.org/en-US/docs/Web/API/Selection
Chris Sullivan

7
function getCaretPosition() {
    var x = 0;
    var y = 0;
    var sel = window.getSelection();
    if(sel.rangeCount) {
        var range = sel.getRangeAt(0).cloneRange();
        if(range.getClientRects()) {
        range.collapse(true);
        var rect = range.getClientRects()[0];
        if(rect) {
            y = rect.top;
            x = rect.left;
        }
        }
    }
    return {
        x: x,
        y: y
    };
}

cái này thực sự hiệu quả với tôi, tôi đã thử tất cả những cái ở trên, nhưng không.
iStudLion

cảm ơn bạn nhưng nó cũng trả về {x: 0, y: 0} trên dòng mới.
hichamkazan

điều này trả về vị trí pixel, không phải độ lệch ký tự
4esn0k Ngày

cảm ơn, tôi đang tìm kiếm lại vị trí pixel từ dấu mũ và nó đang hoạt động tốt.
Sameesh

6

window.getSelection - so với - document.selection

Cái này phù hợp với tôi:

function getCaretCharOffset(element) {
  var caretOffset = 0;

  if (window.getSelection) {
    var range = window.getSelection().getRangeAt(0);
    var preCaretRange = range.cloneRange();
    preCaretRange.selectNodeContents(element);
    preCaretRange.setEnd(range.endContainer, range.endOffset);
    caretOffset = preCaretRange.toString().length;
  } 

  else if (document.selection && document.selection.type != "Control") {
    var textRange = document.selection.createRange();
    var preCaretTextRange = document.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint("EndToEnd", textRange);
    caretOffset = preCaretTextRange.text.length;
  }

  return caretOffset;
}


// Demo:
var elm = document.querySelector('[contenteditable]');
elm.addEventListener('click', printCaretPosition)
elm.addEventListener('keydown', printCaretPosition)

function printCaretPosition(){
  console.log( getCaretCharOffset(elm), 'length:', this.textContent.trim().length )
}
<div contenteditable>some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text</div>

Dòng gọi phụ thuộc vào loại sự kiện, đối với sự kiện chính, hãy sử dụng điều này:

getCaretCharOffsetInDiv(e.target) + ($(window.getSelection().getRangeAt(0).startContainer.parentNode).index());

cho sự kiện chuột sử dụng cái này:

getCaretCharOffsetInDiv(e.target.parentElement) + ($(e.target).index())

trong hai trường hợp này, tôi quan tâm đến các dòng ngắt bằng cách thêm chỉ mục mục tiêu


4
//global savedrange variable to store text range in
var savedrange = null;

function getSelection()
{
    var savedRange;
    if(window.getSelection && window.getSelection().rangeCount > 0) //FF,Chrome,Opera,Safari,IE9+
    {
        savedRange = window.getSelection().getRangeAt(0).cloneRange();
    }
    else if(document.selection)//IE 8 and lower
    { 
        savedRange = document.selection.createRange();
    }
    return savedRange;
}

$('#contentbox').keyup(function() { 
    var currentRange = getSelection();
    if(window.getSelection)
    {
        //do stuff with standards based object
    }
    else if(document.selection)
    { 
        //do stuff with microsoft object (ie8 and lower)
    }
});

Lưu ý: đối tượng phạm vi tự của nó có thể được lưu trữ trong một biến và có thể được chọn lại bất kỳ lúc nào trừ khi nội dung của div có thể thay đổi nội dung.

Tham chiếu cho IE 8 trở xuống: http://msdn.microsoft.com/en-us/library/ms535872(VS.85).aspx

Tham khảo cho các trình duyệt tiêu chuẩn (tất cả các trình duyệt khác): https://developer.mozilla.org/en/DOM/range (nó là tài liệu mozilla, nhưng mã hoạt động trong chrome, safari, opera và ie9 nữa)


1
Cảm ơn, nhưng chính xác thì làm cách nào để lấy 'chỉ mục' của vị trí dấu mũ trong nội dung div?
Bertvan 19/10/10

OK, có vẻ như việc gọi .baseOffset trên .getSelection () thực hiện một mẹo nhỏ. Vì vậy, điều này, cùng với câu trả lời của bạn, trả lời câu hỏi của tôi. Cảm ơn!
Bertvan

2
Thật không may .baseOffset chỉ hoạt động trong webkit (tôi nghĩ). Nó cũng chỉ cung cấp cho bạn phần bù từ phần tử gốc imediate của dấu mũ (nếu bạn có thẻ <b> bên trong <div>, nó sẽ cung cấp phần bù từ đầu của <b> chứ không phải phần đầu của <div> Các phạm vi dựa trên tiêu chuẩn có thể sử dụng range.endOffset range.startOffset range.endContainer và range.startContainer để lấy phần bù từ nút cha của vùng lựa chọn và chính nút (điều này bao gồm các nút văn bản). IE cung cấp range.offsetLeft là bù đắp từ bên trái pixel , và do đó vô dụng.
Nico Burns

Tốt nhất là chỉ nên lưu trữ đối tượng phạm vi chính nó và sử dụng window.getSelection (). Addrange (range); <- tiêu chuẩn và phạm vi.select (); <- IE để định vị lại con trỏ ở cùng một vị trí. range.insertNode (gật đầu); <- tiêu chuẩn và range.pasteHTML (htmlcode); <- IE để chèn văn bản hoặc html vào con trỏ.
Nico Burns,

Đối Rangetượng được trả về bởi hầu hết các trình duyệt và TextRangeđối tượng được trả về bởi IE là những thứ cực kỳ khác nhau, vì vậy tôi không chắc câu trả lời này giải quyết được nhiều điều.
Tim Down

3

Vì điều này khiến tôi mất nhiều thời gian để tìm ra cách sử dụng API window.getSelection mới mà tôi sẽ chia sẻ cho hậu thế. Lưu ý rằng MDN cho thấy có sự hỗ trợ rộng rãi hơn cho window.getSelection, tuy nhiên, quãng đường của bạn có thể thay đổi.

const getSelectionCaretAndLine = () => {
    // our editable div
    const editable = document.getElementById('editable');

    // collapse selection to end
    window.getSelection().collapseToEnd();

    const sel = window.getSelection();
    const range = sel.getRangeAt(0);

    // get anchor node if startContainer parent is editable
    let selectedNode = editable === range.startContainer.parentNode
      ? sel.anchorNode 
      : range.startContainer.parentNode;

    if (!selectedNode) {
        return {
            caret: -1,
            line: -1,
        };
    }

    // select to top of editable
    range.setStart(editable.firstChild, 0);

    // do not use 'this' sel anymore since the selection has changed
    const content = window.getSelection().toString();
    const text = JSON.stringify(content);
    const lines = (text.match(/\\n/g) || []).length + 1;

    // clear selection
    window.getSelection().collapseToEnd();

    // minus 2 because of strange text formatting
    return {
        caret: text.length - 2, 
        line: lines,
    }
} 

Đây là một jsfiddle kích hoạt khi gõ phím . Tuy nhiên, lưu ý rằng các thao tác nhấn phím hướng nhanh, cũng như xóa nhanh dường như bỏ qua các sự kiện.


Làm việc cho tôi! Cảm ơn bạn rất nhiều.
dmodo

Với lựa chọn văn bản này không thể thực hiện được nữa vì nó đã được thu gọn. Tình huống có thể xảy ra: cần đánh giá mọi sự kiện keyUp
hschmieder

0

Một cách thẳng tiến, lặp lại qua tất cả các chidren của div có thể hài lòng cho đến khi nó chạm vào endContainer. Sau đó, tôi thêm phần bù vùng chứa cuối và chúng tôi có chỉ mục ký tự. Nên làm việc với bất kỳ số lượng tổ nào. sử dụng đệ quy.

Lưu ý: yêu cầu điền poly cho tức là để hỗ trợElement.closest('div[contenteditable]')

https://codepen.io/alockwood05/pen/vMpdmZ

function caretPositionIndex() {
    const range = window.getSelection().getRangeAt(0);
    const { endContainer, endOffset } = range;

    // get contenteditableDiv from our endContainer node
    let contenteditableDiv;
    const contenteditableSelector = "div[contenteditable]";
    switch (endContainer.nodeType) {
      case Node.TEXT_NODE:
        contenteditableDiv = endContainer.parentElement.closest(contenteditableSelector);
        break;
      case Node.ELEMENT_NODE:
        contenteditableDiv = endContainer.closest(contenteditableSelector);
        break;
    }
    if (!contenteditableDiv) return '';


    const countBeforeEnd = countUntilEndContainer(contenteditableDiv, endContainer);
    if (countBeforeEnd.error ) return null;
    return countBeforeEnd.count + endOffset;

    function countUntilEndContainer(parent, endNode, countingState = {count: 0}) {
      for (let node of parent.childNodes) {
        if (countingState.done) break;
        if (node === endNode) {
          countingState.done = true;
          return countingState;
        }
        if (node.nodeType === Node.TEXT_NODE) {
          countingState.count += node.length;
        } else if (node.nodeType === Node.ELEMENT_NODE) {
          countUntilEndContainer(node, endNode, countingState);
        } else {
          countingState.error = true;
        }
      }
      return countingState;
    }
  }
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.