拓冰建站拓冰建站
首页 / 资讯中心 / 正文

GrapesJS CanvasSpot 画布标注点 API 全面解析:从内置类型到自定义实现

GrapesJS CanvasSpot 画布标注点 API 全面解析从内置类型到自定义实现【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjsCanvasSpot画布标注点是 GrapesJS 中绘制在画布之上的一层交互元素被广泛用于承载选中框、悬停高亮、间距标注与拖拽落点提示等编辑器交互。本文以 docs/api/canvas_spot.md 为骨架结合 CanvasSpot 模型源码 与 Canvas 模块 的实现系统讲解 CanvasSpot 的数据结构、三大核心方法、内置类型、生命周期事件与自定义扩展方案读完即可用一套 API 掌控画布上的所有覆盖层。CanvasSpot 是什么Canvas spots 是绘制在画布顶部的元素本质上是独立于 iframe 文档内容之外的一层 DOM。它们可以用来表示任何你需要的可视化元素但最常见的用途是渲染信息和管理画布内渲染的组件——例如组件被选中时的蓝色边框、鼠标悬停时的轮廓、margin/padding 间距可视化以及拖拽时的落点指示。从源码结构看CanvasSpot 继承自 ModuleModelCanvasSpot.ts#L64每个 spot 实例都挂在 Canvas 模块的 spots 集合CanvasSpots中与编辑器的事件系统如canvas:spot:add、canvas:spot:update、canvas:spot:remove紧密联动。画布中的物理位置在画布 DOM 结构上spots 层被渲染在data-frames容器内部、data-spots标记的 div 中CanvasView.ts#L54-L63div classcv-canvas__frames>canvasSpot.getBoxRect(); // { width: 100, height: 50, x: 0, y: 0 }参数opts可选默认{}其类型为 GetBoxRectOptions支持local返回局部坐标与toScreen转换到屏幕坐标系选项。其内部解析顺序如下CanvasSpot.ts#L98-L115若设置了attributes.boxRect为函数则调用函数取值为对象则直接返回否则若存在组件元素el与画布视图调用CanvasView.getElBoxRect(el, opts)依据组件真实 DOM 计算盒尺寸兜底返回全 0 的矩形{ x: 0, y: 0, width: 0, height: 0 }。其中el通过componentView?.el获取CanvasSpot.ts#L86-L88这意味着即使只传了component也会自动解析出其视图元素来计算几何信息。getStyle(opts)生成定位样式根据盒尺寸生成可直接应用到 DOM 上的 CSS 样式对象canvasSpot.getStyle(); // { width: 100px, height: ..., ... }实现上CanvasSpot.ts#L125-L136它先取opts.boxRect若传入否则调用this.getBoxRect(opts)然后输出{ width: ${width}px, height: ${height}px, top: 0, left: 0, position: absolute, translate: ${x}px ${y}px, }返回类型为PartialCSSStyleDeclaration。这里值得注意两点使用 CSStranslate属性承载x/y偏移而非top/left使 spot 层可以以左上角为原点、通过独立的 transform 平移避免与动画/变换冲突width/height直接映射盒尺寸因此一个自定义 spot 的渲染器只需拿到getStyle()的结果设置到根元素即可完成定位。isType(type)类型判断检查 spot 是否属于指定类型用作类型守卫TypeScriptthis is CanvasSpotEcanvasSpot.isType(select); // true | false实现为return this.type typeCanvasSpot.ts#L145-L147其中type是属性 getterCanvasSpot.ts#L72-L74。配合泛型isTypeE extends T使用可以在渲染层安全地把 spot 收窄为具体子类型。内置类型Built-in Types通过枚举 CanvasSpotBuiltInTypes 定义了 5 种内置类型类型值含义典型来源select选中态标注组件被选中时由 Editor.addSelected 添加取消选中时移除hover悬停态标注组件悬停时由 Editor.setHovered 添加spacing间距标注margin/padding 可视化悬停组件时由setHovered添加Editor.ts#L730-L732target拖拽落点标注拖放排序时由 ComponentSorter 以固定 idsorter-target添加resize缩放手柄标注组件可缩放时由 SelectComponent.initResize 添加从调用链可以清晰看到内置 spot 如何驱动编辑器交互选中editor.addSelected()→Canvas.addSpot({ type: select, component })editor.removeSelected()→Canvas.removeSpots({ type: select, component })Editor.ts#L656-L687悬停editor.setHovered(cmp)会先移除旧组件上的hover/spacingspot再为新的悬停组件添加Editor.ts#L714-L734拖拽落点ComponentSorter声明了一个固定id: sorter-target、type: target的 spot 常量ComponentSorter.ts#L19-L22拖拽过程中持续更新其位置以指示插入点。这 5 种类型同时也是customSpots配置和hasCustomSpot()判定所针对的枚举范围。通过 Canvas 模块操作 SpotsCanvas 模块editor.Canvas是操作 spot 集合的入口核心方法在 packages/core/src/canvas/index.ts 中。addSpot新增或更新// 添加内置类型的 spot const spot canvas.addSpot({ type: select, // select 是内置 spot 之一 component: editor.getSelected(), }); // 添加自定义类型的 spot const spot canvas.addSpot({ type: my-custom-spot, component: editor.getSelected(), }); // 复用同一 id 即可更新已有 spot canvas.addSpot({ id: spot.id, component: anotherComponent, });实现要点index.ts#L800-L820先按传入属性有id时优先按id在集合中查找若命中则直接spot.set(spotProps)走更新逻辑否则基于componentView或component.view的 cid 生成唯一 id 并new CanvasSpot(...)加入集合。这一设计使得添加即插入、重复 id 即更新成为统一的上层语义。getSpots按属性筛选canvas.addSpot({ type: select, component: cmp1 }); canvas.addSpot({ type: select, component: cmp2 }); canvas.addSpot({ type: target, component: cmp3 }); // 获取全部 spot const allSpots canvas.getSpots(); allSpots.length; // 3 // 按 type 筛选 const allSelectSpots canvas.getSpots({ type: select }); allSelectSpots.length; // 2不传参数返回全部 spot传属性对象时基于spots.where()筛选传入id时按 id 精确匹配index.ts#L839-L841。removeSpots删除// 按属性批量删除 canvas.removeSpots({ type: select }); // 按 spot 实例数组删除 const filteredSpots canvas.getSpots().filter(spot myCustomCondition); canvas.removeSpots(filteredSpots); // 不传参数删除全部 canvas.removeSpots();内部区分传入的是属性还是实例数组数组直接走集合remove对象则先getSpots查询再移除index.ts#L862-L866。hasCustomSpot内置类型接管检查grapesjs.init({ // ... canvas: { // 声明内置 target spot 由自定义渲染接管 customSpots: { target: true } } }); canvas.hasCustomSpot(select); // false canvas.hasCustomSpot(target); // true该方法检查配置customSpots是否声明了某个内置类型由外部接管渲染index.ts#L884-L892。一旦某个内置类型被接管框架内建的高亮/标注渲染就会被禁用改为触发对应事件让自定义渲染器接手。refresh 与 refreshSpots手动刷新// 仅刷新 spot 位置 canvas.refresh({ spots: true }); // 全量刷新含 tools canvas.refresh({ all: true });refreshSpots()内部调用spots.refresh()触发canvas:spot事件CanvasSpots.ts#L32-L35。集合还会监听component:resize、styleable:change、component:update、frame:updated、undo、redo等事件并防抖批量刷新CanvasSpots.ts#L20-L21因此大多数场景下 spot 位置会随组件变化自动更新无需手动调用。生命周期事件spots 集合CanvasSpots.ts在增、改、删、刷新四个时机向编辑器广播事件types.ts#L53-L114事件触发时机回调参数canvas:spot任一 spot 变更后含防抖刷新无canvas:spot:add新增 spot{ spot }canvas:spot:updatespot 属性更新{ spot }canvas:spot:remove移除 spot{ spot }示例editor.on(canvas:spot, () { console.log(Spots, editor.Canvas.getSpots()); }); editor.on(canvas:spot:add, ({ spot }) { console.log(Spot added, spot); }); editor.on(canvas:spot:update, ({ spot }) { console.log(Spot updated, spot); }); editor.on(canvas:spot:remove, ({ spot }) { console.log(Spot removed, spot); });从实现看add/update/remove事件触发后都会再触发一次防抖的canvas:spot刷新CanvasSpots.ts#L49-L54用于统一通知渲染层重绘。自定义 Spot 实战从渲染接管到完整示例用 customSpots 接管内置渲染当canvas.customSpots配置为true或声明了某个内置类型时框架会跳过对应内建渲染。该配置在 config.ts#L88-L98 中定义// 仅禁用 hover 类型 spot 的内建渲染 grapesjs.init({ canvas: { customSpots: { hover: true }, }, }); // 禁用全部内置 spot 的内建渲染 grapesjs.init({ canvas: { customSpots: true, }, });该配置如何影响实际交互可以从内置命令源码中看到直接证据SelectComponent在更新工具栏时检查hasCustomSpot(Select)若已接管则隐藏默认 toolbar 元素SelectComponent.ts#L435-L437悬停高亮逻辑同样根据hasCustomSpot(Hover)决定走showHighlighter还是跳过SelectComponent.ts#L505-L525缩放进场时若hasCustomSpot(Resize)为真则只添加resizespot 而不启动内建 resize 命令SelectComponent.ts#L386-L403ShowOffset间距标注命令在hasCustomSpot(Spacing)时直接跳过内建的 margin/padding 可视化ShowOffset.ts#L39-L41ComponentView.updateStatus在hasCustomSpot(Select)/hasCustomSpot(Target)为真时不追加gjs-selected/gjs-selected-parent等 CSS 类ComponentView.ts#L251-L259。完整示例自定义悬停提示spot下面组合使用addSpot、getSpots、removeSpots与事件实现一个跟随组件悬停显示自定义提示的自定义 spotconst editor grapesjs.init({ container: #gjs, // 声明 hover 类型由自定义渲染接管 canvas: { customSpots: { hover: true } }, }); const customHoverSpot { type: hover-info, component: null, }; // 监听画布 hover 事件内部基于 ComponentSorter 触发 editor.on(component:hover, (component) { // 清掉旧 spot editor.Canvas.removeSpots({ type: hover-info }); // 通过固定 boxRect 定位也可传 component 锚定 editor.Canvas.addSpot({ ...customHoverSpot, component, boxRect: () { const el component.getEl(); const rect el.getBoundingClientRect(); return { width: 120, height: 40, x: rect.left, y: rect.top - 45 }; }, }); }); // 在 canvas:spot:update / canvas:spot 中拿到 spot 并渲染 editor.on(canvas:spot:update, ({ spot }) { if (spot.isType(hover-info)) { const style spot.getStyle(); const el document.querySelector(#my-spot-layer); Object.assign(el.style, style); el.textContent 选中: ${spot.component.getName?.() ?? spot.component.get(type)}; } });手动渲染器与 getStyle 的配合由于getStyle()已经返回包含position: absolute、translate与宽高的完整样式对象任何自定义渲染器纯 DOM、框架组件均可都可以统一按取 style → 应用到根元素的套路实现。若需要将 world 坐标转换为屏幕坐标例如滚动容器内的固定提示层还可以使用canvas.getWorldRectToScreen(boxRect)index.ts#L894-L901。与工具层的取舍何时用 Spot 而非 Tools从源码结构看Canvas 模块同时维护着两类覆盖机制getToolsEl()/getHighlighter()/getBadgeEl()等返回的是 tools 层 DOM 元素index.ts#L166-L249由命令直接操作而 spots 层以数据驱动、自动跟随组件刷新。可以据此给出选型建议需要持久跟踪组件几何选中框、悬停框、间距标注用 Spot事件驱动的自动刷新替你处理滚动、缩放、undo/redo 场景一次性弹出信息或强交互控件toolbar 按钮、badge、resizer 手柄沿用 tools 层更直接避免引入额外的状态管理。总结CanvasSpot 是 GrapesJS 画布覆盖层的数据抽象通过id/type/component/componentView/boxRect描述画什么、画在哪通过getBoxRect()/getStyle()/isType()支撑渲染通过canvas.addSpot()/getSpots()/removeSpots()/hasCustomSpot()完成管理通过canvas:spot*事件串起生命周期。内置的select、hover、spacing、target、resize五类 spot 构成了选中、悬停、间距可视化、拖拽落点与缩放等核心交互的底层支撑而customSpots配置则为开发者留出了完整接管任意内置交互渲染的扩展口子。要进一步深入可继续阅读 Canvas 模块索引、spots 集合实现、内置命令 SelectComponent 以及 canvas 配置定义。【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门