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

Next.js Typescript项目Chrome浏览器惯性滚动卡顿问题求助

故障根因及修复方案

根因排查

  • 滚动状态未持久化存储:你当前在组件内直接定义的data对象属于普通函数变量,组件每次重渲染都会被重新初始化,滚动插值计算依赖的previous值会被频繁清零,导致每帧滚动偏移量计算跳变。Chrome对高频异常transform变更的容错率低于其他浏览器,直接表现为滚动卡顿。
  • 未开启GPU合成层优化:虽然使用了translate3d触发硬件加速,但未明确指定合成层相关属性,Chrome会默认将滚动容器和其他页面元素放在同一个渲染层,每帧滚动都要触发全页重绘,性能开销陡增。
  • 高频回流阻塞渲染:resize事件触发时会直接修改body高度,无防抖的前提下会高频触发页面回流,和requestAnimationFrame的滚动计算抢占渲染线程,Chrome的调度策略会优先处理回流,进一步加剧滚动卡顿。
  • 缺少raf清理逻辑:组件卸载时没有终止requestAnimationFrame的递归调用,额外的无效执行也会占用渲染资源。

修复代码示例

首页代码修改

import type { NextPage } from 'next';
import { useEffect, useRef } from 'react';
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import Nav from '../components/nav';
import useMousePosition from '../hooks/useMousePosition';
import Projects from '../components/projects';
import Experience from '../components/experience';
import Contact from '../components/contact';
// 导入useWindowSize(原代码漏了导入)
import useWindowSize from '../hooks/useWindowSize';

const Home: NextPage = () => {
  const size = useWindowSize();
  const app = useRef<HTMLDivElement>(null);
  // 修正ref类型为HTMLDivElement
  const scrollContainer = useRef<HTMLDivElement>(null);
  // 用useRef持久化滚动状态,避免重渲染重置
  const data = useRef({
    ease: 0.1,
    current: 0,
    previous: 0,
    rounded: 0
  }).current;
  // 存储raf id用于卸载时清理
  const rafId = useRef<number>(0);

  useEffect(() => {
    rafId.current = requestAnimationFrame(skewScrolling);
    // 组件卸载时终止raf递归
    return () => cancelAnimationFrame(rafId.current);
  }, []);

  const setBodyHeight = () => {
    if (scrollContainer.current)
      document.body.style.height = `${scrollContainer.current.getBoundingClientRect().height}px`;
  };

  useEffect(() => {
    setBodyHeight();
  }, [size.height]);

  const skewScrolling = () => {
    if (scrollContainer.current) {
      data.current = window.scrollY;
      data.previous += (data.current - data.previous) * data.ease;
      data.rounded = Math.round(data.previous * 100) / 100;
      scrollContainer.current.style.transform = `translate3d(0, -${data.rounded}px, 0)`;
      rafId.current = requestAnimationFrame(skewScrolling);
    }
  };

  return (
    <div ref={app} className={styles.container}>
      <Head>
        <title>Abhijith Kannan</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Nav />
      {/* 新增willChange等属性强制开启合成层优化 */}
      <div ref={scrollContainer} style={{ zIndex: 10, willChange: 'transform', backfaceVisibility: 'hidden' }}>
        <div className={styles.background}>
          <div className={styles.intro}>
            <img className={styles.backgroundImg} src="/images/back.svg" alt="background" />
            <div className={styles.introContainer}>
              <div className={styles.text}>
                <h2 className={styles.h2}>Hi everyone,</h2>
                <h1 className={styles.h1}>I am <span className={styles.span}>Electrical Engineering</span> and
                  <span className={styles.span}> Frontend Developer</span></h1>
              </div>
              <div></div>
            </div>
            <div>
            </div>
          </div>
        </div>
        <Projects />
        <Experience />
        <Contact />
        <footer className={styles.footer}>Developed by Abhijith Kannan</footer>
      </div>
    </div >
  )
}

export default Home

useWindowSize自定义Hook修改(新增防抖)

import { useState, useEffect } from "react";

// 简易防抖工具函数
function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): T {
  let timer: NodeJS.Timeout;
  return ((...args: any[]) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  }) as T;
}

export default function useWindowSize() {
  function getSize() {
    if (typeof window !== 'undefined') {
      return {
        width: window.innerWidth,
        height: window.innerHeight
      };
    } else {
      return {
        width: 0,
        height: 0
      };
    }
  }

  const [windowSize, setWindowSize] = useState(getSize);

  useEffect(() => {
    // 给resize事件回调加100ms防抖,避免频繁触发回流
    const handleResize = debounce(() => {
      setWindowSize(getSize());
    }, 100);

    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return windowSize;
}

内容的提问来源于stack exchange,提问作者ForgottenTale

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 12:18:03