請大家動動小手,給我一個免費的 Star 吧~
這一章實現匯入匯出為JSON檔案、另存為圖片、上一步、下一步。
github原始碼
gitee原始碼
示例地址
匯出為JSON檔案
提取需要匯出的內容
getView() {
// 複製畫布
const copy = this.render.stage.clone()
// 提取 main layer 備用
const main = copy.find('#main')[0] as Konva.Layer
// 暫時清空所有 layer
copy.removeChildren()
// 提取節點
let nodes = main.getChildren((node) => {
return !this.render.ignore(node) && !this.render.ignoreDraw(node)
})
// 重新裝載節點
const layer = new Konva.Layer()
layer.add(...nodes)
nodes = layer.getChildren()
// 計算節點佔用的區域
let minX = 0
let maxX = copy.width()
let minY = 0
let maxY = copy.height()
for (const node of nodes) {
const x = node.x()
const y = node.y()
const width = node.width()
const height = node.height()
if (x < minX) {
minX = x
}
if (x + width > maxX) {
maxX = x + width
}
if (y < minY) {
minY = y
}
if (y + height > maxY) {
maxY = y + height
}
if (node.attrs.nodeMousedownPos) {
// 修正正在選中的節點透明度
node.setAttrs({
opacity: copy.attrs.lastOpacity ?? 1
})
}
}
// 重新裝載 layer
copy.add(layer)
// 節點佔用的區域
copy.setAttrs({
x: -minX,
y: -minY,
scale: { x: 1, y: 1 },
width: maxX - minX,
height: maxY - minY
})
// 返回可視節點和 layer
return copy
}
1、首先複製一份畫布
2、取出 main layer
3、篩選目標節點
4、計算目標節點佔用區域
5、調整複製畫布的位置和大小
匯出 JSON
使用 stage 的 toJSON 即可。
// 儲存
save() {
const copy = this.getView()
// 透過 stage api 匯出 json
return copy.toJSON()
}
匯入 JSON,恢復畫布
相比匯出,過程會比較複雜一些。
恢復節點結構
// 恢復
async restore(json: string, silent = false) {
try {
// 清空選擇
this.render.selectionTool.selectingClear()
// 清空 main layer 節點
this.render.layer.removeChildren()
// 載入 json,提取節點
const container = document.createElement('div')
const stage = Konva.Node.create(json, container)
const main = stage.getChildren()[0]
const nodes = main.getChildren()
// 恢復節點圖片素材
await this.restoreImage(nodes)
// 往 main layer 插入新節點
this.render.layer.add(...nodes)
// 上一步、下一步 無需更新 history 記錄
if (!silent) {
// 更新歷史
this.render.updateHistory()
}
} catch (e) {
console.error(e)
}
}
1、清空選擇
2、清空 main layer 節點
3、建立臨時 stage
4、透過 Konva.Node.create 恢復 JSON 定義的節點結構
5、恢復圖片素材(關鍵)
恢復圖片素材
// 載入 image(用於匯入)
loadImage(src: string) {
return new Promise<HTMLImageElement | null>((resolve) => {
const img = new Image()
img.onload = () => {
// 返回載入完成的圖片 element
resolve(img)
}
img.onerror = () => {
resolve(null)
}
img.src = src
})
}
// 恢復圖片(用於匯入)
async restoreImage(nodes: Konva.Node[] = []) {
for (const node of nodes) {
if (node instanceof Konva.Group) {
// 遞迴
await this.restoreImage(node.getChildren())
} else if (node instanceof Konva.Image) {
// 處理圖片
if (node.attrs.svgXML) {
// svg 素材
const blob = new Blob([node.attrs.svgXML], { type: 'image/svg+xml' })
// dataurl
const url = URL.createObjectURL(blob)
// 載入為圖片 element
const image = await this.loadImage(url)
if (image) {
// 設定圖片
node.image(image)
}
} else if (node.attrs.gif) {
// gif 素材
const imageNode = await this.render.assetTool.loadGif(node.attrs.gif)
if (imageNode) {
// 設定圖片
node.image(imageNode.image())
}
} else if (node.attrs.src) {
// 其他圖片素材
const image = await this.loadImage(node.attrs.src)
if (image) {
// 設定圖片
node.image(image)
}
}
}
}
}
關於恢復 svg,關鍵在於拖入 svg 的時候,記錄了完整的 svg xml 在屬性 svgXML 中。
關於恢復 gif、其他圖片,拖入的時候記錄其 src 地址,就可以恢復到節點中。
上一步、下一步
其實就是需要記錄歷史記錄
歷史記錄
history: string[] = []
historyIndex = -1
updateHistory() {
this.history.splice(this.historyIndex + 1)
this.history.push(this.importExportTool.save())
this.historyIndex = this.history.length - 1
// 歷史變化事件
this.config.on?.historyChange?.(_.clone(this.history), this.historyIndex)
}
1、從當前歷史位置,捨棄後面的記錄
2、從當前歷史位置,覆蓋最新的 JSON 記錄
3、更新位置
4、暴露事件(用於外部判斷歷史狀態,以此禁用、啟用上一步、下一步)
更新歷史記錄
一切會產生變動的位置都執行 updateHistory,如拖入素材、移動節點、改變節點位置、改變節點大小、複製貼上節點、刪除節點、改變節點的層次。具體程式碼就不貼了,只是在影響的地方執行一句:
this.render.updateHistory()
上一步、下一步方法
prevHistory() {
const record = this.history[this.historyIndex - 1]
if (record) {
this.importExportTool.restore(record, true)
this.historyIndex--
// 歷史變化事件
this.config.on?.historyChange?.(_.clone(this.history), this.historyIndex)
}
}
nextHistory() {
const record = this.history[this.historyIndex + 1]
if (record) {
this.importExportTool.restore(record, true)
this.historyIndex++
// 歷史變化事件
this.config.on?.historyChange?.(_.clone(this.history), this.historyIndex)
}
}
另存為圖片
// 獲取圖片
getImage(pixelRatio = 1, bgColor?: string) {
// 獲取可視節點和 layer
const copy = this.getView()
// 背景層
const bgLayer = new Konva.Layer()
// 背景矩形
const bg = new Konva.Rect({
listening: false
})
bg.setAttrs({
x: -copy.x(),
y: -copy.y(),
width: copy.width(),
height: copy.height(),
fill: bgColor
})
// 新增背景
bgLayer.add(bg)
// 插入背景
const children = copy.getChildren()
copy.removeChildren()
copy.add(bgLayer)
copy.add(children[0], ...children.slice(1))
// 透過 stage api 匯出圖片
return copy.toDataURL({ pixelRatio })
}
主要關注有2點:
1、插入背景層
2、設定匯出圖片的尺寸
匯出的時候,其實就是把當前向量、非向量素材統一輸出為非向量的圖片,設定匯出圖片的尺寸越大,可以保留更多的向量素材細節。
接下來,計劃實現下面這些功能:
- 實時預覽窗
- 對齊效果
- 連線線
- 等等。。。
是不是值得更多的 Star 呢?勾勾手指~
原始碼
gitee原始碼
示例地址