前端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.mfbz.cn/a/582538.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Eclipse内存分析器 Java内存分析工具MAT(Memory Analyzer Tool)的介绍与使用

1.visualvm实时监测 2.Memory Analyzer Tool打开 3.工具的使用可以参考 Java内存分析工具MAT(Memory Analyzer Tool)的介绍与使用 ------------------------ 1.我远程发现是其中一个客户端A请求服务器页面响应&#xff0c;一直得不到响应&#xff0c;然后客户端A一直请求&am…

js 字符串 第一个斜杠前最后一次出现英文字母的位置并添加自定义值,返回新值

要找到字符串中第一个斜杠&#xff08;/&#xff09;前最后一次英文字母出现的位置&#xff0c;可以使用正则表达式配合lastIndexOf方法。以下是实现这一功能的示例代码&#xff1a; 如果是匹配第一个数字前的字母加值可以看这里 function findLastLetterIndexBeforeSlash(str…

外贸旺季外贸人如何做好时间管理

第1步 记住这些原则 50-30-20原则 你工作日里50%的时间应该花在有益于你长期发展目标的事情上&#xff0c;30%的时间应该用于你完成中期(两年左右)目标的事情&#xff0c;20%的时间用于完成未来90天以内需要完成的任务。 “一个篮子”原则 One Bucket 尽可能减少自己接收新任务…

《QT实用小工具·四十六》多边形窗口

1、概述 源码放在文章末尾 该项目实现了可以移动的多边形窗口&#xff0c;项目demo演示如下所示&#xff1a; 项目部分代码如下所示&#xff1a; #include "polygonwindow.h"#include <QBitmap> #include <QQuickItem> #include <QQmlFile> #in…

JAVASE->数据结构|顺序表底层逻辑

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;再无B&#xff5e;U&#xff5e;G-CSDN博客 目标&#xff1a; 1. 什么是 List 2. List 常见接口介绍 3. …

第8章 软件工程

一、软件工程概述 &#xff08;一&#xff09;软件危机 1、含义&#xff1a;落后的软件生产方式无法满足迅速增长的计算机软件需求&#xff0c;从而导致软件开发与维护过程中出现一系列严重问题的现象。 2、解决方案&#xff1a;引入软件工程的思想。 &#xff08;二&#x…

Unity 问题之 开发应用在设备上运行闪屏花屏问题的分析处理

Unity 问题之 开发应用在设备上运行闪屏花屏问题的分析处理 目录 Unity 问题之 开发应用在设备上运行闪屏花屏问题的分析处理 一、简单介绍 二、问题现象 三、问题分析 四、使用空后处理&#xff0c;解决闪屏花屏的显示问题 五、空后处理完整代码 一、简单介绍 Unity 在…

国密SSL证书在等保、关保、密评合规建设中的应用

在等保、关保、密评等合规建设中&#xff0c;网络和通信安全方面的建设是非常重要的部分&#xff0c;需要实现加密保护和安全认证&#xff0c;确保传输数据机密性、完整性以及通信主体可信认证。国密SSL证书应用于等保、关保和密评合规建设中&#xff0c;不仅能够提升网络信息系…

API接口调用失败的常见原因?如何进行排查和处理?

API接口调用失败的常见原因有以下几种&#xff1a; 1. 无效的请求参数&#xff1a;可能是由于请求参数缺失、格式错误或者不符合接口要求导致的。解决方法是检查请求参数是否正确&#xff0c;并确保按照接口文档提供正确的参数。 2. 接口权限不足&#xff1a;有些接口需要特定…

JAVA自定义日期选择器

下载jar地址&#xff0c; https://toedter.com/jcalendar/ jar包下载地址 依赖包如下图所示&#xff1a; 整个项目代码已经上传到CSDN https://download.csdn.net/download/qq_30273575/89241601?ydrefereraHR0cHM6Ly9tcC5jc2RuLm5ldC9tcF9kb3dubG9hZC9tYW5hZ2UvZG93bmxvYWQ…

Swift-31-泛型和类型操作

泛型 Swift泛型(generics) 让我们写出的类型和函数可以使用对于我们或编译器都未知的类型。 很多内建类型(包括可空类型、数组和字典)都是用泛型实现的&#xff0c;比如数组和一些集合就是用泛型方式来实现的。 一种运行时进行类型检查的技术&#xff0c;效率高但是不安全。在…

Java零基础入门到精通_Day 8

1.API 应用程序接口 Java API:指的就是JDK 中提供的各种功能的Java类这些类将底层的实现封装了起来&#xff0c;我们不需要关心这些类是如何实现的&#xff0c;只需要学习这些类如何使用即可&#xff0c;我们可以通过帮助文档来学习这些API如何使用。 2. String String 类…

记录-执行Grad-CAM所遇问题

在执行Grad-CAM所遇问题 1&#xff09; 修改后解决 2&#xff09; 修改后解决&#xff0c;因为numpy需要在cpu上进行&#xff0c;所有需要加上.cpu() 3&#xff09;plt.matshow(heatmap)出错 原因是get_heatmap()中的mean_gradients torch.mean(gradients, dim[0, 2, 3]…

Spring IOC(一)

1. Spring IOC入门 1.1 什么是Spring IoC IoC&#xff08;Inversion of Control&#xff09;&#xff0c;即控制反转&#xff0c;是一种设计原则。简单来说&#xff0c;IoC就是将程序的某种传统控制流程反转了。 在Spring框架中&#xff0c;控制反转体现在对象的创建和管理上。…

面试:Redis(缓存穿透、缓存击穿、缓存雪崩、双写一致、Redis的持久化、Redis的过期策略、Redis的数据淘汰策略、Redis的分布式锁、Redis的集群方案、Redis网络模型)

目录 一、缓存穿透 1、解决方案一&#xff1a; 2、解决方案二&#xff1a; 二、缓存击穿 1、解决方案一&#xff1a; 2、解决方案二&#xff1a; 三、缓存雪崩 1、解决方案一&#xff1a; 2、解决方案二&#xff1a; 3、解决方案三&#xff1a; 4、解决方案四&#…

扭蛋机小程序带来了什么优势?扭蛋机收益攻略

在当下的潮流消费时代&#xff0c;人们对潮玩也日益个性化&#xff0c;扭蛋机作为一种新型的娱乐消费模式&#xff0c;深受大众喜爱。扭蛋机的价格低&#xff0c;各个年龄层的玩家都可以进行购买&#xff0c;潜在玩家量非常大。扭蛋机商品主打热门IP周边等&#xff0c;种类繁多…

Leetcode-面试题 02.02. 返回倒数第 k 个节点

目录 题目 图解 代码 面试题 02.02. 返回倒数第 k 个节点 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/description/ 题目 实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点。返回该节点的值。 注意&…

Q1季度方便速食行业线上市场(京东天猫淘宝)销售数据分析

方便食品行业作为快速消费品市场的重要组成部分&#xff0c;近几年表现出较为强劲的发展势头。当然&#xff0c;每年的食品安全问题也在一定程度上影响着市场的良性健康发展。那么&#xff0c;今年Q1季度方便食品的线上发展如何&#xff1f; 根据鲸参谋数据显示&#xff0c;Q1…

python程序设计语言超详细知识总结

Python 首先 python 并不是简单&#xff0c;什么语言都有基础和高级之分&#xff0c;要想掌握一门语言&#xff0c;必须把高级部分掌握才行。 HelloWorld helloWorld.py print(hello, world)数据类型与变量 变量的数据类型数据类型描述变量的定义方式整数型 (int)整数&…

【Java EE】MyBatis 入门

文章目录 &#x1f340;什么是MyBatis?&#x1f332;如何使用MyBatis&#x1f338;引人Mybatis的相关依赖&#x1f338;配置Mybatis(数据库连接信息)&#x1f338;编写SQL语句(注解/XML)&#x1f338;单元测试 &#x1f333;打印日志 &#x1f340;什么是MyBatis? MyBatis是…
最新文章