偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

從零開(kāi)發(fā)一款圖片編輯器Mitu-Dooring

開(kāi)發(fā) 前端
我最近一直在做數(shù)據(jù)可視化和lowcode/nocode相關(guān)的項(xiàng)目,針對(duì)我自己的工作經(jīng)驗(yàn)和對(duì)lowcode/nocode的探索,也寫(xiě)了一系列低代碼可視化搭建系列文章,今天我們繼續(xù)來(lái)分享可視化相關(guān)的內(nèi)容——可視化圖片編輯器。

[[419482]]

背景介紹

我們知道,為了提高企業(yè)研發(fā)效能和對(duì)客戶需求的快速響應(yīng),現(xiàn)在很多企業(yè)都在著手?jǐn)?shù)字化轉(zhuǎn)型,不僅僅是大廠(阿里,字節(jié),騰訊,百度)在做低代碼可視化這一塊,很多中小企業(yè)也在做,擁有可視化低代碼相關(guān)技術(shù)背景的程序員也越來(lái)受重視。

我最近一直在做數(shù)據(jù)可視化和lowcode/nocode相關(guān)的項(xiàng)目,針對(duì)我自己的工作經(jīng)驗(yàn)和對(duì)lowcode/nocode的探索,也寫(xiě)了一系列低代碼可視化搭建系列文章,今天我們繼續(xù)來(lái)分享可視化相關(guān)的內(nèi)容——可視化圖片編輯器。

在分享過(guò)程中,我會(huì)以最近我寫(xiě)開(kāi)源的一個(gè)項(xiàng)目Mitu為案例,仔細(xì)拆解它的實(shí)現(xiàn)過(guò)程。Mitu主要是輔助H5編輯器 H5-Dooring 做圖像處理用的,大家也可以輕松基于它進(jìn)行二次開(kāi)發(fā)和擴(kuò)展,變成更強(qiáng)大的圖片編輯器。

在文章末尾我會(huì)附上 github 地址 和 demo 地址,方便大家學(xué)習(xí)和體驗(yàn)。接下來(lái)我就來(lái)帶大家介紹和剖析一下這款開(kāi)源圖片編輯器 Mitu。

項(xiàng)目介紹

圖片

以上是圖片編輯器的部分演示效果,我們可以通過(guò)拖拽重組的方式快速生成我們想要的圖片,也能將圖片保存為模版,以便后期復(fù)用。在項(xiàng)目開(kāi)發(fā)之前我也設(shè)計(jì)了一個(gè)簡(jiǎn)單的原型,保證自己的開(kāi)發(fā)方向不會(huì)跑偏,大家可以參考一下:

按照我一向的寫(xiě)作風(fēng)格,我先列一下技術(shù)實(shí)現(xiàn)的大綱,以便大家有選擇且高效率的閱讀和學(xué)習(xí):

  • 可視化編輯器項(xiàng)目搭建和技術(shù)選型
  • 圖形庫(kù)設(shè)計(jì)
  • 屬性編輯器設(shè)計(jì)
  • 自定義圖元控制器實(shí)現(xiàn)
  • 預(yù)覽功能實(shí)現(xiàn)
  • 保存圖片功能實(shí)現(xiàn)
  • 模版保存實(shí)現(xiàn)
  • 導(dǎo)入模版功能實(shí)現(xiàn)
  • 可視化圖片編輯器后期規(guī)劃

好了,話不多說(shuō),接下來(lái)開(kāi)始我們的技術(shù)實(shí)現(xiàn)。

技術(shù)實(shí)現(xiàn)

項(xiàng)目搭建和技術(shù)選型

編輯器的實(shí)現(xiàn)思路和技術(shù)棧無(wú)關(guān),這里我采用了 React 來(lái)實(shí)現(xiàn),當(dāng)然大家如果更喜歡 Vue 或者 sveltejs,也是沒(méi)問(wèn)題的,項(xiàng)目整體技術(shù)選型如下:

  • umi 可擴(kuò)展的企業(yè)級(jí)前端應(yīng)用框架
  • React + Typescript
  • Antd 前端組件庫(kù)
  • fabric 一個(gè)可以簡(jiǎn)化 Canvas 程序編寫(xiě)的庫(kù)
  • localStorage 本地?cái)?shù)據(jù)存儲(chǔ)

當(dāng)然在項(xiàng)目的實(shí)現(xiàn)過(guò)程中還有很多細(xì)節(jié)和思想,接下來(lái)我會(huì)一一和大家介紹。如果大家對(duì) fabric 這個(gè)庫(kù)不太熟悉也不用擔(dān)心,我會(huì)通過(guò)具體功能的實(shí)現(xiàn)來(lái)帶大家熟悉這個(gè)庫(kù)。

在介紹下面的內(nèi)容之前我們先安裝一下 fabric ,然后初始化一個(gè)畫(huà)布。

  1. yarn add fabric 

初始化一個(gè)畫(huà)布:

  1. import { fabric } from "fabric"
  2. import { nanoid } from 'nanoid'
  3. import { useEffect, useState, useRef } from 'react'
  4.  
  5. export default function IndexPage() { 
  6.     const canvasRef = useRef<any>(null); 
  7.     useEffect(() => { 
  8.         canvasRef.current = new fabric.Canvas('canvas'); 
  9.         // 創(chuàng)建一個(gè)文本元素 
  10.         const shape = new fabric.IText(nanoid(8), { 
  11.              text: 'H5-Dooring'
  12.              width : 60, 
  13.              height : 60, 
  14.              fill : '#06c'
  15.              left: 30, 
  16.              top: 30 
  17.          }) 
  18.         // 將文本元素插入畫(huà)布 
  19.         canvasRef.current.add(shape); 
  20.         // 設(shè)置畫(huà)布的背景色 
  21.         canvasRef.current.backgroundColor = 'rgba(255,255,255,1)'
  22.     }) 
  23.     return <canvas id="canvas" width={600} height={400}></canvas> 

這樣我們就創(chuàng)建好了一個(gè)畫(huà)布,并在畫(huà)布中插入了一段可編輯可拖拽的文本,如下:

圖形庫(kù)設(shè)計(jì)

作為一款圖片編輯器,為了提高使用的靈活性我們還需要提供一些基礎(chǔ)圖形方便我們?cè)O(shè)計(jì)圖片,所以我在編輯器里添加了圖形庫(kù):

主要有如文本,圖片,直線,矩形,圓形,三角形,箭頭,馬賽克,當(dāng)然大家可以根據(jù)自己的需求添加更多的基本圖元。我們?cè)趫D片庫(kù)中點(diǎn)擊任意一個(gè)元素即可將其插入畫(huà)布,這塊是利用 fabric 的 add 方法,當(dāng)然 fabric 也內(nèi)制了很多基本圖形,我們可以在文檔中參考一下。為了讓圖形插入更有封裝性,我定義了圖形的基本 schema 結(jié)構(gòu):

  1. const baseShapeConfig = { 
  2.   IText: { 
  3.     text: 'H5-Dooring'
  4.     width : 60, 
  5.     height : 60, 
  6.     fill : '#06c' 
  7.   }, 
  8.   Triangle: { 
  9.     width: 100, 
  10.     height: 100, 
  11.     fill: '#06c' 
  12.   }, 
  13.   Circle: { 
  14.     radius: 50, 
  15.     fill: '#06c' 
  16.   }, 
  17.   Rect: { 
  18.     width : 60, 
  19.     height : 60, 
  20.     fill : '#06c' 
  21.   }, 
  22.   Line: { 
  23.     width: 100, 
  24.     height: 1, 
  25.     fill: '#06c' 
  26.   }, 
  27.   Arrow: {}, 
  28.   Image: {}, 
  29.   Mask: {} 

這樣我們插入圖形的方法就可以這樣寫(xiě):

  1. type ElementType = 'IText' | 'Triangle' | 'Circle' | 'Rect' | 'Line' | 'Image' | 'Arrow' | 'Mask' 
  2.  
  3. const insertShape = (type:ElementType) => { 
  4.     shape = new fabric[type]({ 
  5.         ...baseShapeConfig[type],  
  6.         leftsize[0] / 3, 
  7.         topsize[1] / 3 
  8.     }) 
  9.     canvasRef.current.add(shape); 

后續(xù)我們添加圖形時(shí)只需要定義 schema 即可,但是需要注意的是 fabric 創(chuàng)建圖形的方式并不都都是統(tǒng)一的,我們需要對(duì)特定圖片的創(chuàng)建進(jìn)行特殊判斷,比如直線路徑:

  1. if(type === 'Line') { 
  2.       shape = new fabric.Path('M 0 0 L 100 0', { 
  3.         stroke: '#ccc',  
  4.         strokeWidth: 2, 
  5.         objectCaching: false
  6.         leftsize[0] / 3, 
  7.         topsize[1] / 3 
  8.       }) 

當(dāng)然我們也可以用 switch 來(lái)對(duì)不同情況進(jìn)行不同處理,這樣我們就實(shí)現(xiàn)了一個(gè)基本圖片庫(kù)。

屬性編輯器設(shè)計(jì)

屬性編輯器主要是用來(lái)對(duì)圖形屬性進(jìn)行配置的,比如填充顏色,描邊顏色,描邊寬度,目前我主要定義了這3個(gè)維度,大家也可以基于此繼續(xù)擴(kuò)展更多的可編輯屬性,類(lèi)似于 H5-Dooring 的組件屬性配置面板。

我們可以在編輯器右側(cè)的屬性編輯區(qū)控制圖形的屬性,因?yàn)閷傩阅壳爸挥?個(gè),我就直接硬編碼寫(xiě)上去了,大家也可以用動(dòng)態(tài)渲染的方式來(lái)實(shí)現(xiàn)。需要注意的是我們?cè)趺粗牢覀冞x中的是那個(gè)組件呢? 好在 fabric 提供了一系列 api 幫助我們更好的控制元素對(duì)象,這里我們用 getActiveObject 方法拿到當(dāng)前選中的元素,具體實(shí)現(xiàn)代碼如下:

  1. // ... 
  2. // 定義基礎(chǔ)屬性 
  3. const [attrs, setAttrs] = useState({ 
  4.     fill: '#0066cc'
  5.     stroke: ''
  6.     strokeWidth: 0, 
  7.   }) 
  8. // 更新選中的元素 
  9. const updateAttr = (type: 'fill' | 'stroke' | 'strokeWidth' | 'imgUrl', val:string | number) => { 
  10.     setAttrs({...attrs, [type]: val}) 
  11.     // 獲取當(dāng)前選中元素對(duì)象 
  12.     const obj = canvasRef.current.getActiveObject() 
  13.     // 設(shè)置元素屬性 
  14.     obj.set({...attrs}) 
  15.     // 重新渲染 
  16.     canvasRef.current.renderAll(); 

屬性編輯器的樣式實(shí)現(xiàn)這里我就不一一介紹了,都比較基礎(chǔ),我們來(lái)看一下編輯項(xiàng)的基本結(jié)構(gòu):

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

自定義圖元控制器實(shí)現(xiàn)因?yàn)槟J(rèn)情況下 fabric 沒(méi)有提供刪除按鈕和邏輯,所以我們需要自己二次擴(kuò)展,恰好 fabric 提供了自定義擴(kuò)展的方法,接下來(lái)我們就一起自定義一個(gè)刪除按鈕并實(shí)現(xiàn)刪除邏輯。

具體實(shí)現(xiàn)代碼如下:

  1. // 刪除按鈕 
  2. 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"
  3.  
  4. // 刪除方法 
  5. function deleteObject(eventData, transform) { 
  6.     const target = transform.target; 
  7.     const canvas = target.canvas; 
  8.     canvas.remove(target); 
  9.     canvas.requestRenderAll(); 
  10.  
  11. // 渲染icon 
  12. function renderIcon(ctx, lefttop, styleOverride, fabricObject) { 
  13.       const size = this.cornerSize; 
  14.       ctx.save(); 
  15.       ctx.translate(lefttop); 
  16.       ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); 
  17.       ctx.drawImage(img, -size/2, -size/2, sizesize); 
  18.       ctx.restore(); 
  19.  
  20. // 全局添加刪除按鈕 
  21. fabric.Object.prototype.controls.deleteControl = new fabric.Control({ 
  22.       x: 0.5, 
  23.       y: -0.5, 
  24.       offsetY: -32, // 自定義距元素的偏移距離, 也可以定義offsetX 
  25.       cursorStyle: 'pointer'
  26.       mouseUpHandler: deleteObject, 
  27.       render: renderIcon, 
  28.       cornerSize: 24 
  29. }); 

這樣我們就實(shí)現(xiàn)了自定義元素控制,我們也可以按照類(lèi)似的方法實(shí)現(xiàn)自定義的控件。效果如下:

預(yù)覽功能實(shí)現(xiàn)

預(yù)覽功能我主要是利用原生 canvas 的 toDataURL 方法來(lái)生成base64的數(shù)據(jù),然后賦值給 img 標(biāo)簽。還有一個(gè)細(xì)節(jié)需要注意的是如果我們?cè)陬A(yù)覽之前畫(huà)布仍然有選中狀態(tài)的元素,那么控制點(diǎn)也會(huì)被截取出來(lái),如下:

這樣對(duì)用戶體驗(yàn)非常不好,我們需要在預(yù)覽時(shí)看到一張純粹的圖片,我的方案是在預(yù)覽前取消畫(huà)布所有元素的選中狀態(tài),可以用 fabric 實(shí)例的 discardActiveObject() 方法取消激活狀態(tài),然后更新畫(huà)布即可,具體實(shí)現(xiàn)邏輯如下:

  1. // 1. 取消畫(huà)布所有元素的選中狀態(tài) 
  2. canvasRef.current.discardActiveObject() 
  3. canvasRef.current.renderAll(); 
  4.  
  5. // 2. 將當(dāng)前畫(huà)布轉(zhuǎn)化為圖片的base64地址 
  6. const img = document.getElementById("canvas"); 
  7. const src = (img as HTMLCanvasElement).toDataURL("image/png"); 
  8.  
  9. // 3. 設(shè)置元素url,顯示預(yù)覽彈窗 
  10. setImgUrl(src) 
  11. setIsShow(true

預(yù)覽效果展示:

保存圖片功能實(shí)現(xiàn)

保存圖片其實(shí)和預(yù)覽功能很像,唯一不同的是我們需要把圖片下載到本地,那么我主要是用純前端的方式實(shí)現(xiàn)圖片下載,大家也可以用自己熟悉的前端下載方案,接下來(lái)貼一下我的方案實(shí)現(xiàn):

  1. function download(url:string, filename:string, cb?:Function) { 
  2.   return fetch(url).then(res => res.blob().then(blob => { 
  3.     let a = document.createElement('a'); 
  4.     let url = window.URL.createObjectURL(blob); 
  5.     a.href = url; 
  6.     a.download = filename; 
  7.     a.click(); 
  8.     window.URL.revokeObjectURL(url); 
  9.     cb && cb() 
  10.   })) 

主要是用的window 的 URL 對(duì)象的 createObjectURL 和 revokeObjectURL 方法,兩年前我也在我的文章中分享過(guò)對(duì)應(yīng)的實(shí)現(xiàn),感興趣的可以參考一下。下載的效果如下:

模版保存實(shí)現(xiàn)

在設(shè)計(jì)圖片編輯器的過(guò)程中我們也要考慮保存用戶的資產(chǎn),比如做的比較好的圖片可以保存為模版,以便下次復(fù)用,所以我在編輯器里還實(shí)現(xiàn)的簡(jiǎn)單的模版保存和使用的功能。我們先看一下效果:

我們?cè)谘菔局锌梢钥吹奖4鏋槟0嬷髸?huì)自動(dòng)同步到左側(cè)的模版列表中,我們下次創(chuàng)作時(shí)可以直接導(dǎo)入模版進(jìn)行二次創(chuàng)作。以下是實(shí)現(xiàn)的邏輯圖:

由上圖可以發(fā)現(xiàn)我們保存模版不僅僅是保存圖片,還需要保存圖片對(duì)應(yīng)的 json schema 數(shù)據(jù),之所以要保存 json schema 是為了當(dāng)用戶切換到對(duì)應(yīng)的模版之后可以保證模版的每個(gè)元素都可以還原,類(lèi)似于我們最熟悉的 PSD 源文件。fabric 提供了序列化畫(huà)布的方法 toDatalessJSON(),我們?cè)诒4婺0娴臅r(shí)候只要把序列化后的 json 和圖片一起保存即可,這里方便處理我暫時(shí)存在 localStorage 中,大家也可以使用大容量本地化存儲(chǔ)方案 indexedDB,我之前也基于 indexedDB 封裝了開(kāi)箱即用的緩存庫(kù) xdb,大家可以直接拿來(lái)使用。

  • xdb | 基于promise封裝且支持過(guò)期時(shí)間的開(kāi)箱即用的indexedDB緩存庫(kù)

保存模版的具體實(shí)現(xiàn)如下:

  1. const handleSaveTpl = () => { 
  2.     const val = tplNameRef.current.state.value 
  3.     const json = canvasRef.current.toDatalessJSON() 
  4.     const id = nanoid(8) 
  5.     // 存json 
  6.     const tpls = JSON.parse(localStorage.getItem('tpls') || "{}"
  7.     tpls[id] = {json, t: val}; 
  8.     localStorage.setItem('tpls', JSON.stringify(tpls)) 
  9.     // 存圖片 
  10.     canvasRef.current.discardActiveObject() 
  11.     canvasRef.current.renderAll() 
  12.     const imgUrl = getImgUrl() 
  13.     const tplImgs = JSON.parse(localStorage.getItem('tplImgs') || "{}"
  14.     tplImgs[id] = imgUrl 
  15.     localStorage.setItem('tplImgs', JSON.stringify(tplImgs)) 
  16.     // 更新模版列表 
  17.     setTpls((prev:any) => [...prev, {id, t: val}]) 
  18.     setIsTplShow(false
  19.   } 

導(dǎo)入模版功能實(shí)現(xiàn)

導(dǎo)入模版的本質(zhì)是反序列化 Json Schema,在研究 fabric 的過(guò)程中發(fā)現(xiàn)了其可以直接加載 json 渲染圖形序列,所以我們可以直接將上文保存的 json 直接加載到畫(huà)布:

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

然后我們就可以根據(jù)保存的模版列表,動(dòng)態(tài)切換模版了:

后期規(guī)劃這款圖片編輯器我已經(jīng)在 github 開(kāi)源了,大家可以基于次開(kāi)發(fā)更強(qiáng)大的圖片編輯器。

本文轉(zhuǎn)載自微信公眾號(hào)「 趣談前端」

 

責(zé)任編輯:姜華 來(lái)源: 趣談前端
相關(guān)推薦

2022-08-31 08:32:22

數(shù)據(jù)可視化項(xiàng)目nocode

2021-04-12 08:31:53

PC-Dooring項(xiàng)目PC端搭建

2024-03-06 08:26:29

2021-04-08 14:58:59

開(kāi)發(fā)前端編輯器

2021-11-24 09:12:11

Markdown編輯器Linux

2023-06-20 00:04:18

框架開(kāi)發(fā)UMD

2020-09-16 10:27:50

MarkDown編輯器編程

2019-05-30 08:43:45

JavaScript富文本編輯器編輯器

2022-09-05 13:16:42

MicroVim編輯器

2021-09-11 21:03:09

可視化搭建框架

2021-03-08 06:00:03

Markdown在線編輯器開(kāi)源

2019-11-26 08:43:44

平臺(tái)桌面軟件

2023-01-07 08:09:41

零代碼Dooring組件

2021-06-22 14:47:19

electronDooring架構(gòu)

2021-09-26 16:31:18

滑動(dòng)驗(yàn)證碼開(kāi)發(fā)組件設(shè)計(jì)

2022-05-27 10:00:06

C++游戲引擎

2015-02-12 09:51:24

代碼編輯

2021-03-10 09:15:15

代碼文本編輯器編程

2019-11-11 08:00:00

Doppler遠(yuǎn)程監(jiān)測(cè)工具Linux

2021-10-28 08:42:31

Dooring表單設(shè)計(jì)器數(shù)據(jù)可視化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)