You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

当区块高度超过100vh时CSS translateY动画出现卡顿问题

解决Slide-Up Fade-Out动画在长区块下的卡顿问题

我完全懂你遇到的痛点——当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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 20:42:39