先看dom标签接口分为两层, 外层box容器用来做滚动容器, 内层hold容器撑开外层box使其产生滚动条, 内层view容器用来展示内容
<body>
<div id="box">
<div id="hold"></div>
<div id="view">
</div>
</div>
</body>
样式无非就是调整定位
body,html {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#view{
width: 100%;
}
#view>div:hover {
background: rgb(240, 240, 240);
}
#box {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
height: 500px;
width: 200px;
border: 1px solid black;
overflow: auto;
}
#box>div {
font-size: 14px;
cursor: pointer;
user-select: none;
}
/* 以上单纯用来装饰使用的样式 */
#box {
/* 外层容器需要使用定位属性配合内部的 absolute实现定位*/
/* 超出后出现滚动条 */
position: absolute;
overflow: auto;
}
#view {
/* 定位属性通过js一直控制view容器展示在页面上 */
position: absolute;
top: 0;
}
#view>div {
/* 行高要统一才能计算准确 */
height: 20px;
line-height: 20px;
}
大致的逻辑伪代码如下 真实的计算逻辑就不写了因为懒
// 基础行高
const lineHeight = 20
// 基础数据
const data = new Array(10000000).fill('').map((a, i) => i)
// 整体的容器
const box = document.querySelector('#box')
// 实际展示的容器
const view = document.querySelector('#view')
// 用来撑开容器的dom
const hold = document.querySelector('#hold')
// 根据数据量*行高计算高度进行填充
hold.style.height = lineHeight * data.length + 'px'
const render = (e) => {
// 初始化高度
let top = 0
try {
// 直接读取高度报错就为0
top = e.target.scrollTop
} catch {}
// 设置页面容器的顶部定位
view.style.top = top + 'px'
// 获取数据位置的起点
const start = (top / lineHeight).toFixed(0)
// 获取页面展示的内容数量
const n = box.clientHeight / lineHeight
// 提取数据
const viewData = data.slice(start, start * 1 + n)
// 渲染
view.innerHTML = viewData.map(v => `<div>${v}</div>`).join('')
}
box.addEventListener('scroll', render)
render()