Files
aida_front/src/component/Canvas/OverallCanvas/index.vue

312 lines
8.2 KiB
Vue
Raw Normal View History

2026-01-07 10:27:48 +08:00
<template>
<div class="overall-canvas" ref="el"><canvas ref="canvasRef"></canvas></div>
2026-01-07 10:27:48 +08:00
</template>
<script setup>
2026-01-07 13:02:08 +08:00
import { fabric } from "fabric-with-all";
import { ref, watch, onMounted } from "vue";
2026-01-09 16:41:49 +08:00
const KEYS = {
FILL_X: "location[0]",
FILL_Y: "location[1]",
FILL_ANGLE: "angle",
FILL_SCALEX: "scale[0]",
FILL_SCALEY: "scale[1]",
FILL_GAPX: "object.gapX",
FILL_GAPY: "object.gapY",
O_TOP: "object.top",
O_LEFT: "object.left",
O_SCALE_X: "object.scaleX",
O_SCALE_Y: "object.scaleY",
O_OPACITY: "object.opacity",
O_ANGLE: "object.angle",
O_FLIPX: "object.flipX",
O_FLIPY: "object.flipY",
O_BLENDMODE: "object.blendMode",
};
const ACTIONS = {
2026-01-09 17:05:46 +08:00
ADD: "add",
2026-01-09 16:41:49 +08:00
SELECT: "select",
UPDATE: "update",
DELETE: "delete",
SORT: "sort",
};
2026-01-15 14:10:05 +08:00
const emit = defineEmits(["change-canvas", "init-canvas"]);
2026-01-07 10:27:48 +08:00
const props = defineProps({
2026-01-09 14:40:18 +08:00
list: { type: Array, default: () => [] },
2026-01-16 10:29:03 +08:00
width: { type: Number, required: true },
height: { type: Number, required: true },
2026-01-07 10:27:48 +08:00
});
2026-01-07 13:02:08 +08:00
const el = ref(null);
const canvasRef = ref(null);
const observer = ref(null);
2026-01-09 14:40:18 +08:00
var canvas = null;
2026-01-07 13:02:08 +08:00
onMounted(async () => {
initCanvas();
2026-01-09 14:40:18 +08:00
await setCanvasData();
2026-01-07 13:02:08 +08:00
let throttleTimeout = null;
let lastRunTime = 0;
let trailingTimeout = null;
observer.value = new ResizeObserver((entries) => {
const now = Date.now();
const throttleDelay = 100;
if (!throttleTimeout) {
updateCanvasSize();
lastRunTime = now;
throttleTimeout = setTimeout(() => {
throttleTimeout = null;
}, throttleDelay);
} else {
clearTimeout(trailingTimeout);
trailingTimeout = setTimeout(() => {
updateCanvasSize();
lastRunTime = Date.now();
}, throttleDelay);
}
});
observer.value.observe(el.value);
2026-01-15 14:10:05 +08:00
emit("init-canvas", props.list);
2026-01-07 13:02:08 +08:00
});
onBeforeUnmount(() => {
observer.value.disconnect();
2026-01-09 16:41:49 +08:00
unbindEvent();
2026-01-07 13:02:08 +08:00
});
const initCanvas = () => {
2026-01-09 14:40:18 +08:00
canvas = new fabric.Canvas(canvasRef.value, {
2026-01-07 13:02:08 +08:00
selection: false,
2026-01-09 16:41:49 +08:00
preserveObjectStacking: true,
2026-01-07 13:02:08 +08:00
});
2026-01-09 14:40:18 +08:00
canvas.setWidth(el.value.offsetWidth);
canvas.setHeight(el.value.offsetHeight);
2026-01-09 16:41:49 +08:00
bindEvent();
2026-01-07 13:02:08 +08:00
};
2026-01-09 14:40:18 +08:00
const updateCanvasSize = async () => {
canvas.setWidth(el.value.offsetWidth);
canvas.setHeight(el.value.offsetHeight);
await setCanvasData();
2026-01-07 13:02:08 +08:00
};
2026-01-09 16:41:49 +08:00
// 绑定事件
const bindEvent = () => {
canvas.on("object:modified", onObjectModified);
canvas.on("selection:created", onObjectSelected);
canvas.on("selection:updated", onObjectSelected);
};
// 解绑事件
const unbindEvent = () => {
canvas.off("object:modified", onObjectModified);
canvas.off("selection:created", onObjectSelected);
canvas.off("selection:updated", onObjectSelected);
};
// 处理对象修改事件
const onObjectModified = (e) => {
console.log(e);
const object = e.target;
const action = e.action;
const list = [];
const token = object.token;
2026-01-09 17:10:51 +08:00
if (action === "drag" || action === "rotate") {
2026-01-09 16:41:49 +08:00
list.push({
token,
2026-01-09 16:41:49 +08:00
action: ACTIONS.UPDATE,
key: KEYS.O_TOP,
2026-01-16 10:29:03 +08:00
value: (props.height / canvas.height) * object.top,
2026-01-09 16:41:49 +08:00
});
list.push({
token,
2026-01-09 16:41:49 +08:00
action: ACTIONS.UPDATE,
key: KEYS.O_LEFT,
2026-01-16 10:29:03 +08:00
value: (props.width / canvas.width) * object.left,
2026-01-09 16:41:49 +08:00
});
2026-01-09 17:10:51 +08:00
if (action === "rotate") {
list.push({
token,
2026-01-09 17:10:51 +08:00
action: ACTIONS.UPDATE,
key: KEYS.O_ANGLE,
value: object.angle,
});
}
2026-01-09 16:41:49 +08:00
} else if (action === "scale") {
list.push({
token,
2026-01-09 16:41:49 +08:00
action: ACTIONS.UPDATE,
key: KEYS.O_SCALE_X,
value: object.scaleX,
});
list.push({
token,
2026-01-09 16:41:49 +08:00
action: ACTIONS.UPDATE,
key: KEYS.O_SCALE_Y,
value: object.scaleY,
});
}
emit("change-canvas", list);
};
// 对象选中
const onObjectSelected = (e) => {
const token = e.selected[0].token;
const list = [{ token, action: ACTIONS.SELECT }];
2026-01-09 16:41:49 +08:00
emit("change-canvas", list);
};
2026-01-15 13:42:33 +08:00
// 删除对象
const onDeleteItem = (object) => {
const list = [{ token: object.token, action: ACTIONS.DELETE }];
emit("change-canvas", list);
canvas.remove(object);
canvas.renderAll();
};
2026-01-09 14:40:18 +08:00
const urlToCanvas = (url) => {
2026-01-07 13:02:08 +08:00
return new Promise((resolve, reject) => {
fabric.Image.fromURL(
2026-01-09 14:40:18 +08:00
url,
2026-01-07 13:02:08 +08:00
(object) => {
const imgElement = object.getElement();
// 创建透明 Canvas
const tcanvas = document.createElement("canvas");
tcanvas.width = imgElement.width;
tcanvas.height = imgElement.height;
const ctx = tcanvas.getContext("2d");
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(imgElement, 0, 0);
resolve(tcanvas);
},
{ crossOrigin: "anonymous" }
);
});
};
2026-01-09 14:40:18 +08:00
const setCanvasData = async () => {
canvas.clear();
for (let i = 0; i < props.list.length; i++) {
let item = props.list[i];
2026-01-09 17:05:46 +08:00
await addObject(item);
2026-01-09 14:40:18 +08:00
}
canvas.renderAll();
2026-01-07 13:02:08 +08:00
};
2026-01-09 17:05:46 +08:00
const addObject = async (item) => {
const cwidth = canvas.width;
const cheight = canvas.height;
let pattern = await setFill(item);
let rect = new fabric.Rect({
token: item.token,
2026-01-09 17:05:46 +08:00
width: cwidth,
height: cheight,
fill: pattern,
...item.object,
2026-01-16 10:29:03 +08:00
top: item.object.top / (props.height / canvas.height),
left: item.object.left / (props.width / canvas.width),
2026-01-15 13:42:33 +08:00
onDelete: (v) => onDeleteItem(v),
2026-01-09 17:05:46 +08:00
});
2026-01-16 16:31:54 +08:00
console.log("==========", props)
2026-01-09 17:05:46 +08:00
canvas.add(rect);
};
2026-01-09 16:41:49 +08:00
const setFill = async (item) => {
if (!item) return null;
const cwidth = canvas.width;
const cheight = canvas.height;
let image = await urlToCanvas(item.path);
let offsetX = item.location[0];
let offsetY = item.location[1];
let scaleX = ((cwidth / image.width) * item.scale[0]) / 5;
let scaleY = ((cheight / image.height) * item.scale[1]) / 5;
let scale = cwidth > cheight ? scaleX : scaleY;
let angle = item.angle;
let gapX = item.object.gapX;
let gapY = item.object.gapY;
let patternTransform = fabric.util.composeMatrix({
scaleX: scale,
scaleY: scale,
angle: angle,
});
// 创建透明 Canvas
let tcanvas = document.createElement("canvas");
tcanvas.width = image.width + gapX;
tcanvas.height = image.height + gapY;
let ctx = tcanvas.getContext("2d");
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(image, 0, 0);
let pattern = new fabric.Pattern({
source: tcanvas,
repeat: "repeat",
patternTransform,
offsetX, // 水平偏移
offsetY, // 垂直偏移
});
return pattern;
};
const updataList = async (list) => {
console.log(list);
2026-01-09 16:41:49 +08:00
const objects = canvas.getObjects();
for (let i = 0; i < list.length; i++) {
let item = list[i];
let object = objects.find((o) => o.token === item.token);
2026-01-09 16:41:49 +08:00
if (item.action === ACTIONS.UPDATE) {
if (!object) continue;
2026-01-09 16:41:49 +08:00
let value = item.value;
switch (item.key) {
case KEYS.O_TOP:
2026-01-16 10:29:03 +08:00
object.set("top", value / (props.height / canvas.height));
2026-01-09 16:41:49 +08:00
break;
case KEYS.O_LEFT:
2026-01-16 10:29:03 +08:00
object.set("left", value / (props.width / canvas.width));
2026-01-09 16:41:49 +08:00
break;
case KEYS.O_OPACITY:
object.set("opacity", value);
break;
case KEYS.O_SCALE_X:
object.set("scaleX", value);
break;
case KEYS.O_SCALE_Y:
object.set("scaleY", value);
break;
case KEYS.O_ANGLE:
object.set("angle", value);
break;
case KEYS.O_FLIPX:
object.set("flipX", value);
break;
case KEYS.O_FLIPY:
object.set("flipY", value);
break;
case KEYS.O_BLENDMODE:
object.set("blendMode", value);
break;
case KEYS.FILL_X:
case KEYS.FILL_Y:
case KEYS.FILL_ANGLE:
case KEYS.FILL_SCALEX:
case KEYS.FILL_SCALEY:
case KEYS.FILL_GAPX:
case KEYS.FILL_GAPY:
let pattern = await setFill(
2026-01-14 15:08:25 +08:00
props.list.find((v) => v.token === item.token)
2026-01-09 16:41:49 +08:00
);
object.set("fill", pattern);
break;
}
} else if (item.action === ACTIONS.SELECT) {
if (object) canvas.setActiveObject(object);
2026-01-09 16:41:49 +08:00
} else if (item.action === ACTIONS.SORT) {
let tokens = item.tokens;
2026-01-09 16:41:49 +08:00
canvas.clear();
for (let j = 0; j < tokens.length; j++) {
let object = objects.find((o) => o.token === tokens[j]);
if (object) canvas.add(object);
2026-01-09 16:41:49 +08:00
}
canvas.renderAll();
2026-01-09 17:05:46 +08:00
} else if (item.action === ACTIONS.DELETE) {
if (object) canvas.remove(object);
2026-01-09 17:05:46 +08:00
} else if (item.action === ACTIONS.ADD) {
await addObject(item.data);
2026-01-09 16:41:49 +08:00
}
}
canvas.renderAll();
2026-01-09 14:40:18 +08:00
};
defineExpose({
updataList,
});
2026-01-07 10:27:48 +08:00
</script>
<style lang='less' scoped>
.overall-canvas {
2026-01-07 10:27:48 +08:00
width: 100%;
height: 100%;
}
</style>