我试图使用箭头键在可疑段落之间进行遍历.我不能在所有段落中放置一个包含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();
- }
- };