我试图使用箭头键在可疑段落之间进行遍历.我不能在所有段落中放置一个包含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); };
假设光标位于第三段的顶行并按下向上箭头,我希望它跳转到第二段的底行
解决方法
看起来没有简单的方法可以做到这一点,我有以下工作示例.有一些处理,所以它有点慢,当在段落之间上下移动时,它可以由奇数字符输出.
请告知我可以进行的任何改进.
我所做的是通过每个工作拆分段落,逐个将它们插入一个新元素,检查高度变化 – 当它改变时,添加了一个新行.
此函数返回包含行文本,起始索引和结束索引的行对象数组:
(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(),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","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(); } };