87

Tôi đang tìm thấy rất nhiều điều thú vị, lai tạo về cách thiết lập vị trí con trỏ hoặc dấu mũ trong một DIV có thể chỉnh sửa, nhưng không có cách nào để NHẬN hoặc tìm vị trí của nó ...

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

Vì vậy, khi người dùng đang gõ văn bản, bất cứ lúc nào tôi cũng có thể biết vị trí của con trỏ trong div.

EDIT: 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() { 
    // ... ? 
});
|
102

Các mã sau đây giả định:

  • Luôn có một nút văn bản trong phạm vi 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

Mã số:

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>

|
  • 1

    Đ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ũ bên trong một <a>phần tử bên trong <div>, bạn muốn bù trừ gì? Phần bù trong văn bản bên trong <a>?

    – Hoàng Diễm Thư 17:01:48 20/10/2010
  • 1

    @Richard: Vâng, keyupcó khả năng là sự kiện sai cho việc 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 chính nó.

    – Tạ Khả Ái 10:14:18 20/08/2014
  • 1

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

    – Tạ Nguyệt Nga 15:23:06 21/08/2014
  • 1

    @ giorgio79: Có, vì ngắt dòng tạo ra một <br>hoặc <div>phần tử, vi phạm giả định đầ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

    – Dương Ðông Vy 16:10:50 21/08/2014
  • 1

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

    – Tạ Ngọc Thy 16:30:20 18/07/2016
15

$("#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>

|
13

Thử đi:

Caret.js Nhận tư thế caret và offset từ trường văn bản

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

bản demo: http://ichord.github.com/Caret.js

|
7

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 cấp nút con (ví dụ: nút con có nút con 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 chứa một khởi đầu / kết thúc Caret có thể không phải là phần tử hoặc phần tử trực tiếp của nó

Đây là một cách để có được vị trí bắt đầu và kết thúc dưới dạng giá trị của 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]];
}
|
5

Kinda đến bữa tiệc muộn, nhưng trong trường hợp bất cứ ai khác đang vật lộn. Không có tìm kiếm Google nào tôi tìm thấy trong hai ngày qua đã tìm ra bất cứ thứ gì hoạt động, 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:

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

    return pos;
}

Nó chọn tất cả các cách trở về đầ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 để trả con trỏ về vị trí hiện tại. Nếu bạn muốn làm điều này cho toàn bộ tài liệu (nhiều hơn một đoạn), sau đó 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! :)

|
4
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
    };
}
|
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ự nó có thể được lưu trữ trong một biến và có thể được chọn lại bất cứ lúc nào trừ khi nội dung của div có thể thay đổi nội dung.

Tham khảo cho IE 8 trở xuống: http://msdn.microsoft.com/en-us/l Library / ms535872 (VS85) .aspx

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

|
  • 1

    Cảm ơn, nhưng chính xác làm thế nào để tôi có được 'chỉ số' của vị trí dấu mũ trong nội dung div?

    – Trịnh Tuấn Kiệt 20:22:09 19/10/2010
  • 1

    OK, có vẻ như gọi .base Offerset trên .getSelection () thực hiện thủ thuật. 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!

    – Hoàng Phú Hải 20:32:01 19/10/2010
  • 1

    Thật không may .basePackset chỉ hoạt động trong webkit (tôi nghĩ). Nó cũng chỉ cung cấp cho bạn phần bù từ cha mẹ được bổ sung 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 <b>, chứ không phải bắt đầu <div> Phạm vi dựa trên tiêu chuẩn có thể sử dụng phạm vi phạm vi.end Offerset phạm vi.start Offerset phạm vi.endContainer và phạm vi.startContainer để lấy phần bù từ nút cha của lựa chọn và chính nút đó (bao gồm các nút văn bản). IE cung cấp phạm vi.offsetLeft là bù đắp từ bên trái bằng pixel và vô dụng.

    – Huỳnh Tuyết Loan 20:52:03 19/10/2010
  • 1

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

    – Phạm Tùy Anh 20:56:08 19/10/2010
  • 1

    Đối Rangetượng được trả về bởi hầu hết các trình duyệt và TextRangeđối tượng được IE trả về 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.

    – Tạ Kim Thu 23:44:15 19/10/2010
3

Cái này hoạt động với tôi:

function getCaretCharOffsetInDiv(element) {
    var caretOffset = 0;
    if (typeof window.getSelection != "undefined") {
        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 (typeof document.selection != "undefined" && 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;
} 

đường dây 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:

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

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

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

trong hai trường hợp này, tôi xử lý các dòng ngắt bằng cách thêm chỉ mục đích

|
0

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

Lưu ý: yêu cầu điền nhiều ví dụ để 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;
    }
  }
|
0

Vì điều này khiến tôi mất thời gian để tìm ra bằng 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ó hỗ trợ rộng hơn cho window.getSelection, tuy nhiên, số dặm 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,
        };
    }

    // in case there is nested doms inside editable
    while(selectedNode.parentNode !== editable) {
        selectedNode = selectedNode.parentNode;
    }

    // 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 keyup. Tuy nhiên, lưu ý rằng việc nhấn phím định hướng nhanh, cũng như xóa nhanh dường như là bỏ qua các sự kiện.

|

Câu trả lời của bạn (> 20 ký tự)

Bằng cách click "Đăng trả lời", bạn đồng ý với Điều khoản dịch vụ, Chính sách bảo mật and Chính sách cookie của chúng tôi.

Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ hoặc hỏi câu hỏi của bạn.