Javascript 获取内容可编辑的插入符号索引位置
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3972014/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get contentEditable caret index position
提问by Bertvan
I'm finding tons of good, crossbrowser anwers on how to SET the cursor or caret index position in a contentEditable
element, but none on how to GET or find its index...
我发现了大量关于如何在contentEditable
元素中设置光标或插入符号索引位置的跨浏览器的好答案,但没有关于如何获取或找到它的索引...
What I want to do is know the index of the caret within this div, on keyup
.
我想要做的是知道这个 div 中插入符号的索引,在keyup
.
So, when the user is typing text, I can at any point know its cursor's index within the contentEditable
element.
因此,当用户输入文本时,我可以随时知道其光标在contentEditable
元素内的索引。
EDIT: I'm looking for the INDEXwithin the div contents (text), not the cursor coordinates.
编辑:我正在寻找div 内容(文本)中的INDEX,而不是光标坐标。
<div id="contentBox" contentEditable="true"></div>
$('#contentbox').keyup(function() {
// ... ?
});
回答by Tim Down
The following code assumes:
以下代码假设:
- There is always a single text node within the editable
<div>
and no other nodes - The editable div does not have the CSS
white-space
property set topre
- 可编辑中始终只有一个文本节点
<div>
,没有其他节点 - 可编辑的 div 没有将 CSS
white-space
属性设置为pre
If you need a more general approach that will work content with nested elements, try this answer:
如果您需要更通用的方法来处理嵌套元素的内容,请尝试以下答案:
https://stackoverflow.com/a/4812022/96100
https://stackoverflow.com/a/4812022/96100
Code:
代码:
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>
回答by mwag
A few wrinkles that I don't see being addressed in other answers:
我在其他答案中没有看到的一些皱纹:
- the element can contain multiple levels of child nodes (e.g. child nodes that have child nodes that have child nodes...)
- a selection can consist of different start and end positions (e.g. multiple chars are selected)
- the node containing a Caret start/end may not be either the element or its direct children
- 该元素可以包含多个级别的子节点(例如,具有子节点的子节点具有子节点......)
- 选择可以由不同的开始和结束位置组成(例如选择多个字符)
- 包含插入符号开始/结束的节点可能不是元素或其直接子元素
Here's a way to get start and end positions as offsets to the element's textContent value:
这是一种获取开始和结束位置作为元素 textContent 值的偏移量的方法:
// 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]];
}
回答by Eisa Qasemi
$("#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>
回答by J.Y Han
Try this:
尝试这个:
Caret.js Get caret postion and offset from text field
Caret.js 从文本字段获取插入符号位置和偏移量
https://github.com/ichord/Caret.js
https://github.com/ichord/Caret.js
回答by Soubriquet
Kinda late to the party, but in case anyone else is struggling. None of the Google searches I've found for the past two days have come up with anything that works, but I came up with a concise and elegant solution that will always work no matter how many nested tags you have:
参加聚会有点晚,但以防万一其他人正在挣扎。我在过去两天找到的所有 Google 搜索都没有提出任何有效的方法,但我提出了一个简洁而优雅的解决方案,无论您有多少嵌套标签,该解决方案始终有效:
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>
It selects all the way back to the beginning of the paragraph and then counts the length of the string to get the current position and then undoes the selection to return the cursor to the current position. If you want to do this for an entire document (more than one paragraph), then change paragraphboundary
to documentboundary
or whatever granularity for your case. Check out the API for more details. Cheers! :)
它一直选择回到段落的开头,然后计算字符串的长度以获取当前位置,然后撤消选择以将光标返回到当前位置。如果您想对整个文档(多于一个段落)执行此操作,请更改paragraphboundary
为documentboundary
或任何适合您案例的粒度。查看 API 了解更多详情。干杯! :)
回答by Jonathan R.
This one works for me:
这个对我有用:
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>
The calling line depends on event type, for key event use this:
调用行取决于事件类型,对于关键事件,请使用:
getCaretCharOffsetInDiv(e.target) + ($(window.getSelection().getRangeAt(0).startContainer.parentNode).index());
for mouse event use this:
对于鼠标事件使用这个:
getCaretCharOffsetInDiv(e.target.parentElement) + ($(e.target).index())
on these two cases I take care for break lines by adding the target index
在这两种情况下,我通过添加目标索引来处理中断线
回答by Nishad Up
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
};
}
回答by Nico Burns
//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)
}
});
Note: the range object its self can be stored in a variable, and can be re-selected at any time unless the contents of the contenteditable div change.
注意:range 对象本身可以存储在一个变量中,并且可以随时重新选择,除非 contenteditable div 的内容发生变化。
Reference for IE 8 and lower: http://msdn.microsoft.com/en-us/library/ms535872(VS.85).aspx
IE 8 及更低版本的参考:http: //msdn.microsoft.com/en-us/library/ms535872(VS.85).aspx
Reference for standards (all other) browsers: https://developer.mozilla.org/en/DOM/range(its the mozilla docs, but code works in chrome, safari, opera and ie9 too)
标准(所有其他)浏览器的参考:https: //developer.mozilla.org/en/DOM/range(它是 mozilla 文档,但代码也适用于 chrome、safari、opera 和 ie9)
回答by Chris Sullivan
As this took me forever to figure out using the new window.getSelectionAPI I am going to share for posterity. Note that MDN suggests there is wider support for window.getSelection, however, your mileage may vary.
由于这让我花了很长时间才弄清楚使用新的window.getSelectionAPI,我将分享给后代。请注意,MDN 建议对 window.getSelection 有更广泛的支持,但是,您的里程可能会有所不同。
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,
}
}
Here is a jsfiddlethat fires on keyup. Note however, that rapid directional key presses, as well as rapid deletion seems to be skip events.
这是一个在 keyup 上触发的jsfiddle。但是请注意,快速方向键按下以及快速删除似乎是跳过事件。
回答by alockwood05
A straight forward way, that iterates through all the chidren of the contenteditable div until it hits the endContainer. Then I add the end container offset and we have the character index. Should work with any number of nestings. uses recursion.
一种直接的方式,它遍历 contenteditable div 的所有孩子,直到它到达 endContainer。然后我添加结束容器偏移量,我们就有了字符索引。应该使用任意数量的嵌套。使用递归。
Note: requires a poly fillfor ie to support Element.closest('div[contenteditable]')
注意:需要多边形填充以支持 ieElement.closest('div[contenteditable]')
https://codepen.io/alockwood05/pen/vMpdmZ
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;
}
}