高效地获取元素的可见区域坐标 [英] Efficiently get an element's visible area coordinates
问题描述
StackOverflow是加载 与 问题,但他们都寻求布尔答案。
function getVisibleAreas(e){
..我有兴趣获取元素的实际可见区域。 。
return rectangleSet;
}
更正式地说 - 可见区域元素是CSS坐标中的一组(最好是非重叠的)矩形,其中 elementFromPoint set。 在所有DOM元素(包括iframe)上调用此函数的结果应该是一组非重叠区域集合,union是整个视口区域。 / p> 我的目标是创建某种视口转储数据结构,它可以高效地返回视口中给定点的单个元素,反之亦然 - 用于转储中的给定元素,它将返回可见区域的集合。 实现要求:
(数据结构将传递给远程客户端应用程序,所以当我需要查询视口结构时,我不一定有权访问实际文档)。
隐藏
状态, z-index $ c $标题&页脚等
当然,我可以很幼稚,并且调用 elementFromPoint
对于视口中的每个离散点,但是性能是至关重要的,因为我遍历了所有元素,并且会经常这样做。
请指定我为如何才能实现这一目标。
免责声明:我非常喜欢网页编程概念,所以我可能会使用错误的技术术语。
进度: 我想出了一个实现。该算法非常简单: 这产生一组区域/矩形,每个指向一个单一的元素。 我执行的问题是: 尽我所知, 任何人都可以提出一个更好的方法吗? 是我的实现: 尝试 jsfiddle http://jsfiddle.net/guest271314/ueum30g5/ 请参阅 Element.getBoundingClientRect() StackOverflow is loaded with questions about how to check if an element is really visible in the viewport, but they all seek for a boolean answer. I'm interested in getting the element's actual areas that are visible. Putting it more formally - the visible areas of elements is the set of (preferably non-overlapping) rectangles in CSS coordinates for which The outcome of calling this function on all DOM elements (including iframes) should be a set of non-overlapping area sets which union is the entire viewport area. My goal is to create some kind of a viewport "dump" data structure, which can efficiently return a single element for a given point in the viewport, and vice versa - for a given element in the dump, it will return the set of visible areas.
(The data structure will be passed to a remote client application, so I will not necessarily have access to the actual document when I need to query the viewport structure). Implementation requirements:
elementFromPoint
调用需要很多时间和导致我的算法是相对无用的...
$ b $ pre $ 函数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)] ;
}
}
函数getViewport(){
var viewPortWidth;
var viewPortHeight;
//标准兼容模式下的IE6(例如文档中的第一行使用有效的doctype)
if(
typeof document.documentElement!='undefined'& &
typeof document.documentElement.clientWidth!='undefined'&&
document.documentElement.clientWidth!= 0){
viewPortWidth = document.documentElement.clientWidth,
viewPortHeight = document.documentElement.clientHeight
}
//更符合标准的浏览器(mozilla / netscape / opera / IE7)使用window.innerWidth和window.innerHeight
else if(typeof window.innerWidth!='undefined'){
viewPortWidth = window.innerWidth,
viewPortHeight = window.innerHeight
}
//旧版本IE
else {
viewPortWidth = document.getElementsByTagName('body')[0] .clientWidth,
viewPortHeight = document.getElementsB yTagName('body')[0] .clientHeight
}
return [viewPortWidth,viewPortHeight];
}
函数getLines(){
var onScreen = [];
var viewPort = getViewport();
// TODO:header&页脚
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
// TODO:获取所有客户端矩形
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; (bottom> 0&& bottom< viewPort [1]){
vert [+ bottom] = bottom;
}
if
}
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]);
}
为(var h in horz){
hCoords.push(horz [h]);
}
return [hCoords,vCoords];
}
function getAreaPortions(){
var parts = {}
var lines = getLines();
var hCoords = lines [0];
var vCoords = lines [1]; (j = 1; j
) = new AreaPortion(hCoords [i-1],vCoords [j-1],hCoords [i],vCoords [j]);
个部分[part.getName()] =部分;
}
}
返回部分;
var res = []; ((el,getBoundingClientRect()。bottom <= window.innerHeight
|| el.getBoundingClientRect() ().top< = window.innerHeight)
&& el.getBoundingClientRect()。right< = window.innerWidth){
res.push([el.tagName.toLowerCase() ,el.getBoundingClientRect()]);
};
});
($ 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( 大纲,0.15em纯红色); $(body)。append(JSON.stringify(res,null,4)); console.log(res)};});
body {width:1000px; height:1000px;} img {width:50px; height:50px;背景:navy;}
< script src =https ://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js>< /脚本>
function getVisibleAreas(e) {
...
return rectangleSet;
}
elementFromPoint(x, y)
will return the element if the point (x, y) is contained in (at least) one of the rectangles in the set.hidden
state, z-index
, header & footer etc.
Of course, I could be naïve and call elementFromPoint
for every discrete point in the viewport, But performance is crucial since I iterate over all of the elements, and will do it quite often.
Please direct me as to how I can achieve this goal.
Disclaimer: I'm pretty noob to web programming concepts, so I might have used wrong technical terms.
Progress:
I came up with an implementation. The algorithm is pretty simple:
- Iterate over all elements, and add their vertical / horizontal lines to a coordinates map (if the coordinate is within the viewport).
- Call `document.elementFromPoint` for each "rectangle" center position. A rectangle is an area between two consecutive vertical and two consecutive horizontal coordinates in the map from step 1.
This produces a set of areas / rectangles, each pointing to a single element.
The problems with my implementation are:
- It is inefficient for complicated pages (can take up to 2-4 minutes for a really big screen and gmail inbox).
- It produces a large amount of rectangles per a single element, which makes it inefficient to stringify and send over a network, and also inconvenient to work with (I would want to end up with a set with as few rectangles as possible per element).
As much as I can tell, the elementFromPoint
call is the one that takes a lot of time and causes my algorithm to be relatively useless...
Can anyone suggest a better approach?
Here is my implementation:
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;
}
Try
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/
See 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>
这篇关于高效地获取元素的可见区域坐标的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!