简体   繁体   English

在视口中获取元素的最快方法

[英]Fastest way to get elements in viewport

I am dynamically creating a VERY large HTML file with as many elements as the browser on any given computer can feasibly generate.我正在动态创建一个非常大的 HTML 文件,其中包含任何给定计算机上的浏览器可以生成的尽可能多的元素。

I then need to, as the user scrolls, access the elements of a certain type (let's say div) that are actually within the viewport.然后,当用户滚动时,我需要访问实际上在视口内的某种类型(比如说 div)的元素。

The only way I know how to get a list of the elements visible in the viewport is to loop through all elements and then see if their bounds overlap with the current viewport.我知道如何获取视口中可见元素列表的唯一方法是遍历所有元素,然后查看它们的边界是否与当前视口重叠。 The problem with this is that there are so many elements in the document that this process can not complete quickly enough to let the browser scroll.这样做的问题是文档中的元素太多,这个过程无法快速完成,无法让浏览器滚动。

Is there a faster way to get all elements within the viewport?有没有更快的方法来获取视口中的所有元素?

Divide and conquer分而治之

You can divide the area of the window in smaller areas (FNO blocks).您可以将窗口区域划分为更小的区域(FNO 块)。 If the website is mainly vertical, you don't need more than one column (block width equals document width).如果网站主要是垂直的,则不需要多于一栏(块宽度等于文档宽度)。 After you populate the page, add each node and its descendants to the blocks in which they reside (they can be in more than one block).填充页面后,将每个节点及其后代添加到它们所在的块中(它们可以位于多个块中)。 On the scroll event handler you only need to check the nodes in the visible blocks.在滚动事件处理程序上,您只需要检查可见块中的节点。 If the height of each block equals the height of the viewport, you will only need to check up to two blocks.如果每个块的高度等于视口的高度,则您最多只需要检查两个块。 And no matter how big the page is, you always find the blocks as follows :而且无论页面有多大,您总会找到如下块

var from = Math.trunc( viewport.y / blockHeight ) ;
var to = Math.trunc( viewport.y2 / blockHeight ) ;

In the following example, I dynamically create 10,500 nodes;在以下示例中,我动态创建了 10,500 个节点; after that, you can scroll the page and the visible divs will be listed.之后,您可以滚动页面并列出可见的 div。 You can adapt the function called matches if you want to filter the nodes differently.如果您想以不同的方式过滤节点,您可以调整名为matches 的函数。 I tested this in my tablet with Chrome and 200,000 nodes, after the page was loaded, the scroll was smooth.我在我的平板电脑上用 Chrome 和 200,000 个节点测试了这个,页面加载后,滚动很流畅。 The performance may drop when you add more nodes than the computer/browser can handle, the algorithm works fine.当您添加的节点数量超过计算机/浏览器的处理能力时,性能可能会下降,该算法运行良好。 With the same test machine and 1,000,000 nodes the scrolling wasn't taking place in real time, but it wasn't that slow.使用相同的测试机器和 1,000,000 个节点,滚动不是实时发生的,但并没有那么慢。

Page loading will take time, please be patient.页面加载需要时间,请耐心等待。

 var blocks = [] ; var blockHeight ; var blocksNumber ; function isVisible( element, vp ) { /* This checks if the element is in the viewport area, you could also * check the display and visibility of its style. */ var rect = element.getBoundingClientRect( ) ; var x = rect.left ; var x2 = x + element.offsetWidth ; var y = rect.top ; var y2 = y + element.offsetHeight ; return !( x >= vp.w || y >= vp.h || x2 < 0 || y2 < 0 ) ; } function matches( element, vp ) { /* You can filter the elements even further */ return element && element.id && element.tagName === "DIV" && isVisible( element, vp ) ; } function viewport( ) { this.x = window.pageXOffset ; this.w = window.innerWidth ; this.x2 = this.x + this.w - 1 ; this.y = window.pageYOffset ; this.h = window.innerHeight ; this.y2 = this.y + this.h - 1 ; return this ; } function addMatch( element, array ) { /* An element may be in more than one block, so you need * to check if it wasn't already added. */ for( var i = 0 ; i < array.length ; ++i ) { if( array[i] === element ) return ; } array.push( element ) ; } function onWindowScroll( ) { var msg = document.getElementById( "msg" ) ; var str = "" ; var vp = new viewport( ) ; var from = Math.trunc( vp.y / blockHeight ) ; var to = Math.trunc( vp.y2 / blockHeight ) ; str += "Nodes: " + document.body.childNodes.length + ", blocks: " + blocks.length + ", searching blocks " + from + "-" + to + "<br>" + "Founded: " ; var array = [] ; for( var b = from ; b <= to ; ++b ) { var block = blocks[b] ; for( var i = 0 ; i < block.length ; ++i ) { if( matches( block[i], vp ) ) { addMatch( block[i], array ) ; } } } if( array.length ) { for( var i = 0 ; i < array.length-1 ; ++i ) { str += array[i].id + " " ; } str += array[array.length-1].id ; } else { str += "none" ; } msg.innerHTML = str ; } function onWindowLoad( ) { setTimeout( function( ) { var i = 0 ; /* Lets populate the page */ while( i < 10000 ) { var element = document.createElement( "DIV" ) ; element.className = "first" ; element.innerHTML = i ; element.id = i++ ; document.body.appendChild( element ) ; element = document.createElement( "SECOND" ) ; element.className = "second" ; element.innerHTML = i ; element.id = i++ ; document.body.appendChild( element ) ; } /* Lets add random positioned elements */ var i = 0 ; while( i < 500 ) { var x = Math.floor( Math.random( ) * document.body.offsetWidth ) ; var y = Math.floor( Math.random( ) * document.body.offsetHeight ) ; if( Math.random( ) < 0.5 ) { var element = document.createElement( "DIV" ) ; element.className = "absolute-first" ; } else { var element = document.createElement( "SECOND" ) ; element.className = "absolute-second" ; } element.style.left = x + "px" ; element.style.top = y + "px" ; element.id = "r" + i++ ; element.innerHTML = element.id ; document.body.appendChild( element ) ; } /* Now we create the blocks */ var nodes = document.body.childNodes ; blockHeight = window.innerHeight ; blocksNumber = Math.ceil( document.body.offsetHeight / blockHeight ) ; for( var b = 0 ; b < blocksNumber ; ++b ) { blocks[b] = new Array( ) ; } /* And we add all the nodes into they corresponding blocks */ for( var i = 0 ; i < nodes.length ; ++i ) { addElement( nodes[i] ) ; } addEventListener( "scroll", onWindowScroll, false ) ; onWindowScroll( ) ; // Initialize msg }, 20 ) ; } function addElement( element ) { /* This works fine if the rest of the nodes stayed in the * same position with the same size when element was added. */ if( !element.getBoundingClientRect ) return ; var rect = element.getBoundingClientRect( ) ; var y = rect.top + window.pageYOffset ; var y2 = y + element.offsetHeight ; var from = Math.trunc( y / blockHeight ) ; var to = Math.trunc( y2 / blockHeight ) ; for( var b = from ; b <= to ; ++b ) { blocks[b].push( element ) ; } var nodes = element.childNodes ; if( nodes ) { for( var i = 0 ; i < nodes.length ; ++i ) { addElement( nodes[i] ) ; } } } function removeElement( element ) { /* This works fine if the rest of the nodes stayed in the * same position with the same size when element was added. */ if( !element.getBoundingClientRect ) return ; var rect = element.getBoundingClientRect( ) ; var y = rect.top + window.pageYOffset ; var y2 = y + element.offsetHeight ; var from = Math.trunc( y / blockHeight ) ; var to = Math.trunc( y2 / blockHeight ) ; for( var b = from ; b <= to ; ++b ) { var i = blocks[b].indexOf( element ) ; if( i > -1 ) { blocks[b].splice( i, 1 ) ; } } } addEventListener( "load", onWindowLoad, false ) ;
 body { margin: 0 auto ; text-align: center ; font-family: sans-serif ; } /* Filtered in elements are light green */ .first { height: 50px ; line-height: 50px ; background-color: #cfc ; } /* Filtered out elements are light red */ .second { display: block ; height: 30px ; line-height: 30px ; background-color: #fcc ; box-sizing: border-box ; } /* Filtered in elements are light green */ .absolute-first { background-color: #cfc ; } /* Filtered out elements are light red */ .absolute-second { background-color: #fcc ; } .absolute-first, .absolute-second { position: absolute ; padding: 1pt 5pt 1pt 5pt ; } .first, .second, .absolute-first, .absolute-second { border: 1px solid #444 ; } #msg { position: fixed ; z-index: 1 ; top: 0 ; left: 0 ; width: 100% ; min-height: 24pt ; line-height: 24pt ; border: 1px solid #000 ; background-color: #ffd ; box-sizing: border-box ; font-size: 14pt ; vertical-align: middle ; text-align: left ; padding-left: 3pt ; opacity: 0.7 ; }
 <div id="a"> <div id="a1"> <div id="a11" class="first">a11</div> <div id="a12" class="first">a12</div> </div> <div id="a2" class="first">a2</div> </div> <msg id="msg">Loading page, please wait...</msg>

The only disadvantage is that you have to keep the nodes in their corresponding blocks.唯一的缺点是您必须将节点保留在相应的块中。 The blocks should be updated when you add a new node, when you remove an existing node or when a node is moved or resized.当您添加新节点、删除现有节点或移动或调整节点大小时,应更新块。 This should not be a problem as you said that you first populate the page.这应该不是问题,因为您说您首先填充页面。

The fixed positioned nodes are not taken into account.不考虑固定定位的节点。 It is easy to add support for them, but it would add nothing valuable to the example.添加对它们的支持很容易,但它不会为示例添加任何有价值的东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM