JavaScript/CSS圆形虹膜滚动过渡异常:向下滚动正常展开,向上滚动无平滑收缩且页面跳转
JavaScript/CSS圆形虹膜滚动过渡异常:向下滚动正常展开,向上滚动无平滑收缩且页面跳转
我明白你遇到的棘手问题了——向下滚动时圆形遮罩能完美平滑展开,但往上回滚的时候,页面直接跳回Problem 5,遮罩也瞬间闭合,完全没有预期的平滑收缩效果。这问题核心出在你当前的滚动锁定逻辑上:只要进入过渡区就直接钉死滚动位置,切断了向上滚动时通过滚动位置变化计算收缩比例的可能,直接触发了跳转。
下面是修复后的完整实现,我会同时解释关键修改点:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Circular Reveal Scroll</title> <style> html, body { height: 100%; margin: 0; font-family: sans-serif; overflow-x: hidden; } /* Problems section: 5 fullscreen cards */ #problems { scroll-snap-type: y mandatory; position: relative; height: 500vh; /* 对应5个全屏卡片 */ } #problems .card { height: 100vh; scroll-snap-align: start; display: flex; align-items: center; justify-content: center; font-size: 2rem; color: white; } #problems .card:nth-child(1) { background: #2c3e50; } #problems .card:nth-child(2) { background: #34495e; } #problems .card:nth-child(3) { background: #16a085; } #problems .card:nth-child(4) { background: #27ae60; } #problems .card:nth-child(5) { background: #2980b9; } /* Solutions section: fixed全屏布局 */ #solutions { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #e74c3c; display: flex; align-items: center; justify-content: center; font-size: 2rem; color: white; clip-path: circle(0px at 50% 50%); -webkit-clip-path: circle(0px at 50% 50%); } </style> </head> <body> <div id="problems"> <div class="card">Problem 1</div> <div class="card">Problem 2</div> <div class="card">Problem 3</div> <div class="card">Problem 4</div> <div class="card">Problem 5</div> </div> <div id="solutions">Solution Time!</div> <script> (() => { const problems = document.getElementById('problems'); const solutions = document.getElementById('solutions'); const body = document.body; const html = document.documentElement; /* -------- 稳健的滚动位置获取函数 -------- */ const getScrollY = () => window.pageYOffset || html.scrollTop || body.scrollTop || window.visualViewport?.offsetTop || 0; /* -------- 动画边界计算 -------- */ let viewportHeight = window.innerHeight; let topOfSolutions = problems.offsetTop + problems.offsetHeight; let startReveal = topOfSolutions - viewportHeight; let endReveal = topOfSolutions; let revealRatio = 0; /* 圆形遮罩最大半径:视口对角线的一半 */ const maxRadius = () => Math.hypot(window.innerWidth / 2, window.innerHeight / 2); /* 更新圆形遮罩的clip-path */ const setRadius = (r) => { const clip = `circle(${r}px at 50% 50%)`; solutions.style.clipPath = clip; solutions.style.webkitClipPath = clip; }; /* -------- 核心滚动处理逻辑 -------- */ const handleScroll = () => { const currentY = getScrollY(); // 根据当前滚动位置计算遮罩显示比例 if (currentY <= startReveal) { revealRatio = 0; } else if (currentY >= endReveal) { revealRatio = 1; } else { revealRatio = (currentY - startReveal) / (endReveal - startReveal); } // 应用圆形遮罩 setRadius(revealRatio * maxRadius()); }; /* -------- 窗口大小变化时重新计算边界 -------- */ const handleResize = () => { viewportHeight = window.innerHeight; topOfSolutions = problems.offsetTop + problems.offsetHeight; startReveal = topOfSolutions - viewportHeight; endReveal = topOfSolutions; // 只有当遮罩处于中间状态时才更新半径,避免不必要重绘 if (revealRatio > 0 && revealRatio < 1) { setRadius(revealRatio * maxRadius()); } }; /* -------- 初始化事件监听 -------- */ window.addEventListener('scroll', handleScroll, { passive: true }); window.addEventListener('resize', handleResize); // 页面加载时执行一次初始化计算 handleScroll(); })(); </script> </body> </html>
关键修改说明
- 移除了滚动锁定逻辑:删掉了原代码中的
isLocked状态、lockScroll/unlockScroll函数,以及滚动事件中的锁定代码。现在完全通过滚动位置currentY的变化来驱动遮罩比例,彻底解决了跳转问题。 - 简化核心逻辑:滚动事件只负责根据当前位置计算遮罩显示比例,不再做任何强制锁定或跳转操作。过渡区内的比例变化完全跟随滚动位置,上下滚动都能平滑响应。
- 补充了可运行的基础样式:给Problem卡片和Solution区域添加了全屏布局样式,你可以直接运行代码看到完整效果,无需额外调整。
- 优化响应式处理:窗口大小变化时,仅在遮罩处于中间状态时更新半径,减少不必要的重绘操作。
修改后,向下滚动进入Problem 5的最后一个视口高度区域时,圆形遮罩会从中心平滑展开;向上滚动时,遮罩会随着滚动位置上移逐步收缩,完全符合你预期的平滑过渡效果,不会再出现页面跳转。
内容来源于stack exchange
相关产品推荐
相关产品推荐

