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

news2025/1/10 23:24:05

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

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

我们看看效果。

在这里插入图片描述

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

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 的宽还是高,去和视口宽或高相除

这个属于是 填充策略中的 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 填充策略,和居中对齐算法,把它们弄懂了,缩放功能基本就没啥问题了。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/676907.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot 统⼀功能处理

目录 SpringBoot 统一功能处理概念 统一用户登录权限验证 登录功能代码 Spring拦截器实现步骤: 统一项目访问前缀 第一种方法:重写configurePathMatch方法进行配置 第二种方法:在系统的配置文件.properties中进行配置 统一异常处理返回…

香橙派4 2. 驱动usb2.0芯片cy7c68013

0. 环境 - 香橙派4(Orangepi4_2.1.2_ubuntu_bionic_desktop_linux4.4.179.img) - EZ-USB FX2LP CY7C68013A USB 核心板 1. 下载FX3_SDK_1.3.4_linux EZ-USB™ FX3 Software Development Kit https://www.infineon.com/cms/en/design-support/tools/sdk…

Autosar模式管理实战系列08-BswM与EcuM模块交互详解

本文框架 前言EcuM及BswM交互EcuM及BswM交互总览启动时BswM与EcuM的状态管理交接下电时BswM与EcuM的状态管理交接前言 在Autosar模式管理系列介绍01-BswM文章中,我们对BswM基本内容进行了介绍,我们知道了BswM是根据既定的仲裁规则对来自应用层SWCs或其他底层BSW模块,同时也…

InnoDB 和 MyISAM 的区别

1、InnoDB支持事务,MyISAM不支持; 2、InnoDB支持外键,MyISAM不支持; 3、InnoDB是簇索引,MyISAM是非簇索引; 4、Innodb不支持fulltext全文索引,MyISAM支持; 5、InnoDB支持到行级锁&am…

JavaSE基础语法--数组的拷贝

数组的拷贝方法有好几种,第一种是简单的for循环。通过遍历原数组来给新数组赋值完成数组的拷贝代码如下: import java.util.Arrays;public class TestDemo {public static void main(String[] args) {int[] arr1{1,2,3,4,5,6};int[] arr2new int[arr1.l…

MySQL高级SQL语句操作之存储过程

MySQL高级SQL语句操作之存储过程 一、存储过程介绍二、存储过程操作1、创建存储过程2、调用存储过程3、查看存储过程4、存储过程的参数5、删除存储过程6、存储过程的控制语句6.1 条件语句6.2 循环语句 一、存储过程介绍 存储过程是一组为了完成特殊功能的SQL语句集合存储过程在…

Tik Tok 如何提高账户的活跃度和吸引力?

Tik Tok 是一款非常受欢迎的应用程序,它在全球范围内拥有大量的用户和创作者。Tik Tok 在人工智能技术方面投入了大量的资源,并且正在不断改进和扩展其人工智能技术。Tik Tok 正在不断扩展其业务,例如在音乐、视频制作等方面扩展。这表明 Tik…

如何提升虚拟机性能?除了绑核还有它可以!

第一章 大页内存与小页内存 1.1 小页内存 在Linux系统中,默认情况下,内存管理器将物理内存划分为小页(4KB)或大页(2MB或1GB)的大小。Linux内核会根据需要分配和释放内存,以确保系统的稳定性和性能。 在默认情况下,Linux系统会使用…

在spring事务中扩展业务操作;spring事务同步器TransactionSynchronizationManager

概述 业务上经常会有一些需求是需要在某个数据库操作事务提交之后再去操作。 我常用的就方式有TransactionalEventListener和TransactionSynchronizationManager. 其实TransactionalEventListener背后使用的也是TransactionSynchronizationManager。 注意点:在a…

爬虫(Requests库get和post应用)

Requests库 介绍 Requests是Python中用于进行HTTPS请求的库。它提供了一种简单直观的API,用于发送HTTP,HTTPS请求和处理响应。 request.get()函数 参数 url,一般放置需要请求的URL headers,一般用于User-agent(UA…

制定CRM战略流程是哪些?

CRM战略是企业为了提升核心竞争力,在市场、销售、客户管理等方面开展的一系列改善、创新或转型的措施。目的是建立和维护与客户的关系,增加企业的收入。那么,企业如何制定CRM战略呢? 1、深入了解客户需求 企业需要了解其目标客户…

【算法总结】——子集型回溯

文章目录 子集型回溯例题1——78.子集代码模板1代码模板2 例题2——131.分割回文串代码模板1代码模板2补充:怎么判断回文串双指针dp提前处理 参考资料 子集型回溯 主要学习 分别从 输入 和 答案 去思考的两种代码模板。 例题1——78.子集 例题:78. 子集…

JavaSE基础语法--数组(1)

数组的定义与使用 数组就是存储相同数据类型的一组数据。它有如下特点: 1.数组中存放的数据是一样的 2.数组的空间是连续的 3.每个空间有自己的编号,其实位置的编号为0,即数组的下标 那么在Java里面如何定义一个数组呢? 假设…

【Python入门】Python循环语句(for循环的嵌套应用)

前言 📕作者简介:热爱跑步的恒川,致力于C/C、Java、Python等多编程语言,热爱跑步,喜爱音乐的一位博主。 📗本文收录于Python零基础入门系列,本专栏主要内容为Python基础语法、判断、循环语句、函…

行为型模式--中介者模式

目录 概述 结构 案例实现 优缺点 优点: 缺点: 使用场景 概述 一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复 杂的网状结构,这是一种过度耦合的架构&a…

深入理解深度学习——BERT派生模型:跨语言模型XLM(Cross-lingual Language Model)

分类目录:《深入理解深度学习》总目录 BERT本应在语义理解上具有绝对优势,但其训练语料均为英语单语,受限于此,早期的BERT只在英语文本理解上有优势。随着全球化进程的加速,跨语言的预训练语言模型也具有非常重要的应用…

seldom之数据驱动

seldom之数据驱动 如果自动化某个功能,测试数据不一样而操作步骤是一样的,那么就可以使用参数化来节省测试代码。 seldom是我在维护一个Web UI自动化测试框,这里跟大家分享seldom参数化的实现。 GitHub:GitHub - SeldomQA/seld…

idea设置项目编码为utf8

设置当前项目的编码为utf8 File -> Settings -> Editor -> File Encoding: 设置新建项目的编码为utf8 File -> New Projects Setup -> Settings for New Projects:

Flutter系列(九)ListView实现新闻列表和正文布局

基础工程: Flutter系列(四)底部导航顶部导航图文列表完整代码_摸金青年v的博客-CSDN博客 相关文章: Flutter系列(七)ListView 图文列表详解_flutter 图文列表_摸金青年v的博客-CSDN博客 一、前言 本文用flutter实现新闻…

注册 Google 邮箱(最新:保姆级教程)

文章目录 1、我们使用浏览器打开谷歌邮箱官网(gmail.google.com),进入谷歌邮箱的登录主页,我们点击左下方的创建账号按钮,选择个人用途 2、在进入的界面我们不要着急填写资料,我们先修改语言,点…