本摘要由 AI 生成,仅供参考,内容请以原文为准。
VitePress 文章页 AI 摘要实现详解
前段时间给博客文章页加了一个 AI 摘要 模块。它的目标很直接:当文章比较长时,先给读者一个几秒钟就能看完的摘要,决定要不要继续读全文。
这次实现的重点不在于"在线调用大模型",而在于把现有文章摘要以更自然的方式呈现在文章页里。也就是说,摘要内容本身来自文章 frontmatter,页面负责渲染、动画和交互体验。
整体思路
这套实现可以拆成三步:
- 在文章 frontmatter 中提供摘要文本
- 在文章页正文前插入一个摘要组件
- 给组件补上打字机动画、重播按钮和降级逻辑
这样做的好处是结构简单、没有服务端依赖,也不会在页面加载时额外请求 AI 接口。
第一步:设计摘要数据来源
最简单的做法,就是直接复用文章已有的 summary 字段,同时兼容一个可选的 articleGPT 字段。
组件内部的核心逻辑大致是这样:
const summaryText = computed(() => {
const value = frontmatter.value.summary || frontmatter.value.articleGPT;
if (Array.isArray(value)) {
return value.join(" ").trim();
}
return typeof value === "string" ? value.trim() : "";
});这样设计有几个好处:
- 兼容现有文章,很多文章本来就已经有
summary - 如果以后想把 AI 摘要单独存到
articleGPT,也不用改模板结构 - 支持字符串和数组两种格式,便于后续扩展
文章里只需要这样写元数据:
---
title: 示例文章
description: 这是一篇示例文章
date: 2026-06-16
categories: [开发教程]
tags: [VitePress, AI 摘要]
summary: "这里放摘要内容,文章页会自动渲染 AI 摘要卡片。"
---第二步:在文章页挂载摘要组件
文章页模板在 blog/.vitepress/theme/views/Post.vue。实现时直接把摘要组件插入正文内容前面:
<article class="post-article s-card">
<div class="expired s-card" v-if="postMetaData?.expired >= 180">
本文发表于<strong>{{ postMetaData?.expired }}</strong> 天前,其中的信息可能已经时过境迁
</div>
<ArticleGPT />
<Content id="page-content" class="markdown-main-style" />
</article>这样所有文章页都会自动尝试渲染摘要模块,但真正显示与否由组件自身控制。只要当前文章没有 summary 或 articleGPT,组件就不会输出任何内容。
这种写法比在每篇文章里手动插入组件更省事,也更适合博客这种批量内容场景。
第三步:实现摘要卡片组件
摘要卡片组件位于 blog/.vitepress/theme/components/Aside/Widgets/ArticleGPT.vue。
它的模板结构并不复杂,主要由三部分组成:
- 标题区域:机器人图标、"AI 摘要"标题、重播按钮
- 内容区域:显示逐字输出的摘要文本
- 提示区域:说明这是 AI 摘要,仅供参考
组件通过 v-if="summaryText" 控制显示,这一步很关键:
<div v-if="summaryText" ref="containerRef" class="article-gpt s-card">这样可以避免空摘要时渲染出一个没内容的卡片。
打字机动画怎么做
这次动画没有用第三方库,而是直接用 requestAnimationFrame 自己控制逐字输出。
核心思路是:
- 先把完整摘要拆成字符数组
- 每一帧根据时间差决定是否追加一个字符
- 遇到标点符号时拉长停顿时间,让阅读节奏更自然
关键代码类似这样:
const chars = Array.from(text);
const baseDelay = 28;
const punctuationDelay = 130;
const delay = /[,。!?;:,.!?]/.test(currentChar)
? punctuationDelay
: baseDelay;这样处理后,摘要不会像机器刷屏一样一股脑蹦出来,而是更接近"读一句、停一下"的感觉。
为什么要配合 IntersectionObserver
如果页面一加载就开始动画,用户还没滚到正文区域,动画可能已经播完了。这样摘要区的存在感会很弱。
所以这里又加了一层 IntersectionObserver:
- 先监听摘要卡片是否进入视口
- 真正进入视口后,再延迟一点点启动打字动画
对应的好处是:
- 首屏性能更稳,不需要一上来就执行动画
- 用户滚动到摘要区域时,反馈更明显
- 视觉体验更符合阅读流程
无障碍和降级处理
做这类动画组件时,不能只顾效果,还要考虑"动画不适合所有人"。
这里做了两个降级:
1. 服务端渲染时直接输出最终文本
如果当前环境没有 window,说明代码正在 SSR 阶段执行,这时直接显示完整摘要:
if (typeof window === "undefined") {
renderedText.value = summaryText.value;
return;
}2. 用户偏好减少动画时跳过打字效果
通过 prefers-reduced-motion 检测系统设置,如果用户更倾向少动画,就直接完整显示内容:
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;这一步能避免为了视觉效果牺牲可访问性。
重播按钮的作用
摘要卡片右上角放了一个 Moretti AI 按钮,点击后会重新执行 initializeAnimation()。
这个交互虽然简单,但体验上很有用:
- 用户第一次可能没认真看
- 页面切换回来后想再看一遍摘要
- 可以让组件更像一个"可交互的信息块",而不只是静态提示
同时按钮在动画执行中会被禁用,避免重复点击导致状态混乱。
样式层的几个处理
这个组件的视觉层主要做了几件事:
- 使用渐变背景,和普通卡片拉开层次
- 用
color-mix给边框和阴影增加一点主题色 - 光标闪烁配合逐字输出,强化打字机感
- 在移动端缩小间距和字号,保证不挤占正文空间
因为它本质上是正文前的引导信息,所以样式上不能太重,否则会喧宾夺主;但也不能太弱,否则读者注意不到。
这套方案的边界
需要说明的是,这次实现严格来说更像是AI 摘要展示层,而不是"前端实时生成 AI 摘要"。
也就是说:
- 页面不会在浏览器里调用大模型接口
- 摘要内容需要提前写进 frontmatter
- 组件负责的是读取、展示、动画和交互
这么做的优点是稳定、便宜、易维护,尤其适合静态博客。
如果后面想进一步自动化,可以继续往前走一步:在构建阶段写脚本,批量调用 AI 生成摘要,再回填到每篇文章的 summary 或 articleGPT 字段里。这样仍然能保持前端零运行时依赖。
总结
这次 AI 摘要的实现,其实没有引入复杂架构,核心只是把数据来源、页面挂载点和动效逻辑拆清楚:
summary/articleGPT负责提供内容Post.vue负责把组件放到合适的位置ArticleGPT.vue负责展示、动画和交互细节
对于 VitePress 博客来说,这是一种投入不大、收益很直接的增强方式。尤其是文章偏长、教程类内容较多时,给读者一个先看摘要再决定是否深入的入口,体验会好很多。