图形编辑器开发:缩放至适应画布

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

之前我们实现了画布缩放的功能,本文来讲讲如何让内容缩放至适应画布大小(Zoom to fit)。

我们看看效果。

图形编辑器开发:缩放至适应画布_第1张图片

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

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

线上体验:

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

缩放至适应画布

这里涉及了场景坐标和视图坐标的转换,引入了 zoom 和视口概念。

如果你不理解它们,请看我的这篇文章:

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

总体思路:

  1. 计算包裹住所有图形的大包围盒 bbox(AABB 包围盒,不带旋转的);
  2. 计算新的缩放比 newZoom。需要判断是基于 bbox 的宽,还是基于高进行缩放;
  3. 最后是计算 viewport.x 和 viewport.y,将内容刚好在视口的中间位置。

最重要的是 计算缩放比,是基于 bbox 的宽还是高,去和视口宽或高相除

图形编辑器开发:缩放至适应画布_第2张图片

这个属于是 填充策略中的 contain 策略

更多填充策略,看我的这篇文章:

《在容器内显示图片的五种方案:contain、cover、fill、none、scale-down》

我们需要比较 bbox 的宽高比和视口 viewport 的宽高比。

const viewportRatio = vw / vh;
const bboxRatio = bbox.width / bbox.height;
if (viewportRatio > bboxRatio) {
  // 基于 bbox 的高进行缩放
  newZoom = vh / bbox.height;
} else {
  // 基于宽
  newZoom = vw / bbox.width;
}

然后就是 小矩形在大矩形下垂直水平居中 的简单算法。下面是通过小矩形反推大矩形的位置。

 const newViewportX =
    composedBBox.x - (viewport.width / newZoom - composedBBox.width) / 2;

  const newViewportY =
    composedBBox.y - (viewport.height / newZoom - composedBBox.height) / 2;

这个算法可以看我写的文章:

《图形编辑器:绘制图形需要用到的填充算法》

图形编辑器开发:缩放至适应画布_第3张图片

完整代码:

function zoomToFix() {
  //(1)计算所有图形的大包围盒
  const bbox = getRectsBBox(graphs.map((item) => item.getBBox()));

  //(2)计算 newZoom
  const vh = viewport.height; // 这里可以加个边距
  const vw = viewport.width;
  const viewportRatio = vw / vh;
  const bboxRatio = bbox.width / bbox.height;
  if (viewportRatio > bboxRatio) {
    // basic height scale
    newZoom = vh / bbox.height;
  } else {
    newZoom = vw / bbox.width;
  }

  //(3)计算视口 x 和 y 值
  const newViewportX =
    composedBBox.x - (viewport.width / newZoom - composedBBox.width) / 2;
  const newViewportY =
    composedBBox.y - (viewport.height / newZoom - composedBBox.height) / 2;

  //(4)更新视口对象
  this.setZoom(newZoom);
  this.setViewport({
    x: newViewportX,
    y: newViewportY,
  });
}

加上边距

有时候我们希望给一个边距,就像下面动图一样。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TzmOz1yu-1687424462936)(https://fe-watermelon.oss-cn-shenzhen.aliyuncs.com/%E9%80%82%E5%BA%94%E7%94%BB%E5%B8%83%E5%8A%A0%E8%BE%B9%E8%B7%9D.gif)]

加了 50px 的边距,这样内容就不再紧贴视口边缘了,选中图形图像的控制点不至于跑到视口外。

思路是,计算 newZoom 时用的 vw 和 vh,在原来的基础减去 padding,再去计算。

需要注意的是,后面计算居中时,还是要要用原来的 viewport.x 和 viewport.y。

计算缩放比,对象是减去 padding 的视口宽高;计算位置,对象是原来的视口宽高

代码实现,改一下上面代码的第二步即可。

//(2)计算 newZoom
const padding = 50;
const vh = viewport.height - padding * 2; // 注意考虑 vh 或 vw 是负数的情况
const vw = viewport.width - padding * 2;

选中的图形适应画布

同前面的让所有图形适应画布,bbox 换成选中的图形即可。

const bbox = getRectsBBox(selectGraphs.map((item) => item.getBBox()));

结尾

缩放的大多数功能,本质就是计算新的 zoom 和视口 x,y。

基本上都逃不出 contain 填充策略,和居中对齐算法,把它们弄懂了,缩放功能基本就没啥问题了。

我是前端西瓜哥,欢迎关注我,学习开发一个图形设计工具。

你可能感兴趣的:(算法)