<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } ul { height: 600px; overflow: auto; } li { height: 60px; } </style> </head> <body> <ul> <div class="wrap"></div> </ul> </body> <script> let arr = [...new Array(100).keys()]; const wrap = document.querySelector(".wrap"); const ul = document.querySelector("ul"); const li = document.createElement("li"); ul.append(li); // 获取 li 高度 const liHeight = li.clientHeight; // 根据数组长度,手动设置wrap高度,通过动态修改wrap的paddingTop实现虚拟滚动 wrap.style.height = arr.length * liHeight + "px"; // 一屏展示多少个li 向上取整,防止出现比如一屏展示5.5个li,用户手动滚动整屏时缺失第11个 const screenLiIndex = Math.ceil(ul.clientHeight / liHeight); const vDom = () => { // 比如一屏展示10个 start 取前10个 end 取后20个 paddingTop 设置为第11个的位置 // scrollListLength代表 paddingTop替代的li的长度 scrollListLength = Math.round(ul.scrollTop / liHeight); let start = scrollListLength > screenLiIndex ? scrollListLength - screenLiIndex : 0; let end = scrollListLength > arr.length - screenLiIndex * 2 ? arr.length : scrollListLength + screenLiIndex * 2; wrap.innerHTML = ""; for (let i = start; i < end; i++) { wrap.innerHTML += `<li>${arr[i]}</li>`; } wrap.style.paddingTop = start * liHeight + "px"; }; vDom(); let touchStatus = "end"; // touchStart时的位置,通过和touchEnd的位置比较,执行渲染,防止出现点击就渲染 let touchStartY; let touchEndY; ul.ontouchstart = (e) => { touchStartY = e.changedTouches[0].screenY; touchStatus = "start"; }; ul.ontouchend = (e) => { touchStatus = "end"; touchEndY = e.changedTouches[0].screenY; if (Math.abs(touchEndY - touchStartY) > liHeight / 2) { vDom(); } }; let scrollTimer; ul.onscroll = () => { if (touchStatus === "end") { clearTimeout(scrollTimer); scrollTimer = setTimeout(() => { vDom(); }, 50); } }; </script></html>