Tooltip

浮动 Tooltip(跟随选中元素的悬浮操作条)目前尚未在 @ydesign/react-editor 中提供。
当前版本所有元素操作都集中在顶部的 <Toolbar />。如果你想要"选中文字之后,在文字旁边弹出一个小工具条"这类 Figma/Canva 式的交互,本页说明当前可用的替代方案以及我们未来的计划。
为什么需要 Tooltip?
顶部 Toolbar 是一个水平长条,优点是可见性高、每一项的位置稳定;缺点是:
- 离被操作的元素较远,视线要频繁上下移动
- 小屏 / 多栏布局下横向空间紧张,被迫折叠或滚动
- 对常用的"改颜色、改字号、微调一下大小"等高频操作不够轻量
浮动 Tooltip 的价值就在于:把高频操作直接浮在元素旁边,用完即收。
目前可用的替代方案
在 Ydesign 原生 Tooltip 发布前,你可以通过以下两种方式实现类似体验:
方案 1:自定义浮层监听 selection:changed
思路:用一个常驻的绝对定位 React 组件,监听 Editor 的选中事件 + 画布变换,计算元素在屏幕上的坐标,然后在该位置渲染自己的工具条。
import { useEffect, useState } from 'react';
import { observer } from 'mobx-react-lite';
const FloatingTooltip = observer(({ store }) => {
const [rect, setRect] = useState<DOMRect | null>(null);
const el = store.selectedElements[0];
useEffect(() => {
if (!el || !store.editor) {
setRect(null);
return;
}
const canvas = store.editor.customCanvas.canvas;
const update = () => {
const bounding = el.getBoundingRect(); // Fabric API
const vpt = canvas.viewportTransform!;
const zoom = vpt[0];
setRect(new DOMRect(bounding.left * zoom + vpt[4], bounding.top * zoom + vpt[5], bounding.width * zoom, bounding.height * zoom));
};
update();
// 画布每次渲染之后同步位置
canvas.on('after:render', update);
return () => {
canvas.off('after:render', update);
};
}, [el, store.editor]);
if (!el || !rect) return null;
return (
<div
style={{
position: 'absolute',
left: rect.left,
top: rect.top - 48, // 元素上方 48px
zIndex: 100,
background: '#fff',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
borderRadius: 8,
padding: '4px 8px',
display: 'flex',
gap: 4,
}}
>
{/* 这里放你的高频操作:颜色、字号、对齐… */}
<button onClick={() => store.set({ fontWeight: 'bold' }, el)}>B</button>
<button onClick={() => store.set({ fontStyle: 'italic' }, el)}>I</button>
<input type="color" value={el.fill as string} onChange={e => store.set({ fill: e.target.value }, el)} />
</div>
);
});
// 在 Workspace 的父容器里和 <Workspace /> 并列渲染即可
<div style={{ position: 'relative', flex: 1 }}>
<Workspace store={store} />
<FloatingTooltip store={store} />
</div>;
💡 关键是
canvas.on('after:render', update)—— Fabric 每次重绘后都会触发,这保证了缩放、平移、画布尺寸变化时浮层会实时跟随。
方案 2:借用 Antd 的 Popover 当成浮层容器
如果你只想在元素点击后弹出一个小窗(而不是一直跟随),直接复用 Antd 的 Popover:
import { Popover } from 'antd';
import { observer } from 'mobx-react-lite';
const ElementPopover = observer(({ store, anchorRef, children }) => {
const visible = store.selectedElementsIds.length > 0;
return (
<Popover open={visible} placement="top" trigger="click" content={children} getPopupContainer={() => anchorRef.current}>
{/* 锚点元素 */}
<div ref={anchorRef} style={{ position: 'absolute' /* 坐标同上 */ }} />
</Popover>
);
});
这种方式开发成本最低,但动画和定位会比较"浏览器默认"。
规划中的原生 Tooltip 组件
我们计划引入一个官方 <Tooltip /> 组件,以插件式的方式挂到 <Workspace /> 旁,实现:
- 自动定位:基于 Fabric
getBoundingRect+ 视口变换,实时跟随 - 避让策略:靠近画布边缘时自动翻转到下方 / 侧边
- 类型分化:和 Toolbar 一样,按选中元素类型切换内容(
TextTooltip/ImageTooltip/ …) - 可定制:通过
componentsprop 替换每种类型的 tooltip,或整体禁用 - 多选合并:同时选中多个元素时显示
ManyTooltip
预期 API(草案)
import { Tooltip } from '@ydesign/react-editor/canvas/tooltip';
import Workspace from '@ydesign/react-editor/canvas/workspace';
<div style={{ position: 'relative', flex: 1 }}>
<Workspace store={store} />
<Tooltip store={store} />
</div>;
带类型定制与一键关闭:
const MyImageTooltip = ({ store, element }) => (
<div>
<button onClick={() => store.editor?.imageCropHandler.cropImg.onEnterCrop(element)}>裁剪</button>
</div>
);
// 只自定义图片类型;禁用文字浮层
<Tooltip
store={store}
components={{
image: MyImageTooltip,
textbox: () => null,
}}
/>;
路线图
- M1 — 基础浮层容器 + 自动跟随(单选)
- M2 — 内置
TextTooltip/ImageTooltip/FigureTooltip - M3 — 多选合并(
ManyTooltip) - M4 — 画布边界避让策略
- M5 — 与顶部
<Toolbar />的"同步隐藏"配置(避免功能冗余)
有兴趣参与设计或内测,欢迎在 GitHub Discussions 留言。
与 Polotno Tooltip 的区别
Polotno 的 <Tooltip /> 组件基于 Konva + Blueprint 实现,和 Ydesign(Fabric + Antd)的底层完全不同,因此:
- 坐标计算方式不一样:Polotno 用 Konva 节点 API;Ydesign 用 Fabric 的
getBoundingRect+viewportTransform - 类型替换粒度不一样:Polotno 可以深入到
TextFill级别;Ydesign 预期按元素类型级别替换(与 Toolbar 对齐) - 无法 API 兼容迁移:Polotno 代码不能直接搬过来用
如果你正从 Polotno 迁移一个 tooltip 方案,建议先用上面的"方案 1"手动接,后续切换到原生 <Tooltip /> 时再重构。
下一步
- 👉 Toolbar 顶部工具栏
- 👉 Workspace 画布工作区
- 👉 定制化