看起来没有简单的方法可以做到这一点,我有以下工作示例。有一点处理,所以它有点慢,当在段落之间上下移动时,它可能会被奇怪的字符输出。
请告知我可以进行的任何改进。
http://jsfiddle.net/zQUhV/47/
我所做的是按每个作品拆分段落,将它们一个一个插入一个新元素,检查高度变化 - 当它确实改变时,添加了一个新行。
此函数返回包含行文本、起始索引和结束索引的行对象数组:
(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 函数足以在 contenteditable 段落之间非常准确地遍历:
$(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();
}
};