高效地获取元素的可见区域坐标 [英] Efficiently get an element's visible area coordinates

查看:101
本文介绍了高效地获取元素的可见区域坐标的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

StackOverflow是加载 问题,但他们都寻求布尔答案。

  function getVisibleAreas(e){
..我有兴趣获取元素的实际可见区域。 。
return rectangleSet;
}

更正式地说 - 可见区域元素是CSS坐标中的一组(最好是非重叠的)矩形,其中 elementFromPoint set。

在所有DOM元素(包括iframe)上调用此函数的结果应该是一组非重叠区域集合,union是整个视口区域。 / p>

我的目标是创建某种视口转储数据结构,它可以高效地返回视口中给定点的单个元素,反之亦然 - 用于转储中的给定元素,它将返回可见区域的集合。
(数据结构将传递给远程客户端应用程序,所以当我需要查询视口结构时,我不一定有权访问实际文档)。



实现要求:

  • 显然,实现应该考虑元素的隐藏状态, z-index
  • 我正在寻找一种适用于所有常用浏览器的实现,尤其是移动设备 - Android的Chrome和iOS的Safari。
  • 最好不使用外部库。



    当然,我可以很幼稚,并且调用 elementFromPoint 对于视口中的每个离散点,但是性能是至关重要的,因为我遍历了所有元素,并且会经常这样做。



    请指定我为如何才能实现这一目标。



    免责声明:我非常喜欢网页编程概念,所以我可能会使用错误的技术术语。

    进度:

    我想出了一个实现。该算法非常简单:


    1. 遍历所有元素,并将它们的垂直/水平线添加到坐标图(如果坐标在视口)。
    2. 为每个矩形中心位置调用document.elementFromPoint。矩形是步骤1中地图中两个连续垂直坐标和两个连续水平坐标之间的区域。

    这产生一组区域/矩形,每个指向一个单一的元素。



    我执行的问题是:


    1. 对于复杂的页面来说效率不高(可能需要2-4分钟才能完成大屏幕和Gmail收件箱)。
    2. 每个元素都会产生大量的矩形,这使得通过网络进行字符串化和发送时效率低下,而且使用起来也不方便(我想最终以一组每个元素可能的矩形)。

    尽我所知, 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 var e = all [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 var part(b = 1; i

    ) = 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()]);
    };
    });

    jsfiddle http://jsfiddle.net/guest271314/ueum30g5/



    请参阅 Element.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>< /脚本>


    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.

    function getVisibleAreas(e) {
        ...
        return rectangleSet;
    }
    

    Putting it more formally - the visible areas of elements is the set of (preferably non-overlapping) rectangles in CSS coordinates for which elementFromPoint(x, y) will return the element if the point (x, y) is contained in (at least) one of the rectangles in the set.

    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:

  • Obviously, the implementation should consider element's hidden state, z-index, header & footer etc.
  • I am looking for an implementation that works in all common used browsers, especially mobile - Android's Chrome and iOS's Safari.
  • Preferably doesn't use external libraries.

    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:

    1. Iterate over all elements, and add their vertical / horizontal lines to a coordinates map (if the coordinate is within the viewport).
    2. 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:

    1. It is inefficient for complicated pages (can take up to 2-4 minutes for a really big screen and gmail inbox).
    2. 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屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆