图形编辑器开发:以光标为中心缩放画布

开发 前端
场景坐标系 就是图形所在的二维平面世界所使用的坐标系。单位是像素(px)。坐标系的原点在画布(canvas 元素)的左上角,x 轴向右,y 轴向下。

大家好,我是前端西瓜哥。

画布缩放是图形设计工具中很重要的基础能力。

通过它,我们可以像举着一台摄影机,在图形所在的世界到处游逛,透过镜头,可以只看自己想看的图形;可以拉近摄影机,看到图形的细节;也可以拉远摄影机,总览多个图形之间的关系。

ok,那么我们看看如何实现缩放画布功能。

文中的动图演示来自我正在开发的图形设计工具:

https://github.com/F-star/suika

线上体验:

https://blog.fstars.wang/app/suika/

场景坐标系和视图坐标系

场景坐标系 就是图形所在的二维平面世界所使用的坐标系。单位是像素(px)。坐标系的原点在画布(canvas 元素)的左上角,x 轴向右,y 轴向下。

图形会被绘制到这个平面,理论上它的范围是可以 无限延展 的。(不过实际上我们会给一个上限,但这个值也非常大。无限大的话没有意义,且浮点数是有取值范围的)

然而显示器的宽高是有限的,只能看一个矩形范围内的内容。

所以我们需要引入一个 “摄影机”:视图坐标系,只看部分的区域。

其实就是将原来真实的图形的坐标做一个线性计算转换。

首先是将特定区域 移动 到视口中,就像摄影机从原点移动我们想要观察的某个物体上。不过实际上是物体所在的平面做了一个方向的移动。

然后再做一个缩放,就像摄影机拉近或远离与目标物体距离,效果是物体在镜头下变大或变小。

转换就两步,移动然后缩放

视图矩阵转换

场景坐标系到视图坐标系的转换,我们通过 视图矩阵 相乘来实现。

事实上,任意两个坐标系下坐标的转换,都可以通过一个矩阵乘法来实现。

首先是将坐标进行位移,x 方向位移 -viewport.x,y 方向位移 -viewport.y。这里是负数,虽然我们想要移动 “摄影机”这是因为移动的是画布

<平移矩阵> * 坐标

然后再缩放(缩放值我们会用 zoom 表示):

<缩放矩阵> * 平移后的坐标

所有过程写在一起,就是:

<缩放矩阵> * <平移矩阵> * 坐标

矩阵乘法符合结合律,所以我们的视图矩阵为:

<视图矩阵>
= <缩放矩阵> * <平移矩阵>

矩阵表示为:

计算结果为:

对应的 Canvas 2D 代码:

ctx.scale(zoom, zoom);
ctx.translate(-viewport.x, -viewport.y);

写成一个方法:

// 场景坐标转视图坐标
function sceneCoordsToViewport(x, y, zoom, scrollX, scrollY) {
  return {
  x: (x - scrollX) * zoom,
  y: (y - scrollY) * zoom
  };
}

至于反过来,场景坐标系转视图坐标,计算它的逆矩阵即可:

以光标为中心缩放

首先我们来认清本质,所谓以光标为中心缩放,不变的是什么?

光标所在点在视图坐标系距离视口左上角的相对位置,保持不变。

我们要做的事是,在 zoom 变化后,调整 viewport.x 和 viewport.y 的值,让光标在视图坐标系上相对视口左上角距离不变。

这里得补充一个知识点。就是两个坐标系中距离的转换:

  1. 场景转视图,距离转换为 dist * zoom;
  2. 视图转场景,距离的转换是 dist / zoom,因为视口看到的图形都是缩放(乘以 zoom)后的结果,所以反过来就要除回去。

实现思路是:

  1. 记录好缩放前,光标所在位置的场景坐标;
  2. 计算 (cx, cy) 在旧缩放比(zoom)的场景坐标。
  3. 计算 cx 在新的缩放比(zoom)下,(cx / zoom, cy / zoom)。
  4. 然后二者相减,即可得到新的适口左上角坐标。

代码实现为:

/**
 * 以某点为中心,进行画布缩放
 * @param {number} zoom 新的缩放比
 * @param {number} cx 缩放中心(使用视图坐标)
 * @param {number} cy
 */
const setZoomAndUpdateViewport = (zoom, cx, cy) => {
  const prevZoom = this.zoom;
  this.zoom = zoom;

  // 计算缩放中点的场景坐标
  const { x: sceneCX, y: sceneCY } = viewportCoordsToScene(
    cx,
    cy,
    prevZoom,
    this.viewport.x,
    this.viewport.y,
  );

  // 核心代码
  const newViewportX = sceneCX - cx / zoom;
  const newViewportY = sceneCY - cy / zoom;

  this.viewport.x = newViewportX;
  this.viewport.y = newViewportY;

  this.renderScene();
};

以画布为中心进行缩放

如果缩放时光标不在画布上,比如通过手动输入缩放值时,会 以画布的中心位置进行缩放。

实现同上,只是 cx 和 cy 改成传入视口(即画布)的宽高除以 2:(viewport.width / 2, , (viewport.height / 2)。

结尾

要实现画布缩放,重点是理解场景坐标和视图坐标之间的关系。

场景坐标转视图坐标,首先需要将画布进行移动,让场景坐标的原点和视图坐标的原点对上(场景坐标移动 -viewport.x 和 -viewport.x),然后再进行缩放(乘以 zoom)。

责任编辑:姜华 来源: 前端西瓜哥
相关推荐

2023-10-19 10:12:34

图形编辑器开发缩放图形

2024-01-08 08:30:05

光标图形编辑器开发游标

2024-01-03 08:43:17

图形编辑器旋转控制点缩放控制点

2023-08-31 11:32:57

图形编辑器contain

2023-09-07 08:24:35

图形编辑器开发绘制图形工具

2023-09-26 07:39:21

2023-09-11 09:02:31

图形编辑器模块间的通信

2023-10-10 16:04:30

图形编辑器格式转换

2023-10-08 08:11:40

图形编辑器快捷键操作

2023-08-28 08:10:50

Hex图形编辑器

2023-07-31 08:46:07

图形编辑器图形自动对齐

2023-02-01 09:21:59

图形编辑器标尺

2023-01-18 08:30:40

图形编辑器元素

2023-02-06 16:59:57

Canvas编辑器

2023-04-07 08:02:30

图形编辑器对齐功能

2023-06-12 08:22:56

图形编辑器工具

2023-04-10 08:45:44

图形编辑器排列移动功能

2023-05-09 08:15:32

图形编辑器撤销重做功能

2023-02-09 07:02:30

图形编辑器修改图形

2023-08-08 08:12:25

图形编辑器几何算法
点赞
收藏

51CTO技术栈公众号