1280 λέξεις
6 λεπτά
长文章与多组件 MDX 的 Safari 性能优化
2026-07-10
Χωρίς Ετικέτες
Αυτό το άρθρο δεν έχει μετάφραση στην τρέχουσα γλώσσα. Επιστροφή στην κύρια γλώσσα.

当文章超过约 3000 字,或一个 MDX 页面同时包含大量标题、代码高亮、数学公式、表格和框架组件时,Safari 可能出现滚动掉帧、手势响应延迟和设备发热。问题通常不是“文字太多”本身,而是海量 DOM 节点同时承担过渡状态、滚动容器接管、目录布局读取和组件即时水合造成的叠加成本。

本项目在提交 9dd965d 中完成了针对性修复。下面的规则既是变更记录,也是后续开发必须遵守的性能基线。

一、删除文章内容节点的全局 transition#

旧规则曾给标题、段落、链接、span、列表、引用、代码、表格等几乎所有文章节点统一添加 transition。代码高亮和 KaTeX 会生成大量嵌套节点,Safari 需要持续维护这些节点的过渡状态,导致样式计算和绘制成本随文章长度快速上升。

修复方式是在 src/styles/main.css 中移除这类全局规则:

/* 禁止恢复这种面向正文所有后代节点的规则 */
h1, h2, h3, h4, h5, h6, p, a, span, li, ul, ol,
blockquote, code, pre, table, th, td, strong {
@apply transition;
}

组件按钮、卡片、链接等确实需要反馈的元素仍可按需保留过渡。代码块和公式继续使用明确的保护规则:

pre, pre *, code, code *, .katex, .katex * {
transition: none !important;
}

二、Posts 与 Docs 长文路由使用原生滚动#

OverlayScrollbars 适合部分普通页面的视觉统一,但不应接管文章长页的 body 滚动。长文滚动由 Safari 原生滚动管线处理,通常拥有更好的惯性、合成和功耗表现。

src/layouts/Layout.astro 会识别 Posts 与 Docs 路由:

function isLongFormContentRoute() {
const pathname = stripBasePath(window.location.pathname);
return /^\/(?:[a-zA-Z_-]+\/)?(?:posts|docs)\//.test(pathname);
}

进入长文路由时不初始化 body 级 OverlayScrollbars;如果通过 Swup 从其他页面进入文章页,则销毁已有实例。离开长文路由后才按需重新初始化,并通过实例缓存避免重复创建。

三、目录更新合并到单个动画帧#

长文章滚动时,多个标题可能在同一帧内连续触发 IntersectionObserver。目录组件现在只排队一个 requestAnimationFrame,并用活动标题范围键跳过无变化的更新,从而减少重复的 DOM 样式切换和布局读取。

目录自动跟随保留 smooth 视觉效果,但只会在活动标题范围真正变化后启动,并且通过单个 requestAnimationFrame 合并同一帧内的多次观察器通知。系统开启“减少动态效果”时会自动退回 auto。组件卸载时必须取消尚未执行的帧任务。

文章页依然使用浏览器原生滚动管线。为了兼顾桌面端外观,src/styles/scrollbar.css 仅通过标准滚动条属性与 ::-webkit-scrollbar 伪元素设置宽度、颜色和圆角,不创建额外 JavaScript 滚动容器。

四、多组件 MDX 默认使用 client#

长文中的图表、演示器和交互组件不应批量使用 client:onlyclient:load。这两种策略会让大量组件在页面打开时集中下载、执行和渲染,放大主线程压力。

本项目已将 D3 入门文档中的 17 个图表从:

<Diagram client:only="react" />

改为:

<Diagram client:visible />

浏览器验证结果为:首屏未进入视口的图表激活数量为 0/17;滚动到第一个图表后为 1/17;控制台无 hydration 错误。

只有真正依赖浏览器 API、无法进行服务端渲染的组件才允许使用 client:only。首屏必须立即交互的少数组件才使用 client:load

五、不要盲目给 Astro Islands 外层使用 content-visibility#

content-visibility: auto 对纯静态超长内容可能有效,但实测发现,将它直接施加到包裹 Astro Islands 的章节容器后,可能阻止或延迟 client:visible 的可见性检测,导致组件滚动到视口后仍不激活。

因此本项目没有提交章节级 content-visibility 规则。若未来重新尝试,必须在 Safari 和 Chromium 中逐个验证首屏、滚动进入、反向滚动以及 Swup 路由返回后的水合行为,不能只看 Lighthouse 分数。

六、验证清单#

每次修改长文渲染、目录、滚动容器或 MDX 水合策略后,至少完成以下检查:

  1. 执行 pnpm build,确保生产构建成功。
  2. 在 Safari 或 WebKit 中打开超过 3000 字的页面,持续上下滚动并观察掉帧与发热。
  3. 确认文章页没有启用 body 级 OverlayScrollbars。
  4. 确认首屏外的 client:visible 组件没有提前激活。
  5. 滚动到组件后确认其正常水合,且控制台没有 hydration 错误。
  6. 检查目录高亮、锚点跳转、主题切换和 Swup 前进/返回。
  7. 使用 Safari 长文 MDX 压力测试 作为固定回归样本。

pnpm checkpnpm type-check 当前会被仓库中既有的类型问题阻塞。它们仍应执行或记录,但不能把既有错误误判为本次性能改动产生的回归。

长文章与多组件 MDX 的 Safari 性能优化
https://blog.aquamarinez.com/el/docs/fuwari/advanced-customization/change-log/01-performance-optimization/
Συγγραφέας
Aquamarine
Δημοσιεύτηκε στις
2026-07-10
Άδεια
CC BY-NC-SA 4.0