【发布时间】:2015-01-24 14:51:03
【问题描述】:
StackOverflow 是 loaded with questions 关于如何检查一个元素在视口中是否真的可见,但他们都在寻找一个 boolean 答案。我有兴趣获取元素的实际可见区域。
function getVisibleAreas(e) {
...
return rectangleSet;
}
更正式地说 - 元素的可见区域 是 CSS 坐标中的一组(最好是不重叠的)矩形,elementFromPoint(x, y) 将返回元素,如果点 (x, y ) 包含在(至少)集合中的一个矩形中。
在所有 DOM 元素(包括 iframe)上调用此函数的结果应该是一组不重叠的区域集,其中 union 是整个视口区域。
我的目标是创建某种视口“转储”数据结构,它可以有效地返回视口中给定点的单个元素,反之亦然 - 对于转储中的给定元素,它将返回一组可见区域。 (数据结构将被传递给远程客户端应用程序,因此当我需要查询视口结构时,我不一定可以访问实际文档。
实施要求:
hidden状态、z-index、页眉和页脚等。
当然,我可能会天真并为视口中的每个离散点调用elementFromPoint,但性能至关重要,因为我会迭代所有元素,并且会经常这样做。
请指导我如何实现这一目标。
免责声明:我对网络编程概念非常陌生,所以我可能使用了错误的技术术语。
进展:
我想出了一个实现。算法很简单:
- 遍历所有元素,并将它们的垂直/水平线添加到坐标图(如果坐标在视口内)。
- 为每个“矩形”中心位置调用`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;
}
【问题讨论】:
-
如果你想知道一个元素在视口中的可见高度,可以回复this question。我不会将此标记为重复,因为您的要求可能不同。
-
@t.niese 如果我理解你的问题 - 你问计算和构建数据结构应该在服务器端还是客户端完成 - 答案是我不在乎,只要在线上传递的数据大小是相似的。结果应该是客户端上独立可用的数据以供以后使用。
-
@RoryMcCrossan - 投票赞成你的答案,它让我对
offset概念有了一些了解,但实际上,它不符合我的要求...... -
有没有办法查看
elementFromPoint的javaScript 实现?这对我来说是一个很好的起点。 -
也许你可以半天真地在 elementFromPoint() 扫描中跳跃 10px 而不是 1px。然后,仅当元素与之前的时间不同时,您才回溯(或转到 1px rez)。此外,getBoundingClientRect() 很昂贵,您可以通过在调用它之前使用 if(!e.scrollHeight || !e.scrollWidth) continue; 之类的方法来提前退出循环;
标签: javascript jquery html css visibility