fabric.js圖層功能獨(dú)立顯隱?添加?刪除?預(yù)覽實(shí)現(xiàn)詳解
引言
去年經(jīng)歷了個(gè)虛擬人的項(xiàng)目,其中我參與了前端的部分,一個(gè)用electron寫的編輯器,UI部分用的vue3+tsx的寫法(這種組合是不是沒見過?我也是第一次見,驚得我當(dāng)時(shí)還發(fā)了個(gè)沸點(diǎn))
我所負(fù)責(zé)的部分是讓用戶可以對貼圖進(jìn)行修改,其中就涉及到了圖層功能(類似Photoshop),而我當(dāng)時(shí)選用的fabric是沒有圖層的,因此我就得考慮如何實(shí)現(xiàn)圖層
原理
fabric本身有提供group功能,本意是讓你將畫布上的一些元素組合起來,這也將成為本次圖層功能的基礎(chǔ) 既以一個(gè)group代表一個(gè)圖層,畫布下第一層children只有圖層(group),而在group中,才是用戶實(shí)際繪制的內(nèi)容
效果預(yù)覽
本次demo實(shí)現(xiàn):
- 用戶可手動(dòng)添加/刪除圖層
- 可對每個(gè)圖層進(jìn)行獨(dú)立顯隱操作,并反饋到畫布中
- 可對每個(gè)圖層單獨(dú)預(yù)覽
效果圖:
(別嫌我的樣式丑,我已經(jīng)下班了,沒有UI能夠PUSH我!)
下期預(yù)告
- 讓圖層能夠調(diào)整圖層層級
- 結(jié)合undo + redo + 橡皮擦
小Tips
首先fabric是需要到官方上下載的,在選擇你需要的模塊后再進(jìn)行打包
雖然npm上也可以下載,但那不是官方的包,是有網(wǎng)友打包好以后上傳的,其中沒有包含橡皮擦模塊,很可能會(huì)不符合你的需求
所以我個(gè)人建議你可以自行去官網(wǎng)上打包,然后傳到你的私有npm庫里,然后就可以通過npm來管理了
fabric自定義打包下載地址:Custom Fabric build — Fabric.js Javascript Canvas Library (fabricjs.com)
fabric的事件文檔:Event inspector | Fabric.js Demos (fabricjs.com)
接下來的demo將會(huì)通過直接引入的方式來使用fabric,雖然我平時(shí)寫項(xiàng)目都是ts,但練手demo我個(gè)人建議還是js,問就是省事
完整代碼
目錄:
- fabric.js即為官網(wǎng)下載的插件包,這個(gè)文件就不放了,大家可以自行去官網(wǎng)打包下載
- index.html即本次的頁面,主要負(fù)責(zé)dom的處理,直接扔瀏覽器運(yùn)行就可以
- sketchpad.js 是對fabric的二次封裝,同時(shí)也避免在html中寫太多fabric功能代碼
index.html代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .content { display: flex; } .preview { margin-top: 40px; padding-top: 20px; width: 100%; display: flex; justify-content: center; border-top: 1px solid rgba(0,0,0,0.1); } .preview > img { width: 300px; height: 200px; } .layer-list { width: 300px; display: flex; flex-direction: column; padding-right: 10px; margin-right: 10px; box-sizing: content-box; border-right: 1px solid rgba(0,0,0, 0.2); } .layer { width: 300px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; border: 1px solid rgba(0,0,0, 0.2); } .layer > img { width: 30px; height: 30px; border: 1px solid rgba(0,0,0,0.1); } </style> </head> <body> <div id="app"> <div class="content"> <!-- 左側(cè)的圖層列表 --> <div class="layer-list"> <button style="margin-bottom: 20px;" @click="addLayer">增加圖層</button> <div @click="changeCurrentLayer(item.id)" class="layer" :style="currentLayer === item.id ? 'background-color: rgba(0,0,0, 0.1)' : '' " v-for="item of layers" :key="item.id"> <button @click="changeVisible(item.id)">{{ item.show ? '已顯示' : '已隱藏'}}</button> <img :src="item.data"> <span>{{ item.name }}</span> <button @click="deleteLayer(item.id)">刪除</button> </div> </div> <!-- 右側(cè)的畫板 --> <div class="sketchpad-layout" style="width: 600px;"> <canvas id="sketchpad" width="600" height="400" style="border: 1px solid #ccc;"></canvas> </div> </div> <!-- 對整張畫布進(jìn)行圖片預(yù)覽 --> <div class="preview"> <button @click="updatePreview">整個(gè)畫布預(yù)覽:</button> <img :src="preview"> </div> </div> <!-- 使用vue3來進(jìn)行ui的渲染,懶得操作dom了 --> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <!-- 打包好的fabric文件 --> <script src="./fabric.js"></script> <!-- sketchpad里面就是將fabric封裝了一層 --> <script src="./sketchpad.js"></script> <script> const { createApp } = Vue /** 單條的數(shù)據(jù)類型定義 */ const LayerData = { /** 用于顯示的名稱 */ name: '圖層名稱', /** 圖層的id,也用于管理圖層 */ id: '1111', /** 圖層的顯示狀態(tài) */ show: true, /** 圖層的數(shù)據(jù),用于顯示預(yù)覽圖 */ data: '', } createApp({ data() { return { layers: [],// 圖層數(shù)組,方便管理 sketchpad: null,// 畫板 currentLayer: '',// 當(dāng)前圖層的id preview: '',// 預(yù)覽圖的base64數(shù)據(jù) } }, methods: { /** * 改變圖層的顯示/隱藏 * @param id 圖層的id */ changeVisible(id) { const index = this.layers.findIndex(v => v.id === id); if (index > -1) { this.layers[index].show = !this.layers[index].show; } this.sketchpad.changeLayerVisible(id) }, /** * 刪除圖層 * @param id 圖層的id */ deleteLayer(id) { const index = this.layers.findIndex(v => v.id === id); if (index > -1) { this.layers.splice(index, 1) this.sketchpad.deleteLayer(id) } }, /** * 增加圖層 */ addLayer() { const item = { ...LayerData } item.id = new Date().getTime() item.name = `圖層${this.layers.length + 1}` this.layers.push(item) this.sketchpad.addLayer(item.id) this.changeCurrentLayer(item.id) }, /** 選擇當(dāng)前要操作的圖層 */ changeCurrentLayer(id) { this.currentLayer = id this.sketchpad.changeCurrentLayer(id) }, /** * 更新預(yù)覽圖 */ updatePreview() { this.preview = this.sketchpad.getImage() }, /** 圖層數(shù)據(jù)更新的回調(diào) */ onChangeData(id, data) { const index = this.layers.findIndex(v => v.id === id); if (index > -1) { this.layers[index].data = data; } } }, mounted() { this.sketchpad = new Sketchpad('sketchpad', { change: this.onChangeData }); this.addLayer() } }).mount('#app') </script> </body> </html>
sketchpad.js代碼
console.log('Sketchpad load'); class Sketchpad { /** fabric實(shí)例 */ instance = null; /** 當(dāng)前所在圖層的id */ currentLayer = ''; /** 畫布寬度 */ width = 600; /** 畫布高度 */ height = 600 /** 事件訂閱 */ listeners = { /** * 圖層內(nèi)容變化時(shí)的回調(diào) * @param {string} id 圖層id * @param {base64} data 圖層內(nèi)容的base64格式 */ change: (id, data) => {} } constructor(id, listeners) { this.instance = new fabric.Canvas(id); this.width = this.instance.width; this.height = this.instance.height; this.instance.isDrawingMode = true; this.listeners.change = listeners.change this.instance.on('object:added', ((options) => { if (options.target.type === 'group') return; const groups = this.instance.getObjects() groups.forEach(v => { if (v.layerId === this.currentLayer && v.type === 'group') { v.addWithUpdate(options.target); this.instance.remove(options.target); this.listeners.change(v.layerId, v.toDataURL({ width: this.width, height: this.height })) } }) })) console.log('Sketchpad init') } /** 添加圖層 */ addLayer(id) { const group = new fabric.Group([], { width: this.width, height: this.width, }); // 在這里增加一個(gè)自定義屬性 layerId ,用于區(qū)分圖層 group.layerId = id this.instance.add(group) this.currentLayer = id; this.listeners.change(id, group.toDataURL({ width: this.width, height: this.height })) } /** 改變圖層的顯示/隱藏 */ changeLayerVisible(id) { const groups = this.instance.getObjects() groups.forEach(v => { if (v.layerId === id && v.type === 'group') { v.visible = !v.visible; this.instance.renderAll() // 刷新畫布,改變group的visible屬性,必須通過刷新畫布,才能應(yīng)用新屬性值 } }) } /** 選擇要操作的圖層 */ changeCurrentLayer(id) { this.currentLayer = id } /** 刪除圖層 */ deleteLayer(id) { const groups = this.instance.getObjects() groups.forEach(v => { if (v.layerId === id && v.type === 'group') { this.instance.remove(v) this.instance.renderAll() // 刷新畫布 } }) } /** 獲取畫布數(shù)據(jù),以img標(biāo)簽可以識別的base64格式 */ getImage() { return this.instance.toDataURL() } }
將以上這兩個(gè)文件代碼直接復(fù)制粘貼到編輯器里,然后再去打包個(gè)fabric.js也放進(jìn)編輯器里,就可以運(yùn)行啦
以上就是fabric.js圖層功能獨(dú)立顯隱 添加 刪除 預(yù)覽實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于fabric.js圖層功能實(shí)現(xiàn)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript前端實(shí)用的工具函數(shù)封裝
這篇文章主要為大家介紹了JavaScript前端實(shí)用的一些工具函數(shù)的封裝,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07微信小程序 vidao實(shí)現(xiàn)視頻播放和彈幕的功能
這篇文章主要介紹了微信小程序 vidao實(shí)現(xiàn)視頻播放和彈幕的功能的相關(guān)資料,這里提供實(shí)現(xiàn)代碼及實(shí)現(xiàn)效果圖,需要的朋友可以參考下2016-11-11定時(shí)器在頁面最小化時(shí)不執(zhí)行實(shí)現(xiàn)示例
這篇文章主要為大家介紹了定時(shí)器在頁面最小化時(shí)不執(zhí)行的實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07JavaScript中七種流行的開源機(jī)器學(xué)習(xí)框架
今天小編就為大家分享一篇關(guān)于JavaScript中五種流行的開源機(jī)器學(xué)習(xí)框架,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-10-10Dom-api MutationObserver使用方法詳解
這篇文章主要為大家介紹了Dom-api MutationObserver使用方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11