前端canvas项目实战——简历制作网站(六):加粗、斜体、下划线、删除线(上)

news2024/9/30 21:28:22

目录

  • 前言
  • 一、效果展示
  • 二、实现步骤
    • 1. 视图部分:实现用于切换字体属性的按钮
    • 2. 逻辑部分:点击按钮之后要做什么?
    • 3. 根据Textbox的属性实时更新按钮的状态
  • 三、Show u the code
  • 后记

前言

上一篇博文中,我们实现了对文字的字体、字号和行间距的编辑。

这篇博文是《前端canvas项目实战——简历制作网站》付费专栏系列博文的第六篇——加粗、斜体、下划线、删除线,主要的内容有:

  1. 针对文本框(fabric.Textbox)对象: 扩充属性列表,使用户可以为画布中选中的文本框设置加粗、斜体、下划线和删除线

如有需要,你可以:

  • 点击这里,返回第一篇《前端canvas项目实战——简历制作网站(一)——左侧工具栏》
  • 点击这里,返回上一篇《前端canvas项目实战——简历制作网站(五):右侧属性栏(字体、字号、行间距)》
  • 点击这里,前往下一篇《前端canvas项目实战——简历制作网站(七):加粗、斜体、下划线、删除线(下)》

一、效果展示

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

  • 动态效果演示

  • 本节之后,我们的简历能做成什么样子
    我们可以对部分文字设置加粗,将标题或重要文字和其他部分区别开来,使面试官可以快速定位到重点信息。

二、实现步骤

1. 视图部分:实现用于切换字体属性的按钮

在这里,我把这种按钮命名为SwitchValueButton

import "./index.css";
import React from "react";
import { Button, Tooltip } from "antd";

const SwitchValueButton = (props) => {
  let { handleClick, tip, style, className, children } = props;

  let _props = {
    type: "text",
    className: className || "property-operation",
    onClick: (e) => handleClick(e),
    style: {
      ...style,
      height: "40px",
      padding: "0.5rem 0.75rem",
    },
  };
  
  return (
    <Tooltip title={tip}>
      <Button {..._props}>{children}</Button>
    </Tooltip>
  );
};

export default SwitchValueButton;

代码很简单,实现了一个带有图标的按钮。因为并不是每个用户都可以一看到图标就明白这个按钮的作用,所以还为按钮加上了提示。 即当鼠标的光标移动到按钮上时,按钮上方会弹出对应的Tips,表明这个按钮的作用。

有了这个基础的按钮组件,相信大家实现一组 4 个按钮也没有什么困难。

2. 逻辑部分:点击按钮之后要做什么?

这里的代码和上一节有部分交集,但仍有很多改动。为了便于说明,仍列出了部分在上节出现过的代码。

const updateFontProperty = (key, newValue) => {
  let { activeObject, canvas } = store.getState();

  if (_shouldUpdateTheWholeTextbox(activeObject, key)) {
    _updateFontPropertyForWholeObject(key, newValue);
  } else {
    _updateFontPropertyForSelection(key, newValue);
  }
  canvas.renderAll();
};

const _shouldUpdateTheWholeTextbox = (object, key) => {
  let { text, isEditing, selectionStart, selectionEnd } = object;
  let isSelectAll = text.length === selectionEnd - selectionStart;
  // Bug点1:容易忘记考虑编辑状态时,选中了全部文字
  if (!isEditing || isSelectAll) {
    return true;
  }
  return key === "lineHeight";
};

const _updateFontPropertyForWholeObject = (key, newValue) => {
  let { activeObject } = store.getState();
  // Bug点2:由于部分文字的样式优先级高于整个文本框的样式,先清除整个文本框的对应属性
  activeObject.removeStyle(key);
  newValue = _getNewValueForWholeObject(key, newValue);
  ObjectAPI.updateProperty(activeObject, key, newValue);
};

const _updateFontPropertyForSelection = (key, newValue) => {
  let { activeObject, canvas } = store.getState();
  let { selectionStart, selectionEnd } = activeObject;

  // Bug点3:编辑态时,如果没有选中任何文字,不做任何处理
  if (selectionEnd === selectionStart) {
    return;
  }

  newValue = _getNewValueForSelection(key, newValue);
  let style = {};
  style[key] = newValue;
  activeObject.setSelectionStyles(style);
  // 优化点:删除冗余的数据
  activeObject.minifySelectionStyles();
};

const _getNewValueForWholeObject = (key, value) => {
  let { activeObject } = store.getState();
  if (defaultValues.hasOwnProperty(key)) {
    value = defaultValues[key].init;
    if (activeObject[key] === value) {
      value = defaultValues[key].target;
    }
  }
  return value;
};

const _getNewValueForSelection = (key, value) => {
  let { activeObject } = store.getState();
  if (defaultValues.hasOwnProperty(key)) {
    let { init, target } = defaultValues[key];
    let selectionStyles = activeObject.getSelectionStyles();
    let valueOfFirstChar = selectionStyles[0][key];

    if (valueOfFirstChar === undefined) {
      value = activeObject[key] === init ? target : init;
    } else {
      value = activeObject[key];
    }
  }
  return value;
};

一共6个方法,分为3个部分来讲解:

  • 入口方法及分支判断逻辑
    • updateFontProperty方法: 作为入口方法,本节实现的加粗、斜体、下划线、删除线等4个按钮的点击事件都直接指向它,例如为一个Textbox添加下划线:handleClick={() => updateFontProperty("underline", true)}
    • _shouldUpdateTheWholeTextbox方法: 分支判断方法,判断当前应该为整个Textbox设置新的属性值,还是仅为用户选中的部分文字设置局部的属性。
  • 分支1:为整个Textbox设置新的属性值
    • _updateFontPropertyForWholeObject方法: 通过下面的_getNewValueForWholeObject方法获取到新的属性值后,将新值直接设置给Textbox对象。
    • _getNewValueForWholeObject方法: 获取新值。
      • 如果属性为颜色等用户从下拉菜单中选择的值,直接返回;
      • 如果属性为加粗、斜体、下划线、删除线这类点击了按钮进行切换的值,则返回取反后的值。如下划线,当前值为true则返回false,当前值为false则返回true
  • 分支2:为用户选中的部分文字设置新的属性值
    • _updateFontPropertyForSelection方法: 通过下面的_getNewValueForSelection方法获取到新的属性值后,通过setSelectionStyles方法为选中的文字设置新的属性值。
    • _getNewValueForSelection方法: 以选中的文字的第一个字符的属性为准获取新值,取值的逻辑和上述的_getNewValueForWholeObject类似。

代码注释中的Bug点和优化点在下文中会详细介绍。

3. 根据Textbox的属性实时更新按钮的状态

前面两个小节中的代码组合起来,实现了通过点击按钮来更新Textbox的属性值。现在我们反过来,将Textbox的属性状态体现在这些按钮上。先通过下面的动图来看看我们要实现的效果:

可以看到,按钮的颜色会根据我们选中的文字属性实时变化。这样就完成了Textbox属性和按钮状态的双向链接。以下是代码:

fabric.Textbox.prototype.initialize = (function (fn) {
  return function (text, options, id) {
    fn.call(this, text, options, id);
    
    this.on("selection:changed", function () {
      store.dispatch(actions.updateActiveObject(this));
    });
  };
})(fabric.Textbox.prototype.initialize);

const updateActiveObject = (newActiveObject) => {
  newProperties = {
    ...
    fontWeight: newActiveObject.fontWeight),
    fontStyle: newActiveObject.fontStyle),
    underline: newActiveObject.underline),
    linethrough: newActiveObject.linethrough"),
  };
  return {
    ...
  	activeObjectProperties: newProperties
  };
}

const getIconColor = (key) => {
  let { activeObjectProperties } = store.getState();
  let value = activeObjectProperties[key];
  let initValue = defaultValues[key].init;
  if (value === initValue || value === undefined) {
    return "#000000";
  }
  return "dodgerblue";
};

为了便于说明,这里将整个流程简化为3个方法进行介绍。如需查看完整代码,请留意文末的「Show u the code」部分。

  • 覆盖fabric原型代码: 为了在用户选择的文字更改时,可以立即更新按钮状态,这里覆盖了fabric.Textbox原型里的initialize方法,这个方法用于创建并初始化Textbox原型。我们的改动是:
    • 当初始化结束后,通过Textbox.on("selection:changed")为其添加一个时间监听器,监听selection:change事件。
    • 当代码中通过Textbox.fire("selection:changed")「引发」 一个selection:change事件时,监听器会立即执行立即执行我们设置的「回调方法」。
    • 在回调方法中,我们向中央数据仓库store分发一个数据更新任务,要求通过updateActiveObject方法更新画布中当前选中的对象。
  • updateActiveObject方法: 根据参数中新的被选中对象,更新activeObjectProperties字典。
    • 注意: 为了便于说明,这个方法中的代码有简化,实际获取属性的方法要比直接newActiveObject.xxx要复杂一些,具体见后文中的源代码。
  • getIconColor方法: 通过中央数据仓库中最新的activeObjectProperties字典,判断按钮中的图标应该是用什么颜色。
    • 如果是初始值(如下划线underline的初始值为false,目标值为true),则设置为黑色#000000
    • 否则设置为一种浅蓝色dodgerblue,上文的动图中有显示。

这样,一排美观、精致、且逻辑完整属性按钮就实现了


三、Show u the code

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


后记

这篇博文中,我们实现了用按钮设置文本框的加粗、斜体、下划线、删除线

起初觉得并不复杂的几个功能,在实现中也涌现出了一个又一个的问题。本来想一篇博文写完,但限于篇幅,拆分到两篇博文中。

上述在实现过程中发现的Bug及其解决方法、需要优化的点以及是如何优化的,我都放在了下一篇博文中。相信通过动手解决这些实现过程中遇到的,真实的阻碍和问题,我们会走上学习和成长的快速车道。

如有需要,你可以:

  • 点击这里,返回第一篇《前端canvas项目实战——简历制作网站(一)——左侧工具栏》
  • 点击这里,返回上一篇《前端canvas项目实战——简历制作网站(五):右侧属性栏(字体、字号、行间距)》
  • 点击这里,前往下一篇《前端canvas项目实战——简历制作网站(七):加粗、斜体、下划线、删除线(下)》

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

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

相关文章

ChatGLM3 Linux 部署

1.首先需要下载本仓库&#xff1a; git clone https://github.com/THUDM/ChatGLM3 2.查看显卡对应的torch 版本 官方文档说明&#xff1a; Start Locally | PyTorch 例如&#xff1a; a. 先查看显卡的CUDA版本 nvcc --version 查看对应版本 Previous PyTorch Versions …

Error:No such property: GradleVersion for class: JetGradlePlugin

Gradle版本对照表 Android Gradle 插件版本在项目的根目录&#xff08;不是App目录&#xff09;下的build.gradle文件中&#xff0c;如图 插件所需的Gradle 版本在gradle目录下的gradle-wrapper.properties文件中&#xff0c;如图

安全认证|CISSP认证是什么证书?考了有什么用?能做什么工作?

很多人总是听说CISSP是顶级的信息安全证书&#xff0c;在国内或者国外都有盛誉&#xff0c;那么CISSP到底是个什么样的证书&#xff0c;本期就给大家介绍下&#xff01; 什么是CISSP CISSP&#xff08;Certification for Information System Security Professional&#xff0…

三份天注定,七分靠XX?

文 | 螳螂观察 作者 | 陈小江 1988年&#xff0c;中国宝岛台湾&#xff0c;蒋经国过世后&#xff0c;社会运动风起云涌。在所谓“解严”的时代氛围里&#xff0c;人们对前途虽然迷茫&#xff0c;但却充满打拼的热情。 那时节&#xff0c;40岁的台湾歌手叶启田&#xff0c;开…

【消息队列开发】 实现消费者订阅消息

文章目录 &#x1f343;前言&#x1f333;关于订阅消息方法参数解析&#x1f38b;如何实现将消息推送给消费者&#x1f38d;消费者类&#x1f340;消费消息的流程&#x1f384;如何实现消息确认呢&#xff1f;⭕总结 &#x1f343;前言 本次开发任务 实现消费者订阅消息 &am…

公司内部局域网怎么适用飞书?

随着数字化办公的普及&#xff0c;企业对于内部沟通和文件传输的需求日益增长。飞书作为一款集成了即时通讯、云文档、日程管理、视频会议等多种功能的智能协作平台&#xff0c;已经成为许多企业提高工作效率的首选工具。本文将详细介绍如何在公司内部局域网中应用飞书&#xf…

电脑Wi-Fi无法连接如何排查

Wi-Fi是一个神奇的东西&#xff0c;总是能在某一天莫名其妙的连不上让我们疯狂糟心&#xff01;&#xff01;&#xff01; 呉師傅准备了几个解决方法来帮助大家解决连不上Wi-Fi的问题&#xff1b; 1、疑难解答功能 系统自带的【疑难解答】功能不妨试一试&#xff0c;也能一定…

【AAAI 2024】M2Doc:文档版面分析的可插拔多模态融合方法

一、文章介绍 文档版面分析任务是文档智能的一个关键任务。然而&#xff0c;现有的很多文档版面分析研究方法都基于通用目标检测方法&#xff0c;忽视了文档的文本特征而仅仅只关注于视觉特征。近年来&#xff0c;基于预训练的文档智能模型在很多文档下游任务中都取得了成功&a…

左旋字符串功能的实现

实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; #1ABCD左旋一个字符得到BCDA #2ABCD左旋两个字符得到CDAB 由此图可知&#xff0c;其字符串长度为4&#xff0c;每次经历四次左旋后又回到了初始 位置&#xff0c;所以是以字符串长度len为一个循环&…

微服务cloud--抱团取暖吗 netflix很多停更了

抱团只会卷&#xff0c;卷卷也挺好的 DDD 高内聚 低耦合 服务间不要有业务交叉 通过接口调用 分解技术实现的复杂性&#xff0c;围绕业务概念构建领域模型&#xff1b;边界划分 业务中台&#xff1a; 数据中台&#xff1a; 技术中台&#xff1a; 核心组件 eureka&#x…

(done) ROC曲线 和 AUC值 分别是什么?

来源&#xff1a;https://www.bilibili.com/video/BV1wz4y197LU/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 在二分类问题下&#xff0c;我们的模型通常会输出一个 概率值&#xff0c;通过判断 概率值 和 阈值threshold 的大小…

docker 安装部署 jenkins

今天 小☀ 给大家普及一下什么是 jenkins&#xff01;&#xff01; Jenkins是一个开源软件项目&#xff0c;基于Java开发的持续集成工具。它提供了一个开放易用的软件平台&#xff0c;使软件项目可以进行持续集成。Jenkins起源于Hudson&#xff0c;主要用于持续、自动地构建、…

动态内存数组(malloc、calloc、realloc、free)

一、为什么要创建动态内存数组 动态内存&#xff0c;顾名思义就是说在内存中非固定的申请数组 在学习该项方法前我们申请内存的方法无非就两种&#xff1a;直接创建变量/通过创建数组的方式来申请空间。 那么直接创建变量/通过创建数组的方式来申请空间的缺点就是一旦创建成…

基于python+vue拍卖行系统的设计与实现flask-django-nodejs-php

拍卖行系统的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品&#xff0c;体验高科技时代带给人们的方便&#xff0c;同时也能让用户体会到与以往常规产品不同的体验风格。 与安卓&#xff0c;iOS相比较起来&#xff0c;拍卖行…

2024学习鸿蒙开发,未来发展如何?

一、前言 想要了解一个领域的未来发展如何&#xff0c;可以从如下几点进行&#xff0c;避免盲从&#xff1a; 国家政策落地情况就业市场如何学习 通过上述三点&#xff0c;就能分析出一个行业的趋势。大家可以看到&#xff0c;我上面的总体逻辑就是根据国家政策来分析未来方…

大数据技术在工厂生产数字转型中的应用与价值

hello宝子们...我们是艾斯视觉擅长ui设计和前端开发10年经验&#xff01;希望我的分享能帮助到您&#xff01;如需帮助可以评论关注私信我们一起探讨&#xff01;致敬感谢感恩&#xff01; 随着大数据技术的快速发展&#xff0c;越来越多的企业开始关注并应用大数据技术&#x…

第6讲-MIPS处理器(3)MIPS单周期处理器设计

三. MIPS单周期处理器设计 1.单周期数据通路设计

联合国通过首个全球人工智能决议草案

当地时间3月21日&#xff0c;联合国大会一致通过了全球第一个关于人工智能&#xff08;AI&#xff09;的决议草案&#xff0c;以期能够保护个人数据、保障人权&#xff0c;并能有效监控其安全风险。 该决议由美国提出&#xff0c;包括中国在内的其他121个国家共同参与了制定&am…

移动硬盘故障解析:解决无法访问且位置不可用问题

在我们日常的工作和生活中&#xff0c;移动硬盘已成为存储和传输数据的重要工具。然而&#xff0c;有时我们会遇到移动硬盘无法访问且位置不可用的情况&#xff0c;这无疑给数据的存储和访问带来了极大的困扰。本文将深入探讨这一问题&#xff0c;分析其原因&#xff0c;并给出…