Slidev 上下文菜单定制指南:通过 context-menu.ts 扩展与 frontmatter 开关控制右键菜单
在 Slidev 演示过程中,右键菜单(Context Menu)提供了不依赖键盘即可完成翻页、切换画笔、进入演讲者模式等操作的能力。本篇指南讲解如何通过 ./setup/context-menu.ts 文件向默认菜单追加自定义项(支持图标、禁用态、按场景显隐),以及如何用 frontmatter 中的 contextMenu 配置项按开发/构建模式控制菜单的启用,并基于仓库源码剖析菜单的注册链、事件拦截与渲染机制,帮助你做出既可用又可维护的菜单定制方案。
内置上下文菜单有哪些
在写定制代码之前,先了解 Slidev 默认提供了哪些菜单项。客户端入口在 packages/client/setup/context-menu.ts,其中通过 useNav() 等组合式函数构造了一组默认项:
Previous Click/Next Click:前进/后退一个点击动画,分别由hasPrev/hasNext决定禁用态;Previous Slide/Next Slide:切换幻灯片,依据currentPage与total禁用;- 一条
separator分隔线; Show/Hide editor:仅开发环境显示(show: __DEV__);Show/Hide drawing toolbar:切换画笔工具栏;Show slide overview:打开幻灯片总览;Enter/Exit Presenter Mode:仅在演讲者模式可用或已处于演讲者模式时出现(依赖构建特性开关__SLIDEV_FEATURE_PRESENTER__);Enter/Close fullscreen:非嵌入场景下切换全屏。
值得注意的实现细节:模块级变量 items 会缓存计算结果(if (items) return items),整个应用生命周期内菜单项只构建一次;后续用户提供的 setup 函数通过 setups.reduce((items, fn) => fn(items), ...) 依次串联在这份默认列表之后。理解这一点后你就能明白:自定义 setup 的入参 items 正是"默认项"的 ComputedRef,这是所有定制写法的基础。
创建 setup/context-menu.ts 追加自定义菜单项
按官方文档的方式,在项目根目录创建 ./setup/context-menu.ts,写入如下内容即可向菜单追加一个新条目(文档原文示例,可复制使用):
// ./setup/context-menu.ts
import { useNav } from '@slidev/client'
import { defineContextMenuSetup } from '@slidev/types'
import { computed } from 'vue'
import Icon3DCursor from '~icons/carbon/3d-cursor'
export default defineContextMenuSetup((items) => {
const { isPresenter } = useNav()
return computed(() => [
...items.value,
{
small: false,
icon: Icon3DCursor, // if `small` is `true`, only the icon is shown
label: 'Custom Menu Item', // or a Vue component
action() {
alert('Custom Menu Item Clicked!')
},
disabled: isPresenter.value,
},
])
})
要点解读:
defineContextMenuSetup只是一个类型收窄函数。在 packages/types/src/setups.ts 中,ContextMenuSetup被定义为(items: ComputedRef<ContextMenuItem[]>) => ComputedRef<ContextMenuItem[]>,而defineContextMenuSetup = defineSetup<ContextMenuSetup>,defineSetup内部只是return fn——不改变运行时行为,只为编辑器提供参数推断与校验。- 返回值必须是
computed:菜单项依赖isPresenter等响应式状态,ComputedRef的懒求值保证每次打开菜单时disabled等都取到最新值(渲染模板中对currentContextMenu.items.value的遍历即依赖于此)。 - 保留默认项的写法是展开
...items.value后再追加;如果你希望替换默认项而不是追加,也可以只返回自己的列表——reduce链允许每个 setup 自由决定下一轮的输入。 - 图标
Icon3DCursor来自~icons/carbon/3d-cursor,即 unplugin-icons 的 Carbon 图标集;同样可以传 UnoCSS 图标类名字符串(如默认项使用的'i-carbon:arrow-left')。 disabled绑定到isPresenter,演示了"按场景禁用条目"的用法:处于演讲者模式时该条目以半透明样式呈现且不可点击(渲染层用op40类实现)。
ContextMenuItem 类型结构详解
菜单项的类型定义在 packages/types/src/context-menu.ts,它由"普通选项"与 'separator' 字面量构成联合类型,其中普通选项又按 small 分为两种互斥形态:
| 字段 | 类型 / 约束 | 说明 |
|---|---|---|
action |
() => void |
必填,点击后执行的回调 |
small |
false(可选)/ true |
控制渲染形态:图标按钮或"图标 + 标签"整行项 |
icon |
Component | string |
small: false 时可选;small: true 时必填。字符串按 UnoCSS 图标类名解析,组件则直接 :is 渲染 |
label |
small: false 时 string | Component;small: true 时 string |
整行项可为自定义 Vue 组件;图标按钮项只取字符串,且仅作为 title 悬浮提示显示 |
disabled |
boolean |
禁用态,渲染为 40% 不透明度且无悬停高亮 |
show |
boolean |
可选,show ?? true;设为 false 时该项整体不渲染(内置编辑项用它在生产环境隐藏) |
从源码结构看,这是一个可辨识联合(discriminated union):small: true 分支强制 icon 必填而 label 只能是字符串,small: false 分支则放宽 label 为 string | Component 并让 icon 变为可选。因此当你的菜单项是纯图标按钮时,记得写 small: true,否则 TypeScript 会因 label 缺失组件类型而报错。
分隔线不需要对象,直接在数组中放置字符串 'separator' 即可——渲染模板会将其渲染为一条 w-full my1 border-t 分隔线(见 packages/client/internals/ContextMenu.vue 第 69 行)。
底层实现:注册、拦截与渲染
setup 的加载链
客户端 packages/client/setup/context-menu.ts 通过虚拟模块 #slidev/setups/context-menu 导入用户侧 setup 集合,再用 setups.reduce 把默认 computed 列表逐一交给每个 setup 函数。也就是说,./setup/context-menu.ts 的导出会在应用启动构建菜单时被自动发现并串联进这条 reduce 链,无需任何手动注册。
事件拦截逻辑
右键事件的统一入口在 packages/client/logic/contextMenu.ts。onContextMenu 函数按顺序做了四道守卫:
- 模式守卫:
configs.contextMenu !== true && configs.contextMenu != null && configs.contextMenu !== mode时直接返回——这就是 frontmatter 中dev/build值生效的位置,mode来自 packages/client/env.ts 的运行模式; - 原生菜单直通:
ev.shiftKey || ev.defaultPrevented时放行,即"按住 Shift 再右键"始终唤起浏览器原生菜单,菜单底部也明确提示了这一快捷键; - 嵌入模式守卫:
isEmbedded为真时不弹出自定义菜单(嵌入预览场景由宿主接管交互); - 通过全部守卫后调用
openContextMenu(ev.pageX, ev.pageY),把坐标与setupContextMenu()返回的 items ref 存入currentContextMenu这个shallowRef,并preventDefault()+stopPropagation()。
渲染与关闭行为
packages/client/internals/ContextMenu.vue 负责呈现:菜单是 fixed 定位的毛玻璃面板,通过 left / top 计算属性做视口边界钳制(右缘/下缘溢出时自动收回)。small 项渲染为 40x40 的图标按钮并带 title 提示;整行项用 grid-cols-[35px_1fr] 布局,label 为组件时用 <component :is> 渲染。关闭菜单有三种途径:点击外部(onClickOutside)、按下鼠标中键(ev.buttons & 2 的 capture 阶段监听)、窗口失焦。
另外有一个从源码结构看值得注意的交互:当 frontmatter 未显式设置 contextMenu(isExplicitEnabled 为假)时,菜单底部会额外显示"按住 Shift 右键可打开原生菜单"的提示,并且开发环境下多一个 "Disable custom context menu" 按钮——它通过 firstSlide.update({ frontmatter: { contextMenu: false } }) 热更新第一页 frontmatter,一键把菜单关掉(ContextMenu.vue 第 29-38 行)。
用 frontmatter 的 contextMenu 控制启用范围
菜单的开关由幻灯片 frontmatter(即 slides.md 首段 YAML)中的 contextMenu 字段控制,类型定义在 packages/types/src/frontmatter.ts:
---
# 可取值为 boolean、'dev' 或 'build',默认 true
contextMenu: false
---
结合类型与 onContextMenu 的模式守卫:
| 取值 | 效果 |
|---|---|
true(或不写,解析默认 null 表示默认行为) |
开发与构建环境均启用;不写时菜单底部会保留"Shift + 右键唤起原生菜单"的提示区 |
false |
全局禁用自定义菜单,右键直接走浏览器原生菜单 |
'dev' |
仅开发模式(slidev 启动的 dev server)启用 |
'build' |
仅构建产物(如 slidev export / 部署后页面)启用 |
注意 docs/custom/index.md 中的 frontmatter 参考表也列出了该项("enable Slidev's context menu, can be boolean, 'dev' or 'build'")。由于拦截发生在 onContextMenu 内,'build' 取值意味着开发者自己演示时没有菜单、而观众看部署版本时有菜单,适合"演示者靠快捷键、观众靠菜单"的分工场景。
验证与常见问题
- 看不到新菜单项:确认文件位于项目根目录下的
./setup/context-menu.ts(Slidev 约定 setup 目录),且默认导出为defineContextMenuSetup包裹的函数;返回的必须是computed而非普通数组,否则disabled等状态不会随导航响应。 - 图标不显示:字符串图标依赖 UnoCSS 图标规则与
~icons/导入二选一,字符串形式需确保对应图标集合(如 Carbon)已在 uno 配置中可用;组件形式则直接用 unplugin-icons 的 Vue 组件导入。 - 想临时关闭菜单又不改代码:开发模式下直接右键呼出菜单,点击底部的 "Disable custom context menu" 即可,它会替你写入
contextMenu: false。 - 菜单项点击无效:检查是否被
disabled命中——模板中禁用项虽仍响应点击事件绑定,但 Slidev 用op40样式表达不可用;更可靠的做法是在action内自行防御,或让disabled与真实前置条件保持一致(如示例中绑定isPresenter)。
小结
Slidev 的上下文菜单定制建立在一条清晰的分层上:类型层(packages/types/src/context-menu.ts 的可辨识联合)约束条目形态,setup 层(defineContextMenuSetup + 虚拟模块 reduce 链)支持函数式扩展,逻辑层(logic/contextMenu.ts)处理模式/Shift/嵌入三道守卫,视图层(internals/ContextMenu.vue)负责渲染、边界钳制与关闭时机。掌握这条链路后,你既能按文档示例快速追加一个带图标、可响应式禁用的自定义条目,也能精确理解 contextMenu: dev | build 在各环境的实际行为。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00