【发布时间】:2011-07-28 12:47:08
【问题描述】:
我的问题有两个部分,但它们是相关的。
首先 - 我有包含一些文本的 Contenteditable DIV,我需要获取此 DIV 的总行数(行数)。有可能吗?
其次 - 我需要获得插入符号行的位置,如果它在第 1、2、3 等行上......
有人可以帮忙吗?
【问题讨论】:
标签: html wysiwyg contenteditable caret
我的问题有两个部分,但它们是相关的。
首先 - 我有包含一些文本的 Contenteditable DIV,我需要获取此 DIV 的总行数(行数)。有可能吗?
其次 - 我需要获得插入符号行的位置,如果它在第 1、2、3 等行上......
有人可以帮忙吗?
【问题讨论】:
标签: html wysiwyg contenteditable caret
直接的答案是没有任何方法可以真正得到这些数字。但是,您可以将许多不同的变通方法应用于 estimate(我使用 estimate,因为我认为它们不能 100% 准确)那些价值观。
回答您的第一个问题,如何获取元素中的行数。假设元素使用一致的line-height,您可以通过将元素height 除以line-height 来找到行数。如果您的元素带有margins、paddings 或在元素内区分line-heights,这当然会变得更加复杂。如果这还不够,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();
}
});
【讨论】: