如何获取 ContentEditable 区域中的行数和当前插入符号行位置?
Posted
技术标签:
【中文标题】如何获取 ContentEditable 区域中的行数和当前插入符号行位置?【英文标题】:How to get number of rows in ContentEditable area and current caret line position? 【发布时间】:2011-07-28 12:47:08 【问题描述】:我的问题有两个部分,但它们是相关的。
首先 - 我有包含一些文本的 Contenteditable DIV,我需要获取此 DIV 的总行数(行数)。有可能吗?
其次 - 我需要获得插入符号行的位置,如果它在第 1、2、3 等行上......
有人可以帮忙吗?
【问题讨论】:
【参考方案1】:直接的答案是没有任何方法可以真正得到这些数字。但是,您可以将许多不同的变通方法应用于 estimate(我使用 estimate,因为我认为它们不能 100% 准确)那些价值观。
回答您的第一个问题,如何获取元素中的行数。假设元素使用一致的line-height
,您可以通过将元素height
除以line-height
来找到行数。如果您的元素带有margin
s、padding
s 或在元素内区分line-height
s,这当然会变得更加复杂。如果这还不够,line-height
属性不一定必须是数值,即它可以是 normal
或 % 等。关于line-height
属性和如何在 SO 上获得它的数字表示,所以我不会对此进行太多详细介绍。为了我的回答,我确实在元素中专门分配了一个公共line-height
。
您的问题中更成问题的部分是在元素中找到插入符号的位置。同样,没有一种方法可以只为您返回正确的答案。相反,估计插入符号行位置的一种方法是在当前插入符号位置 (selection
) 创建一个 range
,在此处插入一个虚拟节点,获取节点 offset
相对于容器offset
,根据它的top offset
计算行数,然后去掉dummy元素。
当您不能hide()
或将其留空时,虚拟元素会出现其他问题。
这似乎是一种非常糟糕的做法,而且确实如此。尝试使用箭头导航时,它当然不能完美地工作。那么为什么不将getClientRects()
用于selection
呢?当它只是没有任何空格等的文本时,它可以正常工作,但是当你在那里扔几个<br /><br />
时,它不会再返回你那里的行位置。此外,当您位于一行的第一个或最后一个字符时,它有时会给出不正确的行行,因为它会根据可用空间量向上或向下抛出元素。
如果您正在寻找一种万无一失的方法来查找插入符号的位置和行数,那么没有。如果粗略估计就足够了,那么我的解决方案就是一个好的开始(它当然可以做更多的工作,并且对 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() 获取范围。为了使它们更可行,我使选择扩展了一个字符,然后创建范围,然后检查 Rect 位置,然后将插入符号移回一个字符。
为什么?
因为在一行的第一个字符上的插入符号,它的顶部参数与前一行处于同一级别,因此通过对其应用额外的字符,它可以检查它是否实际上是一个新行。 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();
);
【讨论】:
@Niklas 谢谢你的回答,我可以接受你在这里写的“相当不错的估计”。但是,您的示例中有一个小错误,我真的找不到问题所在。当您单击某行中间的某个位置时,似乎 contenteditable 元素会记住此水平位置,而当您使用箭头上下移动时,插入符号会改变每行的水平位置,这有点令人沮丧。使用向上和向下箭头键时的正常行为是,插入符号只是向上或向下跳到当前行,但不会改变其水平位置。 @Frodik 我也注意到了这一点(尽管仅在 firefox 上),我相信这是创建一个元素并将其注入其中,然后将其删除的结果。我尝试将解决方案更改为使用 getClientRects(),但它的问题比这个解决方案还要多。我确切地知道您遇到的问题,但很遗憾我没有找到解决方案。 @Niklas 我正在使用 Chrome,它的行为相同。如果有可能克服这个问题,您能否再次考虑一下您的解决方案。因为否则这个解决方案将毫无用处,普通用户肯定无法接受。 @Frodik 好的,我们开始吧。尝试 2:jsfiddle.net/niklasvh/mKQUH/129 用我的不同做法更新我的答案 @Niklas 感谢您的更新!虽然,我看到一些新的小错误。抱歉,如果我很挑剔,但如果您也可以摆脱这些,它将使您的解决方案完全可用。问题 #1 是当您在任何一行上点击“End”键时,插入符号不会跳到行尾,而是跳到下一行的开头。问题 #2 是插入符号水平放置的任何位置,当您使用向上或向下箭头时,它总是在行首跳转并且不保持水平位置。问题 #3 - 无法导航到最后一个字母...以上是关于如何获取 ContentEditable 区域中的行数和当前插入符号行位置?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 IE 的 contentEditable div 中获取选定的文本节点?
如何在 IE 的 contentEditable div 中获取选定的文本节点?
js之向div contenteditable光标位置添加字符