如何获取ContentEditable区域中的行数和当前插入符行位置?

发布于 2024-10-30 03:35:46 字数 153 浏览 1 评论 0原文

我的问题有两个部分,但它们是相关的。

首先 - 我有 Contenteditable DIV 和一些文本,我需要获取该 DIV 的总行数。是否可以 ?

其次 - 我需要获取插入符行位置,如果它位于第 1、2、3 行等...

任何人都可以帮忙吗?

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 ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

淡写薰衣草的香 2024-11-06 03:35:46

直接的答案是没有任何方法可以真正获得这些数字。但是,有许多不同的解决方法可以应用于估计(我使用估计,因为我认为它们不能 100% 准确)价值观。

要回答您的第一个问题,如何获取元素中的行数。假设元素使用一致的line-height,则可以通过将元素height除以line-height得出行数。当然,如果元素内有 marginpadding 或区分 line-height 的元素,这当然会变得更加复杂。如果这还不够严重,则 line-height 属性不一定必须是数值,即它可以是 normal 或 % 等。关于 line-height 属性以及如何在这里获取它的数字表示有很多问题,因此我不会对此进行过多的详细介绍。为了回答的其余部分,我专门在元素上分配了一个通用的 line-height

您的问题中更有问题的部分是找到元素内的插入符号位置。同样,没有一种方法可以只返回正确的答案。相反,您可以用来估计插入符号行位置的一种方法是在当前插入符号位置(选择)创建一个范围,插入一个虚拟节点,获取节点相对于容器offsetoffset,根据其top offset计算行数,然后删除虚拟元素。

当您无法hide()它或将其留空时,虚拟元素会出现其他问题。

这似乎是一种非常糟糕的做法,事实确实如此。当尝试使用箭头导航时,它肯定不能完美地工作。那么为什么不直接使用 getClientRects() 进行选择呢?当它只是没有任何空格等的文本时它工作得很好,但是当你立即在其中抛出一些
gt;
>
时,它不会返回给你线的位置已经在那里了。此外,当您位于一行的第一个或最后一个字符时,有时会给出不正确的行,因为它会根据可用空间量向上或向下抛出元素。

如果您正在寻找一种万无一失的方法来查找插入符位置和行数,那么没有。如果粗略估计就足够了,那么我的解决方案是一个好的开始(它当然需要更多的工作,并且支持 IE,在这种情况下应该容易得多,因为 TextRange 对象实际上给出了以像素为单位的偏移量)。

我对此的看法:

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();  
    }

});

示例: http://jsfiddle.net/niklasvh/mKQUH/

编辑:尝试2
http://jsfiddle.net/niklasvh/mKQUH/129/

如前所述,创建虚拟元素有时会使插入符号跳得到处都是,所以我再次尝试使用 getClientRects() 来获取范围。为了使它们更加可行,我将选择范围自身扩展了一个字符,然后创建范围,然后检查矩形位置,然后将插入符号向后移动一个字符。

为什么?

由于一行第一个字符上的插入符号的顶部参数与前一行处于同一级别,因此通过对其应用额外的字符,它可以检查它是否实际上是一个新行。 getClientRects() 的另一个问题是它在空换行符上不起作用。为了适应这一点,我使用之前创建的虚拟元素作为这些情况下的后备。

最终结果:

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();  
    }

});

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();  
    }

});
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文