☰
Slidev 绘图与标注实战指南:完整配置项详解与源码级实现原理
2026/11/2 23:47:49 网站建设 项目流程

Slidev 绘图与标注实战指南:完整配置项详解与源码级实现原理

【免费下载链接】slidevPresentation Slides for Developers项目地址: https://gitcode.com/GitHub_Trending/sl/slidev

本文基于 Slidev 官方文档 docs/features/drawing.md 展开,系统讲解 Slidev 内置绘图与标注功能的使用方式:如何开启绘制工具栏、手写笔识别、绘图持久化、按场景禁用绘图以及跨实例同步等全部配置项,并深入 packages/client/composables/useDrawings.ts、packages/slidev/node/integrations/drawings.ts 等源码,解释配置背后的特征开关、状态同步链路与 SVG 落盘机制。读完本文,你可以完全掌控 Slidev 的现场标注行为,并理解其数据在浏览器与服务端之间的流转过程。

功能概览:基于 drauu 的绘图工具栏

Slidev 内置了由 drauu 驱动的绘图与标注能力(文档 frontmatter 中relates关联了 drauu 项目),允许你在任意幻灯片上直接绘制、圈点、画箭头和几何图形。

开启方式:点击导航栏中的画笔(carbon-pen)图标打开绘图工具栏;Presenter Mode(演讲者模式)中同样可用。你创建的绘图与标注会实时自动同步到所有实例。

从源码看,工具栏按钮的显示受特征开关与配置双重控制。NavControls.vue 中画笔按钮的渲染条件是:

<IconButton v-if="__SLIDEV_FEATURE_DRAWINGS__ && (!configs.drawings.presenterOnly || isPresenter)" class="relative" :title="drawingEnabled ? 'Hide drawing toolbar' : 'Show drawing toolbar'" :active="drawingEnabled" @click="drawingEnabled = !drawingEnabled"

即:__SLIDEV_FEATURE_DRAWINGS__编译期开关为真,且(未开启presenterOnly或当前处于演讲者模式)时才显示。工具栏组件 DrawingControls.vue 本身也在 Controls.vue 中按同样的条件动态加载,presenterOnly: true时非演讲者窗口连工具栏都不会加载。

工具栏提供了完整的绘制模式与画笔控制(见 DrawingControls.vue):

  • 六种模式:钢笔(stylus)、直线(line)、箭头(arrow)、椭圆(ellipse)、矩形(rectangle)、擦除(eraseLine);
  • 七个预设画笔颜色,定义在 useDrawings.ts 的brushColors中,依次为#ff595e、#ffca3a、#8ac926、#1982c4、#6a4c93、#ffffff、#000000;
  • 线宽滑杆(1–15)、撤销/重做、清空当前页、固定工具栏(pin)与关闭等按钮。

绘制层由 DrawingLayer.vue 挂载一个全屏 SVG 承载,drauu在onMounted时mount到该 SVG,并监听缩放scale将coordinateScale设为1 / scale,保证画布缩放时笔迹坐标不失真;drawingEnabled为 false 时该层pointer-events-none,不拦截导航操作。

键盘快捷键

useDrawings.ts 在绘图开启且未在输入时,注册了完整的快捷键体系(数字键 1–7 对应brushColors下标,实现了对应色选择):

按键作用
Ctrl/Cmd + Z撤销
Ctrl/Cmd + Shift + Z重做
Esc关闭绘图模式
L直线模式(line)
A箭头模式(arrow)
S钢笔模式(stylus)
R矩形模式(rectangle)
E椭圆模式(ellipse)
C清空当前页绘图
1–7切换画笔颜色(红/黄/绿/蓝/紫/白/黑)

被处理的按键会preventDefault()并stopPropagation(),避免与翻页等全局快捷键冲突。

手写笔设备使用:输入类型自动识别

在平板(例如配备 Apple Pencil 的 iPad)上使用手写笔时,Slidev 会智能识别输入类型:无需先开启绘图模式,即可直接用笔在幻灯片上绘制,同时手指或鼠标仍负责翻页导航。

这一行为的实现位于 useDrawings.ts:

const drauuOptions: DrauuOptions = reactive({ brush, acceptsInputTypes: computed(() => (drawingEnabled.value && (!configs.drawings.presenterOnly || isPresenter.value)) ? undefined : ['pen' as const]), coordinateTransform: false, })

关键在于acceptsInputTypes:只有当“绘图模式开启 且(非 presenterOnly 或处于演讲者模式)”时才接受所有输入类型(undefined表示不限);否则仅接受pen(手写笔)指针事件。换言之,未开启绘图时鼠标/触摸事件被忽略,手写笔却可以直接落笔,从而实现了“笔绘图、手指翻页”的分离。

持久化绘图:persist 配置与 SVG 落盘

通过 frontmatter 配置,可以将绘图保存为 SVG 文件,并让它们出现在导出的 PDF 或托管站点中:

--- drawings: persist: true ---

根据 packages/types/src/frontmatter.ts 中DrawingsOptions的定义,persist的完整取值为boolean | string:传true时写入默认的.slidev/drawings目录;传字符串则相对入口文件所在目录解析为自定义目录(见 drawings.ts 中 resolveDrawingsDir 的resolve(dirname(options.entry), options.data.config.drawings.persist))。

persist的完整链路涉及三层:

  1. 编译期开关:options.ts 将__SLIDEV_FEATURE_DRAWINGS_PERSIST__定义为!!options.data.config.drawings.persist。SlidesShow.vue 据此决定即使禁用了实时绘图也要加载DrawingLayer,以便渲染已持久化的笔迹;PrintSlideClick.vue 则在导出打印时为每一页点击帧叠加DrawingPreview,这正是“绘图出现在导出 PDF 中”的原因。
  2. 服务端读取:开发服务器启动时,serverRef.ts 调用loadDrawings(options)把磁盘上已有的绘图载入服务端共享状态;loadDrawings 用fast-glob匹配目录下的*.svg,以文件名数字部分作为页码键(非数字文件名忽略),并剥掉首尾的<svg>...</svg>包裹行,只保留内部路径数据。
  3. 服务端写盘:每当共享状态中的drawings键发生变化,serverRef.ts 检查options.data.config.drawings.persist && key === 'drawings'后调用writeDrawings。writeDrawings 会按canvasWidth与aspectRatio计算画布宽高,生成<svg width=... height=...>头尾包裹后的完整 SVG,逐页写入{页码}.svg。

写入安全性:isSafeDrawingKey

writeDrawings对每个键先做安全校验,isSafeDrawingKey 要求键必须是纯数字(/^\d+$/),且拼出的目标路径不能以..开头或为绝对路径,从而拒绝路径穿越写入。该逻辑有专门测试覆盖,见 drawings.test.ts:接受'3',拒绝'../../evil'、'a/b'、空字符串与'abc'。

禁用绘图:enabled 与 presenterOnly

完全禁用

--- drawings: enabled: false ---

仅开发模式可用

--- drawings: enabled: dev ---

根据 frontmatter.ts 的类型定义,enabled取值为boolean | 'dev' | 'build',默认为true。其求值发生在编译期,options.ts 中:

const matchMode = (mode: string | boolean) => mode === true || mode === options.mode // ... __SLIDEV_FEATURE_DRAWINGS__: matchMode(options.data.config.drawings.enabled),

即enabled: true在所有模式生效;enabled: 'dev'只在开发模式生效;enabled: 'build'只在构建/导出生效;enabled: false则任何模式都不生效。由于它是编译期常量,false时工具栏按钮与绘图层会被直接裁剪,而非运行时隐藏。

仅演讲者模式可用

--- drawings: presenterOnly: true ---

如前文NavControls.vue条件所示,presenterOnly: true时普通播放窗口的画笔按钮不再渲染;同时 useDrawings.ts 中acceptsInputTypes的计算也会因此收窄,普通窗口即使手写笔也不接受绘图输入(仅保留pen类型,且绘图开关默认关闭)。

绘图同步:syncAll 与跨实例机制

默认情况下,Slidev 会在所有实例之间同步绘图。如果你正在与他人共享幻灯片,可能希望禁用这种全量同步:

--- drawings: syncAll: false ---

加上该配置后,只有演讲者实例的绘图才会向其他实例同步。

这一行为对应 useDrawings.ts 的一行核心逻辑:

const syncUp = computed(() => configs.drawings.syncAll || isPresenter.value)
  • syncAll: true(默认):任何实例的改动都会patchDrawingState广播;
  • syncAll: false:仅当当前窗口是演讲者(isPresenter)时才会把自己的笔迹写入共享状态,观众窗口的涂鸦只留在本地。

同步链路解析

绘图状态的同步底座是 packages/client/state/drawings.ts:

// @ts-expect-error - virtual module import serverDrawingState from 'server-reactive:drawings?diff' // ... export const { init: initDrawingState, onPatch: onPatchDrawingState, onUpdate: onDrawingUpdate, patch: patchDrawingState, state: drawingState, } = createSyncState<DrawingsState>( serverDrawingState, serverDrawingState, __SLIDEV_FEATURE_DRAWINGS_PERSIST__, )

server-reactive:drawings?diff是由 vite-plugin-vue-server-ref 提供的虚拟模块:服务端维护Record<页码, SVG 内部数据>状态,客户端拉取 diff 补丁。整条数据流为:

  1. 用户落笔 → drauu 触发changed事件 → useDrawings.ts 在!disableDump && syncUp时drauu.dump()出序列化数据,按当前页码patchDrawingState;
  2. 补丁经由 server-ref 通道广播到所有实例;
  3. 其他实例的 onPatchDrawingState 回调 用drauu.load(data)重绘,并用disableDump标志防止“加载 → 再 dump → 再广播”的回环;
  4. 翻页时watch(currentSlideNo)触发 loadCanvas,按页加载对应笔迹或清空画布。

另外值得注意的本地状态:drawingEnabled(是否开启绘图)、drawingPinned(工具栏固定)、brush(颜色/线宽/模式)都通过useLocalStorage持久化到浏览器(键分别为slidev-drawing-enabled、slidev-drawing-pinned、slidev-drawing-brush),刷新后画笔设置保持不变,但绘图内容本身存在服务端共享状态(及磁盘 SVG,若开启了persist)。

配置速查表

综合 frontmatter.ts 中 DrawingsOptions 定义 与 config.ts 中 ResolvedDrawingsOptions:

配置项类型默认值说明
drawings.persistboolean \| stringfalse持久化到磁盘,true用默认.slidev/drawings目录,字符串可指定相对入口文件的自定义目录;决定__SLIDEV_FEATURE_DRAWINGS_PERSIST__开关
drawings.enabledboolean \| 'dev' \| 'build'true是否启用绘图功能,按模式匹配编译进__SLIDEV_FEATURE_DRAWINGS__开关
drawings.presenterOnlybooleanfalse仅允许从演讲者模式绘制,非演讲者窗口不显示画笔按钮
drawings.syncAllbooleantrue是否在所有实例间同步绘图;false时仅演讲者实例可同步给他人

小结

Slidev 的绘图与标注功能覆盖了“现场标注—跨实例同步—持久留痕—导出呈现”的完整闭环:工具栏由编译期特征开关与 frontmatter 配置共同控制,输入类型识别让手写笔设备开箱即用,persist通过服务端loadDrawings/writeDrawings把笔迹以带画布尺寸的 SVG 文件按页落盘(并有路径穿越防护测试背书),而syncAll则决定了多实例场景下谁是唯一广播方。所有关键实现都可以从 useDrawings.ts、state/drawings.ts、integrations/drawings.ts 与 vite/serverRef.ts 中逐行对照本文验证。

【免费下载链接】slidevPresentation Slides for Developers项目地址: https://gitcode.com/GitHub_Trending/sl/slidev

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询