无限画布 DraggableBorad 原理

类似 Figma 这样强大的无限画布,本身是用 WebAssembly 技术实现的,复刻起来难度太大, 如果只想要一个基于传统 HTML & CSS & Javascript 的版本,可以快速使用和修改,可以做到吗?

Heptabase 也有类似这样的无线画布,现在的需求是做一个通用的无线画布组件,其主要特点就是通常响应 drag 的操作为主, 控制画布缩放、移动,控制内部的节点移动和变换等等。

scale: 1
offset: 0 0
origin: 0 0
Block
Node

不考虑节点拖动的前提下,支持触摸板的手势交互,基本操作方面:

  • 用滚轮来控制画布移动
  • Control 键配合滚动来缩放画布
  • 按住空格键配合左键拖动画布

源代码地址

查看这个源码文件,其中引入了 React、Tailwind、react-use 几个工具库,很简单的 demo,你可以在安装好依赖后改改这个文件试试,看看能不能做出你想要的效果。

文件虽然有 200 行,其核心工作只有 onWheel 的部份,不过考虑到鼠标用户只能 Y 轴滚动,需要额外添加一个空格键来配合左键拖动,如此一来就可以全方位拖拽。

看 Pointer 事件代码,先确认用户是想要缩放还是移动?缩放是需要按下 Ctrl 键的,移动是按下 Space 键的, 缩放本身很简单,直接设置 scale 的值即可。

const handleScale = deltaY => { let ratio = -deltaY scaleMark.current += ratio * 0.015 setScale(scaleMark.current) } // 伪代码,实际代码请参考 Github 源文件

需要注意的是,在 React 里设置 setScale 不是一件“立刻生效” 的事,对于别处可能会用到 scale 变量来参与计算时, 就会因为数据的更新不及时变成一个 Bug,因此,对于这种要参与计算的值,应该先赋给一个 ref 变量,而每次 state 更新总是读取这个 ref 变量来更新, 这样就可以做到保证数据时效性、一致性的同时,正常 render 渲染。

到这虽然实现了缩放功能,但是有个大问题:没有用上缩放原点。 事实上应该在缩放之前,先根据鼠标的位置计算出缩放原点,这样缩放时才能做到缩放中心不变。

这件事是在 updateOrigin 函数中实现的,过程如下:

  1. 读取 Event 给出的 event.clientXclientY,这个是鼠标相对于整个网页的位置
  2. 将上述 xy 转换成画板内部的坐标点位,记作 coord
  3. coord 再同本来的缩放原点 origin 相加,就得到本次 Event 正确的缩放原点
  4. 由于 origin 发生了变化,因此需要重新计算画板的偏移量,记作 offset,计算方法见代码

改完 origin 之后为什么要改 offset 呢?

因为 csstransformorigin offset 两个值是相互作用的,如果用户的鼠标从左上角移动到右下角, 就明显是一个非常巨大的 origin 改变,此时如果 offset 不做任何调整,用户的观感就是画面突然跳飞了特别远, 因此,offset 这个值优先服务给 css(而不是人阅读),是一个紧密配合 originscale 的参数,目的是为了配合用户的交互,让用户感觉画面的移动是自然的。

如此一来,onWheel 内部的计算就成了一个有机结合的整体,offset, scale, origin 三方互相影响,每次计算都要考虑到三方的变化,就可以正常实现缩放功能了。

有了 updateOrigin 来控制原点,offset 功能也就简单了,只需要根据用户的移动设置 offset 即可。

const handleMove = e => { let moveX = e.deltaX * 0.15 let moveY = e.deltaY * 0.15 let next = { x: offsetMark.current.x - moveX, y: offsetMark.current.y - moveY, } offsetMark.current = next }

同时,也要调用一次 updateOrigin 来确保 origin 值的正确性,同时确保 setOffset 确实改变了其值,render 也就随之刷新。

这样一来,可以随意放大、缩小、移动的画板就实现了。 自行实现这个功能稍显麻烦,后面如果要添加移动节点、连线、选中等功能也很复杂,想降低开发成本且开箱即用的话,可以考虑使用下面这些库:

  • draggable-board,正在开发中,请期待。
  • react-flow,成熟、美观的开源库。