Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >优化下自己3年前写的代码

优化下自己3年前写的代码

作者头像
神奇的程序员
发布于 2024-03-22 09:31:07
发布于 2024-03-22 09:31:07
16600
代码可运行
举报
运行总次数:0
代码可运行

前言

我的截图插件js-web-screen-shot,在三年的时间里,经历了从1.0.0到1.9.9的版本迭代。随着功能的不断增加,原本的入口文件变得越来越复杂和混乱,代码行数已接近1500行。

最近,在着手开发2.0大版本的功能,面对这些复杂的代码,我感到非常困扰,这也使得很多想要为项目贡献新功能的人因为代码的复杂性而望而却步。

经过综合考量后,我决定优化和拆分入口文件中的冗余部分,使结构更加简洁明了。本文就跟大家分享下我的优化过程,欢迎各位感兴趣的开发者阅读本文。

逻辑梳理

从入参开始,逐行分析代码,捋清函数间的依赖关系,这是我们首先要做的。我在做复杂的事情时,习惯把自己脑子里想的东西以思维导图的形式呈现出来,如下图所示,插件从实例化到加载,总共分为9个步骤:

  • 获取用户配置
  • 创建辅助DOM(webrtc模式时需要用到)
  • 实例化全局响应式对象
  • 提取可选配置
  • 获取截图区域的canvas容器
  • 修改容器的可滚动状态
  • 加载截图组件
  • 调整容器层级
  • 创建事件监听

这9个步骤中,加载截图组件是其核心处理逻辑,也是依赖关系最错综复杂的地方。此处就不做过多赘述了,感兴趣的开发者可以结合图中的路线去翻阅main.ts文件中的**load方法**。

截图流程梳理

制定方案

分析完load方法,以及与其关联的类内部的私有方法。它们都有1个共同点:

  • 在截图期间对类内部引用类型和基本类型数据的各种计算与修改

那么,我们能做的就是把这些计算逻辑拆分成方法,独立出去,只关注输入于输出,这样就大大降低了代码的复杂度,使其更易维护。

代码拆分

我新建了两个ts文件,用来存放拆分出来的方法。

  • LoadCoreComponents.ts 处理组件中的数据计算处理方法
  • mouseDownCore.ts 处理鼠标的按下、移动、抬起事件

考虑到load方法所依赖的方法较多,在ts文件里用function去声明的话,后续维护查找时不够直观。因此,我采用了const+export的方式。

组件方法拆分

LoadCoreComponents.ts文件中,我拆了19个方法出来。在本章节中,我将挑几个具有代表性的方法来做讲解。

操作裁剪框

在操作裁剪框的时候,方法内部需要修改类内部基本类型的数据,我们都知道:在js里,当函数的参数类型是基本类型的时候,通过值传递。那么,拆分出来后,如何来更新这部分数据呢?

聪明的开发者应该已经想到了。没错,那就是通过回调函数来实现更新,代码如下所示:

  • 函数的入参接受一个回调函数,返回值为genericMethodPostbackType类型,定义了三个属性:
    • code
    • msg
    • data
  • 在函数内部定义res,经过一系列的计算后,修改res对象里的值,在恰当的时机去执行回调函数来更新数据
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const operatingCutOutBox = (
  currentX: number,
  currentY: number,
  startX: number,
  startY: number,
  width: number,
  height: number,
  context: CanvasRenderingContext2D,
  data: InitData,
  dpr: number,
  containerInfo: {
    screenShotContainer: HTMLCanvasElement | null | undefined;
    screenShotImageController: HTMLCanvasElement;
  },
  containerVariable: {
    movePosition: movePositionType;
    cutOutBoxBorderArr: Array<cutOutBoxBorder>;
    borderOption: number | null;
  },
  callerCallback: (res: genericMethodPostbackType) => void
) => {
  const res: genericMethodPostbackType = { code: 0, msg: "", data: null };
  // canvas元素不存在
  if (containerInfo.screenShotContainer == null) {
    return;
  }
  // 获取鼠标按下时的坐标
  const { moveStartX, moveStartY } = containerVariable.movePosition;

  // 裁剪框边框节点事件存在且裁剪框未进行操作,则对鼠标样式进行修改
  if (
    containerVariable.cutOutBoxBorderArr.length > 0 &&
    !data.getDraggingTrim()
  ) {
    //...其他代码省略,这里会经过一系列的计算,修改res对象的值,最后调用callerCallback方法来更新数据...//
    callerCallback(res)
};

然后,我们来看下调用时的代码,传入了croppingBoxCallerCallback函数,在函数内部,根据code来更新类内部所依赖的的数据。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 执行裁剪框操作函数
    operatingCutOutBox(
      currentX,
      currentY,
      startX,
      startY,
      width,
      height,
      this.screenShotCanvas,
      this.data,
      this.dpr,
      {
        screenShotContainer: this.screenShotContainer,
        screenShotImageController: this.screenShotImageController
      },
      {
        movePosition: this.movePosition,
        cutOutBoxBorderArr: this.cutOutBoxBorderArr,
        borderOption: this.borderOption
      },
      this.croppingBoxCallerCallback
    );
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  // 裁剪框回调
  // 对组件内部所依赖的数据做处理
  private croppingBoxCallerCallback = (res: genericMethodPostbackType) => {
    const { code, data } = res;
    if (code === 1 && typeof data === "number") {
      this.borderOption = data;
    }
    if (code === 2 && typeof data === "boolean") {
      this.mouseInsideCropBox = data;
    }
    if (code === 3 && typeof data === null) {
      this.borderOption = null;
    }
    if ((code === 4 || code === 5) && typeof data != null) {
      this.tempGraphPosition = res.data as drawCutOutBoxReturnType;
    }
  };

注意:此处只列举了关键代码,完整代码请移步:

  • LoadCoreComponents.ts-operatingCutOutBox
  • main.ts-operatingCutOutBox
处理涂鸦绘制

在画布上进行涂鸦绘制时,会更新类内部的 drawStatus变量,我们拆分出来后,也是用同样的办法去更新,除了更新类内部的变量外,我们还用到了类内部的方法showLastHistory,我们只需要把它当作参数传入,在需要的时候调用即可,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const handleGraffitiDraw = (
  drawStatus: boolean,
  startX: number,
  startY: number,
  tempWidth: number,
  tempHeight: number,
  currentX: number,
  currentY: number,
  degreeOfBlur: number,
  data: InitData,
  useRatioArrow: boolean,
  containerInfo: {
    screenShotCanvas: CanvasRenderingContext2D;
  },
  containerFn: {
    showLastHistory: () => void;
  },
  callerCallback: (res: genericMethodPostbackType) => void
) => {
  const res: genericMethodPostbackType = {
    code: 0,
    data: null,
    msg: ""
  };
  switch (data.getToolName()) {
    case "square":
      drawRectangle(
        startX,
        startY,
        tempWidth,
        tempHeight,
        data.getSelectedColor(),
        data.getPenSize(),
        containerInfo.screenShotCanvas
      );
      break;
    case "round":
      drawCircle(
        containerInfo.screenShotCanvas,
        currentX,
        currentY,
        startX,
        startY,
        data.getPenSize(),
        data.getSelectedColor()
      );
      break;
    case "right-top":
      // 绘制等比例箭头
      if (useRatioArrow) {
        drawLineArrow(
          containerInfo.screenShotCanvas,
          startX,
          startY,
          currentX,
          currentY,
          30,
          10,
          data.getPenSize(),
          data.getSelectedColor()
        );
        break;
      }
      // 绘制递增变粗箭头
      new DrawArrow().draw(
        containerInfo.screenShotCanvas,
        startX,
        startY,
        currentX,
        currentY,
        data.getSelectedColor(),
        data.getPenSize()
      );
      break;
    case "brush":
      // 画笔绘制
      drawPencil(
        containerInfo.screenShotCanvas,
        currentX,
        currentY,
        data.getPenSize(),
        data.getSelectedColor()
      );
      break;
    case "mosaicPen":
      // 当前为马赛克工具则修改绘制状态
      // 前面做了判断,此处需要特殊处理
      if (!drawStatus) {
        containerFn.showLastHistory();
        // 返回一个特殊值,用于修改调用组件的内部状态
        res.code = 1;
        res.data = true;
        res.msg = "需要更新组件状态";
        callerCallback(res);
      }
      // 绘制马赛克,为了确保鼠标位置在绘制区域中间,所以对x、y坐标进行-10处理
      drawMosaic(
        currentX - 10,
        currentY - 10,
        data.getMosaicPenSize(),
        degreeOfBlur,
        containerInfo.screenShotCanvas
      );
      break;
    default:
      break;
  }
  return res;
};

在调用的时候,因为这个回调函数不会在类内部的其他地方复用,因此我们只需要函数内部多声明一个函数即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
      const callerCallback = (res: genericMethodPostbackType) => {
        if (
          res.code === 1 &&
          res.data != null &&
          typeof res.data === "boolean"
        ) {
          this.drawStatus = res.data;
        }
      };
      // 处理涂鸦绘制
      handleGraffitiDraw(
        this.drawStatus,
        startX,
        startY,
        tempWidth,
        tempHeight,
        currentX,
        currentY,
        this.degreeOfBlur,
        this.data,
        this.plugInParameters.getRatioArrow(),
        {
          screenShotCanvas: this.screenShotCanvas
        },
        { showLastHistory: this.showLastHistory },
        callerCallback
      );

鼠标事件拆分

在类内部处理鼠标事件时,代码也比较冗余,有很多逻辑可以拆出去,为了便于维护,我创建了独立的文件mouseDownCore.ts 来放这些拆出来的方法,因为拆分思路与组件方法的拆分思路是一致的,本章节就不做过多的代码讲解了。

在鼠标事件的处理中,有很多地方涉及到引用类型的数据修改(直接赋值,如下图所示),如果直接在拆分出来的函数内部去改的话,类内部的变量并不会得到更新,因为引用地址发生了改变,那么有没有什么更好的办法呢?

相信很多开发者已经想到了,那就是用Object.assign,这样就可以在不改变引用地址的情况下去更新对象内部的值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  // 保存边框节点信息
  Object.assign(
    containerVariable.cutOutBoxBorderArr,
    saveBorderArrInfo(data.getBorderSize(), containerVariable.drawGraphPosition)
  );

完整代码请移步:

  • MouseEventHandling.ts-mouseUpCore
  • main.ts-mouseUpEvent

优化后的效果

代码经过拆分与优化后,入口文件的代码行数从1459优化到了843

项目地址

本文所列举的代码,其对应的项目请移步:

  • js-screen-shot
  • 在线文档
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-03-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 神奇的程序员 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
实现Web端自定义截屏
当客户在使用我们的产品过程中,遇到问题需要向我们反馈时,如果用纯文字的形式描述,我们很难懂客户的意思,要是能配上问题截图,这样我们就能很清楚的知道客户的问题了。
神奇的程序员
2021/02/04
2.6K0
我的截图插件被Gitee使用了
前言 上周六有个群友@我说Gitee的反馈模块新增了截图功能,我就去体验了下,发现他们用的就是我的插件😁,本文就跟大家分享下这个插件,欢迎各位感兴趣的开发者阅读本文。 插件地址与实现原理 本插件采用原生js实现,可以集成在任意一个web项目中,插件npm地址与GitHub地址请移步: js-screen-shot(npm)[1] js-screen-shot(GitHub)[2] 插件的实现原理请移步: 实现Web端自定义截屏[3] 实现Web端自定义截屏(JS版)[4] 在线体验本插件,可移步我的开源项目
神奇的程序员
2022/04/10
4.8K1
我的截图插件被Gitee使用了
FLIP,一种高端优雅但简单易用的前端动画思维
有一种能够快速实现复杂动画交互的动画思维 FLIP,为了介绍这个动画思维,我准备了三个案例,大家可以在上面的视频中观看。
用户6901603
2024/01/25
1K0
FLIP,一种高端优雅但简单易用的前端动画思维
绚丽烟花:HTML5 Canvas 烟花效果实现(文末附完整代码)
在节日或特殊场合,绚丽的烟花总能带来无尽的欢乐和惊喜。今天,我们将通过 HTML5 Canvas 实现一个绚丽的烟花效果,让你的网页也能绽放出美丽的烟花。
码事漫谈
2025/01/27
1.1K1
绚丽烟花:HTML5 Canvas 烟花效果实现(文末附完整代码)
使用 HarmonyOS NEXT 实现签名板的功能
大家好,我是一只会打代码的羊。今天来分享一篇之前使用 ArkTS API9 版本实现的签名板功能,目前鸿蒙已经推出 API12 了,对比 API9 的时候,现在实现一个功能太简单了。这期主要讲一下之前实现功能有多难受,以及如何实现。,现在这套代码也是可以直接迁移到 API12 版本的。
陈杨
2025/03/15
800
使用 HarmonyOS NEXT 实现签名板的功能
71.HarmonyOS NEXT PicturePreviewImage组件深度剖析:从架构设计到核心代码实现
本组件是HarmonyOS NEXT平台的高性能图片预览核心模块,主要解决以下问题:
全栈若城
2025/03/16
570
三步走:把Scribble Diffusion AI 画图搬进app!
最近AIGC 简直是杀疯了,领导动不动就让我们在APP 里引入大语言模型,引入AI画图……说搞就搞!本期基于最近在app 里引入AI画图小程序的操作,给大家做一波实践分享。
海岛船长加西亚
2023/04/11
1.4K1
三步走:把Scribble Diffusion AI 画图搬进app!
前端代码层面优化的一些想法
这样连续使用三元选择符并不利于理解,并且如果有更多的类型,会导致过长的三元判断,可以使用map替换:
HenryYang
2022/08/30
1.2K0
61.Harmonyos NEXT 图片预览组件之数据模型设计与实现
图片预览组件采用了模型驱动的设计思想,将不同的交互状态抽象为独立的数据模型,实现了状态管理的解耦和代码的高内聚。本文将详细介绍图片预览组件中的四个核心数据模型:ScaleModel、RotateModel、OffsetModel和CommonLazyDataSourceModel。
全栈若城
2025/03/14
810
利用Phaser开发微信小游戏(排行榜小结)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
bering
2019/12/03
2.4K0
vue 3.0 拖拽组件
拖拽容器 12af53b2-4f10-48f0-85c4-061e86225d47.gif 使用 // html <Move :data='initSize' @update='update' > <div :style='style'></div> </Move> // ts import { Move, MoveBlock } from './components/move' export default defineComponent({ components: { Move
copy_left
2021/01/29
1.7K0
61.Harmonyos NEXT 图片预览组件之数据模型设计与实现
图片预览组件采用了模型驱动的设计思想,将不同的交互状态抽象为独立的数据模型,实现了状态管理的解耦和代码的高内聚。本文将详细介绍图片预览组件中的四个核心数据模型:ScaleModel、RotateModel、OffsetModel和CommonLazyDataSourceModel。
全栈若城
2025/03/15
620
61.Harmonyos NEXT 图片预览组件之数据模型设计与实现
76.HarmonyOS NEXT ImageItemView组件深度剖析:边界处理与高级特性(二)
该组件通过精心的状态管理和手势交互设计,实现了专业级的图片查看体验。核心优势包括:
全栈若城
2025/03/15
830
浅谈基于QT的截图工具的设计与实现
在介绍截图工具设计与实现前,让我们先通过介绍QT的绘图基础知识,让读者有一个比较感性的认识。
w4ngzhen
2023/10/18
6060
浅谈基于QT的截图工具的设计与实现
实现Web端自定义截屏(原生JS版)
前几天我发布了一个web端自定义截图的插件,在使用过程中有开发者反馈这个插件无法在vue2项目中使用,于是,我就开始找问题,发现我的插件是基于Vue3的开发的,由于Vue3的插件和Vue2的插件完全不兼容,因此插件也就只能在Vue3项目中使用。
神奇的程序员
2022/04/10
3.1K0
可视化拖拽组件库一些技术要点原理分析(二)
本文是对《可视化拖拽组件库一些技术要点原理分析》的补充。上一篇文章主要讲解了以下几个功能点:
谭光志
2021/01/20
1.4K0
可视化拖拽组件库一些技术要点原理分析(二)
我希望按照我的思路尽可能将canvas基础讲明白
写在前面 canvas很多人写过,我之前的博客里面也写过关于canvas的教程,但是后面我觉得其实不太好,因为很多东西都是很模糊的,没有非常直观清晰的将canvas讲解明白,究其原因,还是这个属性使用的不够多,导致很多属性不够熟练,但是我希望这篇文章可以将这个属性彻底的讲明白,毕竟只是一个标签而已,怎么讲都不会太复杂,他之所以不太好学原因就在于他自带的方法太多,加上很多的效果都是需要方法之间的相互配合使用,所以难度和复杂度就直接升高了很多,它不像html的其他标签一样,比如p、span等都只是自带了一些样
何处锦绣不灰堆
2022/05/31
3770
我希望按照我的思路尽可能将canvas基础讲明白
「小程序JAVA实战」小程序视频封面处理(48)
PS:截图也是通过ffmpge的方式,小程序工具的坑很多,官网都没介绍返回截图,但是小程序工具就返回截图了,这就是个坑。
IT架构圈
2019/07/08
1.6K0
「小程序JAVA实战」小程序视频封面处理(48)
Wallpaper透视效果的C++实现
Wallpaper的透视图实际上包含了两张图,一张是非透视图,即正常情况下能够被看到的图片,另一张是透视图,即鼠标移到上面才会部分显示的图片。
DearXuan
2022/01/19
1.5K0
手势魅力-设置一个触摸菜单
本篇为一移动端博文,个人觉得这篇外文还可以,就翻译了一下,最终实现的一个效果是:用手势创建一个本地菜单(点击一菜单按钮,实现设置一个触摸侧滑,滑动滑出效果,如下文中的gif图所示),主要涉及的知识点有移动端三大触摸事件(touchstart,touchmove,touchend),触摸属性,以及实现侧边栏动画,在处理移动端点击,拖动,滑动时,是不得要考虑用户的触摸手势,判断手指在页面上到底是点击还是滑动的,利用原生js的方法封装点击,移动,抬起功能函数,尽管移动(手机)端与pc端有很多相似之处,但还是有很多要注意的地方的,如果你想获得该Demo的源码,复制该标题后台回复[手势魅力-设置一个触摸菜单]就可以了的,初次翻译,如果有误导的地方,欢迎路过的老师,多提意见和指正,如果你想阅读英文原文,扫文末下方二维码或者跳转到指定链接就可以了的
itclanCoder
2020/10/28
2.1K0
手势魅力-设置一个触摸菜单
推荐阅读
相关推荐
实现Web端自定义截屏
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验