Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
547 views
in Technique[技术] by (71.8m points)

html - How to get number of rows in ContentEditable area and current caret line position?

my question has two parts, but they are related.

Firstly - I have Contenteditable DIV with some text and I need to get total number of rows (lines) of this DIV. Is it possible ?

Secondly - I need to get caret row position, if it is on row number 1, 2, 3, etc....

Can anybody help ?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The direct answer is that there is no method that actually gets you those numbers. However, there are a number of different work arounds which you can apply to estimate (I use estimate, because I don't think they can be made 100% accurate) those values.

To answer your first question, how to get the number of lines in the element. Assuming the element uses a consistent line-height, you can find the number of lines by dividing the element height with the line-height. This of course gets significantly more complicated if you got elements with margins, paddings, or differentiating line-heights within the element. If that isn't enough of a problem, the line-height property doesn't necessarily have to be a numerical value, i.e. it can be normal or a % etc. There's quite a few questions regarding the line-height property and how to get a numerical representation of it here on SO so I won't go too much into detail on that. For the sake of rest of my answer, I did specifically assign a common line-height across the element.

The far more problematic part of your question is finding the caret position within the element. Again, there isn't a method which will just return you the right answer. Instead, one approach you can take to estimate the caret row position is to make a range at the current caret position (selection), insert a dummy node there, get the nodes offset relative to the containers offset, calculate the number of lines based on the top offset of it, and then remove the dummy element.

Additional problems with the dummy element arise when you can't hide() it or leave it empty.

It seems like a very bad way of doing it, and it is. It certainly doesn't work flawlessly when trying to navigate with arrows. So why not just use getClientRects() for the selection? It works fine when it is just text without any space or such, but immidiately when you throw a few <br /><br /> in there, it won't return you the line position there anymore. Additionally, when you are at the first or last character of a line, it would give sometimes incorrect line rows, as it throws the element either up or down depending on the amount of space available.

If you are looking for a fool-proof way of finding the caret position and number of lines, there isn't one. If rough estimates are enough, than my solution is a good start (it certainly could use some more work, and support for IE which should be lot easier in this case as the TextRange object actually gives offsets in pixels).

My take on this:

var lineHeight = parseInt($('#editable').css('line-height'));
//var ce = $('#editable')[0].getClientRects();
var ce = $('#editable').position();
console.log("Lines: "+$('#editable').height()/lineHeight);
$('#editable').bind('click keyup keydown',function(){
   if(window.getSelection){
        range = window.getSelection().getRangeAt(0);
      range.insertNode($('<canvas />').attr('id','tempCaretFinder')[0]);
       var p = $('canvas#tempCaretFinder').position();
       $('canvas#tempCaretFinder').remove();
       console.log("Caret line: "+(Math.ceil((p.top-ce.top)/lineHeight)+1));

    }else if(document.selection) { 
        // the IE way, which should be relatively easier. as TextRange objects return offsets directly.
        range = document.selection.createRange();  
    }

});

Example: http://jsfiddle.net/niklasvh/mKQUH/

EDIT: Try 2 http://jsfiddle.net/niklasvh/mKQUH/129/

As mentioned, creating dummy elements made the caret jump all over the place sometimes, so I had a go again with the getClientRects() for ranges. To make them more feasible, I made the selection extend itself one character, then create the range, then check the Rect position, and then move the caret back one character.

Why?

Because the caret on the first character of a line, would have its top parameter at the same level as the previous line, so with applying an extra character to it, it can check whether it is actually a new line. Another problem with the getClientRects() was that it didn't work on empty line breaks. To accomodate this, I used the previous creating of a dummy element as a fallback in those situations.

End result:

var lineHeight = parseInt($('#editable').css('line-height'));
//var ce = $('#editable')[0].getClientRects();
var ce = $('#editable').offset();
console.log("Lines: "+$('#editable').height()/lineHeight);
$('#editable').bind('click keyup keydown',function(e){
    //alert($(window).scrollTop());
   if(window.getSelection){
       var save = window.getSelection();
       var s = window.getSelection();
       s.modify('extend','forward','character');
     // s.modify('extend','forward','line');
       range = s.getRangeAt(0);
       var p = range.getClientRects();
       var top;
       //console.log(p);
       if (typeof p[1] != "undefined"){
           top = p[1].top+$(window).scrollTop();
               }else if (typeof p[0] != "undefined"){
                top = p[0].top+$(window).scrollTop();    
               }
       else{
          // sigh... let's make a real work around then
           range.insertNode($('<canvas />').attr('id','tempCaretFinder')[0]);
       var p = $('canvas#tempCaretFinder').offset();
       $('canvas#tempCaretFinder').remove();
           top = p.top;

       }

     // console.log(top-ce.top);
       console.log("Caret line: "+(Math.ceil((top-ce.top)/lineHeight)));
       save.modify('move','backward','character');
       /*
       range = s.getRangeAt(0);
      range.insertNode($('<canvas />').attr('id','tempCaretFinder')[0]);
       var p = $('canvas#tempCaretFinder').position();
       $('canvas#tempCaretFinder').remove();
       console.log("Caret line: "+(Math.ceil((p.top-ce.top)/lineHeight)+1));
       console.log(e.which);

       switch(e.which){
           case 40:
             s.modify("move", "forward","line");

             break;  
                    case 38:
             s.modify("move", "backward","lineboundary");

             break;  
       }
       */


    }else if(document.selection) {
        // the IE way, which should be relatively easier. as TextRange objects return offsets directly.
        range = document.selection.createRange();  
    }

});

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...