大家好,我是前端西瓜哥。
之前我们实现了画布缩放的功能,本文来讲讲如何让内容缩放至适应画布大小(Zoom to fit)。
我们看看效果。
文中的动图演示来自我正在开发的图形设计工具:
https://github.com/F-star/suika
线上体验:
https://blog.fstars.wang/app/suika/
这里涉及了场景坐标和视图坐标的转换,引入了 zoom 和视口概念。
如果你不理解它们,请看我的这篇文章:
《图形编辑器开发:以光标为中心缩放画布》
总体思路:
最重要的是 计算缩放比,是基于 bbox 的宽还是高,去和视口宽或高相除。
这个属于是 填充策略中的 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;
这个算法可以看我写的文章:
《图形编辑器:绘制图形需要用到的填充算法》
完整代码:
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 填充策略,和居中对齐算法,把它们弄懂了,缩放功能基本就没啥问题了。
我是前端西瓜哥,欢迎关注我,学习开发一个图形设计工具。