我尝试使用箭头键在可疑段落之间进行遍历。我不能在所有段落中放置一个包含div,因为它可能被其他不可编辑的元素划分。
我需要能够确定第一行的字符长度,以便当光标在行上时按下向上箭头键然后它将跳到上一段 - 希望保持光标位置相对于这条线。
我可以使用:
获取游标索引function cursorIndex() {
return window.getSelection().getRangeAt(0).startOffset;
}
并将其设置为:在此处找到 - Javascript Contenteditable - set Cursor / Caret to index
var setSelectionRange = function(element, start, end) {
var rng = document.createRange(),
sel = getSelection(),
n, o = 0,
tw = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, null);
while (n = tw.nextNode()) {
o += n.nodeValue.length;
if (o > start) {
rng.setStart(n, n.nodeValue.length + start - o);
start = Infinity;
}
if (o >= end) {
rng.setEnd(n, n.nodeValue.length + end - o);
break;
}
}
sel.removeAllRanges();
sel.addRange(rng);
};
var setCaret = function(element, index) {
setSelectionRange(element, index, index);
};
假设光标位于第三段的顶行并按下向上箭头,我希望它跳转到第二段的底行
答案 0 :(得分:3)
看起来没有简单的方法可以做到这一点,我有以下工作示例。有一些处理,所以它有点慢,当在段落之间上下移动时,它可以由奇数字符输出。
请告知我可以做出的任何改进。
我所做的是将每个作品拆分成段,逐个将它们插入一个新元素,检查高度变化 - 当它确实改变时,添加了一个新行。
此函数返回一个包含行文本,起始索引和结束索引的行对象数组:
(function($) {
$.fn.lines = function(){
words = this.text().split(" "); //split text into each word
lines = [];
hiddenElement = this.clone(); //copies font settings and width
hiddenElement.empty();//clear text
hiddenElement.css("visibility", "hidden");
jQuery('body').append(hiddenElement); // height doesn't exist until inserted into document
hiddenElement.text('i'); //add character to get height
height = hiddenElement.height();
hiddenElement.empty();
startIndex = -1; // quick fix for now - offset by one to get the line indexes working
jQuery.each(words, function() {
lineText = hiddenElement.text(); // get text before new word appended
hiddenElement.text(lineText + " " + this);
if(hiddenElement.height() > height) { // if new line
lines.push({text: lineText, startIndex: startIndex, endIndex: (lineText.length + startIndex)}); // push lineText not hiddenElement.text() other wise each line will have 1 word too many
startIndex = startIndex + lineText.length +1;
hiddenElement.text(this); //first word of the next line
}
});
lines.push({text: hiddenElement.text(), startIndex: startIndex, endIndex: (hiddenElement.text().length + startIndex)}); // push last line
hiddenElement.remove();
lines[0].startIndex = 0; //quick fix for now - adjust first line index
return lines;
}
})(jQuery);
现在你可以使用它来测量直到光标点的字符数,并在遍历段落时应用它以保持光标相对于行的开头的位置。然而,当将'i'的宽度考虑为'm'的宽度时,这会产生非常不准确的结果。
相反,最好找到直到光标点的线宽:
function distanceToCaret(textElement,caretIndex){
line = findLineViaCaret(textElement,caretIndex);
if(line.startIndex == 0) {
// +1 needed for substring to be correct but only first line?
relativeIndex = caretIndex - line.startIndex +1;
} else {
relativeIndex = caretIndex - line.startIndex;
}
textToCaret = line.text.substring(0, relativeIndex);
hiddenElement = textElement.clone(); //copies font settings and width
hiddenElement.empty();//clear text
hiddenElement.css("visibility", "hidden");
hiddenElement.css("width", "auto"); //so width can be measured
hiddenElement.css("display", "inline-block"); //so width can be measured
jQuery('body').append(hiddenElement); // doesn't exist until inserted into document
hiddenElement.text(textToCaret); //add to get width
width = hiddenElement.width();
hiddenElement.remove();
return width;
}
function findLineViaCaret(textElement,caretIndex){
jQuery.each(textElement.lines(), function() {
if(this.startIndex <= caretIndex && this.endIndex >= caretIndex) {
r = this;
return false; // exits loop
}
});
return r;
}
然后将目标线分成字符,并通过逐个添加字符找到最接近上面宽度的点,直到达到该点:
function getCaretViaWidth(textElement, lineNo, width) {
line = textElement.lines()[lineNo-1];
lineCharacters = line.text.replace(/^\s+|\s+$/g, '').split("");
hiddenElement = textElement.clone(); //copies font settings and width
hiddenElement.empty();//clear text
hiddenElement.css("visibility", "hidden");
hiddenElement.css("width", "auto"); //so width can be measured
hiddenElement.css("display", "inline-block"); //so width can be measured
jQuery('body').append(hiddenElement); // doesn't exist until inserted into document
if(width == 0) { //if width is 0 index is at start
caretIndex = line.startIndex;
} else {// else loop through each character until width is reached
hiddenElement.empty();
jQuery.each(lineCharacters, function() {
text = hiddenElement.text();
prevWidth = hiddenElement.width();
hiddenElement.text(text + this);
elWidth = hiddenElement.width();
caretIndex = hiddenElement.text().length + line.startIndex;
if(hiddenElement.width() > width) {
// check whether character after width or before width is closest
if(Math.abs(width - prevWidth) < Math.abs(width - elWidth)) {
caretIndex = caretIndex -1; // move index back one if previous is closes
}
return false;
}
});
}
hiddenElement.remove();
return caretIndex;
}
使用以下keydown函数就足以在令人满意的段落之间准确地遍历:
$(document).on('keydown', 'p[contenteditable="true"]', function(e) {
//if cursor on first line & up arrow key
if(e.which == 38 && (cursorIndex() < $(this).lines()[0].text.length)) {
e.preventDefault();
if ($(this).prev().is('p')) {
prev = $(this).prev('p');
getDistanceToCaret = distanceToCaret($(this), cursorIndex());
lineNumber = prev.lines().length;
caretPosition = getCaretViaWidth(prev, lineNumber, getDistanceToCaret);
prev.focus();
setCaret(prev.get(0), caretPosition);
}
// if cursor on last line & down arrow
} else if(e.which == 40 && cursorIndex() >= $(this).lastLine().startIndex && cursorIndex() <= ($(this).lastLine().startIndex + $(this).lastLine().text.length)) {
e.preventDefault();
if ($(this).next().is('p')) {
next = $(this).next('p');
getDistanceToCaret = distanceToCaret($(this), cursorIndex());
caretPosition = getCaretViaWidth(next, 1, getDistanceToCaret);
next.focus();
setCaret(next.get(0), caretPosition);
}
//if start of paragraph and left arrow
} else if(e.which == 37 && cursorIndex() == 0) {
e.preventDefault();
if ($(this).prev().is('p')) {
prev = $(this).prev('p');
prev.focus();
setCaret(prev.get(0), prev.text().length);
}
// if end of paragraph and right arrow
} else if(e.which == 39 && cursorIndex() == $(this).text().length) {
e.preventDefault();
if ($(this).next().is('p')) {
$(this).next('p').focus();
}
};