草庐IT

javascript - 高效获取元素可见区域坐标

coder 2023-08-02 原文

StackOverflow 是 loaded with questions关于如何检查一个元素是否真的在视口(viewport)中可见,但他们都在寻找一个 bool 值答案。我对获取元素的实际可见区域很感兴趣。

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

更正式地说 - 元素的可见区域是 CSS 坐标中的一组(最好是不重叠的)矩形, elementFromPoint(x, y) 如果点 (x, y) 包含在(至少)集合中的一个矩形中,将返回该元素。

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

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

实现要求:

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

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

    请指导我如何实现这个目标。

    免责声明:我对网络编程概念一窍不通,所以我可能使用了错误的技术术语。

    进度:

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

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

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

    我的实现的问题是:

    1. 对于复杂的页面来说效率很低(对于真正的大屏幕和 gmail 收件箱可能需要 2-4 分钟)。
    2. 它会为每个元素生成大量矩形,这使得通过网络进行字符串化和发送时效率低下,而且使用起来也不方便(我希望最终得到一个具有尽可能少的集合每个元素尽可能长方形)。

    据我所知,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>

    关于javascript - 高效获取元素可见区域坐标,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27146403/

    有关javascript - 高效获取元素可见区域坐标的更多相关文章

    1. ruby - 即时确定方法的可见性 - 2

      我正在编写一个方法,它将在一个类中定义一个实例方法;类似于attr_accessor:classFoocustom_method(:foo)end我通过将custom_method函数添加到Module模块并使用define_method定义方法来实现它,效果很好。但我无法弄清楚如何考虑类(class)的可见性属性。例如,在下面的类中classFoocustom_method(:foo)privatecustom_method(:bar)end第一个生成的方法(foo)必须是公共(public)的,第二个(bar)必须是私有(private)的。我怎么做?或者,如何找到调用我的cust

    2. ruby - 简单获取法拉第超时 - 2

      有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

    3. ruby - 从 Ruby 中的主机名获取 IP 地址 - 2

      我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge

    4. ruby - 获取模块中定义的所有常量的值 - 2

      我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

    5. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

      我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

    6. Ruby 从大范围中获取第 n 个项目 - 2

      假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

    7. ruby - Net::HTTP 获取源代码和状态 - 2

      我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

    8. ruby - 在哈希的键数组中追加元素 - 2

      查看我的Ruby代码:h=Hash.new([])h[0]=:word1h[1]=h[1]输出是:Hash={0=>:word1,1=>[:word2,:word3],2=>[:word2,:word3]}我希望有Hash={0=>:word1,1=>[:word2],2=>[:word3]}为什么要附加第二个哈希元素(数组)?如何将新数组元素附加到第三个哈希元素? 最佳答案 如果您提供单个值作为Hash.new的参数(例如Hash.new([]),完全相同的对象将用作每个缺失键的默认值。这就是您所拥有的,那是你不想要的。您可以改用

    9. ruby - 没有类方法获取 Ruby 类名 - 2

      如何在Ruby中获取BasicObject实例的类名?例如,假设我有这个:classMyObjectSystem我怎样才能使这段代码成功?编辑:我发现Object的实例方法class被定义为returnrb_class_real(CLASS_OF(obj));。有什么方法可以从Ruby中使用它? 最佳答案 我花了一些时间研究irb并想出了这个:classBasicObjectdefclassklass=class这将为任何从BasicObject继承的对象提供一个#class您可以调用的方法。编辑评论中要求的进一步解释:假设你有对象

    10. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

      是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

    随机推荐