我正在动态地创建一个非常大的HTML文件,其中包含尽可能多的元素,因为任何给定计算机上的浏览器都可以生成。在视口中获取元素的最快方法
然后我需要,当用户滚动,访问某一类型的元素(假设DIV),其实际上是在视口内。
我知道如何在视口中可见的元素列表的唯一方法是通过所有的元素循环,然后看看他们的范围与当前视图重叠。问题在于文档中有太多元素,这个过程不能足够快地完成以使浏览器滚动。
有更快的方式来获取视口内的所有元素吗?
我正在动态地创建一个非常大的HTML文件,其中包含尽可能多的元素,因为任何给定计算机上的浏览器都可以生成。在视口中获取元素的最快方法
然后我需要,当用户滚动,访问某一类型的元素(假设DIV),其实际上是在视口内。
我知道如何在视口中可见的元素列表的唯一方法是通过所有的元素循环,然后看看他们的范围与当前视图重叠。问题在于文档中有太多元素,这个过程不能足够快地完成以使浏览器滚动。
有更快的方式来获取视口内的所有元素吗?
分而治之
您可以将在较小的区域(FNO块)的窗口区域。如果网站主要是垂直的,则不需要多个列(块宽等于文档宽度)。填充页面后,将每个节点及其后代添加到它们所在的块(它们可以位于多个块中)。在滚动事件处理程序中,您只需检查可见块中的节点。如果每个块的高度等于视口的高度,则只需要检查两个块。 而不管页面有多大,你总能找到块如下:
var from = Math.trunc(viewport.y/blockHeight) ;
var to = Math.trunc(viewport.y2/blockHeight) ;
在下面的例子中,我dinamically创造10500个节点,在这之后,你可以滚动页面和可见的div会被列出。如果您想以不同方式过滤节点,则可以调整称为匹配的函数。我使用Chrome和200,000个节点在我的平板电脑上测试了这一点,在页面加载后,滚动非常顺畅。当您添加更多的节点时,性能可能会下降,而不是计算机/浏览器可以处理的,算法运行正常。使用相同的测试机器和1,000,000个节点,滚动不是实时进行,但速度并不慢。
页面装载需要时间,请耐心等待。
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>
唯一的缺点是,你必须保持节点及其对应的块。添加新节点,删除现有节点或节点移动或调整大小时应更新块。这应该不会成为问题,因为你说你先填充页面。
固定位置的节点没有考虑在内。很容易为它们添加支持,但它不会为示例增添任何价值。
所有这些元素在列表视图? – Wolfgang
查看此答案 - http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport – marmeladze
如果您正在生成家长节点可以让一个监听者跟踪哪个父母在视口中(比如http://imakewebthings.com/waypoints/),然后运行一个可见性检查(就像一个@marmeladze链接到的)有限的给该父母的子女 –