Slidev 键盘快捷键配置指南:defineShortcutsSetup 与 ShortcutOptions 深度实战
本篇指南讲解如何在 Slidev 幻灯片中自定义键盘快捷键:从创建 ./setup/shortcuts.ts 入手,结合 @slidev/types 中的 NavOperations 与 ShortcutOptions 类型,掌握默认快捷键体系、键位绑定格式(字符串与计算布尔值)、以及底层注册与自动重复(autoRepeat)的实现机制。读完后你可以为演讲场景定制任意按键行为(如把翻页绑定到 enter/backspace),并理解每条快捷键在 Slidev 客户端中的完整调用链。
一、起点:默认的快捷键体系
在动手自定义之前,先了解 Slidev 内置了哪些快捷键。它们在 packages/client/setup/shortcuts.ts 中以 ShortcutOptions[] 数组的形式定义,官方文档 Navigation Actions 表格 中列出的核心按键如下:
| 快捷键 | 作用 |
|---|---|
right / space |
下一个动画或幻灯片 |
left / shift+space |
上一个动画或幻灯片 |
up / down |
上一张 / 下一张幻灯片(跨页) |
shift+left / shift+right |
上一张 / 下一张幻灯片(跨页) |
o / 反引号 |
打开/关闭快速总览(Quick Overview) |
d |
切换深色模式 |
g |
显示跳转到……(goto...)对话框 |
escape |
关闭总览 |
enter(总览中) |
跳转到当前选中的幻灯片 |
f |
切换全屏 |
pageUp / pageDown |
上一页 / 下一页 |
从源码结构看,完整的默认快捷键(packages/client/setup/shortcuts.ts)比文档表格更细,例如 next_space 明确排除了 shift 修饰键(and(space, not(shift))),方向键翻页还额外受 navViaArrowKeys 条件约束——即总览模式未打开且没有正在拖拽的元素时才生效:
// packages/client/setup/shortcuts.ts
const navViaArrowKeys = and(not(showOverview), not(activeDragElement))
let shortcuts: ShortcutOptions[] = [
{ name: 'next_space', key: and(space, not(shift)), fn: next, autoRepeat: true },
{ name: 'prev_space', key: and(space, shift), fn: prev, autoRepeat: true },
{ name: 'next_right', key: and(right, not(shift), navViaArrowKeys), fn: next, autoRepeat: true },
{ name: 'prev_left', key: and(left, not(shift), navViaArrowKeys), fn: prev, autoRepeat: true },
// ... 其余条目(pageUp/pageDown、总览内导航、goto、深色模式切换等)
]
每条默认快捷键都带有 name 字段(如 goto、next_space),这一点在后面"迁移警告"一节至关重要。
二、Getting Started:创建 ./setup/shortcuts.ts
按官方文档 docs/custom/config-shortcuts.md 给出的步骤,在幻灯片项目根目录(slides.md 所在处)下创建 setup/shortcuts.ts 文件:
import type { NavOperations, ShortcutOptions } from '@slidev/types'
import { defineShortcutsSetup } from '@slidev/types'
export default defineShortcutsSetup((nav: NavOperations, base: ShortcutOptions[]) => {
return [
...base, // keep the existing shortcuts
{
key: 'enter',
fn: () => nav.next(),
autoRepeat: true,
},
{
key: 'backspace',
fn: () => nav.prev(),
autoRepeat: true,
},
]
})
在 setup 函数中,通过返回一个新的快捷键数组即可自定义键盘行为。上面的示例将 next 操作绑定到 enter、将 prev 操作绑定到 backspace。
这里有两个参数值得注意:
nav: NavOperations:导航操作集合。从 packages/types/src/setups.ts 的类型定义看,它提供了next、prev、nextSlide、prevSlide、go(按序号跳转)、goFirst、goLast、downloadPDF、toggleDark、toggleOverview、toggleDrawing、escapeOverview、showGotoDialog等方法,覆盖了演讲控制的主要场景。base: ShortcutOptions[]:当前所有"基础快捷键"的完整数组。通过...base展开保留既有绑定(官方示例特意加了注释keep the existing shortcuts),再追加或覆盖你自己的条目。
默认快捷键与导航操作的完整说明,可参考 Navigation Actions 文档。
setup 文件是如何被加载的
这个约定并非魔法。服务端侧的虚拟模块模板 packages/slidev/node/virtual/setups.ts 会为 shortcuts(以及其他 9 个 setup 模块)生成一个虚拟模块 /@slidev/setups/shortcuts,其内容是按项目根目录对 setup/shortcuts.{ts,js,mts,mjs} 做 glob 导入后过滤掉空值组成的数组:
// packages/slidev/node/virtual/setups.ts
const setupModules = ['shiki', 'code-runners', 'monaco', 'mermaid', 'mermaid-renderer', 'main', 'root', 'routes', 'shortcuts', 'context-menu']
客户端则在 packages/client/setup/shortcuts.ts 中遍历这些 setup 并逐个应用:
for (const setup of setups) {
shortcuts = setup(context, shortcuts)
}
也就是说,你的 setup 函数会串行接到链上——前一个 setup 的返回值就是后一个 setup 收到的 base。这也意味着:只要你的函数返回的数组里丢掉了所有带基础 name 的条目,就会触发警告(见下文第五节)。
三、Key Binding Format:字符串与计算布尔值
文档中 "Key Binding Format" 一节指出:每个快捷键的 key 可以是字符串(如 'Shift+Ctrl+A'),也可以是计算布尔值(computed boolean)。字符串格式的键位语义遵循 VueUse 的 useMagicKeys(官方文档直接指向了该 API),因此 useMagicKeys 支持的所有按键名和修饰键组合写法都适用。
从 ShortcutOptions 的完整类型定义(packages/types/src/setups.ts)可以确认各字段的精确含义:
// packages/types/src/setups.ts
export interface ShortcutOptions {
key: string | Ref<boolean> // 字符串键名,或响应式的布尔条件
fn?: () => void // 触发时执行的回调
autoRepeat?: boolean // 按住不放时是否自动连续触发
name?: string // 可选标识名
}
两种形式在实现层面的处理路径不同,见 packages/client/logic/shortcuts.ts:
function shortcut(key: string | Ref<boolean>, fn: Fn, autoRepeat = false) {
if (typeof key === 'string')
key = magicKeys[key] // 字符串 → 通过 useMagicKeys 转成对应的 Ref<boolean>
// ...
}
计算布尔值则让你可以像默认快捷键那样自由组合逻辑。packages/client/setup/shortcuts.ts 中使用 @vueuse/math 的 and / not / or 做组合(例如 and(d, not(drawingEnabled)) 表示"d 键按下且未处于绘制模式时才切换深色模式")。你可以照葫芦画瓢在自定义 setup 中写条件键,例如"仅在非绘制状态下、且总览未打开时,用 n 键跳到第一页":
// ./setup/shortcuts.ts
import type { NavOperations, ShortcutOptions } from '@slidev/types'
import { defineShortcutsSetup } from '@slidev/types'
import { and, not } from '@vueuse/math'
import { magicKeys, showOverview } from '@slidev/client'
export default defineShortcutsSetup((nav: NavOperations, base: ShortcutOptions[]) => [
...base,
{
// 计算布尔值形式:n 键按下 && 未打开总览
key: and(magicKeys.n, not(showOverview)),
fn: nav.goFirst,
},
])
autoRepeat 用于需要"按住连续翻页"的场景,官方示例中 enter/backspace 都开启了它;对于点一下触发一次的动作(如打开 goto 对话框)则保持默认(不重复)。
四、注册与触发机制:快捷键实际如何生效
自定义的数组最终在 packages/client/logic/shortcuts.ts 的 registerShortcuts 中被注册。这一段源码揭示了两个容易踩坑的细节:
1. 快捷键有全局的"启用开关"
// packages/client/logic/shortcuts.ts
const enabled = and(
not(isInputting), // 不在输入状态(如 Monaco 编辑器、输入框)
not(isOnFocus), // 焦点不在交互元素上
not(isPrintMode), // 非打印模式
shortcutsEnabled, // 用户可开启/关闭
not(shortcutsLocked), // 未被锁定
)
每个快捷键的最终触发条件是 and(key, enabled),即你的按键条件与上述全局条件同时满足。这解释了"为什么在代码编辑器里按方向键不会翻页"。
2. autoRepeat 的递减间隔实现
// packages/client/logic/shortcuts.ts
const trigger = () => {
clearTimeout(timer)
if (!source.value) {
count = 0
return
}
if (autoRepeat) {
timer = setTimeout(trigger, Math.max(1000 - count * 250, 150))
count++
}
fn()
}
return watch(source, trigger, { flush: 'sync' })
按住按键时,首次间隔约 1000ms,每触发一次减少 250ms,最快收敛到 150ms——形成"越按越快"的手感。这个行为对 autoRepeat: true 的自定义按键同样生效,无需自己处理定时器。
另外,f 键切换全屏走的是另一条注册路径 strokeShortcut(基于 onKeyStroke,且忽略 ev.repeat),它不经过 defineShortcutsSetup 的数组,因此无法通过 setup 覆盖(packages/client/logic/shortcuts.ts)。
五、迁移陷阱:为什么必须"保留基础快捷键"
packages/client/setup/shortcuts.ts 的末尾有一段防御性检查(packages/client/setup/shortcuts.ts):
const baseShortcutNames = new Set(shortcuts.map(s => s.name))
for (const setup of setups) {
shortcuts = setup(context, shortcuts)
}
const remainingBaseShortcutNames = shortcuts.filter(s => s.name && baseShortcutNames.has(s.name))
if (remainingBaseShortcutNames.length === 0) {
const message = [
'========== WARNING ==========',
'defineShortcutsSetup did not return any of the base shortcuts.',
'See https://sli.dev/custom/config-shortcuts.html for migration.',
'If it is intentional, return at least one shortcut with one of the base names (e.g. name:"goto").',
].join('\n\n')
alert(message)
console.warn(message)
}
这段逻辑在 setup 链执行完毕后,检查最终数组中是否还残留任何带有基础 name(如 goto、next_space)的条目。如果一条都没有,浏览器会直接 alert + console.warn 提醒你可能漏写了 ...base。若你有意完全接管快捷键体系(例如演讲时只用方向键),至少要显式返回一个保留基础 name 的条目来消除警告。反过来,如果只是想替换某个按键(比如让 d 不再切换深色模式),标准做法是先展开 ...base 再按 name 过滤/覆盖对应条目,而不是凭空重建整个数组。
六、小结与延伸
- 入口文件:项目根目录下的
setup/shortcuts.ts,默认导出defineShortcutsSetup(...)的返回值;服务端由虚拟模块 packages/slidev/node/virtual/setups.ts 按setup/shortcuts.{ts,js,mts,mjs}自动发现。 - 类型契约:
NavOperations(可用动作)与ShortcutOptions(key/fn/autoRepeat/name)均定义在 packages/types/src/setups.ts,defineShortcutsSetup只是类型标注函数(packages/types/src/setups.ts)。 - 键位格式兼容
useMagicKeys的字符串写法,也支持and/or/not组合的响应式布尔条件。 - 生效受全局启用条件约束(非输入、非聚焦、非打印、未被锁定),
autoRepeat提供内置的递减间隔重复。 - 默认快捷键清单见 packages/client/setup/shortcuts.ts 与 docs/guide/ui.md,自定义后请保留
...base或显式返回一个带基础name的条目。
按上述方式,你可以为远程演讲(方向键被 IME 占用时改用 enter/backspace 翻页)、录屏演示(避免误触)等场景定制一套专属的 Slidev 快捷键方案。
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 StartedRust0623
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