如何用CSS和JavaScript实现滚动列表的居中吸附效果
垂直滚动列表居中吸附解决方案
一、纯CSS修正方案
你之前的CSS方案未生效,核心问题是滚动容器(ul)缺少明确的高度和滚动属性——scroll-snap必须依赖有可滚动区域的容器才能正常工作。以下是修正后的完整代码:
修正后CSS代码
.wrapper { width: 100vw; height: 100vh; background-color: #e5ddde; display: flex; justify-content: center; overflow: hidden; /* 隐藏容器外溢出内容 */ } li { text-transform: uppercase; font-family: Futura; font-size: 40px; color: grey; scroll-snap-align: center; /* 核心吸附规则:项居中对齐容器 */ margin: 80px 0; /* 添加间距,确保居中视觉效果清晰 */ } li.active { color: black; } ul { list-style: none; padding: 0; margin: 0; height: 100%; /* 让容器高度匹配父级,形成滚动区域 */ overflow-y: auto; /* 开启垂直滚动 */ scroll-snap-type: y mandatory; /* 强制滚动停止时吸附到最近项 */ scroll-behavior: smooth; /* 可选:添加平滑滚动过渡 */ scrollbar-width: none; /* 隐藏滚动条(可选) */ } ul::-webkit-scrollbar { display: none; /* 适配webkit内核浏览器隐藏滚动条 */ }
关键修正点
- 给
ul设置height: 100%和overflow-y: auto,明确可滚动区域 - 给列表项添加上下间距,避免项与项过度紧凑,强化居中吸附的视觉感知
- 清除列表默认的内外边距,避免布局偏移
- 可选添加
scroll-behavior: smooth优化过渡体验
如果纯CSS方案仍存在边缘场景问题(如旧浏览器兼容性、自定义交互需求),可以用JavaScript实现更精准的控制。
二、JavaScript实现方案
通过监听滚动事件,计算最近列表项的位置,手动调整滚动位置实现居中吸附,同时支持自定义交互逻辑。
完整代码示例
HTML(保持不变)
<div class="wrapper"> <ul> <li>Name One</li> <li>Name Two</li> <li>Name Three</li> <li class="active">Name Four -----</li> <li>Name One</li> <li>Name Two</li> <li>Name Three</li> <li>Name Four</li> <li>Name One</li> <li>Name Two</li> <li>Name Three</li> <li>Name Four</li> <li>Name One</li> <li>Name Two</li> <li>Name Three</li> <li>Name Four</li> </ul> </div>
CSS(基础布局)
.wrapper { width: 100vw; height: 100vh; background-color: #e5ddde; display: flex; justify-content: center; overflow: hidden; } li { text-transform: uppercase; font-family: Futura; font-size: 40px; color: grey; margin: 80px 0; } li.active { color: black; } ul { list-style: none; padding: 0; margin: 0; height: 100%; overflow-y: auto; scroll-behavior: smooth; scrollbar-width: none; } ul::-webkit-scrollbar { display: none; }
JavaScript代码
const listContainer = document.querySelector('ul'); const listItems = Array.from(document.querySelectorAll('li')); let scrollTimeout; // 计算让最近项居中的目标滚动位置 function getTargetScrollTop() { const containerHeight = listContainer.clientHeight; const scrollTop = listContainer.scrollTop; // 遍历找到距离容器中心最近的列表项 const closestItem = listItems.reduce((prev, curr) => { const prevCenter = prev.offsetTop + prev.clientHeight / 2; const currCenter = curr.offsetTop + curr.clientHeight / 2; const containerCenter = scrollTop + containerHeight / 2; const prevDistance = Math.abs(prevCenter - containerCenter); const currDistance = Math.abs(currCenter - containerCenter); return currDistance < prevDistance ? curr : prev; }); // 计算让该项居中的滚动值 return closestItem.offsetTop - (containerHeight - closestItem.clientHeight) / 2; } // 滚动停止后触发吸附逻辑 listContainer.addEventListener('scroll', () => { clearTimeout(scrollTimeout); // 滚动停止150ms后执行吸附(可根据需求调整延迟时间) scrollTimeout = setTimeout(() => { const targetTop = getTargetScrollTop(); listContainer.scrollTo({ top: targetTop, behavior: 'smooth' }); // 更新active类标记当前居中项 listItems.forEach(item => item.classList.remove('active')); const activeItem = listItems.find(item => { const itemCenter = item.offsetTop + item.clientHeight / 2; const containerCenter = listContainer.scrollTop + listContainer.clientHeight / 2; return Math.abs(itemCenter - containerCenter) < item.clientHeight / 2; }); activeItem?.classList.add('active'); }, 150); }); // 页面加载时让默认active项居中 window.addEventListener('load', () => { listContainer.scrollTo({ top: getTargetScrollTop(), behavior: 'smooth' }); });
JS方案优势
- 可自定义吸附触发的延迟时间,适配不同交互场景
- 精准控制
active类的切换逻辑 - 兼容对
scroll-snap支持不佳的旧浏览器 - 可扩展更多自定义交互(如滚动时的项高亮动画)
内容的提问来源于stack exchange,提问作者anywhereseason
相关产品推荐
相关产品推荐

