- 前端
- 桌面应用
- AI 应用
- MCP 服务
【免费下载链接】open-pencil
AI-native design editor. Open-source Figma alternative.
GradientEditorBar是 Open-Pencil(开源 Figma 替代方案,AI 原生设计编辑器)中用于渐变编辑器的无头(headless)可拖拽渐变条原语:它只负责提供状态与指针事件处理,把渐变条的外观完全交给应用自己渲染。读完本文,你将掌握GradientEditorBar的完整 Props / Events / Slots 契约、指针拖拽与 Pointer Capture 的底层原理、它与GradientEditorRoot、GradientEditorStop、useGradientStops的组合方式,以及如何在 Vue 应用中用 scoped slot 自定义出属于自己的渐变条 UI。
GradientEditorBar 是什么
GradientEditorBar是 Open-Pencil 无头 UI 体系中渐变编辑器(GradientEditor)的三个基础原语之一,位于 packages/vue/src/primitives/GradientEditor,与GradientEditorRoot、GradientEditorStop一同从@open-pencil/vue包导出(见 packages/vue/src/index.ts)。
它的定位是"可拖拽渐变条原语":管理当前渐变点(stop)的选中、创建和拖拽行为,并通过默认插槽把「渐变条状态 + 拖拽处理函数」整体暴露给调用方。组件本身不渲染任何可见的渐变点图形——那条彩色渐变背景、每个圆点手柄的外观、尺寸、圆角,全部由应用通过v-slot提供的渲染契约自行决定。这也是"无头组件"(headless component)的典型设计:逻辑与表现完全解耦,便于不同产品复用同一套交互逻辑而呈现完全不同的视觉。
源码实现:GradientEditorBar.vue
Props:输入哪些数据
GradientEditorBar接收三个必填 prop(见 GradientEditorBar.vue):
| Prop | 类型 | 必填 | 说明 |
|---|---|---|---|
stops | GradientStop[] | 是 | 当前渐变点列表 |
activeStopIndex | number | 是 | 当前选中(激活)的渐变点下标 |
barBackground | string | 是 | 渐变条的 CSS background 字符串 |
其中GradientStop类型来自@open-pencil/scene-graph包,表示一个渐变点(颜色color+ 位置position,位置为 0~1 的小数)。barBackground是一个完整的 CSS background 值,通常由调用方根据 stops 动态计算(见下文useGradientStops),它让渐变条本体可以直接渲染出"颜色从左到右过渡"的预览效果。
组件还支持一个可选 propui,用于注入主题类名(例如ui.bar对应渐变条容器元素的 class)。Open-Pencil 自身的填充面板就传入了{ bar: 'relative mb-2 h-6 rounded' }来美化渐变条容器(见 src/components/fill-picker/GradientEditor.vue)。
Events:对外发出哪些交互
组件定义了两个事件(见 GradientEditorBar.vue):
| 事件 | 载荷 | 触发时机 |
|---|---|---|
selectStop | index: number | 某个渐变点被选中(按下)时 |
dragStop | index: number, position: number | 渐变点被拖拽的过程中,持续发出 |
position是归一化后的 0~1 小数,表示渐变点在条上的横向位置。调用方在dragStop处理器中把新位置写回渐变数据模型,即可实现拖拽实时更新(Open-Pencil 中对应root.actions.dragStop)。
Default Slot:完整渲染契约
GradientEditorBar的默认插槽是它最核心的对外接口——一个「完整渐变条渲染契约」,把状态和拖拽处理函数一并交出。插槽 props 如下(与官方文档一致):
{ stops: GradientStop[] activeStopIndex: number barBackground: string barRef: (el: unknown) => void onStopPointerDown: (index: number, event: PointerEvent) => void onPointerMove: (event: PointerEvent) => void onPointerUp: () => void draggingIndex: number | null }注意:组件实际实现中(GradientEditorBar.vue)把指针处理函数封装为actions.stopPointerDown(见模板中:actions="actions"的插槽绑定),并通过barRef(由@vueuse/core的templateRef维护)将容器元素引用暴露给插槽,用于命中测试与 Pointer Capture。因此在实际使用中,插槽里拿到的是stops、activeStopIndex、bar-background、actions、dragging-index等字段。文档中的onStopPointerDown/onPointerMove/onPointerUp与实现中的actions.stopPointerDown为同一职责的不同命名表达,使用时以当前仓库源码为准。
指针拖拽的底层原理
渐变条的核心交互——拖拽渐变点改位置——的实现位于 GradientEditorBar.vue,逻辑清晰且值得直接阅读:
按下(
stopPointerDown):发出selectStop选中该点,记录draggingIndex,并调用barRef.value?.setPointerCapture(e.pointerId)把指针捕获绑定到渐变条容器上。Pointer Capture 保证即使用户把指针拖出渐变条边界,pointermove/pointerup事件仍会继续派发给渐变条,拖拽不会"脱手"。移动(
onPointerMove):先校验draggingIndex !== null且容器hasPointerCapture(e.pointerId),然后通过el.getBoundingClientRect()把指针的clientX换算为归一化位置:const pos = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))结果被钳制在 0~1 之间,因此无论指针如何越界,渐变点位置都不会超出条的范围;随后发出
dragStop(index, pos)。松开(
onPointerUp):将draggingIndex重置为null,结束拖拽会话。
这套实现不依赖任何拖拽库,仅用原生 Pointer Events + Pointer Capture 完成,代码量小、行为可预期,适合作为自定义渐变条的交互参考。
与 useGradientStops 的数据流配合
barBackground、stops、activeStopIndex这些数据从哪来?答案在useGradientStops组合式函数中(useGradientStops.ts)。它以fill(渐变填充对象)为输入,输出渐变编辑所需的全套状态与操作:
stops:由fill.gradientStops ?? []计算得出;activeStopIndex:当前激活点下标(初始为 0);barBackground:自动由 stops 生成 CSS 渐变字符串——linear-gradient(to right, ${stops.map(s => colorToCSS(s.color) + ' ' + s.position * 100 + '%')})(见 useGradientStops.ts),每个渐变点的颜色与百分比位置被序列化进 background,渐变条本体因此无需额外渲染即可呈现连续渐变预览;dragStop(index, position):将新位置写回对应渐变点并触发onUpdate回调。
GradientEditorRoot(GradientEditorRoot.vue)在内部调用useGradientStops,再通过作用域插槽把上述状态与actions(含dragStop、selectStop、updateStopPosition等)暴露给GradientEditorBar。于是典型的数据流是:
GradientEditorRoot (fill 输入, useGradientStops 状态) │ 通过 slot 下发 stops / activeStopIndex / barBackground / actions ▼ GradientEditorBar (接收 props,管理指针拖拽) │ 发出 selectStop / dragStop ▼ root.actions.selectStop / dragStop → 写回新的 Fill → @update 上抛给应用完整使用示例
官方文档给出了最小可运行的骨架示例——用v-slot="ctx"拿到渲染契约,再交给自己的渐变条组件:
<GradientEditorBar :stops="stops" :active-stop-index="activeStopIndex" :bar-background="barBackground" @select-stop="selectStop" @drag-stop="dragStop" v-slot="ctx" > <MyGradientBar v-bind="ctx" /> </GradientEditorBar>在实际项目中,GradientEditorBar通常是放在GradientEditorRoot的插槽里使用的,因为stops/activeStopIndex/barBackground都由 root 通过useGradientStops提供。Open-Pencil 自己的填充面板 src/components/fill-picker/GradientEditor.vue 就是最完整的实战范本,值得对照阅读:
<GradientEditorRoot :fill="fill" @update="emit('update', $event)" v-slot="root"> <GradientEditorBar :stops="root.stops" :active-stop-index="root.activeStopIndex" :bar-background="root.barBackground" :ui="{ bar: 'relative mb-2 h-6 rounded' }" @select-stop="root.actions.selectStop" @drag-stop="root.actions.dragStop" v-slot="bar" > <GradientEditorStop v-for="(stop, idx) in bar.stops" :key="idx" :stop="stop" :index="idx" :active="idx === bar.activeStopIndex" :dragging="idx === bar.draggingIndex" :removable="bar.stops.length > 2" :style="{ left: `${stop.position * 100}%`, background: colorToCSS(stop.color) }" @select="root.actions.selectStop" @update-position="root.actions.updateStopPosition" @remove="root.actions.removeStop" @pointerdown.stop="bar.actions.stopPointerDown(idx, $event)" /> </GradientEditorBar> </GradientEditorRoot>这段代码揭示了几条关键用法:
draggingIndex用于视觉反馈:通过:dragging="idx === bar.draggingIndex"判断当前拖拽中的点,从而切换高亮样式;- 渐变点定位完全交给应用:
GradientEditorBar不负责摆放手柄,调用方用left: position * 100%的绝对定位来放置每个点,并传入colorToCSS(stop.color)作为点背景色; - 按下处理要阻止冒泡:
@pointerdown.stop="bar.actions.stopPointerDown(idx, $event)"避免点击事件穿透到其他逻辑; - 渐变点本体复用
GradientEditorStop:它提供位置、透明度、颜色、激活状态及对应的修改/删除动作(详见 GradientEditorStop 文档与 GradientEditorStop.vue 实现)。GradientEditorStop还内置了键盘无障碍支持:交互模式下可用左右方向键(配合 Shift 每步 10%)微调位置、Home/End 跳到两端、Delete/Backspace 删除点,并暴露role="slider"与aria-valuenow等无障碍属性。
常见组合模式与选型建议
- 只想要一条能拖的渐变条:单独使用
GradientEditorBar,把插槽渲染成自己的条与手柄,自己维护stops并在dragStop里更新数据。 - 想要完整的渐变编辑面板:用
GradientEditorRoot包裹GradientEditorBar(配合GradientEditorStop),由useGradientStops统一管理类型切换(线性/径向/角度/菱形)、点增删、位置/颜色/透明度编辑,参考上面的填充面板示例。 - 完全自定义视觉:因为插槽是完整渲染契约,你可以把渐变条渲染成任意形态——轨道、胶囊、色带,甚至把手柄换成自定义图标,交互逻辑零改动。
相关 API
- GradientEditorRoot:协调渐变类型、激活点与颜色/位置/透明度变更,接收
fill、发出update,是渐变编辑器的状态中枢; - GradientEditorStop:单个渐变点的位置、透明度、颜色与激活状态,以及修改、删除动作;
- useGradientStops:
useGradientStops(fill, onUpdate)组合式函数,管理激活点、渐变类型、拖拽与位置/颜色/透明度变更,可在不重复实现逻辑的前提下搭建自己的渐变编辑界面。
三个原语 + 一个组合式函数共同构成了 Open-Pencil 的渐变编辑基础能力:GradientEditorBar专注"条与拖拽",GradientEditorRoot专注"状态与数据流",GradientEditorStop专注"点的呈现与编辑",useGradientStops则是 root 背后的逻辑引擎。
- 前端
- 桌面应用
- AI 应用
- MCP 服务
【免费下载链接】open-pencil
AI-native design editor. Open-source Figma alternative.
相关推荐
open-pencil GradientEditorBar 详解:用 Headless Vue 原语构建可拖拽的渐变 Stop 条
open pencil GradientEditorBar 详解:用 Headless Vue 原语构建可拖拽的渐变 Stop 条 本文基于 open penc
前端桌面应用AI 应用MCP 服务Open-Pencil SDK 渐变编辑器指南:GradientEditorBar 无头组件原理与实战
Open Pencil SDK 渐变编辑器指南:GradientEditorBar 无头组件原理与实战 GradientEditorBar 是 Open Pen
前端桌面应用AI 应用MCP 服务Open-Pencil SDK:GradientEditorBar 无头渐变编辑条原语实战指南
Open Pencil SDK:GradientEditorBar 无头渐变编辑条原语实战指南 GradientEditorBar 是 Open Pencil
前端桌面应用AI 应用MCP 服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考