Javascript 从FF/Webkit中的像素位置创建折叠范围
使用JavaScript,我想从像素位置创建一个折叠的范围,以便在文档流中插入新节点,位于该位置标识的范围之后 这可以通过Internet Exporer中的TextRange对象(moveToPoint(x,y)方法)完成 如何在FireFox和Webkit中做到这一点?Javascript 从FF/Webkit中的像素位置创建折叠范围,javascript,range,position,Javascript,Range,Position,使用JavaScript,我想从像素位置创建一个折叠的范围,以便在文档流中插入新节点,位于该位置标识的范围之后 这可以通过Internet Exporer中的TextRange对象(moveToPoint(x,y)方法)完成 如何在FireFox和Webkit中做到这一点? 我可以从document.elementFromPoint(x,y)的位置获取容器元素。但是,当位置恰好位于文本节点内时,如何获取有关构建范围所需的文本偏移量的更多信息?以下是我从像素位置获取文本节点内字符位置的调查结果:
我可以从document.elementFromPoint(x,y)的位置获取容器元素。但是,当位置恰好位于文本节点内时,如何获取有关构建范围所需的文本偏移量的更多信息?以下是我从像素位置获取文本节点内字符位置的调查结果:
- 标准化方法:使用document.caretRangeFromPoint(x,y)从位置获取范围 . 这正是我想要的。问题在于,截至本文撰写之日(2010年7月),Chrome是唯一一款实现这种方法的web浏览器
- 带有专有textRange.moveToPoint(x,y)的MS IE方式
- Firefox方法:如果像素位置(x,y)是从鼠标事件中获取的,那么Firefox将向事件对象添加两个有用的属性:rangParent和rangeOffset
- 对于Safari&Opera(实际上是唯一的跨浏览器方法)来说,是重新构造文本节点的包含框,然后使用包含框内的像素位置推断字符位置。为此,您必须:
- 将所有文本节点包装到元素中(标注信息仅适用于元素,不适用于文本节点)
- 调用span.getClientRects()获取每个textNode的包含框(包装成一个文本)。如果文本节点跨越多行,您将得到多个框
- 找到包含(x,y)像素位置的框,并根据总像素宽度和文本长度,使用简单的“三个规则”推断字符位置
- 在MSIE下,您写道:
var range = document.selection.createRange();
range.moveToPoint(x, y);
对于其他浏览器,其想法是确定x/y位置的HTML元素,并在其上创建一个单字符选择。根据range.getBoundingClientRect()
,您可以确定单字符选择是在x/y位置之前还是之后。然后,我们可以选择下一个字符,直到选择位置到达x/y位置。我为Firefox、Safari和Chrome编写了以下实现:
var nodeInfo = getSelectionNodeInfo(x, y);
var range = document.createRange();
range.setStart(nodeInfo.node, nodeInfo.offsetInsideNode);
range.setEnd(nodeInfo.node, nodeInfo.offsetInsideNode);
/**
Emulates MSIE function range.moveToPoint(x,y) b
returning the selection node info corresponding
to the given x/y location.
@param x the point X coordinate
@param y the point Y coordinate
@return the node and offset in characters as
{node,offsetInsideNode} (e.g. can be passed to range.setStart)
*/
function getSelectionNodeInfo(x, y) {
var startRange = document.createRange();
window.getSelection().removeAllRanges();
window.getSelection().addRange(startRange);
// Implementation note: range.setStart offset is
// counted in number of child elements if any or
// in characters if there is no childs. Since we
// want to compute in number of chars, we need to
// get the node which has no child.
var elem = document.elementFromPoint(x, y);
var startNode = (elem.childNodes.length>0?elem.childNodes[0]:elem);
var startCharIndexCharacter = -1;
do {
startCharIndexCharacter++;
startRange.setStart(startNode, startCharIndexCharacter);
startRange.setEnd(startNode, startCharIndexCharacter+1);
var rangeRect = startRange.getBoundingClientRect();
} while (rangeRect.left<x && startCharIndexCharacter<startNode.length-1);
return {node:startNode, offsetInsideNode:startCharIndexCharacter};
}
var nodeInfo=getSelectionNodeInfo(x,y);
var range=document.createRange();
range.setStart(nodeInfo.node、nodeInfo.offsetInsideNode);
range.setEnd(nodeInfo.node,nodeInfo.offsetInsideNode);
/**
模拟MSIE函数范围。移动点(x,y)b
返回相应的选择节点信息
到给定的x/y位置。
@参数x点x坐标
@参数y点y坐标
@以字符形式返回节点和偏移量,如下所示
{node,offsetInsideNode}(例如,可以传递到range.setStart)
*/
函数getSelectionNodeInfo(x,y){
var startRange=document.createRange();
getSelection().removeAllRanges();
window.getSelection().addRange(startRange);
//实施说明:range.setStart偏移量为
//计入子元素的数量(如果有或
//如果没有孩子,在角色中。因为我们
//要计算字符数,我们需要
//获取没有子节点的节点。
var elem=document.elementFromPoint(x,y);
var startNode=(elem.childNodes.length>0?elem.childNodes[0]:elem);
var startCharIndexCharacter=-1;
做{
startCharIndexCharacter++;
setStart(startNode,startCharIndexCharacter);
startRange.setEnd(startNode,startCharIndexCharacter+1);
var rangeRect=startRange.getBoundingClientRect();
}while(rangeRect.left这是我为旧浏览器实现的caretRangeFromPoint
:
if (!document.caretRangeFromPoint) {
document.caretRangeFromPoint = function(x, y) {
var log = "";
function inRect(x, y, rect) {
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
}
function inObject(x, y, object) {
var rects = object.getClientRects();
for (var i = rects.length; i--;)
if (inRect(x, y, rects[i]))
return true;
return false;
}
function getTextNodes(node, x, y) {
if (!inObject(x, y, node))
return [];
var result = [];
node = node.firstChild;
while (node) {
if (node.nodeType == 3)
result.push(node);
if (node.nodeType == 1)
result = result.concat(getTextNodes(node, x, y));
node = node.nextSibling;
}
return result;
}
var element = document.elementFromPoint(x, y);
var nodes = getTextNodes(element, x, y);
if (!nodes.length)
return null;
var node = nodes[0];
var range = document.createRange();
range.setStart(node, 0);
range.setEnd(node, 1);
for (var i = nodes.length; i--;) {
var node = nodes[i],
text = node.nodeValue;
range = document.createRange();
range.setStart(node, 0);
range.setEnd(node, text.length);
if (!inObject(x, y, range))
continue;
for (var j = text.length; j--;) {
if (text.charCodeAt(j) <= 32)
continue;
range = document.createRange();
range.setStart(node, j);
range.setEnd(node, j + 1);
if (inObject(x, y, range)) {
range.setEnd(node, j);
return range;
}
}
}
return range;
};
}
if(!document.caretRangeFromPoint){
document.caretRangeFromPoint=函数(x,y){
var log=“”;
函数inRect(x,y,rect){
return x>=rect.left&&x=rect.top&&y是上面Julien答案的扩展。这可以处理多行。需要稍微调整一下,但似乎有效。它通过获取从开始到结束的选择的高度和单个字母选择的高度,除以两个字母并取整来查找行数。可能存在以下情况:在这里这不起作用,但在大多数情况下
function getLineCount(node, range) {
if ((node) && (range)) {
range.setStart(node, 0);
range.setEnd(node, 1);
var r = range.getBoundingClientRect();
var h1 = r.bottom - r.top;
range.setEnd(node, node.length);
r = range.getBoundingClientRect();
return Math.round((r.bottom - r.top) / h1);
}
};
这是上面代码的一个调整版本,使用上面的行计数例程。它还可以更好地处理节点内的选择,但位于实际文本的右侧。所有这些都没有优化,但我们在用户时间内,因此毫秒可能不太重要
function getSelectionNodeInfo(x, y) {
var startRange = document.createRange();
window.getSelection().removeAllRanges();
window.getSelection().addRange(startRange);
// Implementation note: range.setStart offset is
// counted in number of child elements if any or
// in characters if there is no childs. Since we
// want to compute in number of chars, we need to
// get the node which has no child.
var elem = document.elementFromPoint(x, y);
console.log("ElementFromPoint: " + $(elem).attr('class'));
var startNode = (elem.childNodes.length>0?elem.childNodes[0]:elem);
var lines = getLineCount(startNode, startRange);
console.log("Lines: " + lines);
var startCharIndexCharacter = 0;
startRange.setStart(startNode, 0);
startRange.setEnd(startNode, 1);
var letterCount = startNode.length;
var rangeRect = startRange.getBoundingClientRect();
var rangeWidth = 0
if (lines>1) {
while ((rangeRect.bottom < y) && (startCharIndexCharacter < (letterCount-1))) {
startCharIndexCharacter++;
startRange.setStart(startNode, startCharIndexCharacter);
startRange.setEnd(startNode, startCharIndexCharacter + 1);
rangeRect = startRange.getBoundingClientRect();
rangeWidth = rangeRect.right - rangeRect.left
}
}
while (rangeRect.left < (x-(rangeWidth/2)) && (startCharIndexCharacter < (letterCount))) {
startCharIndexCharacter++;
startRange.setStart(startNode, startCharIndexCharacter);
startRange.setEnd(startNode, startCharIndexCharacter + ((startCharIndexCharacter<letterCount) ? 1 : 0));
rangeRect = startRange.getBoundingClientRect();
rangeWidth = rangeRect.right - rangeRect.left
}
return {node:startNode, offsetInsideNode:startCharIndexCharacter};
}
函数getSelectionNodeInfo(x,y){
var startRange=document.createRange();
getSelection().removeAllRanges();
window.getSelection().addRange(startRange);
//实施说明:range.setStart偏移量为
//计入子元素的数量(如果有或
//如果没有孩子,在角色中。因为我们
//要计算字符数,我们需要
//获取没有子节点的节点。
var elem=document.elementFromPoint(x,y);
log(“ElementFromPoint:”+$(elem.attr('class'));
var startNode=(elem.childNodes.length>0?elem.childNodes[0]:elem);
变量行=getLineCount(startNode,startRange);
控制台日志(“行:+行);
var startCharIndexCharacter=0;
startRange.setStart(startNode,0);
startRange.setEnd(startNode,1);
var letterCount=起始节点长度;
var rangeRect=startRange.getBoundingClientRect();
var rangeWidth=0
如果(行数>1){
而((rangeRect.bottomtextRange.select();
var range = window.getSelection().getRangeAt(0);
function createCollapsedRangeFromPoint(x, y) {
var doc = document;
var position, range = null;
if (typeof doc.caretPositionFromPoint != "undefined") {
position = doc.caretPositionFromPoint(x, y);
range = doc.createRange();
range.setStart(position.offsetNode, position.offset);
range.collapse(true);
} else if (typeof doc.caretRangeFromPoint != "undefined") {
range = doc.caretRangeFromPoint(x, y);
} else if (typeof doc.body.createTextRange != "undefined") {
range = doc.body.createTextRange();
range.moveToPoint(x, y);
}
return range;
}