深度画布联调
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
import { computed, ref, markRaw, onMounted, reactive, nextTick } from 'vue'
|
||||
const dialogVisible = ref(true)
|
||||
const config = ref({
|
||||
url: 'https://s3-alpha-sig.figma.com/img/ea2f/590e/9638f62a2fc91e31f33db0022db1642c?Expires=1773014400&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=M0B8oJJOk~dGG0aZAqOIocAp7T0LFdJ9FYmCrEZVTCRzYxM6SJRNtYMTX-rTO3Z~s14QINh~o-S41XiZnBv-0zcKjuWot~VVaNHfd0~1LesfNe2KwvCinT~72btFut1pheLnKE-wWCX5ewtonxU77bnw386YPMTqv7DBZzksf2udsJA7NmOYD6~TUG3Q2dWSt~zPH~lkaidscPqpCnCbqzljCEi4RiHY4U3A45l5XypcX2umqn1UaYUFCTqV9471J4qdB6Dg2pcKocdp-7-3s1De6Q~2SmBOrSgDQ~KEADCB2lhKfhxgWmy0lwMvhTd4l90ygVZDWZRABgjHNrGUvg__'
|
||||
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
<span class="icon"><svg-icon name="export" size="12" /></span>
|
||||
<span class="text">Export</span>
|
||||
</button>
|
||||
<button class="export" @click="emit('export-local')">
|
||||
<!-- <button class="export" @click="emit('export-local')">
|
||||
<span class="text">保存本地</span>
|
||||
</button>
|
||||
<button class="export" @click="emit('import-local')">
|
||||
<span class="text">本地导入</span>
|
||||
</button>
|
||||
<button class="workbench" @click="emit('export-close')">
|
||||
</button> -->
|
||||
<button class="workbench" @click="onWorkbench">
|
||||
<span class="icon"><svg-icon name="dc-workbench" size="20" /></span>
|
||||
<span class="text">Workbench</span>
|
||||
</button>
|
||||
@@ -30,16 +30,18 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed } from 'vue'
|
||||
import { exportCanvasToImage } from '../tools/exportMethod'
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
const props = defineProps({
|
||||
zoom: { default: 1, type: Number },
|
||||
step: { default: 0.1, type: Number }
|
||||
})
|
||||
const emit = defineEmits(['export', 'export-local', 'import-local', 'export-close'])
|
||||
const emit = defineEmits(['export', 'export-local', 'import-local', 'export-close', 'workbench'])
|
||||
const importLocalImage = inject('importLocalImage') as (isRecord?: boolean) => void
|
||||
const stateManager = inject('stateManager') as any
|
||||
const toolManager = inject('toolManager') as any
|
||||
const objectManager = inject('objectManager') as any
|
||||
const canvasManager = inject('canvasManager') as any
|
||||
const tool = computed(() => toolManager.currentTool.value)
|
||||
const historyIndex = computed(() => stateManager.historyIndex.value)
|
||||
const historyList = computed(() => stateManager.historyList.value)
|
||||
@@ -87,6 +89,11 @@
|
||||
const layer = await importLocalImage(false)
|
||||
objectManager.setFillRepeat(layer?.info?.id)
|
||||
}
|
||||
const onWorkbench = async () => {
|
||||
exportCanvasToImage(canvasManager.canvas).then((url) => {
|
||||
emit('workbench', { url })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@export="exportCanvas"
|
||||
@export-local="exportCanvasToLocalStorage"
|
||||
@import-local="importCanvasFromLocalStorage"
|
||||
@export-close="exportCanvasAndClose"
|
||||
@workbench="(v) => emit('workbench', v)"
|
||||
/>
|
||||
<brush-control-panel :currentTool="toolManager.currentTool.value" />
|
||||
<zoom
|
||||
@@ -44,7 +44,7 @@
|
||||
import { KeyEventManager } from './manager/events/KeyEventManager'
|
||||
import { ObjectManager } from './manager/ObjectManager'
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const emit = defineEmits(['workbench', 'close'])
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Object,
|
||||
@@ -87,14 +87,15 @@
|
||||
provide('objectManager', objectManager)
|
||||
|
||||
const observer = ref(null)
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
keyEventManager.registerEvents()
|
||||
canvasManager.initCanvas({
|
||||
await canvasManager.initCanvas({
|
||||
canvasRef,
|
||||
canvasViewWidth: canvasContainerRef.value.clientWidth,
|
||||
canvasViewHeight: canvasContainerRef.value.clientHeight,
|
||||
canvasWidth: props.config.width || 750,
|
||||
canvasHeight: props.config.height || 600
|
||||
canvasHeight: props.config.height || 600,
|
||||
url: props.config.url || ''
|
||||
})
|
||||
stateManager.onMounted()
|
||||
canvasManager.onMounted()
|
||||
@@ -129,7 +130,7 @@
|
||||
canvasViewWidth: canvasContainerRef.value.clientWidth,
|
||||
canvasViewHeight: canvasContainerRef.value.clientHeight
|
||||
})
|
||||
canvasManager.resetZoom()
|
||||
canvasManager.resetZoom(true, true)
|
||||
}
|
||||
/** 导入本地图片 */
|
||||
const importLocalImage = (isRecord = true) => {
|
||||
@@ -182,10 +183,6 @@
|
||||
stateManager.clearState(true)
|
||||
})
|
||||
}
|
||||
// 导出画布并关闭
|
||||
const exportCanvasAndClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import '@vue-flow/core/dist/style.css';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<fullscreen-dialog v-model="dialogVisible" hide-destroy>
|
||||
<div class="canvas-box">
|
||||
<depth-canvas :config="config" @close="close" />
|
||||
<depth-canvas :config="config" @workbench="onWorkbench" @close="onClose" />
|
||||
</div>
|
||||
</fullscreen-dialog>
|
||||
</template>
|
||||
@@ -12,23 +12,25 @@
|
||||
import { ref } from 'vue'
|
||||
const dialogVisible = ref(false)
|
||||
const config = ref({
|
||||
width: 750,
|
||||
height: 600,
|
||||
id: '',
|
||||
url: ''
|
||||
})
|
||||
|
||||
const open = (options) => {
|
||||
config.value = options
|
||||
dialogVisible.value = true
|
||||
// config.value = options || {}
|
||||
const defaultConfig = {
|
||||
canvasWidth: 750,
|
||||
canvasHeight: 600,
|
||||
canvasViewWidth: 750,
|
||||
canvasViewHeight: 600,
|
||||
}
|
||||
config.value = { ...defaultConfig, ...options || {} }
|
||||
}
|
||||
const close = () => {
|
||||
// 工作区
|
||||
const onWorkbench = (options) => {
|
||||
dialogVisible.value = false
|
||||
config.value.onWorkbench?.(options)
|
||||
}
|
||||
// 关闭
|
||||
const onClose = () => {
|
||||
dialogVisible.value = false
|
||||
config.value.onClose?.()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
close
|
||||
|
||||
@@ -286,14 +286,19 @@ export class AnimationManager {
|
||||
/**
|
||||
* 重置缩放(带平滑动画)
|
||||
* @param {Boolean} animated 是否使用动画
|
||||
* @param {Boolean} adaptive 是否自适应缩放
|
||||
*/
|
||||
async resetZoom(animated = true) {
|
||||
async resetZoom(animated = true, adaptive = false) {
|
||||
const canvasViewWidth = this.canvasManager.canvasViewWidth;
|
||||
const canvasViewHeight = this.canvasManager.canvasViewHeight;
|
||||
const canvasWidth = this.canvasManager.canvasWidth;
|
||||
const canvasHeight = this.canvasManager.canvasHeight;
|
||||
const panX = canvasViewWidth / 2 - canvasWidth / 2
|
||||
const panY = canvasViewHeight / 2 - canvasHeight / 2
|
||||
const scaleX = canvasViewWidth / canvasWidth * 0.8
|
||||
const scaleY = canvasViewHeight / canvasHeight * 0.8
|
||||
const scale = Math.min(scaleX, scaleY, 1)
|
||||
const panX = canvasViewWidth / 2 - canvasWidth * scale / 2
|
||||
const panY = canvasViewHeight / 2 - canvasHeight * scale / 2
|
||||
|
||||
return new Promise((resolve) => {
|
||||
if (animated) {
|
||||
// 停止任何进行中的动画
|
||||
@@ -322,7 +327,7 @@ export class AnimationManager {
|
||||
|
||||
// 使用GSAP同时动画缩放和平移
|
||||
gsap.to(viewTransform, {
|
||||
zoom: 1,
|
||||
zoom: scale,
|
||||
panX: panX,
|
||||
panY: panY,
|
||||
duration: 0.5,
|
||||
@@ -342,16 +347,16 @@ export class AnimationManager {
|
||||
},
|
||||
onComplete: () => {
|
||||
// 确保最终状态准确
|
||||
this.canvas.setViewportTransform([1, 0, 0, 1, panX, panY]);
|
||||
this.currentZoom.value = 100;
|
||||
this.canvas.setViewportTransform([scale, 0, 0, scale, panX, panY]);
|
||||
this.currentZoom.value = scale * 100;
|
||||
this._zoomAnimation = null;
|
||||
this._panAnimation = null;
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.canvas.setViewportTransform([1, 0, 0, 1, panX, panY]);
|
||||
this.currentZoom.value = 100;
|
||||
this.canvas.setViewportTransform([scale, 0, 0, scale, panX, panY]);
|
||||
this.currentZoom.value = scale * 100;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AnimationManager } from './AnimationManager'
|
||||
import { detectDeviceType } from '../tools/index'
|
||||
import { CanvasEventManager } from "./events/CanvasEventManager";
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
import { createId } from '../../tools/tools'
|
||||
|
||||
// 自定义画布转对象属性
|
||||
fabric.Object.prototype.customProperties = ["top", "left", "width", "height", "scaleX", "scaleY", "info", "thumbnail"];
|
||||
@@ -28,6 +29,7 @@ interface CanvasInitOptions {
|
||||
canvasViewHeight?: number
|
||||
canvasWidth?: number
|
||||
canvasHeight?: number
|
||||
url?: string
|
||||
}
|
||||
export class CanvasManager {
|
||||
stateManager: any
|
||||
@@ -63,17 +65,36 @@ export class CanvasManager {
|
||||
enableRetinaScaling: true,
|
||||
backgroundColor: '#fff',
|
||||
})
|
||||
this.setCanvasViewSize(options)
|
||||
if (options.url) {
|
||||
await new Promise((resolve, reject) => {
|
||||
fabric.Image.fromURL(options.url, async (img) => {
|
||||
this.canvasWidth = img.width
|
||||
this.canvasHeight = img.height
|
||||
this.setCanvasViewSize(options)
|
||||
img.set({
|
||||
left: 0,
|
||||
top: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
info: {
|
||||
id: createId("image"),
|
||||
name: "图片图层",
|
||||
}
|
||||
})
|
||||
this.canvas.add(img)
|
||||
await this.layerManager.updateLayerThumbnailsById(img.info.id)
|
||||
resolve(img)
|
||||
}, { crossOrigin: 'anonymous' })
|
||||
})
|
||||
} else {
|
||||
this.setCanvasViewSize(options)
|
||||
}
|
||||
this.canvas.clipPath = new fabric.Rect({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: this.canvasWidth,
|
||||
height: this.canvasHeight
|
||||
})
|
||||
// 画布居中
|
||||
const canvasX = this.canvasViewWidth / 2 - this.canvasWidth / 2
|
||||
const canvasY = this.canvasViewHeight / 2 - this.canvasHeight / 2
|
||||
this.canvas.viewportTransform = [1, 0, 0, 1, canvasX, canvasY]
|
||||
|
||||
// 动画管理器
|
||||
this.animationManager = new AnimationManager(this.canvas, {
|
||||
@@ -83,6 +104,7 @@ export class CanvasManager {
|
||||
defaultEase: "power2.lin",
|
||||
defaultDuration: 0.3, // 缩短默认动画时间
|
||||
});
|
||||
|
||||
this.setupCanvasEvents()
|
||||
this.setupBrushEvents()
|
||||
|
||||
@@ -100,11 +122,13 @@ export class CanvasManager {
|
||||
// })
|
||||
// // 文字
|
||||
// const text = await this.layerManager.createTextLayer('Hello World');
|
||||
// this.layerManager.updateLayers()
|
||||
// this.layerManager.setActiveID(text.info.id)
|
||||
// this.stateManager.setIsRecord(true)
|
||||
/** 测试-结束 */
|
||||
|
||||
|
||||
this.resetZoom(false, true)// 画布居中
|
||||
|
||||
this.layerManager.updateLayers()
|
||||
this.stateManager.recordState()
|
||||
// this.stateManager.toolManager.setTool(OperationType.RECTANGLE)
|
||||
}
|
||||
@@ -147,8 +171,8 @@ export class CanvasManager {
|
||||
if (obj) this.canvas.setActiveObject(obj)
|
||||
this.renderAll()
|
||||
}
|
||||
resetZoom() {
|
||||
this.animationManager.resetZoom()
|
||||
resetZoom(animated = true, adaptive = true) {
|
||||
this.animationManager.resetZoom(animated, adaptive)
|
||||
}
|
||||
// 使用动画管理器的缩放方法
|
||||
animateZoom(point, targetZoom, options = {}) {
|
||||
@@ -247,7 +271,6 @@ export class CanvasManager {
|
||||
this.renderAll()
|
||||
callback?.(true)
|
||||
})
|
||||
|
||||
}
|
||||
dispose() {
|
||||
this.animationManager?.dispose()
|
||||
|
||||
Reference in New Issue
Block a user