从零开发一款图片编辑器Mitu

简介: 我们知道,为了提高企业研发效能和对客户需求的快速响应,现在很多企业都在着手数字化转型,不仅仅是大厂(阿里,字节,腾讯,百度)在做低代码可视化这一块,很多中小企业也在做,拥有可视化低代码相关技术背景的程序员也越来受重视。

网络异常,图片无法展示
|




背景介绍



我们知道,为了提高企业研发效能和对客户需求的快速响应,现在很多企业都在着手数字化转型,不仅仅是大厂(阿里,字节,腾讯,百度)在做低代码可视化这一块,很多中小企业也在做,拥有可视化低代码相关技术背景的程序员也越来受重视。


我最近一直在做数据可视化和lowcode/nocode相关的项目,针对我自己的工作经验和对lowcode/nocode的探索,也写了一系列低代码可视化搭建系列文章,今天我们继续来分享可视化相关的内容——可视化图片编辑器


在分享过程中,我会以最近我写开源的一个项目Mitu为案例,仔细拆解它的实现过程。Mitu主要是辅助H5编辑器 H5-Dooring 做图像处理用的,大家也可以轻松基于它进行二次开发和扩展,变成更强大的图片编辑器。


在文章末尾我会附上 github 地址 和 demo 地址,方便大家学习和体验。接下来我就来带大家介绍和剖析一下这款开源图片编辑器 Mitu


项目介绍



网络异常,图片无法展示
|


以上是图片编辑器的部分演示效果,我们可以通过拖拽重组的方式快速生成我们想要的图片,也能将图片保存为模版,以便后期复用。在项目开发之前我也设计了一个简单的原型,保证自己的开发方向不会跑偏,大家可以参考一下:


网络异常,图片无法展示
|


按照我一向的写作风格,我先列一下技术实现的大纲,以便大家有选择且高效率的阅读和学习:


  • 可视化编辑器项目搭建和技术选型
  • 图形库设计
  • 属性编辑器设计
  • 自定义图元控制器实现
  • 预览功能实现
  • 保存图片功能实现
  • 模版保存实现
  • 导入模版功能实现
  • 可视化图片编辑器后期规划


好了,话不多说,接下来开始我们的技术实现。


技术实现



网络异常,图片无法展示
|


项目搭建和技术选型


编辑器的实现思路和技术栈无关,这里我采用了 React 来实现,当然大家如果更喜欢 Vue 或者 sveltejs,也是没问题的,项目整体技术选型如下:


  • umi 可扩展的企业级前端应用框架
  • React + Typescript
  • Antd 前端组件库
  • fabric 一个可以简化 Canvas 程序编写的库
  • localStorage 本地数据存储


当然在项目的实现过程中还有很多细节和思想,接下来我会一一和大家介绍。如果大家对 fabric 这个库不太熟悉也不用担心,我会通过具体功能的实现来带大家熟悉这个库。

在介绍下面的内容之前我们先安装一下 fabric ,然后初始化一个画布。


yarn add fabric

初始化一个画布:

import { fabric } from "fabric";
import { nanoid } from 'nanoid';
import { useEffect, useState, useRef } from 'react';
export default function IndexPage() {
    const canvasRef = useRef<any>(null);
    useEffect(() => {
        canvasRef.current = new fabric.Canvas('canvas');
        // 创建一个文本元素
        const shape = new fabric.IText(nanoid(8), {
             text: 'H5-Dooring',
             width : 60,
             height : 60,
             fill : '#06c',
             left: 30,
             top: 30
         })
        // 将文本元素插入画布
        canvasRef.current.add(shape);
        // 设置画布的背景色
        canvasRef.current.backgroundColor = 'rgba(255,255,255,1)';
    })
    return <canvas id="canvas" width={600} height={400}></canvas>
}


这样我们就创建好了一个画布,并在画布中插入了一段可编辑可拖拽的文本,如下:


网络异常,图片无法展示
|


图形库设计


作为一款图片编辑器,为了提高使用的灵活性我们还需要提供一些基础图形方便我们设计图片,所以我在编辑器里添加了图形库:


网络异常,图片无法展示
|


主要有如文本图片直线矩形圆形三角形箭头马赛克,当然大家可以根据自己的需求添加更多的基本图元。我们在图片库中点击任意一个元素即可将其插入画布,这块是利用 fabricadd 方法,当然 fabric 也内制了很多基本图形,我们可以在文档中参考一下。为了让图形插入更有封装性,我定义了图形的基本 schema 结构:


const baseShapeConfig = {
  IText: {
    text: 'H5-Dooring',
    width : 60,
    height : 60,
    fill : '#06c'
  },
  Triangle: {
    width: 100,
    height: 100,
    fill: '#06c'
  },
  Circle: {
    radius: 50,
    fill: '#06c'
  },
  Rect: {
    width : 60,
    height : 60,
    fill : '#06c'
  },
  Line: {
    width: 100,
    height: 1,
    fill: '#06c'
  },
  Arrow: {},
  Image: {},
  Mask: {}
}

这样我们插入图形的方法就可以这样写:


type ElementType = 'IText' | 'Triangle' | 'Circle' | 'Rect' | 'Line' | 'Image' | 'Arrow' | 'Mask'
const insertShape = (type:ElementType) => {
    shape = new fabric[type]({
        ...baseShapeConfig[type], 
        left: size[0] / 3,
        top: size[1] / 3
    })
    canvasRef.current.add(shape);
}


后续我们添加图形时只需要定义 schema 即可,但是需要注意的是 fabric 创建图形的方式并不都都是统一的,我们需要对特定图片的创建进行特殊判断,比如直线路径:


if(type === 'Line') {
      shape = new fabric.Path('M 0 0 L 100 0', {
        stroke: '#ccc', 
        strokeWidth: 2,
        objectCaching: false,
        left: size[0] / 3,
        top: size[1] / 3
      })
}


当然我们也可以用 switch 来对不同情况进行不同处理,这样我们就实现了一个基本图片库。


网络异常,图片无法展示
|


属性编辑器设计


属性编辑器主要是用来对图形属性进行配置的,比如填充颜色描边颜色描边宽度,目前我主要定义了这3个维度,大家也可以基于此继续扩展更多的可编辑属性,类似于 H5-Dooring 的组件属性配置面板。


网络异常,图片无法展示
|


我们可以在编辑器右侧的属性编辑区控制图形的属性,因为属性目前只有3个,我就直接硬编码写上去了,大家也可以用动态渲染的方式来实现。需要注意的是我们怎么知道我们选中的是那个组件呢? 好在 fabric 提供了一系列 api 帮助我们更好的控制元素对象,这里我们用 getActiveObject 方法拿到当前选中的元素,具体实现代码如下:


// ...
// 定义基础属性
const [attrs, setAttrs] = useState({
    fill: '#0066cc',
    stroke: '',
    strokeWidth: 0,
  })
// 更新选中的元素
const updateAttr = (type: 'fill' | 'stroke' | 'strokeWidth' | 'imgUrl', val:string | number) => {
    setAttrs({...attrs, [type]: val})
    // 获取当前选中元素对象
    const obj = canvasRef.current.getActiveObject()
    // 设置元素属性
    obj.set({...attrs})
    // 重新渲染
    canvasRef.current.renderAll();
}

属性编辑器的样式实现这里我就不一一介绍了,都比较基础,我们来看一下编辑项的基本结构:


<span className={styles.label}>描边宽度: </span>
<InputNumber size="small" min={0} value={attrs.strokeWidth}  onChange={(v) => updateAttr('strokeWidth', v)} />

自定义图元控制器实现


因为默认情况下 fabric 没有提供删除按钮和逻辑,所以我们需要自己二次扩展,恰好 fabric 提供了自定义扩展的方法,接下来我们就一起自定义一个删除按钮并实现删除逻辑。


网络异常,图片无法展示
|


具体实现代码如下:


// 删除按钮
const deleteIcon = "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Ccircle style='fill:%23F44336;' cx='299.76' cy='439.067' r='218.516'/%3E%3Cg%3E%3Crect x='267.162' y='307.978' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -222.6202 340.6915)' style='fill:white;' width='65.545' height='262.18'/%3E%3Crect x='266.988' y='308.153' transform='matrix(0.7071 0.7071 -0.7071 0.7071 398.3889 -83.3116)' style='fill:white;' width='65.544' height='262.179'/%3E%3C/g%3E%3C/svg%3E";
// 删除方法
function deleteObject(eventData, transform) {
    const target = transform.target;
    const canvas = target.canvas;
    canvas.remove(target);
    canvas.requestRenderAll();
}
// 渲染icon
function renderIcon(ctx, left, top, styleOverride, fabricObject) {
      const size = this.cornerSize;
      ctx.save();
      ctx.translate(left, top);
      ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
      ctx.drawImage(img, -size/2, -size/2, size, size);
      ctx.restore();
}
// 全局添加删除按钮
fabric.Object.prototype.controls.deleteControl = new fabric.Control({
      x: 0.5,
      y: -0.5,
      offsetY: -32, // 自定义距元素的偏移距离, 也可以定义offsetX
      cursorStyle: 'pointer',
      mouseUpHandler: deleteObject,
      render: renderIcon,
      cornerSize: 24
});


这样我们就实现了自定义元素控制,我们也可以按照类似的方法实现自定义的控件。效果如下:


网络异常,图片无法展示
|


预览功能实现


预览功能我主要是利用原生 canvastoDataURL 方法来生成base64的数据,然后赋值给 img 标签。还有一个细节需要注意的是如果我们在预览之前画布仍然有选中状态的元素,那么控制点也会被截取出来,如下:


网络异常,图片无法展示
|


这样对用户体验非常不好,我们需要在预览时看到一张纯粹的图片,我的方案是在预览前取消画布所有元素的选中状态,可以用 fabric 实例的 discardActiveObject() 方法取消激活状态,然后更新画布即可,具体实现逻辑如下:


// 1. 取消画布所有元素的选中状态
canvasRef.current.discardActiveObject()
canvasRef.current.renderAll();
// 2. 将当前画布转化为图片的base64地址
const img = document.getElementById("canvas");
const src = (img as HTMLCanvasElement).toDataURL("image/png");
// 3. 设置元素url,显示预览弹窗
setImgUrl(src)
setIsShow(true)

预览效果展示:


网络异常,图片无法展示
|


保存图片功能实现


保存图片其实和预览功能很像,唯一不同的是我们需要把图片下载到本地,那么我主要是用纯前端的方式实现图片下载,大家也可以用自己熟悉的前端下载方案,接下来贴一下我的方案实现:


function download(url:string, filename:string, cb?:Function) {
  return fetch(url).then(res => res.blob().then(blob => {
    let a = document.createElement('a');
    let url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = filename;
    a.click();
    window.URL.revokeObjectURL(url);
    cb && cb()
  }))
}


主要是用的windowURL 对象的 createObjectURLrevokeObjectURL 方法,两年前我也在我的文章中分享过对应的实现,感兴趣的可以参考一下。下载的效果如下:


网络异常,图片无法展示
|


模版保存实现


在设计图片编辑器的过程中我们也要考虑保存用户的资产,比如做的比较好的图片可以保存为模版,以便下次复用,所以我在编辑器里还实现的简单的模版保存和使用的功能。我们先看一下效果:


网络异常,图片无法展示
|


我们在演示中可以看到保存为模版之后会自动同步到左侧的模版列表中,我们下次创作时可以直接导入模版进行二次创作。以下是实现的逻辑图:


网络异常,图片无法展示
|



由上图可以发现我们保存模版不仅仅是保存图片,还需要保存图片对应的 json schema 数据,之所以要保存 json schema 是为了当用户切换到对应的模版之后可以保证模版的每个元素都可以还原,类似于我们最熟悉的 PSD 源文件。fabric 提供了序列化画布的方法 toDatalessJSON(),我们在保存模版的时候只要把序列化后的 json 和图片一起保存即可,这里方便处理我暂时存在 localStorage 中,大家也可以使用大容量本地化存储方案 indexedDB,我之前也基于 indexedDB 封装了开箱即用的缓存库 xdb,大家可以直接拿来使用。



保存模版的具体实现如下:

const handleSaveTpl = () => {
    const val = tplNameRef.current.state.value
    const json = canvasRef.current.toDatalessJSON()
    const id = nanoid(8)
    // 存json
    const tpls = JSON.parse(localStorage.getItem('tpls') || "{}")
    tpls[id] = {json, t: val};
    localStorage.setItem('tpls', JSON.stringify(tpls))
    // 存图片
    canvasRef.current.discardActiveObject()
    canvasRef.current.renderAll()
    const imgUrl = getImgUrl()
    const tplImgs = JSON.parse(localStorage.getItem('tplImgs') || "{}")
    tplImgs[id] = imgUrl
    localStorage.setItem('tplImgs', JSON.stringify(tplImgs))
    // 更新模版列表
    setTpls((prev:any) => [...prev, {id, t: val}])
    setIsTplShow(false)
  }


导入模版功能实现


导入模版的本质是反序列化 Json Schema,在研究 fabric 的过程中发现了其可以直接加载 json 渲染图形序列,所以我们可以直接将上文保存的 json 直接加载到画布:


// 1.加载前清空画布
canvasRef.current.clear();
// 2.重置画布背景色
canvasRef.current.backgroundColor = 'rgba(255,255,255,1)';
// 3. 渲染json
canvasRef.current.loadFromJSON(tpls[id].json, canvasRef.current.renderAll.bind(canvasRef.current))


然后我们就可以根据保存的模版列表,动态切换模版了:



网络异常,图片无法展示
|



目录
相关文章
|
1月前
|
前端开发
业余时间开发了个海报编辑器
为了满足撰写博客或录制教程视频时对高质量海报的需求,我利用业余时间开发了一款海报编辑器。第一版功能简单,支持固定尺寸、黑底白字的标题。后来经过优化,增加了背景图、模糊效果、文字样式调整等功能,使海报更具吸引力。目前该编辑器已上线,欢迎大家试用并反馈。[访问海报编辑器](https://tool.share888.top/#/poster)
72 6
业余时间开发了个海报编辑器
|
4月前
|
存储 安全 数据安全/隐私保护
Django 后端架构开发:富文本编辑器权限管理与 UEditor 、Wiki接入,实现 Markdown 文本编辑器
Django 后端架构开发:富文本编辑器权限管理与 UEditor 、Wiki接入,实现 Markdown 文本编辑器
177 0
|
5月前
|
移动开发 前端开发 JavaScript
基于 HTML5 和 Canvas 开发的在线图片编辑器
基于 HTML5 和 Canvas 开发的在线图片编辑器
112 0
|
2月前
|
运维 Java Linux
【运维基础知识】掌握VI编辑器:提升你的Java开发效率
本文详细介绍了VI编辑器的常用命令,包括模式切换、文本编辑、搜索替换及退出操作,帮助Java开发者提高在Linux环境下的编码效率。掌握这些命令,将使你在开发过程中更加得心应手。
39 2
|
4月前
|
JavaScript
在Vue项目中vue-quill-editor的安装与使用【详细图解+过程+包含图片的缩放拖拽】
文章详细介绍了在Vue项目中安装和使用`vue-quill-editor`的步骤,包括如何通过npm安装、局部挂载、在Vue页面中引入和配置使用。同时,还提供了如何实现图片的缩放和拖拽功能的进阶教程,涉及到安装额外的插件`quill-image-drop-module`和`quill-image-resize-module`,以及对Webpack配置的调整。最后,文章还提供了实际效果展示和一些后续可能的拓展功能,如上传视频和将图片上传到服务器等。
在Vue项目中vue-quill-editor的安装与使用【详细图解+过程+包含图片的缩放拖拽】
|
5月前
|
小程序
【微信小程序-原生开发】富文本编辑器 editor 的使用教程
【微信小程序-原生开发】富文本编辑器 editor 的使用教程
679 0
【微信小程序-原生开发】富文本编辑器 editor 的使用教程
|
6月前
|
JavaScript 数据安全/隐私保护 开发者
开源图片编辑器推荐-可用于海报编辑、商品设计、封面设计、标签设计等场景
推荐开源图片编辑器,基于fabric.js和Vue开发,适合海报、Logo等设计场景。拥有4.4K GitHub Stars,特性包括自定义字体、素材、模板,支持插件扩展、右键菜单及快捷键。提供图片滤镜、裁剪、拖拽、PSD导入、水印设置和分类素材管理。适用于非专业设计者,易于二次开发。
开源图片编辑器推荐-可用于海报编辑、商品设计、封面设计、标签设计等场景
|
5月前
|
JavaScript
【vue】 Tinymce 富文本编辑器 不想让上传的图片转换成base64,而是链接
【vue】 Tinymce 富文本编辑器 不想让上传的图片转换成base64,而是链接
342 0
|
6月前
CSDN--MD编辑器学习--图片插入尺寸和对齐方式
CSDN--MD编辑器学习--图片插入尺寸和对齐方式
118 0
|
5月前
|
开发工具
vi编辑器,现在vi\vim是文本文件进行编辑的最佳选择,Vim是vi的加强的版本,兼容vi的所有指令,vim编辑器有三种工作模式,一开始进入的是命令模式,命令模式i是插入的意思,两下y+p复制内容
vi编辑器,现在vi\vim是文本文件进行编辑的最佳选择,Vim是vi的加强的版本,兼容vi的所有指令,vim编辑器有三种工作模式,一开始进入的是命令模式,命令模式i是插入的意思,两下y+p复制内容
下一篇
DataWorks