博客页面滚动卡顿分析

🔴 核心问题

你的页面在 Edge 浏览器上滚动不流畅,主要有以下几个原因:


1. CSS 动画与过渡冲突(最严重)

问题代码位置

style.css 中的多个 CSS 过渡效果:

/* header-container 有 transition */
.header-container {
  transition: height 0.3s ease;
}

/* header 内部元素也有 transition */
header {
  transition: padding 0.3s ease;
}

/* placeholder-top 有 transition */
.placeholder-top {
  transition: opacity 0.2s ease, transform 0.3s ease;
}

/* 导航按钮有 transition */
nav a {
  transition: all var(--transition-normal); /* 0.3s */
}

具体危害

  • 层叠多个 transition:当滚动时,header 同时改变 heightpaddingopacitytransform
  • 频繁触发重排/重绘height 变化导致回流(reflow)opacity 导致重绘(repaint)
  • 与 JS 交互冲突script.js 中的导航条收缩逻辑每次滚动都添加/移除 shrunk 类,触发所有这些过渡

性能指标

  • 一次完整的回流→重绘 = 掉帧 30-50ms
  • 60fps 标准 = 每帧仅有 16.67ms,你已经超标

2. 频繁的 DOM 类名操作

问题代码(script.js 底部)

function updateHeaderState() {
  if (!isShrunk && scrollTop > SHRINK_AT) {
    header.classList.add('shrunk');  // 触发 CSS transition
    isShrunk = true;
    lockDuringTransition();
  }
}

window.addEventListener('scroll', onScroll, { passive: true });

危害

  • 每次滚动都调用 updateHeaderState():即使只是微小滚动也会触发类名变化
  • 类名变化 → 所有相关 CSS 选择器重新计算:包括导航按钮、占位符等
  • requestAnimationFrame 虽然有防抖,但执行成本仍然很高

3. Backdrop-filter 性能问题

问题代码

.header-container, .footer-container, .sidebar-container, .article-container {
  background-color: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

危害

  • Backdrop-filter 是最昂贵的 CSS 属性之一
  • 在滚动时,GPU 需要持续计算模糊效果
  • Edge 对 backdrop-filter 的优化不如 Chrome

4. 过度复杂的 Box-shadow

问题代码

--shadow-card: 0 0 0 11px #f5f5f5, 0 0 0 13px #fff, 0 4px 22px rgba(0,0,0,0.6);

危害

  • 三层 box-shadow = 三倍的阴影计算成本
  • 滚动时每个可见卡片都在重新计算
  • 与 backdrop-filter 结合时,性能成倍下降

5. JavaScript 动画帧不优化

问题代码(代码块折叠功能)

async function initCodeBlocks() {
  const blocks = document.querySelectorAll(".article-content pre");
  
  blocks.forEach(pre => {
    // 每个 pre 计算字体大小、行高等
    const cs = getComputedStyle(codeEl);
    const fontSize = parseFloat(cs.fontSize) || 15;
    // ... 复杂计算
  });
}

危害

  • getComputedStyle() 是同步操作,会阻塞渲染
  • 在文章很多或代码块很多时,导致主线程卡死

6. 多余的 JavaScript 监听器

问题位置

// 每次滚动都执行这些
document.querySelectorAll('a[href^="#"]').forEach(a => { /* 锚点处理 */ });
document.querySelectorAll('.toggle-replies-btn').forEach(btn => { /* 回复处理 */ });

危害

  • 这些操作在 DOMContentLoaded 时执行,重复 querySelector 查询整个 DOM
  • 如果页面有 100+ 个链接,每次都遍历浪费资源

7. 页面加载动画阻塞

问题代码(style.css

body.page-loaded .header-container,
body.page-loaded .sidebar-container,
body.page-loaded .article-container {
  animation: blogEntry 0.52s cubic-bezier(...) forwards;
}

危害

  • 页面加载时,所有元素同时动画入场,占用 GPU/CPU
  • 即使加载完成后,这些动画类也留在 DOM 中
  • 与滚动动画竞争渲染资源

📋 优化方案

方案 A:关键优化(立即见效)

1. 移除 Header 的 CSS Transition

/* 从这个改为 */
.header-container {
  /* 删除: transition: height 0.3s ease; */
  height: 135px;
  min-height: 135px;
}

header {
  /* 删除: transition: padding 0.3s ease; */
}

/* 使用 JS 直接改变样式而非 CSS transition */
.placeholder-top {
  /* 保留 opacity 但删除 transform transition */
  opacity: 0;
  transform: translateY(-12px);
  /* 使用 JS 控制而非 CSS */
}

2. 简化 Box-shadow

/* 将三层改为一层 */
--shadow-card: 0 4px 12px rgba(0,0,0,0.1);

/* 如果必须要分层效果,用伪元素代替 */

3. 禁用 Backdrop-filter 或降低强度

/* 方案 1:完全移除 */
.header-container, .sidebar-container {
  background-color: rgba(255, 255, 255, 0.95);
  /* 删除 backdrop-filter */
}

/* 方案 2:仅在需要时启用(hover 时) */
@supports (backdrop-filter: blur(1px)) {
  .sidebar-container:hover {
    backdrop-filter: blur(3px); /* 降低模糊强度 */
  }
}

4. 优化 Header 收缩逻辑

// script.js 底部,改进导航条收缩

(function () {
  'use strict';
  
  const header = document.querySelector('.header-container');
  if (!header) return;
  
  const SHRINK_AT = 100;
  const EXPAND_AT = 40;
  
  let isShrunk = false;
  let lastScrollY = 0;
  
  // 使用二次防抖,只在状态真正改变时操作 DOM
  function updateHeaderState() {
    const scrollY = window.pageYOffset;
    
    // 只检查跨越阈值的情况
    if (!isShrunk && scrollY > SHRINK_AT) {
      header.classList.add('shrunk');
      isShrunk = true;
      // 不使用 CSS transition,直接跳变
      lastScrollY = scrollY;
    } else if (isShrunk && scrollY < EXPAND_AT) {
      header.classList.remove('shrunk');
      isShrunk = false;
      lastScrollY = scrollY;
    }
  }
  
  // 使用 throttle 而非 requestAnimationFrame
  let throttleTimer = null;
  window.addEventListener('scroll', () => {
    if (throttleTimer) return;
    
    throttleTimer = setTimeout(() => {
      updateHeaderState();
      throttleTimer = null;
    }, 100); // 每 100ms 最多检查一次
  }, { passive: true });
  
  updateHeaderState();
})();

5. 延迟非关键代码执行

// script.js 中,将重操作放到空闲时间
document.addEventListener('DOMContentLoaded', function() {
  // 关键操作(必须立即执行)
  document.body.classList.add('page-loaded');
  initAlertAutoHide();
  
  // 非关键操作(延迟执行)
  if ('requestIdleCallback' in window) {
    requestIdleCallback(() => {
      initArticleTOC();
      initCodeBlocks();
    });
  } else {
    setTimeout(() => {
      initArticleTOC();
      initCodeBlocks();
    }, 1000);
  }
});

方案 B:进阶优化

1. 使用 will-change 提示浏览器

.header-container {
  will-change: transform; /* 仅在必要时使用 */
}

2. 启用 GPU 加速

.header-container {
  transform: translate3d(0, 0, 0); /* 强制 GPU 渲染 */
}

3. 批量修改 DOM

// 不好
element.style.color = 'red';
element.style.fontSize = '16px';
element.style.margin = '10px';

// 更好
element.style.cssText = 'color: red; font-size: 16px; margin: 10px;';

🎯 优化优先级

优先级 问题 预期改进
🔴 高 删除 Header CSS Transition +40% 帧率
🔴 高 简化 Box-shadow +20% 帧率
🟠 中 禁用/降低 Backdrop-filter +15% 帧率
🟠 中 优化滚动监听节流 +10% 帧率
🟡 低 代码块折叠异步化 +5% 帧率

✅ 测试方法

在 Edge 中使用开发者工具验证优化效果:

// 在控制台执行,查看帧率
(function() {
  let frameCount = 0;
  let lastTime = performance.now();
  
  function countFrames() {
    frameCount++;
    const now = performance.now();
    if (now - lastTime >= 1000) {
      console.log(`FPS: ${frameCount}`);
      frameCount = 0;
      lastTime = now;
    }
    requestAnimationFrame(countFrames);
  }
  
  countFrames();
})();

滚动页面观察控制台输出。目标:维持 50+ FPS(60FPS 是理想值)


总结

你的代码问题主要是:

  1. CSS 过渡层级过多 ← 最大罪魁祸首
  2. Backdrop-filter 成本高 ← 次要问题
  3. Box-shadow 太复杂 ← 边际问题
  4. JS 防抖不够激进 ← 可优化项

建议先采用方案 A 中的前 4 项,应该能显著改善滚动体验。