Fix foldable background layout
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user