如何实现无限滚动列表的半圆滚动视觉效果?
问题描述
我有一个列表,初始显示5个项,每次滚动时加载5个新项直到全部内容加载完成。想要实现圆形左半部分的滚动视觉效果,但因为无法控制最终列表项总数,所以打算模拟半圆滚动动画。目前代码存在两个问题:
- 滚动时应该显示更多列表项
- 上下半部分列表项使用相同计算逻辑,但上半部分位移距离比下半部分大,原因不明
核心需求:滚动时列表项的移动效果模拟处于圆形左半部分的状态。
原代码
HTML
<div class="items-selector"> <div class="list"> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> </div> </div>
CSS
.items-selector { overflow-x:hidden; overflow-y:auto; height:300px; width:350px; } .list { display:flex; flex-direction:column; gap: 48px; align-items:flex-end; } .item { width:50px; height:50px; background:#cecece; display:flex; align-items:center; justify-content:center; border-radius:100%; }
JavaScript
const container = document.querySelector(".items-selector"); const yMax = container.clientHeight/2; const xMax = container.clientWidth-25; container.addEventListener("scroll", function(e) { //must get containers on scroll to retreive current position var items = container.querySelectorAll(".item"); for(var i = items.length-1; i >= 0; i--) { var item = items[i]; var itemTop = item.getBoundingClientRect().top + item.clientHeight/2; var yScrollPercentage = (itemTop - yMax)/itemTop; var xAxisPercentage = (xMax*yScrollPercentage)/100; var xAmoutToRemove = (xMax-(xMax*xAxisPercentage)); var xMove; if(itemTop >= yMax ) { //When item if on the bottom half of the container xMove = -1*xAmoutToRemove; } if(itemTop < yMax) { //When item if on the top half of the container xMove = -1*xMax + ((-1*xMax) + xAmoutToRemove); } item.style.transform = "translate("+xMove+"px, 0)" } });
问题修复与优化
1. 滚动时显示更多列表项
当前.list的gap设为48px,加上item自身50px高度,单个item占据98px垂直空间,300px容器只能显示3个左右的item。减小gap值(比如改为20px),单个item占70px,容器可显示4-5个item,滚动时也能看到更多内容。
2. 上下半部分位移不一致问题
原计算逻辑中yScrollPercentage的公式(itemTop - yMax)/itemTop存在范围不对称的问题,导致x轴计算偏差。正确做法是基于item中心相对于容器中心的偏移比例,用圆形左半部分的坐标公式计算位移:x = -sqrt(r² - y²)(r为圆半径,即容器半高;y为item中心相对容器中心的垂直偏移)。
修改后的完整代码
HTML(不变)
<div class="items-selector"> <div class="list"> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> </div> </div>
CSS(调整间距并优化过渡)
.items-selector { overflow-x:hidden; overflow-y:auto; height:300px; width:350px; position: relative; } .list { display:flex; flex-direction:column; gap: 20px; /* 减小间距,显示更多项 */ align-items:flex-end; padding: 10px 0; /* 避免item贴边 */ } .item { width:50px; height:50px; background:#cecece; display:flex; align-items:center; justify-content:center; border-radius:100%; position: relative; transition: transform 0.1s ease; /* 增加平滑滚动过渡 */ }
JavaScript(修正位移计算+滚动加载)
const container = document.querySelector(".items-selector"); const containerRect = container.getBoundingClientRect(); const yCenter = containerRect.top + container.clientHeight / 2; // 容器中心垂直坐标 const radius = container.clientHeight / 2; // 圆半径(容器半高) const maxXOffset = container.clientWidth - 50; // 最大x偏移量(容器宽度- item宽度) // 初始化时先计算一次位置 updateItemPositions(); container.addEventListener("scroll", function() { updateItemPositions(); // 滚动加载逻辑 if (container.scrollTop + container.clientHeight >= container.scrollHeight - 20) { const list = document.querySelector(".list"); // 每次加载5个新项 for (let i = 0; i < 5; i++) { const newItem = document.createElement("div"); newItem.className = "item"; list.appendChild(newItem); } updateItemPositions(); } }); function updateItemPositions() { const items = container.querySelectorAll(".item"); items.forEach(item => { const itemRect = item.getBoundingClientRect(); const itemCenterY = itemRect.top + item.clientHeight / 2; // 计算item中心相对容器中心的垂直偏移 const yOffset = itemCenterY - yCenter; // 圆形左半部分x坐标计算,映射到实际偏移范围 const normalizedX = -Math.sqrt(Math.pow(radius, 2) - Math.pow(yOffset, 2)); const xMove = (normalizedX / radius) * maxXOffset; item.style.transform = `translate(${xMove}px, 0)`; }); }
内容的提问来源于stack exchange,提问作者Mario Andrade
相关产品推荐
相关产品推荐

