老牌開源 SVG 編輯器 SVGEdit 是如何架構的?

大家好,我是前端西瓜哥。這次簡單看看 SVGEdit 的架構。

SVGEdit 的版本為 7.2.0。

SVGEdit 一款非常老牌的 SVG 圖形編輯器,用于編輯處理 SVG,start 數目前是 5.8k。

它的優點在于經過多年的開發,完成度高,較為成熟,功能相當豐富

  1. 有豐富的工具:選擇工具、鉛筆工具、鋼筆工具(三階貝塞爾)、直線、各種圖形、圖片、文字等;
  2. 畫布縮放、圖形縮放旋轉、編組、復制粘貼、層級排布修改、對齊;
  3. 網格線、標尺、圖層管理、導入導出 SVG;
  4. 歷史記錄,支持撤銷重做(編輯器的基本功能)等等。

缺點是幾乎不維護了,提交很少,大概一個月一提交,最新版 7.2.0 也是 22 年 8 月的時候了。

UI 比較簡陋,很簡單就能看到一些 UI bug。

如果要找一個 UI 好看的,可以看看開源項目 :Method-Draw,這個 UI 好看很多。它 fork 了 SVG-Edit 并做了一些改造。Method-Draw 標榜簡單易用,所以有意去掉了 SVGEdit 的一些高級功能,比如圖層。

技術棧

Web Component + SVG + Rollup + i18next

UI 使用了 Web Component,瀏覽器原生支持的組件方案。

作為一款 SVG 編輯器,選擇 SVG 沒有毛病,這樣渲染效果就完全交給瀏覽器,不需要根據標準去實現渲染效果,自己專心寫編輯器的業務邏輯即可。

沒有用 TypeScript,因為是很老的項目,當時 TypeScript 尚未大行其道。如果要做新項目,建議還是上 TypeScript,大型復雜軟件還是很需要類型系統的。

打包用了 Rollup。國際化用了 i18next。

模塊設計

UI 層對應 Editor 類,該類繼承了 EditorStartup 類,后者做一些初始化操作。

編輯器內核對應 SvgCanvas 類。

SvgCanvas 感覺太多讀寫屬性的方法(getXx 和 setXx)了,學 Java 學的。可以抽幾個小類把耦合性強的方法封裝起來。

有插件機制,插件對象通過 addExtension 方法注入到 SvgCanvas.extensions 對象。

可以看到注冊了 grid(網格)、markers(標尺)之類的插件。

關于 UI 層和內核層的通信,UI 層改數據,會直接改內核層,然后再改 UI 層。

這里的 zoom 有兩個數據源,可能會出現改了一個忘記改另一個的情況。建議只使用一個內核層數據源,改這個數據源后通過事件通知 UI 層或其他層做數據同步。多數據源是壞文明。

渲染方案

渲染方案是 SVG。

SVG 編輯器用 SVG,相當合理。

對于圖形樹的實現、圖形拾取(點選)、圖形渲染,SVGEdit 都交給瀏覽器都去實現。

SVG 對一般前端開發是非常好上手的,不需要太多圖形知識,本質就是一個有層級的 DOM 元素樹,前端同學再熟悉不過了,只是元素專門用來描述圖形。

但因為遠離底層,不方便做一些渲染優化和緩存,圖形多的時候很卡,不適合做高性能圖形編輯器。

靈活性也較差,比如一個 0.5 線寬的直線還把畫布縮小了,根本就點不中好不好,希望點擊區域可以外擴一些,或想點中一個設置為不可見的圖形點擊區域。這個做不了。

當然一個可以考慮的方案是 SVG 只是單純做渲染,圖形拾取自己實現。

但 SVG 有一個強大的優點:方便做功能擴展,進行二次開發

比如你要在圖形編輯器里加一個新的模塊,比如倒計時、一個表單組件,網上找到輪子集成進去會很方便。因為 SVG 里面可以嵌入 DOM 元素,DOM 元素里也可以嵌入 SVG。

UI 層

UI 層原本是基于 jQuery UI 的,但后面丟棄 jQuery 改用 Web Component 進行了重構。

順帶一提,有個叫做 jPicker 的基于 jQuery 的拾色器插件,也做了魔改,去掉對 jQuery 的依賴。

它沒有用 React、Vue 這些 UI 框架,而是選擇 Web Component,我認為這是一個糟糕的決策。

Web Component 雖然被 瀏覽器原生支持,但并不是主流,生態一般,輪子不如 React 和 Vue 豐富。

我們繼續看代碼。

以左側欄 LeftPanel 為例,HTML 為:

這里的 se-button 就是一個 Web Component 組件,里面有局部樣式和交互邏輯,類似 React 和 Vue。

全局樣式文件是 svgedit.css

LeftPanel 類初始化后會調用 init 方法。

該方法會:

  1. 讀取前面的 HTML 創建一個 template 元素,然后添加 DOM 樹中。

  2. 給一些 DOM 元素綁定了事件響應函數。

$id 這些是工具類方法。

下面代碼的作用是,給選擇工具按鈕綁定方法,該方法更改編輯器的模式為選擇模式。

LeftPanel 的 init 方法是在 EditorStartUp 類(這個是 Editor 的父類)的 init 方法中被調用的。

圖形拾取

點選 圖形的圖形拾取是交給瀏覽器,監聽鼠標按下事件的方式,讀取 mouseEvent.target

因為可能有組的存在,所以會不斷地讀 parentNode 找最頂層的 group 元素,直到當前 group 結束。

框選 會在點中空白區域出現,并將當前模式(currentMode)臨時切換為 multiselect。

期間產生的選區矩形元素保存在 svgCanvas.rubberBox 屬性中。

拖拽修改選區矩形寬高時,會遞歸 SVG 樹,計算它們的 bbox,判斷是否和選區矩形相交。將相交的圖形放到 selectedElements 屬性中。

工具管理

切換工具使用 SvgCanvas.setMode('line') 的方式。

不同工具都有各自實現的事件響應函數,當用戶進行鼠標操作時,會執行 mouseDownEvent、mouseMoveEvent、mouseUpEvent,會根據 mode 執行不同的工具的方法。

鋼筆工具對應 svgCanvas.pathActions 屬性,對應 pathActionsMethod 方法,沒有用類,而是用了閉包的方式進行邏輯封裝。

圖形工具的邏輯倒沒有抽一個對象出來,直接寫在 ouseDownEvent、mouseMoveEvent、mouseUpEvent 里。

操作的歷史記錄

我以前的文章說過,歷史記錄需要維護一個撤銷棧和一個重做棧。

兩個棧等價于一個數組或雙向鏈表中,加上一個指針,該指針指向多個命令中的當前命令。

撤銷就是把指向往左移動,重做往右移,新操作則把指針后面的命令丟掉,然后把這個新的操作加到數組中,并將指針后移。

SVGEdit 的歷史命令都保存在 UndoManager 類的 undoStack 數組中,并用 undoStackPointer 指針指向最新命令的位置。

SVGEdit 使用了 patch(打補丁)的方式記錄歷史操作,沒有使用圖形樹快照的方式

下面是移動一個矩形產生的操作命令,它記錄了修改圖形屬性的命令,記錄了一個元素修改前后的屬性。

這里有個特殊的 BatchCommand 批量命令對象,它的 stack 數組記錄了一次性要操作的子命令。

其實就是 宏命令。宏命令的作用是將多個命令組合在一起批量執行,以實現一步執行多個操作。

各種命令類保存在 svgCanvas.history 中。

簡評

UI 框架應該選擇 React 或 Vue

這樣項目才會有更多人使用,作為一款比較古早的編輯器才可能煥發第二春。

最好是 Vue3,國內很多中小廠在用,那里的程序員不喜歡造輪子,這樣他們就會用你這個編輯器,然后社區繁榮,贏。當然最好 React 和 Vue 都做。

SVGEdit 丟掉 jQuery 用 Web Component,我不是很理解,外國比較流行這個?這樣很難集成進公司的項目中,不利于發展。

不要使用單例

我看不少地方用了單例,單例模式有個問題,如果頁面需要同時有多個編輯器實例,比如做兩張圖紙對比功能。

那它們就會因為單例的對象共享導致沖突,比如改了編輯器 A 的設置屬性會同時改了編輯器 B 的,這不是我們想要的。

類的面向對象風格是比較好的解法,每個對象都要創建一個新的實例,就不會沖突了。

較多的 UI bug

選中一個元素就能復現,此外 UI 也不好看。

監聽鼠標事件應該放到 document 下

放到 SVG 的容器或 SVG 上其實并不是很好的做法,當光標移到這些元素外時,監聽就消失了,綁定到 doucment 下即使光標移動到瀏覽器外都能監聽。

結尾

SVGEdit 支持的功能很多,但 UI 比較復古,小 bug 有點多的樣子。

但如果你要做 SVG 編輯器,與其從零開始,不如基于 SVGEdit 做去二次開發。

我是前端西瓜哥,關注我,學習更多圖形編輯器知識。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/168143.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/168143.shtml
英文地址,請注明出處:http://en.pswp.cn/news/168143.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

大眾博客系統測試報告【改】

一、項目背景 大眾博客系統采用前后端分離的方法來實現,同時使用了數據庫來存儲相關的數據,同時將其部署到云服務器上。前端主要有四個頁面構成:登錄頁、列表頁、詳情頁以及編輯頁,以上模擬實現了最簡單的大眾博客系統。其結合后端…

Tars-GO 開發

默認環境是安裝好的 創建服務: tarsgo make App Server Servant GoModuleName Tars 實例的名稱,有三個層級,分別是 App(應用)、Server(服務)、Servant(服務者,有時也稱 Object&am…

LeetCode Hot100 74.搜索二維矩陣

題目: 給你一個滿足下述兩條屬性的 m x n 整數矩陣: 每行中的整數從左到右按非嚴格遞增順序排列。每行的第一個整數大于前一行的最后一個整數。 給你一個整數 target ,如果 target 在矩陣中,返回 true ;否則&#x…

數據結構——堆的實現

堆的實現-----C語言版 目錄:一、堆的實現1.1堆的定義1.2堆的實現1.2.1堆的各個接口1.2.2堆的向上調整1.2.3堆的向下調整1.2.4堆的定義聲明和初始化1.2.5堆的數據處理1.2.6堆的判空和堆的數據個數以及堆銷毀1.2.7堆的代碼實現 二、TOP—K問題 目錄: 一、…

C++ 文件和流、異常處理、動態內存、預處理器

一、C文件和流&#xff1a; 在C中進行文件處理&#xff0c;需要包含頭文件<iostream>和<fstream>。fstream標準庫定義的三個新的數據類型&#xff1a; 數據類型 描述 ofstream 該數據類型表示輸出文件流&#xff0c;用于創建文件并向文件寫入信息。 ifstream …

vscode項目推送到git

1、打開項目文件 打開文件后點擊vs code左側工具欄中第三個源代碼管理圖標&#xff0c;點擊初始化倉庫&#xff0c;此時會創建一個本地倉庫會檢查該項目中的文件變更 2、創建遠程倉庫 點擊克隆/下載&#xff0c;復制HTTPS地址 3、添加遠程地址 1&#xff09;圖形化操作 2…

Leetcode刷題之用隊列實現棧(C語言版)

Leetcode刷題之用隊列實現棧&#xff08;C語言版&#xff09; 一、題目描述二、題目要求三、題目示例四、題目解析Ⅰ、MyStack* myStackCreateⅡ、void myStackPush(MyStack* obj, int x)Ⅲ、int myStackPop(MyStack* obj)Ⅳ、int myStackTop(MyStack* obj)Ⅴ、bool myStackEmp…

文件夾重命名:徹底擺脫數字困擾,批量修改文件夾名去除數字

在日常生活和工作中&#xff0c;經常會遇到需要修改文件夾名稱的情況。有時候是因為文件夾名稱中包含了數字&#xff0c;有時候是因為文件夾名稱不符合規范。無論出于什么原因&#xff0c;修改文件夾名稱都是一件非常繁瑣的事情。尤其是需要修改大量文件夾名稱時&#xff0c;手…

Jenkins 整合 Docker 自動化部署

Docker 安裝 Jenkins 配置自動化部署 1. Docker 安裝 Jenkins 1.1 拉取鏡像文件 docker pull jenkins/jenkins1.2 創建掛載文件目錄 mkdir -p $HOME/jenkins_home1.3 啟動容器 docker run -d -p 8080:8080 -v $HOME/jenkins_home:/var/jenkins_home --name jenkins jenkin…

CentOS rpm安裝Nginx和配置

CentOS rpm安裝Nginx和配置 官方下載地址: http://nginx.org/en/download.html 介紹 Nginx(“engine x”)是一款由俄羅斯的程序設計師Igor Sysoev所開發高性能的 Web和 反向代理 服務器&#xff0c;也是一個 IMAP/POP3/SMTP 代理服務器。 rpm包安裝 #安裝nginx&#xff0c…

k8s部署的java服務查看連接nacos緩存的配置文件

一、問題描述 k8s部署的java服務&#xff0c;使用nacos中的配置文件&#xff0c;需要在緩存中查看該服務具體是使用到了哪些配置文件 二、解決 參考文檔: https://nacos.io/zh-cn/docs/system-configurations.html 文檔描述如下: 進入java服務容器進入用戶目錄下的nacos&a…

4-Docker命令之docker version

1.docker version介紹 docker version命令是用于查看docker容器的版本信息 2.docker version用法 docker version [參數] [root@centos79 ~]# docker version --helpUsage: docker version [OPTIONS]Show the Docker version informationOptions:-f, --format string Fo…

Android 12.0 mt6771新增分區功能實現四

1.前言 在12.0的系統rom開發中,在對某些特殊模塊中關于數據的存儲方面等需要新增分區來保存, 所以就需要在系統分區新增分區,接下來就來實現這個功能,看第四部分的新增分區的實現過程 2.mt6771新增分區功能實現四的核心類 device/mediatek/mt6771/ueventd.mt6771.rcdevice…

Java枚舉詳解

一、什么是枚舉類型 枚舉類型是一種特殊的數據類型&#xff0c;用于定義一組固定的命名常量。枚舉類型提供了一種更強大、更安全和更易讀的方式來表示一組相關的常量。 在Java中&#xff0c;枚舉類型是通過使用enum關鍵字來定義的。枚舉類型可以包含一個或多個枚舉常量&#xf…

常見狀態碼總結

常見狀態碼總結 2xx 200 OK&#xff1a;表示服務器成功處理了客戶端的請求&#xff0c;并返回所請求的數據。這是最常見的狀態碼&#xff0c;表示一切正常。201 Created&#xff1a;表示服務器成功處理了客戶端的 POST 請求&#xff0c;并在服務器上創建了新的資源。204 No C…

vue005——vue組件入門(非單文件組件和單文件組件)

一、非單文件組件 1.1、單文件組件的使用 1.1.1、局部注冊 1、第一步&#xff1a;創建school組件 2、第二步&#xff1a;注冊組件&#xff08;局部注冊&#xff09; 3、第三步&#xff1a;使用組件&#xff08;編寫組件標簽&#xff09; <!DOCTYPE html> <html>…

設計模式—里氏替換原則

1.概念 里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說&#xff0c;任何基類可以出現的地方&#xff0c;子類一定可以出現。 LSP是繼承復用的基石&#xff0c;只有當衍生類可以替換掉基類&#xff0c;軟件單位的功能不受到影…

Spring注解方式整合第三方框架

目錄 Spring整合MyBatis 原有xml方式整合配置如下 注解方式&#xff1a; Import可以導入如下三種類 第三方框架是指由其他開發者或團隊開發的軟件模塊或庫&#xff0c;供開發者在自己的應用程序中使用。這些框架通常提供了一系列已經封裝好的功能或工具&#xff0c;可節省開…

使用flask返回json格式的數據

Flask Flask是一個使用Python編寫的輕量級Web框架&#xff0c;它的設計理念是保持簡單、靈活和易擴展。它的核心是Werkzeug和Jinja2&#xff0c;并且它本身只提供了非常基礎的Web框架功能&#xff0c;例如路由和請求處理等。 使用Flask可以快速創建一個Web應用程序&#xff0c;…

跳躍游戲Ⅱ[中等]

優質博文&#xff1a;IT-BLOG-CN 一、題目 給定一個長度為n的0索引整數數組nums。初始位置為nums[0]。每個元素nums[i]表示從索引i向前跳轉的最大長度。換句話說&#xff0c;如果你在nums[i]處&#xff0c;你可以跳轉到任意nums[i j]處: 0 < j < nums[i] i j < n …