☰
OpenPencil 响应式工具栏:useToolbarState 钩子实现移动端分类分页导航
2026/9/29 7:48:35 网站建设 项目流程
  • 前端
  • 桌面应用
  • AI 应用
  • MCP 服务

【免费下载链接】open-pencil

AI-native design editor. Open-source Figma alternative.

项目地址:https://gitcode.com/gh_mirrors/op/open-pencil
点击查看免费下载

本文围绕 OpenPencil 前端 SDK(@open-pencil/vue)中的useToolbarState()组合式函数展开,讲解如何基于ToolbarRoot构建在小屏设备上按类别分页切换的响应式工具栏,并深入源码与编辑器内实际调用场景,验证其分类边界、翻页语义与滑动方向动画的工作原理。

一、useToolbarState 的定位与解决的问题

OpenPencil 是一个 AI 原生的开源设计编辑器,其编辑器界面依赖大量 Vue 组合式函数与“无头(headless)”原语(primitive)来组织交互逻辑。在小屏设备上,工具栏空间有限,无法一次性展示所有工具,因此需要将工具按类别分组、分页展示。

useToolbarState()正是为此设计的“移动端分类分页状态助手”,官方 API 文档将其定位为mobile-category paging state(移动端分类分页状态)。它返回:

  • 当前分类索引mobileCategory
  • 滑动方向slideDirection
  • 翻页能力判断hasPrev/hasNext
  • 翻页动作goPrev()/goNext()
  • 工具激活判定isActive与工具选择键activeKeyForTool

该组合式函数只负责展示层(presentation-oriented)状态,不直接读取编辑器上下文,因此它天然与 useToolbar(读取ToolbarRoot提供的工具上下文)互补:useToolbar回答“当前有哪些工具、哪个处于激活态”,而useToolbarState回答“在窄屏上当前应该展示哪一页工具、能否翻页”。

二、返回状态与方法的完整语义

源码位于 packages/vue/src/primitives/Toolbar/useToolbarState.ts,其完整返回对象如下:

成员类型语义
mobileCategoryRef<number>当前分类页码,从 0 开始,范围[0, CATEGORY_COUNT - 1]
slideDirectionRef<number>滑动方向,-1表示向后翻(上一页),1表示向前翻(下一页),用于驱动入场/离场动画
hasPrevComputedRef<boolean>是否存在上一页(mobileCategory > 0)
hasNextComputedRef<boolean>是否存在下一页(mobileCategory < CATEGORY_COUNT - 1)
isActivefunction判断某个工具定义在当前激活工具下是否处于激活态
activeKeyForToolfunction给定工具定义与激活工具,返回应当显示为“选中”的工具键
goPrev()function翻到上一页,同时将slideDirection置为-1
goNext()function翻到下一页,同时将slideDirection置为1

分类数量边界:CATEGORY_COUNT

源码第 5 行定义了分类总数常量:

const CATEGORY_COUNT = 3

这意味着mobileCategory只会在0、1、2三个页面间切换。在 OpenPencil 编辑器内,这三页分别对应:工具页(tools)、编辑动作页(edit)、排列动作页(arrange)(见下文对 MobileToolbar.vue 的分析)。

翻页的边界保护

goPrev()与goNext()均带边界保护,越界调用会被静默忽略:

function goPrev() { if (!hasPrev.value) return slideDirection.value = -1 mobileCategory.value-- } function goNext() { if (!hasNext.value) return slideDirection.value = 1 mobileCategory.value++ }

因此即使父组件在最后一页反复触发goNext,页码也不会越界;hasPrev/hasNext可用来渲染禁用态的导航按钮。

滑动方向与动画

slideDirection并不参与页码计算,它专门供动画库读取:MobileToolbar.vue中slideVariants按方向计算位移(下一页从右向左滑入x: 20 → 0,上一页则反向),从而让用户明确感知“前进/后退”的方向语义。

三、激活态与选中态的两个辅助函数

useToolbarState额外暴露两个纯函数形式的辅助方法(它们同时也是模块级导出,可直接按需导入测试)。

isToolbarToolActive:判断工具是否激活

export function isToolbarToolActive(tool: EditorToolDef, activeTool: Tool): boolean { return tool.key === activeTool || (tool.flyout?.includes(activeTool) ?? false) }
  • 若工具自身的key等于当前激活工具,返回true;
  • 若该工具带有flyout(弹出子工具组)且flyout列表包含当前激活工具,也视为激活;
  • flyout不存在时通过?? false兜底,保证安全比较。

getToolbarToolSelection:解析当前应选中的工具键

export function getToolbarToolSelection( tool: EditorToolDef, activeTool: Tool, flyoutSelections?: ReadonlyMap<Tool, Tool> ): Tool { if (tool.flyout?.includes(activeTool)) return activeTool return flyoutSelections?.get(tool.key) ?? tool.key }
  • 如果激活工具属于该工具的 flyout 子组,直接返回激活工具(子工具自身高亮);
  • 否则优先返回用户在 flyout 中的历史选择(flyoutSelections映射),没有记录时回退到工具主键tool.key。

这两处逻辑在 MobileToolbar.vue 中被用于同时驱动 flyout 按钮的selected-tool与普通工具按钮的active状态。

四、与相关 API 的组合使用方式

useToolbarState属于工具栏 API 家族中的“展示层”成员,官方文档(use-toolbar-state.md)列出的关联 API 包括:

  • ToolbarRoot:无头工具栏结构原语,负责提供工具、激活工具与选择行为的上下文;
  • ToolbarItem:单个工具栏工具的无头原语,暴露激活态与选择行为;
  • useToolbar:读取ToolbarRoot注入的本地工具栏上下文。

典型的分工是:ToolbarRoot通过作用域插槽暴露{ tools, activeTool, flyoutSelections, actions },useToolbarState提供分页导航状态,ToolbarItem负责渲染单个工具按钮并接入选择动作。

从@open-pencil/vue的导出面看(packages/vue/src/index.ts),ToolbarRoot、ToolbarItem与useToolbar由 packages/vue/src/primitives/Toolbar/index.ts 统一导出;useToolbarState作为独立组合式函数同包导出,供应用层工具栏直接消费。

五、编辑器内的真实调用场景

组装:Toolbar.vue

OpenPencil 应用层的工具栏组件 src/components/Toolbar/Toolbar.vue 是useToolbarState的实际消费方。它同时基于useViewportKind()区分桌面与移动端渲染:

  • 桌面端渲染DesktopToolbar,无需分页;
  • 移动端渲染MobileToolbar,并将useToolbarState()解构出的六个成员传入:
const { mobileCategory, slideDirection, hasPrev, hasNext, goPrev, goNext } = useToolbarState()

模板中通过:mobile-category、:slide-direction、:has-prev、:has-next传给子组件,@prev="goPrev"与@next="goNext"绑定导航按钮事件。

分页渲染:MobileToolbar.vue

在 MobileToolbar.vue 内部,三个分类页面通过v-if/v-else-if/v-else分支渲染:

  • mobileCategory === 0:tools(data-test-id="mobile-toolbar-tools"),渲染全部工具按钮与 flyout;
  • mobileCategory === 1:edit(data-test-id="mobile-toolbar-edit"),渲染编辑动作组editActions;
  • 其余(即2):arrange(data-test-id="mobile-toolbar-arrange"),渲染排列动作组arrangeActions。

页面切换由AnimatePresence mode="popLayout" :custom="slideDirection"驱动,配合slideVariants(初始位移动画)完成方向感知的转场:

const slideVariants = { initial: (dir: unknown) => ({ opacity: 0, x: (dir as number) * 20 }), animate: { opacity: 1, x: 0 }, exit: (dir: unknown) => ({ opacity: 0, x: (dir as number) * -20 }) }

hasPrev/hasNext同时控制左右导航按钮的禁用与透明度:不可翻页时按钮通过navigationClass(true)获得禁用样式,并通过:animate="{ opacity: hasPrev ? 1 : 0 }"淡出隐藏,对应按钮的data-test-id分别为mobile-toolbar-prev与mobile-toolbar-next。

六、动手实践:构建自己的分页工具栏

useToolbarState不依赖任何编辑器内部上下文,因此可以在自定义工具栏壳层中直接复用。一个最小可运行的分页工具栏骨架如下:

<script setup lang="ts"> import { useToolbarState } from '@open-pencil/vue' import { ToolbarRoot, ToolbarItem } from '@open-pencil/vue' const { mobileCategory, hasPrev, hasNext, goPrev, goNext } = useToolbarState() </script> <template> <div class="toolbar-shell"> <button :disabled="!hasPrev" @click="goPrev">‹</button> <ToolbarRoot v-slot="{ tools, activeTool, actions }"> <ToolbarItem v-for="tool in tools" :key="tool.key" :tool="tool.key" v-slot="{ active, actions }"> <button :class="{ active }" @click="actions.select">{{ tool.key }}</button> </ToolbarItem> </ToolbarRoot> <button :disabled="!hasNext" @click="goNext">›</button> <p>当前分类索引:{{ mobileCategory }}</p> </div> </template>

要点:

  1. 用hasPrev/hasNext控制导航按钮的禁用态;
  2. 用mobileCategory决定当前展示哪一组内容(可参照MobileToolbar.vue的三个v-if分支);
  3. 若希望复用编辑器内置的激活态逻辑,直接解构isActive与activeKeyForTool即可;
  4. 若需要方向动画,将slideDirection透传给转场动画的custom参数。

七、小结

useToolbarState()是 OpenPencil 移动端工具栏的“分页大脑”:它以CATEGORY_COUNT = 3为边界管理分类页码,通过goPrev()/goNext()与hasPrev/hasNext提供安全翻页,通过slideDirection驱动方向感知的滑动动画,并通过isActive/activeKeyForTool两个辅助函数解决 flyout 子工具与历史选择的激活态解析。

从实现层面看,packages/vue/src/primitives/Toolbar/useToolbarState.ts 是完全展示层的纯状态逻辑,不触碰编辑器 store 或 DOM,因此既易于单元测试,也可在任意基于ToolbarRoot的自定义工具栏壳层中复用。对希望基于 OpenPencil 构建自定义响应式工具栏(或深入理解其编辑器移动端交互)的开发者,useToolbarState是一个值得优先阅读与复用的入口。

  • 前端
  • 桌面应用
  • AI 应用
  • MCP 服务

【免费下载链接】open-pencil

AI-native design editor. Open-source Figma alternative.

项目地址:https://gitcode.com/gh_mirrors/op/open-pencil
点击查看免费下载
上一篇:如何使用TegraRcmGUI实现Switch payload注入与设备深度探索
下一篇:SVG路径编辑器完全指南:从入门到精通的矢量绘图之旅 🌐✏️

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

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

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

立即咨询