first commit
This commit is contained in:
151
app/components/designer/DesignerCanvas.vue
Normal file
151
app/components/designer/DesignerCanvas.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
|
||||
import type { Canvas as FabricCanvas } from "fabric";
|
||||
|
||||
const props = defineProps<{
|
||||
size: number;
|
||||
registerCanvas: (payload: {
|
||||
canvas: FabricCanvas;
|
||||
fabric: typeof import("fabric");
|
||||
}) => void;
|
||||
unregisterCanvas: () => void;
|
||||
backgroundColor: string;
|
||||
}>();
|
||||
|
||||
const canvasElement = ref<HTMLCanvasElement | null>(null);
|
||||
const containerElement = ref<HTMLDivElement | null>(null);
|
||||
const isReady = ref(false);
|
||||
|
||||
let fabricCanvas: FabricCanvas | null = null;
|
||||
let fabricModule: typeof import("fabric") | null = null;
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
const syncCanvasDomStyles = () => {
|
||||
if (!fabricCanvas) {
|
||||
return;
|
||||
}
|
||||
const canvases = [fabricCanvas.lowerCanvasEl, fabricCanvas.upperCanvasEl];
|
||||
canvases.forEach((element) => {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.classList.add("rounded-full");
|
||||
element.style.borderRadius = "9999px";
|
||||
element.style.backgroundColor = "transparent";
|
||||
});
|
||||
};
|
||||
|
||||
const updateCssDimensions = (dimension?: number) => {
|
||||
if (!fabricCanvas) {
|
||||
return;
|
||||
}
|
||||
const targetSize =
|
||||
dimension ??
|
||||
containerElement.value?.clientWidth ??
|
||||
containerElement.value?.clientHeight ??
|
||||
null;
|
||||
if (!targetSize) {
|
||||
return;
|
||||
}
|
||||
fabricCanvas.setDimensions(
|
||||
{
|
||||
width: targetSize,
|
||||
height: targetSize,
|
||||
},
|
||||
{ cssOnly: true }
|
||||
);
|
||||
fabricCanvas.calcOffset();
|
||||
fabricCanvas.requestRenderAll();
|
||||
syncCanvasDomStyles();
|
||||
};
|
||||
|
||||
const observeContainer = () => {
|
||||
if (!containerElement.value || !fabricCanvas) {
|
||||
return;
|
||||
}
|
||||
resizeObserver?.disconnect();
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
const dimension = Math.min(entry.contentRect.width, entry.contentRect.height);
|
||||
updateCssDimensions(dimension);
|
||||
});
|
||||
resizeObserver.observe(containerElement.value);
|
||||
updateCssDimensions();
|
||||
};
|
||||
|
||||
const setupCanvas = async () => {
|
||||
if (typeof window === "undefined" || !canvasElement.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
fabricModule = await import("fabric");
|
||||
|
||||
const { Canvas } = fabricModule;
|
||||
fabricCanvas = new Canvas(canvasElement.value, {
|
||||
backgroundColor: "transparent",
|
||||
selection: true,
|
||||
preserveObjectStacking: true,
|
||||
});
|
||||
|
||||
fabricCanvas.setDimensions({ width: props.size, height: props.size });
|
||||
|
||||
props.registerCanvas({ canvas: fabricCanvas, fabric: fabricModule });
|
||||
observeContainer();
|
||||
syncCanvasDomStyles();
|
||||
isReady.value = true;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
setupCanvas();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
props.unregisterCanvas();
|
||||
resizeObserver?.disconnect();
|
||||
resizeObserver = null;
|
||||
fabricCanvas = null;
|
||||
fabricModule = null;
|
||||
isReady.value = false;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
(next, prev) => {
|
||||
if (!isReady.value || next === prev || !fabricCanvas) {
|
||||
return;
|
||||
}
|
||||
fabricCanvas.setDimensions({ width: next, height: next });
|
||||
updateCssDimensions();
|
||||
fabricCanvas.renderAll();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative mx-auto flex max-w-[min(720px,100%)] items-center justify-center overflow-hidden rounded-3xl border border-slate-700/60 bg-slate-900/80 p-6 shadow-xl shadow-slate-950/40"
|
||||
>
|
||||
<div class="relative aspect-square w-full">
|
||||
<div class="absolute inset-4 sm:inset-5 md:inset-6 lg:inset-8">
|
||||
<div ref="containerElement" class="relative h-full w-full">
|
||||
<canvas
|
||||
ref="canvasElement"
|
||||
class="absolute inset-0 h-full w-full rounded-full"
|
||||
:width="size"
|
||||
:height="size"
|
||||
/>
|
||||
<div
|
||||
v-if="!isReady"
|
||||
class="absolute inset-0 grid place-items-center rounded-full bg-slate-900/70"
|
||||
>
|
||||
<span class="text-sm font-medium text-slate-400">Loading canvas…</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user