Fix foldable background layout

This commit is contained in:
Codex Agent
2026-01-24 10:41:24 +01:00
parent 6c83f4ee4e
commit 0f4d7450ff
4 changed files with 201 additions and 48 deletions

View File

@@ -464,6 +464,8 @@ export type FabricRenderOptions = {
backgroundGradient: { angle?: number; stops?: string[] } | null;
backgroundImageUrl?: string | null;
backgroundImagePanels?: BackgroundImagePanel[];
canvasWidth?: number;
canvasHeight?: number;
readOnly: boolean;
};
@@ -578,6 +580,8 @@ export async function applyBackground(
backgroundImagePanels?: BackgroundImagePanel[],
): Promise<void> {
try {
const canvasWidth = canvas.getWidth() || CANVAS_WIDTH;
const canvasHeight = canvas.getHeight() || CANVAS_HEIGHT;
if (typeof canvas.setBackgroundImage === 'function') {
canvas.setBackgroundImage(null, canvas.requestRenderAll.bind(canvas));
} else {
@@ -587,7 +591,7 @@ export async function applyBackground(
}
if (backgroundImagePanels?.length) {
applyBackgroundFill(canvas, color, gradient);
applyBackgroundFill(canvas, color, gradient, canvasWidth, canvasHeight);
const panelImages = await Promise.all(
backgroundImagePanels.map(async (panel) => ({
@@ -600,10 +604,27 @@ export async function applyBackground(
if (!element) {
return;
}
const clipPath = new fabric.Rect({
left: panel.centerX - panel.width / 2,
top: panel.centerY - panel.height / 2,
width: panel.width,
height: panel.height,
originX: 'left',
originY: 'top',
absolutePositioned: true,
selectable: false,
evented: false,
});
const needsSwap = Math.abs(panel.rotation) % 180 === 90;
const targetWidth = needsSwap ? panel.height : panel.width;
const targetHeight = needsSwap ? panel.width : panel.height;
const scale = Math.max(targetWidth / (element.width || targetWidth), targetHeight / (element.height || targetHeight));
const scale = getAdaptiveScale({
sourceWidth: element.width || targetWidth,
sourceHeight: element.height || targetHeight,
targetWidth,
targetHeight,
mode: 'contain',
});
const scaleX = panel.mirrored ? -scale : scale;
const scaleY = scale;
element.set({
@@ -616,6 +637,7 @@ export async function applyBackground(
angle: panel.rotation,
selectable: false,
evented: false,
clipPath,
});
canvas.add(element);
});
@@ -628,9 +650,13 @@ export async function applyBackground(
try {
const image = await loadFabricImage(resolveAssetUrl(backgroundImageUrl));
if (image) {
const scaleX = CANVAS_WIDTH / (image.width || CANVAS_WIDTH);
const scaleY = CANVAS_HEIGHT / (image.height || CANVAS_HEIGHT);
const scale = Math.max(scaleX, scaleY);
const scale = getAdaptiveScale({
sourceWidth: image.width || canvasWidth,
sourceHeight: image.height || canvasHeight,
targetWidth: canvasWidth,
targetHeight: canvasHeight,
mode: 'cover',
});
image.set({
originX: 'left',
originY: 'top',
@@ -657,20 +683,22 @@ export async function applyBackground(
console.warn('[Fabric] applyBackground failed', error);
}
applyBackgroundFill(canvas, color, gradient);
applyBackgroundFill(canvas, color, gradient, canvasWidth, canvasHeight);
}
function applyBackgroundFill(
canvas: fabric.Canvas,
color: string,
gradient: { angle?: number; stops?: string[] } | null,
canvasWidth: number,
canvasHeight: number,
): void {
let background: string | fabric.Gradient<'linear'> = color;
if (gradient?.stops?.length) {
const angle = ((gradient.angle ?? 180) * Math.PI) / 180;
const halfWidth = CANVAS_WIDTH / 2;
const halfHeight = CANVAS_HEIGHT / 2;
const halfWidth = canvasWidth / 2;
const halfHeight = canvasHeight / 2;
const x = Math.cos(angle);
const y = Math.sin(angle);
@@ -718,6 +746,27 @@ async function loadFabricImage(url: string): Promise<fabric.Image | null> {
});
}
function getAdaptiveScale({
sourceWidth,
sourceHeight,
targetWidth,
targetHeight,
mode = 'cover',
}: {
sourceWidth: number;
sourceHeight: number;
targetWidth: number;
targetHeight: number;
mode?: 'cover' | 'contain';
}): number {
const safeSourceWidth = sourceWidth || targetWidth || 1;
const safeSourceHeight = sourceHeight || targetHeight || 1;
const widthRatio = targetWidth / safeSourceWidth;
const heightRatio = targetHeight / safeSourceHeight;
return mode === 'contain' ? Math.min(widthRatio, heightRatio) : Math.max(widthRatio, heightRatio);
}
function resolveAssetUrl(url: string): string {
if (url.startsWith('http')) {
return url;

View File

@@ -15,8 +15,10 @@ export async function withFabricCanvas<T>(
handler: (canvas: fabric.Canvas, element: HTMLCanvasElement) => Promise<T>,
): Promise<T> {
const canvasElement = document.createElement('canvas');
canvasElement.width = CANVAS_WIDTH;
canvasElement.height = CANVAS_HEIGHT;
const targetWidth = options.canvasWidth ?? CANVAS_WIDTH;
const targetHeight = options.canvasHeight ?? CANVAS_HEIGHT;
canvasElement.width = targetWidth;
canvasElement.height = targetHeight;
const canvas = new fabric.Canvas(canvasElement, {
selection: false,