高效获取元素的可见区域坐标
Efficiently get an element's visible area coordinates
StackOverflow加载了有关如何检查元素在视口中是否真的可见的问题,但它们都寻求布尔答案。我有兴趣获取元素可见的实际区域。
function getVisibleAreas(e) {
...
return rectangleSet;
}
更正式地说 - 元素的可见区域是CSS坐标中的一组(最好是非重叠的)矩形,如果点(x,y)包含在(至少)集合中的一个矩形中,elementFromPoint(x, y)
将返回元素。
在所有 DOM 元素(包括 iframe)上调用此函数的结果应该是一组非重叠区域集,其联合是整个视口区域。
我的目标是创建某种视口"转储"数据结构,它可以有效地返回视口中给定点的单个元素,反之亦然 - 对于转储中的给定元素,它将返回一组可见区域。(数据结构将传递给远程客户端应用程序,因此当我需要查询视口结构时,我不一定有权访问实际文档)。
实施要求:
hidden
状态,z-index
,页眉和页脚等。当然,我可能很天真,为视口中的每个离散点调用elementFromPoint
,但性能至关重要,因为我会遍历所有元素,并且会经常这样做。
请指导我如何实现这一目标。
免责声明:我对Web编程概念非常陌生,所以我可能使用了错误的技术术语。
进展:
我想出了一个实现。算法非常简单:
- 遍历所有元素,并将其垂直/水平线添加到坐标图中(如果坐标在视口内)。
- 为每个"矩形"中心位置调用"document.elementFromPoint"。矩形是步骤 1 中地图中两个连续垂直坐标和两个连续水平坐标之间的区域。
这将生成一组区域/矩形,每个区域/矩形指向单个元素。
我的实现问题是:
- 对于复杂的页面来说,它效率低下(对于一个非常大的屏幕和 gmail 收件箱,最多可能需要 2-4 分钟)。
- 它为每个元素生成大量矩形,这使得通过网络串化和发送效率低下,并且使用起来也不方便(我希望最终得到一个每个元素具有尽可能少的矩形的集合)。
据我所知,elementFromPoint
调用是花费大量时间并导致我的算法相对无用的调用......
谁能提出更好的方法?
这是我的实现:
function AreaPortion(l, t, r, b, currentDoc) {
if (!currentDoc) currentDoc = document;
this._x = l;
this._y = t;
this._r = r;
this._b = b;
this._w = r - l;
this._h = b - t;
center = this.getCenter();
this._elem = currentDoc.elementFromPoint(center[0], center[1]);
}
AreaPortion.prototype = {
getName: function() {
return "[x:" + this._x + ",y:" + this._y + ",w:" + this._w + ",h:" + this._h + "]";
},
getCenter: function() {
return [this._x + (this._w / 2), this._y + (this._h / 2)];
}
}
function getViewport() {
var viewPortWidth;
var viewPortHeight;
// IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document)
if (
typeof document.documentElement != 'undefined' &&
typeof document.documentElement.clientWidth != 'undefined' &&
document.documentElement.clientWidth != 0) {
viewPortWidth = document.documentElement.clientWidth,
viewPortHeight = document.documentElement.clientHeight
}
// the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight
else if (typeof window.innerWidth != 'undefined') {
viewPortWidth = window.innerWidth,
viewPortHeight = window.innerHeight
}
// older versions of IE
else {
viewPortWidth = document.getElementsByTagName('body')[0].clientWidth,
viewPortHeight = document.getElementsByTagName('body')[0].clientHeight
}
return [viewPortWidth, viewPortHeight];
}
function getLines() {
var onScreen = [];
var viewPort = getViewport();
// TODO: header & footer
var all = document.getElementsByTagName("*");
var vert = {};
var horz = {};
vert["0"] = 0;
vert["" + viewPort[1]] = viewPort[1];
horz["0"] = 0;
horz["" + viewPort[0]] = viewPort[0];
for (i = 0 ; i < all.length ; i++) {
var e = all[i];
// TODO: Get all client rectangles
var rect = e.getBoundingClientRect();
if (rect.width < 1 && rect.height < 1) continue;
var left = Math.floor(rect.left);
var top = Math.floor(rect.top);
var right = Math.floor(rect.right);
var bottom = Math.floor(rect.bottom);
if (top > 0 && top < viewPort[1]) {
vert["" + top] = top;
}
if (bottom > 0 && bottom < viewPort[1]) {
vert["" + bottom] = bottom;
}
if (right > 0 && right < viewPort[0]) {
horz["" + right] = right;
}
if (left > 0 && left < viewPort[0]) {
horz["" + left] = left;
}
}
hCoords = [];
vCoords = [];
//TODO:
for (var v in vert) {
vCoords.push(vert[v]);
}
for (var h in horz) {
hCoords.push(horz[h]);
}
return [hCoords, vCoords];
}
function getAreaPortions() {
var portions = {}
var lines = getLines();
var hCoords = lines[0];
var vCoords = lines[1];
for (i = 1 ; i < hCoords.length ; i++) {
for (j = 1 ; j < vCoords.length ; j++) {
var portion = new AreaPortion(hCoords[i - 1], vCoords[j - 1], hCoords[i], vCoords[j]);
portions[portion.getName()] = portion;
}
}
return portions;
}
尝试
var res = [];
$("body *").each(function (i, el) {
if ((el.getBoundingClientRect().bottom <= window.innerHeight
|| el.getBoundingClientRect().top <= window.innerHeight)
&& el.getBoundingClientRect().right <= window.innerWidth) {
res.push([el.tagName.toLowerCase(), el.getBoundingClientRect()]);
};
});
JSFIDDLE http://jsfiddle.net/guest271314/ueum30g5/
请参阅 Element.getBoundingClientRect()
$.each(new Array(180), function () {
$("body").append(
$("<img>"))
});
$.each(new Array(180), function () {
$("body").append(
$("<img>"))
});
var res = [];
$("body *").each(function (i, el) {
if ((el.getBoundingClientRect().bottom <= window.innerHeight || el.getBoundingClientRect().top <= window.innerHeight)
&& el.getBoundingClientRect().right <= window.innerWidth) {
res.push(
[el.tagName.toLowerCase(),
el.getBoundingClientRect()]);
$(el).css(
"outline", "0.15em solid red");
$("body").append(JSON.stringify(res, null, 4));
console.log(res)
};
});
body {
width : 1000px;
height : 1000px;
}
img {
width : 50px;
height : 50px;
background : navy;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
性能是否足够(尤其是在移动设备上),并且结果不是您要求的矩形集,但是您是否考虑过使用位图来存储结果?
请注意,某些元素可能具有3d css转换(例如倾斜,旋转),某些元素可能具有边框半径,某些元素可能具有不可见的背景 - 如果您想为"像素元素"功能包含这些功能,那么矩形集无法帮助您 - 但位图可以容纳所有视觉特征。
生成位图的解决方案相当简单(我想...未测试):
- 创建可见屏幕大小的画布。
- 递归遍历所有元素,按 z 顺序排序,忽略隐藏
- 对于画布中的每个元素绘制一个矩形,矩形的颜色是元素的标识符(例如,可以是增量计数器)。如果需要,您可以根据元素的视觉特征(倾斜、旋转、边框半径等)修改矩形。
- 将画布另存为无损格式,例如 PNG 而不是 JPG
- 将位图作为屏幕上元素的元数据发送
要查询哪个元素位于点 (x,y),您可以检查像素 (x,y) 处位图的颜色,颜色将告诉您元素是什么。
如果你能抛弃IE,这里有一个简单的:
function getElementVisibleRect(el) {
return new Promise((resolve, reject) => {
el.style.overflow = "hidden";
requestAnimationFrame((timeStamp) => {
var br = el.getBoundingClientRect();
el.style.overflow = "";
resolve(br);
});
});
}
即便如此,Promises也很容易填充,requestAnimationFrame()
可以追溯到IE 8。到2016年,你唯一应该费心给任何旧IE上的可怜灵魂的就是清晰的体验。
- onkeyup无法动态创建多个文本区域
- EaseJS拖放;放下(动画CC)电影剪辑的鼠标坐标
- FabricJs-限制主对象内添加对象的移动区域
- 如何在d3.js中返回路径的y坐标
- 在谷歌地图上获取事件的x,y坐标
- 将文本区域行/列转换为屏幕 x/y 坐标
- 从谷歌地图Api中的区域名称获取坐标
- 为坐标之间的区域添加背景色
- 如何计算坐标以将元素保留在用户屏幕的区域中
- 高效获取元素的可见区域坐标
- 如何使用google maps JavaScript API v3获取google地图中查看区域中心的坐标
- 使用javascript更改区域标记的坐标
- 谷歌地图区域坐标和多边形
- 获取键盘焦点在文本区域或输入上的坐标
- JavaScript-获取区域地图形状的坐标
- 计算坐标区域的黑色像素
- 使用JavaScript或Coldfusion绘制和填充基于4或5个坐标点的区域
- 将元素拖放到iframe中.可降区域坐标错误,碰撞错误
- 文本区域中特定行的垂直坐标
- 如何找到给定坐标的区域