首页
/ Slidev 键盘快捷键配置指南:defineShortcutsSetup 与 ShortcutOptions 深度实战

Slidev 键盘快捷键配置指南:defineShortcutsSetup 与 ShortcutOptions 深度实战

2026-09-05 12:06:30作者:凌朦慧Richard

本篇指南讲解如何在 Slidev 幻灯片中自定义键盘快捷键:从创建 ./setup/shortcuts.ts 入手,结合 @slidev/types 中的 NavOperationsShortcutOptions 类型,掌握默认快捷键体系、键位绑定格式(字符串与计算布尔值)、以及底层注册与自动重复(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 字段(如 gotonext_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 的类型定义看,它提供了 nextprevnextSlideprevSlidego(按序号跳转)、goFirstgoLastdownloadPDFtoggleDarktoggleOverviewtoggleDrawingescapeOverviewshowGotoDialog 等方法,覆盖了演讲控制的主要场景。
  • 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/mathand / 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.tsregisterShortcuts 中被注册。这一段源码揭示了两个容易踩坑的细节:

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(如 gotonext_space)的条目。如果一条都没有,浏览器会直接 alert + console.warn 提醒你可能漏写了 ...base。若你有意完全接管快捷键体系(例如演讲时只用方向键),至少要显式返回一个保留基础 name 的条目来消除警告。反过来,如果只是想替换某个按键(比如让 d 不再切换深色模式),标准做法是先展开 ...base 再按 name 过滤/覆盖对应条目,而不是凭空重建整个数组。

六、小结与延伸

  • 入口文件:项目根目录下的 setup/shortcuts.ts,默认导出 defineShortcutsSetup(...) 的返回值;服务端由虚拟模块 packages/slidev/node/virtual/setups.tssetup/shortcuts.{ts,js,mts,mjs} 自动发现。
  • 类型契约:NavOperations(可用动作)与 ShortcutOptionskey / fn / autoRepeat / name)均定义在 packages/types/src/setups.tsdefineShortcutsSetup 只是类型标注函数(packages/types/src/setups.ts)。
  • 键位格式兼容 useMagicKeys 的字符串写法,也支持 and / or / not 组合的响应式布尔条件。
  • 生效受全局启用条件约束(非输入、非聚焦、非打印、未被锁定),autoRepeat 提供内置的递减间隔重复。
  • 默认快捷键清单见 packages/client/setup/shortcuts.tsdocs/guide/ui.md,自定义后请保留 ...base 或显式返回一个带基础 name 的条目。

按上述方式,你可以为远程演讲(方向键被 IME 占用时改用 enter/backspace 翻页)、录屏演示(避免误触)等场景定制一套专属的 Slidev 快捷键方案。

登录后查看全文
热门项目推荐
相关项目推荐