当区块高度超过100vh时CSS translateY动画出现卡顿问题
我完全懂你遇到的痛点——当section高度超过100vh时,滚动到底部触发过渡动画时的卡顿,本质是滚动条显示/隐藏导致的布局重排,让translateY动画出现跳帧。下面是几个经过验证的修复方案,一步步帮你解决问题:
方案1:提前预留滚动条宽度,避免布局跳动
滚动条的出现会让页面宽度减少约17px(不同系统/浏览器略有差异),当你切换body的overflow-y属性时,active的section(height:auto)会瞬间重新计算宽度,引发卡顿。
修复方式:
用CSS的scrollbar-gutter属性(现代浏览器支持),让body始终预留滚动条的位置,即使没有滚动条也不会收缩:
body { scrollbar-gutter: stable; }
如果需要兼容旧浏览器,可以动态计算滚动条宽度并设置padding-right:
// 计算滚动条宽度 const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; $('body').css('padding-right', `${scrollbarWidth}px`);
方案2:调整动画触发时机,避开滚动到底部的临界状态
原来的判断条件$(window).scrollTop() + $(window).height() >= $(document).height()太极限,刚好在滚动条消失的瞬间触发动画,此时DOM正在重排,很容易冲突。我们可以提前一点触发,比如当滚动到当前section底部前50px时就启动过渡:
修改JavaScript中的判断逻辑:
// 替换向下滚动的条件 if( e.originalEvent.deltaY > 0 && nS.length && $(window).scrollTop() + $(window).height() >= $(cS).offset().top + $(cS).height() - 50 ){ // ... 原有逻辑 }
方案3:重构section定位逻辑,隔离非active区块的布局影响
原来的position:absolute会让所有section叠加在一起,active区块的height:auto会直接影响body的总高度,导致滚动条频繁变化。我们可以用固定定位处理非active区块,让它们完全不影响body布局:
修改CSS:
section{ position: fixed; /* 固定定位,不影响body高度 */ top: 0; left: 0; contain: content; height: 100vh; width: 100vw; background: none; transition: transform ease-in-out .75s, opacity ease-in-out .75s; overflow-y: hidden; display: flex; opacity: 0; transform: translateY(100%); /* 默认在视口下方 */ } section.active{ position: relative; /* 恢复正常定位,让内容撑开高度 */ transform: translateY(0); opacity: 1; height: auto; overflow-y: auto; }
这样非active的section固定在视口外,body的高度只由当前active的section决定,滚动条只会在active区块内容超过视口时出现,避免了频繁的布局变化。
方案4:优化JavaScript过渡逻辑,减少同步DOM操作
原来的代码中,同步修改CSS和等待setTimeout可能导致浏览器连续重排,用requestAnimationFrame包裹DOM修改,让浏览器在下一帧渲染时执行,能有效减少卡顿:
修改JavaScript中的动画触发代码:
// 向下滚动时的动画部分 await new Promise( r => setTimeout(r,100) ); requestAnimationFrame(() => { $(cS).css({ 'transform': 'translateY(-100%)' }); }); await new Promise( r => setTimeout(r,750) ); // 向上滚动时的动画部分 await new Promise( r => setTimeout(r,100) ); requestAnimationFrame(() => { $(pS).css({ 'transform':'translateY(0)' }); }); await new Promise( r => setTimeout(r,750) );
整合后的完整代码示例
CSS
body { scrollbar-gutter: stable; /* 预留滚动条宽度 */ margin: 0; } section{ position: fixed; top: 0; left: 0; contain: content; height: 100vh; width: 100vw; background: none; transition: transform ease-in-out .75s, opacity ease-in-out .75s; overflow-y: hidden; display: flex; opacity: 0; transform: translateY(100%); } section.active{ position: relative; transform: translateY(0); opacity: 1; height: auto; overflow-y: auto; } section > *:last-of-type { margin-bottom: 4vh; } section > *:first-of-type { margin-top: 8vh; }
JavaScript
let transitioning = false; const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; $('body').css('padding-right', `${scrollbarWidth}px`); // 兼容旧浏览器 $('body') .on('mousewheel',async function(e){ if( transitioning ) return false let cS = $('section.active'), nS = $(cS).next('section'), pS = $(cS).prev('section'); // 向下滚动逻辑 if( e.originalEvent.deltaY > 0 && nS.length && $(window).scrollTop() + $(window).height() >= $(cS).offset().top + $(cS).height() - 50 ){ transitioning = true; $('body').css({ 'overflow-y':'hidden' }); await new Promise( r => setTimeout(r,100) ); requestAnimationFrame(() => { $(cS).css({ 'transform': 'translateY(-100%)' }); }); await new Promise( r => setTimeout(r,750) ); $(cS).removeClass('active'); $(nS).addClass('active'); $('body').css({ 'overflow-y':'' }) transitioning = false; } // 向上滚动逻辑 if( e.originalEvent.deltaY < 0 && pS.length && $(window).scrollTop() <= 0 ){ transitioning = true; $('body').css({ 'overflow-y':'hidden' }) $(cS).removeClass('active'); $(pS).addClass('active'); await new Promise( r => setTimeout(r,100) ); requestAnimationFrame(() => { $(pS).css({ 'transform':'translateY(0)' }); }); await new Promise( r => setTimeout(r,750) ); $('body').css({ 'overflow-y':'' }) transitioning = false; } })
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <!-- 你的section结构 --> <section class="active"> <!-- 内容,可超过100vh --> </section> <section> <!-- 下一个区块内容 --> </section>
这些改动从根源上解决了滚动条变化导致的布局重排问题,同时优化了动画的触发时机和执行方式,应该能完美复刻你想要的平滑过渡效果。
内容的提问来源于stack exchange,提问作者HymnZzy

