前端canvas项目实战——在线图文编辑器(九):逻辑画布

目录

  • 前言
  • 一、 效果展示
  • 二、 实现步骤
    • 1. 调整布局,最大化利用屏幕空间
    • 2. 添加逻辑画布
    • 3. 添加遮罩
    • 4. 居中显示逻辑画布
    • 5. 一个容易被忽视的bug点
  • 三、Show u the code
  • 后记

前言

上一篇博文中,我们实现了一组通用的功能按钮:复制、删除、锁定和层叠顺序

这篇博文是《前端canvas项目实战——在线图文编辑器》付费专栏系列博文的第九篇——逻辑画布,主要的内容有:

  1. 调整页面布局,将画布区域扩展至整个屏幕的剩余空间中。
  2. 区分「物理画布」和「逻辑画布」,为实现「缩放」、「辅助线」等功能打基础。

如有需要,你可以:

  • 点击这里,返回第一篇《前端canvas项目实战——在线图文编辑器(一)——左侧工具栏》
  • 点击这里,返回上一篇《前端canvas项目实战——在线图文编辑器(八):复制、删除、锁定、层叠顺序》

一、 效果展示

  • 动手体验
    CodeSandbox会自动对代码进行编译,并提供地址以供体验代码效果
    由于CSDN的链接跳转有问题,会导致页面无法工作,请复制以下链接在浏览器打开:
    https://5sd7gz.csb.app/

  • 效果演示


二、 实现步骤

1. 调整布局,最大化利用屏幕空间

在之前的博文中,我们的实现包含「左侧工具栏」、「画布」和「右侧属性栏」3个部分。他们是依次从左到右进行排的,因此我们可以看到屏幕的右侧和下方有空余的区域,既浪费,又不美观

要处理这个问题,我们需要修改canvas-page/index.js文件中的html部分,来充分利用屏幕空间:

	.content-container {width: 100%;height: 100vh;display: flex;flex-direction: row;justify-content: space-between;}.left-side-tools-container {width: 80px;height: 100%;...}.right-side-props-container {width: 16.25rem;height: 100%;...}.scalable {...position: absolute;top: 0;bottom: 0;left: 80px;right: 16.25rem;}<div className="content-container"><LeftSideTools canvas={canvas}/><div className="scalable"><canvas id="canvas"/></div><RightSideProps w={canvasWidth} h={canvasHeight} u={canvasSizeUnit}/></div>

可以看到,html标签的排布很简洁,下面对CSS中的样式进行说明:

  • .canvas-container: 作为父级容器
    • 首先通过width: 100%;height: 100vh;占满屏幕100%的宽度和高度,为3个子标签提供足够的空间。
    • 然后通过display: flex;flex-direction: row;justify-content: space-between;设置子标签在水平方向流式布局,并按等间距排列。
  • .left-side-tools-container: 占据80px的宽度并占满父标签100%的高度。
  • .right-side-props-container: 占据16.25rem的宽度并占满父标签100%的高度。
  • .scalable: 作为canvas的父级标签,采用绝对定位:
    • 上下两端都和父标签.canvas-container对齐。
    • 左侧从80px开始,即开始于工具栏右侧。
    • 右侧从16.25rem开始,即结束于属性栏左侧。

经过这样的调整,canvas就可以填充除工具栏和属性栏外的所有屏幕空间。

2. 添加逻辑画布

通常情况下,我们并不需要自己的画布填充满所有的空余区域,但又想让它居中显示在屏幕中央。这里,我们引入「逻辑画布」的概念来实现这样的需要,以下对几个概念做简要的说明:

  • 物理画布:canvas对象所占有的全部区域。
  • 逻辑画布: 一个相对于「物理画布canvas的概念。在canvas中,将我们关注的部分区域作为逻辑画布,在逻辑画布上,我们可以添加各种对象。
  • 背景区域: 可以理解为背景区域 = 物理画布 - 逻辑画布。即在画布中,但不在逻辑画布中的区域称之为「背景区域」。在背景区域中,只显示辅助线等少数的对象,其他对象都不会被显示出来。

可以通过下图来加深理解:

代码实现:

    useEffect(() => {const parentElement = document.getElementsByClassName("scalable")[0];let canvas = new fabric.Canvas("canvas", {width: parentElement.offsetWidth,height: parentElement.offsetHeight});...addLogicCanvas(canvas, logicCanvasWidth, logicCanvasHeight);...}, []);const addLogicCanvas = (canvas, width, height) => {const logicCanvas = new fabric.Rect({left: 0,top: 0,width,height,fill: "white",stroke: "lightgray",selectable: false,evented: false,});canvas.add(logicCanvas);// 逻辑画布永远置底canvas.sendToBack(logicCanvas);canvas.renderAll();...};

代码逻辑比较简单,以下做简要说明:

  • useEffect: 画布页面初始化的阶段,根据父级标签scalable的宽度和高度实例化「物理画布canvas对象,并向其中添加「逻辑画布logicCanvas
  • addLogicCanvas: 创建并添加逻辑画布,有以下要点:
    • 逻辑画布」实际上是一个填充色为白色的fabric.Rect矩形对象。
    • 它不可以通过鼠标点击被用户选择,不参与任何监听事件
    • 通过canvas.sendToBack方法使逻辑画布用于置于所有对象的最底层,否则会遮盖住其他对象。

3. 添加遮罩

有了逻辑画布,我们可以在其上添加和拖动各种各样的对象。但有一种情况的表现还不尽如人意,见下面的动图:

当我们把一个对象拖出逻辑画布时,预期它应该被遮盖或隐藏,但实际的表现是:它仍然显示在那里。

为了实现这个小需求点,我们可以为画布添加clipPath,即「遮罩范围」:

	const updateClipPath = (canvas, logicCanvas) => {const {left, top, width, height} = logicCanvas;canvas.clipPath = new fabric.Rect({left,top,width,height,absolutePositioned: true,selectable: false,evented: false,});};

和逻辑画布类似,遮罩是一个和逻辑画布相同位置、相同大小fabric.Rect矩形区域,同样不可以被选中,不参与任何监听事件。

设置了clipPath之后,我们来看看效果:

可以看到,对象被拖出逻辑画布的区域被隐藏了,只有选择框的控制线和控制点仍可以显示。

4. 居中显示逻辑画布

前面几个小节中,为了美观和便于说明,我直接使用了居中后的页面进行截图。实际上,我们在初始化时设置了逻辑画布的坐标为(0, 0):

    const logicCanvas = new fabric.Rect({left: 0,top: 0,...});

要居中显示逻辑画布,需要引入canvasviewport视口」概念。

视口: 可以理解为可视窗口。当逻辑画布很小时,我们可以看到它的全貌。反之,当它很大,或者被放大超出了窗口的大小,我们就只能看到它的局部。这个我们能看到的区域就称为viewport视口。

先看下面一张图:

红色方框的区域就是上述的「视口」, 起初,视口的中心在「红色十字」的位置,我们看到的逻辑画布就在屏幕的左上方。想要看到逻辑画布处于视口正中间,就需要把视口向左上角移动一定的距离,使得视口中心和「蓝色十字」重合。

那么问题就简化为,如何计算出水平和竖直两个方向上的位移量。由图中可以很方便的计算出:

  • 水平方向的偏移量 = (canvas.width - logicCanvas.width) / 2;
  • 竖直方向的偏移量 = (canvas.height - logicCanvas.height) / 2;

因此有以下代码:

    let panX = -(canvas.width - logicCanvas.width) / 2;let panY = -(canvas.height - logicCanvas.height) / 2;...canvas.absolutePan(new fabric.Point(panX, panY));...

canvas.absolutePan方法的作用即对当前画布的视口做绝对的平移。 如此,我们的逻辑画布就可以居中显示在屏幕中央了。

5. 一个容易被忽视的bug点

由于「逻辑画布」也是canvas中的一个fabric.Rect对象,且在Z轴上必须永远置底,所以会造成原有的「移至底层」功能出现bug,具体的表现就是:将一个对象移至底层之后,由于被逻辑画布完全遮挡,这个对象就再也看不到,也无法选中了

具体的表现如下图:

这个问题修复的逻辑很简单:把一个对象移至底层之后,再向上移动一层,使它在Z轴上必然比逻辑画布高即可:

	if (selectedItem.key === "toBottom") {canvas.sendToBack(object);// 逻辑画布应该永远置底,所有对象都应该高于逻辑画布canvas.bringForward(object);}

问题修复后的效果,不再截图徒增篇幅。


三、Show u the code

按照惯例,本节的完整代码我也托管在了CodeSandbox中,点击前往,查看完整代码


后记

这篇博文中对画布进行调整的内容不算太多,但十分重要,会作为后续多篇博文的基础。后续的博文中,我们会依次实现通过工具缩放画布、通过鼠标滚轮缩放画布、鼠标拖动移动视口等功能。

如有需要,你可以:

  • 点击这里,返回第一篇《前端canvas项目实战——在线图文编辑器(一)——左侧工具栏》
  • 点击这里,返回上一篇《前端canvas项目实战——在线图文编辑器(八):复制、删除、锁定、层叠顺序》

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

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

相关文章

FreeRTOS之列表

1.FreeRTOS的列表和列表项十分重要。列表类相当于链表&#xff0c;列表项则相当于链表中的节点。列表项的地址是非连续的&#xff0c;列表项的数量可随时修改。在OS中的任务状态和数量会发生改变&#xff0c;因此使用列表可以很好的满足需求。 列表和列表项的相关定义与操作函…

电商独立站||跨境电商独立站网站搭建|功能系统搭建||API接口接入

搭建多语言跨境电商独立站系统 前台主要功能模块 短信接口 第三方登陆 支付方式 会员中心 代购订单列表 - new 会员签到 -1000(1) new 支付密码 ---1000 国内流程 -----5000 new 订单运单多退少补 -1000 未付款运单取消功能 - 修改运单运输方式 -----1000 年费会员 -----3000 …

大型零售企业,适合什么样的企业邮箱大文件解决方案?

大型零售企业通常指的是在全球或特定地区内具有显著市场影响力和知名度的零售商。这些企业不仅在零售业务收入上达到了惊人的规模&#xff0c;而且在全球范围内拥有广泛的销售网络和实体店铺。它们在快速变化的零售行业中持续创新&#xff0c;通过实体店、电商平台等多种渠道吸…

C#队列(Queue)的基本使用

概述 在编程中&#xff0c;队列&#xff08;Queue&#xff09;是一种常见的数据结构&#xff0c;它遵循FIFO&#xff08;先进先出&#xff09;的原则。在C#中&#xff0c;.NET Framework提供了Queue<T>类&#xff0c;它位于System.Collections.Generic命名空间下&#x…

【深度学习实战(26)】标签处理之语义分割标签转换,数据集划分

一、标签转换 我们在使用labeme标签工具&#xff0c;标注完数据后会获得json文件。在标注结束过后&#xff0c;我们需要通过标签转换操作&#xff0c;生成jpg格式原始图片和png格式mask标签图。 1.1 使用img_b64_to_arr将json标签中二进制图像数据变成numpy格式数据&#xf…

selenium在Pycharm中结合python的基本使用、交互、无界面访问

下载 下载与浏览器匹配的浏览器驱动文件&#xff0c;这里一定注意的是&#xff0c;要选择和浏览器版本号相同的驱动程序&#xff0c;否则后面会有很多问题。 &#xff08;1&#xff09;浏览器&#xff08;以google为例&#xff09;版本号的查询&#xff1a; 我这里的版本号是1…

java实现模板填充word,word转pdf,pdf转图片

Java实现Word转PDF及PDF转图片 在日常开发中&#xff0c;我们经常需要将文件操作&#xff0c;比如&#xff1a; 根据模板填充wordword文档中插入图片Word文档转换为PDF格式将PDF文件转换为图片。 这些转换可以帮助我们在不同的场景下展示或处理文档内容。下面&#xff0c;我将…

Leetcode—1256. 加密数字【中等】Plus(bitset、find_first_not_of、erase)

2024每日刷题&#xff08;120&#xff09; Leetcode—1256. 加密数字 实现代码 class Solution { public:string encode(int num) {string ans;num 1;while(num ! 0) {ans to_string(num & 1);num num >> 1;}if(ans.empty()) {return "";} else {stri…

coreldraw2024精简版绿色版安装包免费下载

CorelDRAW 2024是一款矢量图形设计软件&#xff0c;于2024年3月5日正式在全球范围内发布。这款软件在多个方面进行了更新和改进&#xff0c;为用户提供了更多高效、灵活和便捷的设计工具。 首先&#xff0c;CorelDRAW 2024新增了绘画笔刷功能&#xff0c;这些笔刷不仅模拟了传…

Ubuntu20.04 [Ros Noetic]版本——在catkin_make编译时出现报错的解决方案

今天在新的笔记本电脑上进行catkin_make的编译过程中遇到了报错&#xff0c;这个报错在之前也遇到过&#xff0c;但是&#xff0c;我却忘了怎么解决。很是头痛&#xff01; 经过多篇博客的查询&#xff0c;特此解决了这个编译报错的问题&#xff0c;于此特地记录&#xff01;&…

[论文笔记]SEARCHING FOR ACTIVATION FUNCTIONS

引言 今天带来另一篇激活函数论文SEARCHING FOR ACTIVATION FUNCTIONS的笔记。 作者利用自动搜索技术来发现新的激活函数。通过结合详尽的搜索和基于强化学习的搜索&#xff0c;通过实验发现最佳的激活函数 f ( x ) x ⋅ sigmoid ( β x ) f(x) x \cdot \text{sigmoid}(βx…

瓦片编辑器成功移植到小熊猫C++ 2.25.1版本,解决_findnext移植问题

移植之后出现绿色屏幕闪退 查了版本回滚直到不闪退&#xff0c;发现是在读取自定义文件上出问题 然后在找读取自定义文件函数&#xff0c;发现是读取图片部分出问题 然后就卡住了 调试半天&#xff0c;不是数据溢出&#xff0c;于是就看 函数_findnext,网上搜 ———_findn…

4.Docker本地镜像发布至阿里云仓库、私有仓库、DockerHub

文章目录 0、镜像的生成方法1、本地镜像发布到阿里云仓库2、本地镜像发布到私有仓库3、本地镜像发布到Docker Hub仓库 Docker仓库是集中存放镜像的地方&#xff0c;分为公共仓库和私有仓库。 注册服务器是存放仓库的具体服务器&#xff0c;一个注册服务器上可以有多个仓库&…

项目开发规范

Restful REST&#xff0c;表述性状态转换&#xff0c;他是一种软件架构风格 使用URL定位资源&#xff0c;HTTP动词描述操作 根据发出请求类型来区分操作 GET&#xff1a; 查询id为1的用户POST&#xff1a;新增用户PUT&#xff1a;修改用户DELETE&#xff1a;删除id为1的用户 …

springboot权限验证学习-上

创建maven项目 创建父工程 这类项目和原来项目的区别在于&#xff0c;打包方式是pom 由于pom项目一般都是用来做父项目的&#xff0c;所以该项目的src文件夹可以删除掉。 创建子工程 子工程pom.xml 父工程pom.xml 添加依赖 父工程导入依赖包 <!--导入springboot 父工程…

18.Nacos配置管理-微服务读取Nacos中的配置

需要解决的问题 1.实现配置更改热更新&#xff0c;而不是改动了配置文件还要去重启服务才能生效。 2.对多个微服务的配置文件统一集中管理。而不是需要对每个微服务逐一去修改配置文件&#xff0c;特别是公共通用的配置。 配置管理服务中的配置发生改变后&#xff0c;回去立…

病理组学+配对 mIHC 验证+转录组多组学

目录 病理DeepRisk网络模型构建 DPS和新辅助化疗 mIHC 验证 STAD转录组层面 病理DeepRisk网络模型构建 自有数据训练&#xff0c;TCGA数据进行验证&#xff0c;然后配对mIF验证&#xff0c;最后还在转录组层面分析。 该模型基于中山数据集&#xff08;n 1120&#xff09…

【AIGC调研系列】Sora级别的国产视频大模型-Vidu

Vidu能够达到Sora级别的标准。Vidu被多个来源认为是国内首个Sora级别的视频大模型[2][3][4]。它采用了团队原创的Diffusion与Transformer融合的架构U-ViT&#xff0c;能够生成长达16秒、分辨率高达1080P的高清视频内容[1][6]。此外&#xff0c;Vidu的一致性、运动幅度都达到了S…

【Spring】IOC/DI中常用的注解@Lazy、@Scope与@Conditional

目录 1、Lazy 懒加载bean 1.1、与component配合使用 1.2、与Bean注解配合使用 2、Scope bean的作用域 2.1、不指定Scope 2.2、指定Scope为 prototype 3、Conditional 条件注解 1、Lazy 懒加载bean Lazy用于指定单例bean实例化的时机&#xff0c;在没有指定此注解时&…

基于SpringBoot+Vue校园竞赛管理系统的设计与实现

项目介绍&#xff1a; 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;竞赛信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行…