+
{
className="colorInput"
/>
{drawColors.length > 1 && (
- deleteDrawColor(i)}
- className="indicator-item badge badge-secondary w-3 h-3 p-0 text-sm bg-black text-white border-black cursor-pointer block text-center"
- style={{ lineHeight: '0.5rem' }}
- >
- x
-
+ className="absolute top-[-6px] right-[-6px] rounded-full w-3 h-3 cursor-pointer"
+ />
)}
)
diff --git a/src/i18n/en.json b/src/i18n/en.json
index d92977c..fab59fb 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -90,7 +90,7 @@
"line6": "All drawings support transparency configurations"
},
"BoardMode": {
- "line1": "Drawing board support for configuring background color and transparency configurations",
+ "line1": "The drawing board supports background configuration, including colour, background image, and transparency",
"line2": "The drawing board supports customized width and height configurations",
"line3": "Support for drawing cache enable. Enabling caching will improve drawing performance in the presence of large amounts of drawing content, while disabling caching will improve canvas clarity.",
"line4": "Supports guide line on and off"
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index 38aa0a7..15c25df 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -90,7 +90,7 @@
"line6": "所有绘制内容支持透明度配置"
},
"BoardMode": {
- "line1": "画板支持配置背景颜色和透明度配置",
+ "line1": "画板支持配置背景配置, 包括颜色, 背景图, 透明度",
"line2": "画板支持自定义宽高配置",
"line3": "支持绘制缓存开启. 在存在大量绘制内容的情况下,启用缓存将提高绘制性能,而禁用缓存则会提升画布清晰度",
"line4": "支持辅助线的开启与关闭"
diff --git a/src/store/board.ts b/src/store/board.ts
index ebd6829..dc48f21 100644
--- a/src/store/board.ts
+++ b/src/store/board.ts
@@ -11,6 +11,10 @@ import { paintBoard } from '@/utils/paintBoard'
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { alignGuideLine } from '@/utils/common/fabricMixin/alignGuideLine'
+import {
+ updateCanvasBackgroundImage,
+ handleBackgroundImageWhenCanvasSizeChange
+} from '@/utils/common/background'
interface BoardState {
mode: string // operating mode
@@ -19,7 +23,9 @@ interface BoardState {
canvasWidth: number // canvas width 0.1 ~ 1
canvasHeight: number // canvas height 0.1 ~ 1
backgroundColor: string // canvas background color
- backgroundOpacity: number // canvas background opacity
+ backgroundOpacity: number // canvas background color opacity
+ hasBackgroundImage: boolean // canvas background image
+ backgroundImageOpacity: number // canvas background Image opacity
isObjectCaching: boolean // fabric objectCaching
openGuideLine: boolean // does the guide line show
}
@@ -33,6 +39,9 @@ interface BoardAction {
updateCanvasHeight: (height: number) => void
updateBackgroundColor: (color: string) => void
updateBackgroundOpacity: (opacity: number) => void
+ updateBackgroundImage: (image: string) => void
+ updateBackgroundImageOpacity: (opacity: number) => void
+ cleanBackgroundImage: () => void
updateCacheState: () => void
updateOpenGuideLine: () => void
}
@@ -51,6 +60,8 @@ const useBoardStore = create
()(
canvasHeight: 1,
backgroundColor: 'rgba(255, 255, 255, 1)',
backgroundOpacity: 1,
+ hasBackgroundImage: false,
+ backgroundImageOpacity: 1,
isObjectCaching: true,
openGuideLine: false,
updateMode: (mode) => {
@@ -101,6 +112,21 @@ const useBoardStore = create()(
backgroundOpacity: 1
})
}
+
+ const backgroundImage = paintBoard?.canvas
+ ?.backgroundImage as fabric.Image
+ if (backgroundImage) {
+ handleBackgroundImageWhenCanvasSizeChange()
+ set({
+ hasBackgroundImage: true,
+ backgroundOpacity: backgroundImage.opacity
+ })
+ } else {
+ set({
+ hasBackgroundImage: false,
+ backgroundOpacity: 1
+ })
+ }
},
updateCanvasWidth: (width) => {
const oldWidth = get().canvasWidth
@@ -146,6 +172,45 @@ const useBoardStore = create()(
return {}
})
},
+ updateBackgroundImage: (image) => {
+ const canvas = paintBoard.canvas
+ const oldBackgroundImage = canvas?.backgroundImage as fabric.Image
+ if (canvas && image !== oldBackgroundImage?.src) {
+ updateCanvasBackgroundImage(image)
+ set({
+ hasBackgroundImage: true
+ })
+ }
+ },
+ cleanBackgroundImage: () => {
+ set({
+ hasBackgroundImage: false
+ })
+ const canvas = paintBoard.canvas
+ if (canvas) {
+ canvas.setBackgroundImage(null as unknown as string, () => {
+ paintBoard.render()
+ })
+ }
+ },
+ updateBackgroundImageOpacity: (opacity) => {
+ set((state) => {
+ const canvas = paintBoard.canvas
+ if (canvas && opacity !== state.backgroundImageOpacity) {
+ const backgroundImage = canvas?.backgroundImage as fabric.Image
+ if (backgroundImage) {
+ backgroundImage.set({
+ opacity
+ })
+ }
+
+ return {
+ backgroundImageOpacity: opacity
+ }
+ }
+ return {}
+ })
+ },
updateCacheState() {
const oldCacheState = get().isObjectCaching
set({
diff --git a/src/store/files.ts b/src/store/files.ts
index 921821a..8642d9d 100644
--- a/src/store/files.ts
+++ b/src/store/files.ts
@@ -21,6 +21,7 @@ export interface IBoardData {
version: string // fabric version
objects: fabric.Object[]
background: string // canvas background color (rgba)
+ backgroundImage: fabric.Image
}
interface IFile {
@@ -54,7 +55,7 @@ interface FileAction {
}
const initId = uuidv4()
-export const BOARD_VERSION = '1.3.1'
+export const BOARD_VERSION = '1.4.0'
const useFileStore = create()(
persist(
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index 5c2cfbd..f73eaec 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -68,4 +68,8 @@ declare module 'fabric/fabric-impl' {
export interface Control {
pointIndex: number
}
+
+ export interface Image {
+ src: string
+ }
}
diff --git a/src/utils/common/background.ts b/src/utils/common/background.ts
new file mode 100644
index 0000000..8247324
--- /dev/null
+++ b/src/utils/common/background.ts
@@ -0,0 +1,63 @@
+import { paintBoard } from '../paintBoard'
+import { fabric } from 'fabric'
+import useBoardStore from '@/store/board'
+
+export const handleBackgroundImageWhenCanvasSizeChange = (isRender = true) => {
+ const backgroundImage = paintBoard?.canvas?.backgroundImage as fabric.Image
+ if (backgroundImage) {
+ updateCanvasBackgroundImageRect(backgroundImage)
+ if (isRender) {
+ paintBoard.canvas?.requestRenderAll()
+ }
+ }
+}
+
+export const updateCanvasBackgroundImage = (data: string) => {
+ const canvas = paintBoard.canvas
+ if (!canvas) {
+ return
+ }
+ fabric.Image.fromURL(
+ data,
+ (image) => {
+ updateCanvasBackgroundImageRect(image)
+
+ canvas.setBackgroundImage(image, () => {
+ paintBoard.render()
+ })
+ },
+ {
+ crossOrigin: 'anonymous'
+ }
+ )
+}
+
+export const updateCanvasBackgroundImageRect = (image: fabric.Image) => {
+ const canvas = paintBoard?.canvas
+ if (!canvas) {
+ return
+ }
+
+ const canvasWidth = canvas.getWidth()
+ const canvasHeight = canvas.getHeight()
+
+ const imgWidth = image.width as number
+ const imgHeight = image.height as number
+
+ const scaleWidth = canvasWidth / imgWidth
+ const scaleHeight = canvasHeight / imgHeight
+
+ const scale = Math.min(scaleWidth, scaleHeight)
+ image.scale(scale)
+
+ const imgLeft = canvasWidth / 2 - (imgWidth * scale) / 2
+ const imgTop = canvasHeight / 2 - (imgHeight * scale) / 2
+
+ image.set({
+ left: imgLeft,
+ top: imgTop,
+ originX: 'left',
+ originY: 'top',
+ opacity: useBoardStore.getState().backgroundImageOpacity
+ })
+}
diff --git a/src/utils/event/windowEvent.ts b/src/utils/event/windowEvent.ts
index 63eead9..d2abe44 100644
--- a/src/utils/event/windowEvent.ts
+++ b/src/utils/event/windowEvent.ts
@@ -4,6 +4,7 @@ import { ImageElement } from '../element/image'
import { fabric } from 'fabric'
import useFileStore from '@/store/files'
import useBoardStore from '@/store/board'
+import { handleBackgroundImageWhenCanvasSizeChange } from '../common/background'
export class WindowEvent {
constructor() {
@@ -114,6 +115,7 @@ export class WindowEvent {
canvas.setHeight(
window.innerHeight * useBoardStore.getState().canvasHeight
)
+ handleBackgroundImageWhenCanvasSizeChange()
}
}
}
diff --git a/src/utils/history.ts b/src/utils/history.ts
index 677e18f..59a6cbe 100644
--- a/src/utils/history.ts
+++ b/src/utils/history.ts
@@ -1,8 +1,10 @@
import useFileStore, { IBoardData } from '@/store/files'
import { paintBoard } from './paintBoard'
import { diff, unpatch, patch, Delta } from 'jsondiffpatch'
-import { cloneDeep, omit } from 'lodash'
+import { cloneDeep } from 'lodash'
import { getCanvasJSON, handleCanvasJSONLoaded } from './common/loadCanvas'
+import useBoardStore from '@/store/board'
+import { handleBackgroundImageWhenCanvasSizeChange } from './common/background'
const initState = {}
@@ -18,10 +20,7 @@ export class History {
const canvas = paintBoard.canvas
if (canvas) {
const canvasJson = getCanvasJSON()
- this.canvasData = {
- ...omit(canvasJson, 'objects'),
- objects: cloneDeep(canvasJson?.objects ?? [])
- }
+ this.canvasData = cloneDeep(canvasJson ?? {})
}
}
@@ -39,10 +38,7 @@ export class History {
} else {
this.index++
}
- this.canvasData = {
- ...omit(canvasJson, 'objects'),
- objects: cloneDeep(canvasJson?.objects ?? [])
- }
+ this.canvasData = cloneDeep(canvasJson ?? {})
useFileStore.getState().updateBoardData(canvasJson)
}
}
@@ -58,11 +54,12 @@ export class History {
canvas.requestRenderAll()
useFileStore.getState().updateBoardData(canvasJson)
- this.canvasData = {
- ...omit(canvasJson, 'objects'),
- objects: cloneDeep(canvasJson?.objects ?? [])
- }
+ this.canvasData = cloneDeep(canvasJson ?? {})
paintBoard.triggerHook()
+
+ if ((delta as unknown as IBoardData)?.backgroundImage) {
+ handleBackgroundImageWhenCanvasSizeChange()
+ }
})
}
}
@@ -78,11 +75,12 @@ export class History {
canvas.requestRenderAll()
useFileStore.getState().updateBoardData(canvasJson)
- this.canvasData = {
- ...omit(canvasJson, 'objects'),
- objects: cloneDeep(canvasJson?.objects ?? [])
- }
+ this.canvasData = cloneDeep(canvasJson ?? {})
paintBoard.triggerHook()
+
+ if ((delta as unknown as IBoardData)?.backgroundImage) {
+ handleBackgroundImageWhenCanvasSizeChange()
+ }
})
}
}
@@ -93,6 +91,8 @@ export class History {
this.diffs = []
this.canvasData = {}
useFileStore.getState().updateBoardData(initState)
+ useBoardStore.getState().updateBackgroundColor('#ffffff')
+ useBoardStore.getState().cleanBackgroundImage()
}
initHistory() {
diff --git a/src/utils/paintBoard.ts b/src/utils/paintBoard.ts
index b137a20..9299a8a 100644
--- a/src/utils/paintBoard.ts
+++ b/src/utils/paintBoard.ts
@@ -18,6 +18,7 @@ import { renderPencilBrush } from './element/draw/basic'
import { getEraserWidth } from './common/draw'
import { autoDrawData } from './autodraw'
import { handleCanvasJSONLoaded } from './common/loadCanvas'
+import { handleBackgroundImageWhenCanvasSizeChange } from './common/background'
import useFileStore from '@/store/files'
import useDrawStore from '@/store/draw'
@@ -42,7 +43,8 @@ export class PaintBoard {
this.canvas = new fabric.Canvas(canvasEl, {
selectionColor: 'rgba(101, 204, 138, 0.3)',
preserveObjectStacking: true,
- enableRetinaScaling: true
+ enableRetinaScaling: true,
+ backgroundVpt: false
})
fabric.Object.prototype.set({
borderColor: '#65CC8A',
@@ -85,6 +87,7 @@ export class PaintBoard {
const { files, currentId } = useFileStore.getState()
const file = files?.find((item) => item?.id === currentId)
if (file && this.canvas) {
+ this.canvas.clear()
this.canvas.loadFromJSON(file.boardData, () => {
if (this.canvas) {
if (file.viewportTransform) {
@@ -397,6 +400,7 @@ export class PaintBoard {
updateCanvasWidth = debounce((width) => {
if (this.canvas) {
this.canvas.setWidth(window.innerWidth * width)
+ handleBackgroundImageWhenCanvasSizeChange()
useFileStore.getState().updateCanvasWidth(width)
}
}, 500)
@@ -404,6 +408,7 @@ export class PaintBoard {
updateCanvasHeight = debounce((height) => {
if (this.canvas) {
this.canvas.setHeight(window.innerHeight * height)
+ handleBackgroundImageWhenCanvasSizeChange()
useFileStore.getState().updateCanvasHeight(height)
}
}, 500)