滚动动画:固定与绝对定位切换的实现问题求助
解决方案
以下是实现你需求的完整代码示例,核心是通过滚动方向判断和状态标记解决上下滚动的逻辑冲突,同时精准控制各区块的定位与动画触发时机:
HTML 结构
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>区块滚动动画</title> <link rel="stylesheet" href="styles.css"> </head> <body> <!-- 前置占位内容,提供滚动空间 --> <div class="placeholder pre"> <p>向下滚动查看效果</p> <p>占位内容</p><p>占位内容</p><p>占位内容</p><p>占位内容</p><p>占位内容</p> <p>占位内容</p><p>占位内容</p><p>占位内容</p><p>占位内容</p><p>占位内容</p> </div> <div class="bloc" id="block4">区块4</div> <div class="bloc" id="block5">区块5</div> <div class="bloc" id="block6">区块6</div> <!-- 后置占位内容,提供滚动空间 --> <div class="placeholder post"> <p>继续滚动...</p> <p>占位内容</p><p>占位内容</p><p>占位内容</p><p>占位内容</p><p>占位内容</p> <p>占位内容</p><p>占位内容</p><p>占位内容</p><p>占位内容</p><p>占位内容</p> <p>占位内容</p><p>占位内容</p><p>占位内容</p><p>占位内容</p><p>占位内容</p> </div> <script src="script.js"></script> </body> </html>
CSS 样式
* { margin: 0; padding: 0; box-sizing: border-box; } .placeholder { min-height: 80vh; padding: 2rem; background: #f5f5f5; } .bloc { width: 100%; height: 200px; line-height: 200px; text-align: center; font-size: 2rem; color: #fff; transition: all 0.5s ease; position: relative; } #block4 { background: #2d3436; } #block5 { background: #0984e3; } #block6 { background: #e17055; } /* 初始隐藏区块5、6 */ #block5, #block6 { opacity: 0; transform: translateY(100%); } /* 固定定位类 */ .fixed-top { position: fixed; top: 0; left: 0; right: 0; } .fixed-block5 { position: fixed; top: 400px; /* 区块4高度200px + 间距200px */ left: 0; right: 0; opacity: 1; transform: translateY(0); } .fixed-block6 { position: fixed; top: 600px; /* 区块4+5高度400px + 间距200px */ left: 0; right: 0; opacity: 1; transform: translateY(0); } /* 区块联动移动状态 */ .group-move { position: absolute; top: auto; } /* 向下淡出动画类 */ .slide-down-fade { opacity: 0; transform: translateY(100%); }
JavaScript 逻辑
const block4 = document.getElementById('block4'); const block5 = document.getElementById('block5'); const block6 = document.getElementById('block6'); // 计算关键滚动触发点 const block4Top = block4.offsetTop; const blockHeight = block4.offsetHeight; const spacing = 200; // 区块间距 const triggerBlock5 = block4Top + blockHeight + spacing; const triggerBlock6 = triggerBlock5 + blockHeight + spacing; const triggerGroupMove = block6.offsetTop + blockHeight; // 状态控制变量 let lastScrollTop = 0; let isGroupMoving = false; window.addEventListener('scroll', () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const isScrollDown = scrollTop > lastScrollTop; // 向下滚动逻辑 if (isScrollDown) { // 重置向上滚动的动画类 block5.classList.remove('slide-down-fade'); block6.classList.remove('slide-down-fade'); // 步骤1:区块4固定到顶部 if (scrollTop >= block4Top && !isGroupMoving) { block4.classList.add('fixed-top'); } // 步骤2:区块5从下方滑入并固定 if (scrollTop >= triggerBlock5 && !isGroupMoving) { block5.classList.add('fixed-block5'); } // 步骤3:区块6从下方滑入并固定 if (scrollTop >= triggerBlock6 && !isGroupMoving) { block6.classList.add('fixed-block6'); } // 步骤4:三个区块联动向上移动 if (scrollTop + window.innerHeight >= triggerGroupMove) { isGroupMoving = true; // 取消固定定位,切换为绝对定位联动 block4.classList.remove('fixed-top'); block5.classList.remove('fixed-block5'); block6.classList.remove('fixed-block6'); block4.classList.add('group-move'); block5.classList.add('group-move'); block6.classList.add('group-move'); // 保持间距同步移动 const offset = scrollTop - block4Top; block4.style.top = `${offset}px`; block5.style.top = `${offset + blockHeight + spacing}px`; block6.style.top = `${offset + (blockHeight + spacing) * 2}px`; } } // 向上滚动逻辑 else { // 从联动状态返回固定状态 if (isGroupMoving) { const offset = scrollTop - block4Top; block4.style.top = `${offset}px`; block5.style.top = `${offset + blockHeight + spacing}px`; block6.style.top = `${offset + (blockHeight + spacing) * 2}px`; // 回到区块6固定触发点,恢复固定状态 if (scrollTop <= triggerBlock6) { isGroupMoving = false; block4.classList.remove('group-move'); block5.classList.remove('group-move'); block6.classList.remove('group-move'); block4.style.top = ''; block5.style.top = ''; block6.style.top = ''; block4.classList.add('fixed-top'); block5.classList.add('fixed-block5'); block6.classList.add('fixed-block6'); } } // 回到区块4初始位置,依次淡出区块6、5 else if (scrollTop <= block4Top) { block4.classList.remove('fixed-top'); // 步骤5:区块6向下淡出 if (scrollTop <= triggerBlock6 - (blockHeight + spacing) * 2) { block6.classList.add('slide-down-fade'); block6.classList.remove('fixed-block6'); } // 步骤6:区块5向下淡出 if (scrollTop <= triggerBlock5 - (blockHeight + spacing)) { block5.classList.add('slide-down-fade'); block5.classList.remove('fixed-block5'); } } } lastScrollTop = scrollTop; }); // 页面加载初始化隐藏区块5、6 window.addEventListener('load', () => { block5.style.opacity = '0'; block5.style.transform = 'translateY(100%)'; block6.style.opacity = '0'; block6.style.transform = 'translateY(100%)'; });
关键解决点说明
- 滚动方向判断:通过对比当前与上一次滚动位置,明确区分上下滚动逻辑,避免状态冲突;
- 状态标记:用
isGroupMoving标记是否处于区块联动状态,防止不同阶段的定位逻辑互相干扰; - 精准触发点:提前计算各动画的滚动触发位置,确保动画时机准确;
- 分层逻辑处理:向上滚动时先处理从联动状态返回的情况,再处理区块淡出逻辑,保证步骤5、6的顺序执行。
内容的提问来源于stack exchange,提问作者Diamondra Sandra RAVONISON
相关产品推荐
相关产品推荐

