前端圖形學(xué)實(shí)戰(zhàn): 從零開(kāi)發(fā)幾何畫(huà)板(vue3 + vite版)
前言
hello, 大家好, 我是徐小夕, 今天又到了我們的博學(xué)時(shí)間。
本文是 100+前端幾何學(xué)應(yīng)用案例 專(zhuān)欄的第二篇文章, 在第一篇文章幾何學(xué)在前端邊界計(jì)算中的應(yīng)用和原理分析 中我介紹了幾何學(xué)在前端領(lǐng)域里的應(yīng)用, 同時(shí)用 vue3 帶大家一起實(shí)現(xiàn)了常見(jiàn)圖形的邊界計(jì)算算法, 并且分享了如何用幾何原理和Web Dom生成任意三角形的方式:
如果大家感興趣可以在 gitee 查看我的具體代碼實(shí)現(xiàn): https://gitee.com/lowcode-china/euryd
接下來(lái)就繼續(xù)這個(gè)話(huà)題, 我們進(jìn)一步擴(kuò)展, 來(lái)從零實(shí)現(xiàn)一個(gè)幾何畫(huà)板。
你將收獲
- vue3 + vite的實(shí)用技巧
- 幾何畫(huà)板的基本開(kāi)發(fā)思路
元素創(chuàng)建,
編輯,
拖拽,
圖層管理
撤銷(xiāo)和重做
導(dǎo)入導(dǎo)出
- 利用幾何和代數(shù)學(xué)知識(shí)解決前端問(wèn)題
demo演示
在分享方案之前, 我先給大家演示一下做好的demo, 這樣可以更好的理解我們接下來(lái)要做的事情:
技術(shù)實(shí)現(xiàn)
我們繼續(xù)沿用上一篇文章幾何學(xué)在前端邊界計(jì)算中的應(yīng)用和原理分析的工程, 由于幾何畫(huà)板相當(dāng)于一個(gè)獨(dú)立的小應(yīng)用, 具備一定的復(fù)雜度, 這里我們來(lái)對(duì) vite 工程配置一下對(duì) less 的支持:
安裝 less 和less-loader (推薦yarn, pnpm)
在vite.config.ts里做如下配置:
這樣配置完成之后我們就可以在 vite項(xiàng)目 里用 less 的方式寫(xiě)樣式代碼了, modifyVars屬性里面的配置是為了指定 less 全局變量的地址, 這樣我們可以把主題, 通用樣式放在該目錄下, 以便直接在項(xiàng)目的任何頁(yè)面直接使用。
好了, 準(zhǔn)備工作完成了, 我們開(kāi)始接下來(lái)的實(shí)現(xiàn)部分。
1. 畫(huà)板搭建
畫(huà)板搭建主要是靜態(tài)和交互部分, 這里簡(jiǎn)單和大家介紹一下基本構(gòu)造:
上圖可知畫(huà)板主要分兩個(gè)部分:
- 畫(huà)布區(qū)(包含記錄鼠標(biāo)移動(dòng)坐標(biāo)的文本提示)
- 側(cè)邊控件區(qū)
畫(huà)布的點(diǎn)陣背景我們用 css 的背景樣式實(shí)現(xiàn), 這塊網(wǎng)上也有很多教程, 我就不一一和大家分析了,這里直接上實(shí)現(xiàn)的代碼, 大家可以拿來(lái)就用:
整個(gè)畫(huà)板應(yīng)用的基本結(jié)構(gòu)如下:
如果大家對(duì)這一塊知識(shí)感興趣的可以參考我實(shí)現(xiàn)的代碼, 具體代碼地址: https://gitee.com/lowcode-china/euryd
接下來(lái)我們開(kāi)始進(jìn)行比較核心的方案設(shè)計(jì)。
2. 創(chuàng)建并繪制幾何圖形?
因?yàn)槭钱?huà)版應(yīng)用, 所以圖形的創(chuàng)建一定要簡(jiǎn)單且靈活, 控制權(quán)交給用戶(hù)和鼠標(biāo), 所以這里實(shí)現(xiàn)的效果如下:
用戶(hù)只需要選擇對(duì)應(yīng)的圖形, 用鼠標(biāo)在畫(huà)布里拖動(dòng)即可創(chuàng)建任意大小比例的圖形, 為了實(shí)現(xiàn)這一效果, 我們需要做如下準(zhǔn)備:
- 定義圖形的schema結(jié)構(gòu)
- 根據(jù)鼠標(biāo)光標(biāo)的位置計(jì)算圖形創(chuàng)建的元信息(圖形id, 頂點(diǎn)坐標(biāo), 寬高樣式等屬性)
(1)定義圖形的schema結(jié)構(gòu)
像任何可視化低代碼產(chǎn)品一樣, 我們都需要一個(gè)統(tǒng)一且可擴(kuò)展的組件schema結(jié)構(gòu), 目的是為了更好的識(shí)別組件和派發(fā)屬性。畫(huà)板應(yīng)用里的圖形, 我們可以設(shè)計(jì)如下 schhema 結(jié)構(gòu):
具體配置只要具備通用性, 我們就可以結(jié)合自己的業(yè)務(wù)來(lái)配置。
定義好了 schema, 我們只需要實(shí)現(xiàn)對(duì)應(yīng)的圖形即可, 這里以矩形為例和大家分享一下實(shí)現(xiàn)細(xì)節(jié)。
(2)根據(jù)鼠標(biāo)光標(biāo)的位置計(jì)算圖形創(chuàng)建的元信息
我們都知道, 要想通過(guò)鼠標(biāo)拖動(dòng)來(lái)創(chuàng)建任意一個(gè)矩形, 我們需要知道幾個(gè)條件:
- 鼠標(biāo)按下的初始點(diǎn)的坐標(biāo)
- 鼠標(biāo)拖動(dòng)過(guò)程中的實(shí)時(shí)位置
這兩個(gè)問(wèn)題其實(shí)都可以在全局實(shí)現(xiàn), 基于組件設(shè)計(jì)的原子化原則, 我們可以在畫(huà)布組件里捕獲并計(jì)算出鼠標(biāo)的實(shí)時(shí)位置, 然后派發(fā)給其他組件消費(fèi), 這樣我們也可以是實(shí)現(xiàn)記錄鼠標(biāo)移動(dòng)坐標(biāo)的文本提示 這一功能了。
在上一篇文章中已經(jīng)介紹了如何用 vue3 的組合式函數(shù)來(lái)實(shí)現(xiàn)通用 hooks, 我們接下來(lái)要做的就是把 useMouse 獲取到的結(jié)果加工后讓其他組件能使用, 這里我用 vue3 的toRefs 來(lái)實(shí)現(xiàn)。先來(lái)看一下代碼:
BaseBoard 就是我們的畫(huà)布組件, 我們使用這個(gè)組件可以在頁(yè)面上創(chuàng)建任意數(shù)量的畫(huà)布, 同時(shí)由于vue3 的組合函數(shù)支持使用defineProps 來(lái)定義組件的props, 所以我們可以通過(guò)它定義組件的屬性, 這里對(duì)外暴露了兩個(gè)屬性:
- msg 用來(lái)在外部控制畫(huà)布的名稱(chēng)
- onMouseChange 用來(lái)將內(nèi)部鼠標(biāo)監(jiān)聽(tīng)的事件傳到外部, 讓外部可以拿到內(nèi)部是事件運(yùn)行時(shí)
我們使用 useMouse 的時(shí)候就可以實(shí)時(shí)拿到鼠標(biāo)的x, y的絕對(duì)坐標(biāo), 再減去畫(huà)布在頁(yè)面的實(shí)際偏移cardOffset.x, cardOffset.y, 就可以得出鼠標(biāo)在畫(huà)布中正確的坐標(biāo):
這樣我們就可以通過(guò)onMouseChange回調(diào)把鼠標(biāo)相對(duì)畫(huà)布的坐標(biāo)實(shí)時(shí)傳給父組件了:
同時(shí)我們?cè)诖a中發(fā)現(xiàn)了 defineExpose, 這個(gè) api 作用就是把需要暴露的數(shù)據(jù)導(dǎo)出,供父組件使用,相當(dāng)于子傳父, 我們可以在父組件里拿到暴露的值, 在這里我們把畫(huà)布的 dom 暴露出來(lái), 讓父組件可以拿到子組件的dom。
有了以上的前提, 我們就可以來(lái)創(chuàng)建矩形元素了, 為了更好的管理畫(huà)布中的元素, 我們定義一個(gè)元素集合canvasBox:
當(dāng)用戶(hù)選擇一個(gè)圖形, 在畫(huà)布中按下鼠標(biāo)的那一刻, 我們創(chuàng)建一個(gè)基本的元數(shù)據(jù):
由上面的代碼可知, 我們會(huì)創(chuàng)建一個(gè)矩形的元數(shù)據(jù), 包含了矩形的:
- 元素類(lèi)型
- 矩形的唯一key(方便后續(xù)快速查找該圖形)
- 矩形的初始化樣式
同時(shí)我們?cè)?nbsp;templateDot 變量中緩存了鼠標(biāo)的初始位置, 方便后續(xù)生成矩形完整的元數(shù)據(jù)。
我們?cè)趫D中可以看出當(dāng)拖動(dòng)鼠標(biāo)時(shí)矩形是實(shí)時(shí)跟隨鼠標(biāo)創(chuàng)建的, 要想實(shí)現(xiàn)這個(gè)效果, 我們需要對(duì)鼠標(biāo)的mousemove 進(jìn)行監(jiān)聽(tīng), 并動(dòng)態(tài)更新矩形的元數(shù)據(jù), 如下:
由代碼可知我是通過(guò)實(shí)時(shí)改變矩形元素的 left 和 top 來(lái)實(shí)現(xiàn)矩形跟隨鼠標(biāo)實(shí)時(shí)更新的, 我們使用 transform 也可以實(shí)現(xiàn)同樣的效果, 感興趣的朋友可以嘗試一下。
這里順便擴(kuò)展一下, 我們平時(shí)看到的拖拽框架, 對(duì)組件進(jìn)行多選操作時(shí)也用了同樣的方式, 通過(guò)鼠標(biāo)拖拽滑動(dòng)來(lái)產(chǎn)生多選區(qū)域:
感興趣的朋友可以把這個(gè)方案進(jìn)行擴(kuò)展, 實(shí)現(xiàn)更有意思的應(yīng)用場(chǎng)景。
3. 移動(dòng), 編輯幾何圖形
有了上面創(chuàng)建元素的基礎(chǔ), 我們繼續(xù)來(lái)實(shí)現(xiàn)移動(dòng)和編輯元素的功能。
3.1 移動(dòng)元素
首先我們需要找到當(dāng)前要移動(dòng)的元素, 然后動(dòng)態(tài)改變它的位置, 因?yàn)槊總€(gè)元素我都設(shè)置唯一的key, 所以當(dāng)元素被選中的時(shí)候我們就可以根據(jù)key找到此元素, 并只對(duì)該元素進(jìn)行操作:
以上代碼中主要是通過(guò)計(jì)算鼠標(biāo)移動(dòng)的位置差(通過(guò)緩存鼠標(biāo)上一步的坐標(biāo))來(lái)改變?cè)氐?nbsp;left 和top 值, 在 mouseup 時(shí)重置緩存變量即可完成一次移動(dòng)過(guò)程。
這里有一個(gè)細(xì)節(jié)需要注意, 就是如果在鼠標(biāo)按下之后沒(méi)有拖動(dòng)(也就是好點(diǎn)擊畫(huà)布的操作), 其實(shí)需要把mousedown創(chuàng)建的元素清空刪除, 所以才有了上述代碼的第一步判斷。
3.2 編輯元素
編輯元素其實(shí)和移動(dòng)元素的模式差不多, 改變的是元素的靜態(tài)屬性, 比如我們可以編輯元素的背景顏色, 邊框樣式等, 這里我以刪除元素為例給大家介紹一下實(shí)現(xiàn)過(guò)程。
首先我們展示一下元素的 dom 結(jié)構(gòu):
當(dāng)我們雙擊元素的時(shí)候, 我們通過(guò)key會(huì)給當(dāng)前選中元素一個(gè)激活態(tài), 此時(shí)v-if的刪除按鈕就會(huì)顯示, 我們綁定一個(gè)刪除方法 handleDel :
刪除元素的方法是典型的單向操作, 比較簡(jiǎn)單, 如果我們要改變?cè)氐恼w屬性, 我們需要設(shè)計(jì)一個(gè)屬性面板,并實(shí)現(xiàn)表單渲染器來(lái)動(dòng)態(tài)的更新元素的屬性, 類(lèi)似于 H5-Dooring 中的編輯面板:
在后面的文章中我會(huì)實(shí)現(xiàn)一個(gè)min版的屬性編輯器來(lái)完善我們的幾何畫(huà)板。
4. 圖層管理, 圖片導(dǎo)出等方案介紹
圖層管理也是編輯器常用的功能, 有了我們之前設(shè)計(jì)的 canvasBox, 我們就很容易實(shí)現(xiàn)一個(gè)圖層管理面板了, 我們只需要把存儲(chǔ)在canvasBox 元素?cái)?shù)組遍歷到圖層面板, 并對(duì)其綁定操作方法即可實(shí)現(xiàn)涂圖層管理的常用功能, 比如:
- 顯示隱藏
- 快捷刪除
- 批量刪除
- 多選
- 圖層移動(dòng)
- 切換元素
等等功能, 如 H5-Dooring 中的圖層管理面板: