Improves all-day event header animation performance

Optimizes all-day event header animation by caching DOM elements.

This change avoids redundant DOM queries during animation,
resulting in smoother transitions when adding or removing all-day
events. It also introduces a destroy method for proper cleanup
of cached elements.
This commit is contained in:
Janus Knudsen 2025-09-03 00:25:05 +02:00
parent 7f387cfa30
commit 77592278d3

View file

@ -19,14 +19,76 @@ export interface HeaderRenderer {
* Base class with shared addToAllDay implementation * Base class with shared addToAllDay implementation
*/ */
export abstract class BaseHeaderRenderer implements HeaderRenderer { export abstract class BaseHeaderRenderer implements HeaderRenderer {
// Cached DOM elements to avoid redundant queries
private cachedCalendarHeader: HTMLElement | null = null;
private cachedAllDayContainer: HTMLElement | null = null;
private cachedHeaderSpacer: HTMLElement | null = null;
abstract render(calendarHeader: HTMLElement, context: HeaderRenderContext): void; abstract render(calendarHeader: HTMLElement, context: HeaderRenderContext): void;
/**
* Get cached calendar header element
*/
private getCalendarHeader(): HTMLElement | null {
if (!this.cachedCalendarHeader) {
this.cachedCalendarHeader = document.querySelector('swp-calendar-header');
}
return this.cachedCalendarHeader;
}
/**
* Get cached all-day container element
*/
private getAllDayContainer(): HTMLElement | null {
if (!this.cachedAllDayContainer) {
const calendarHeader = this.getCalendarHeader();
if (calendarHeader) {
this.cachedAllDayContainer = calendarHeader.querySelector('swp-allday-container');
}
}
return this.cachedAllDayContainer;
}
/**
* Get cached header spacer element
*/
private getHeaderSpacer(): HTMLElement | null {
if (!this.cachedHeaderSpacer) {
this.cachedHeaderSpacer = document.querySelector('swp-header-spacer');
}
return this.cachedHeaderSpacer;
}
/**
* Calculate all-day height based on number of rows
*/
private calculateAllDayHeight(targetRows: number): {
targetHeight: number;
currentHeight: number;
heightDifference: number;
} {
const root = document.documentElement;
const targetHeight = targetRows * ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT;
const currentHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0');
const heightDifference = targetHeight - currentHeight;
return { targetHeight, currentHeight, heightDifference };
}
/**
* Clear cached DOM elements (call when DOM structure changes)
*/
private clearCache(): void {
this.cachedCalendarHeader = null;
this.cachedAllDayContainer = null;
this.cachedHeaderSpacer = null;
}
/** /**
* Expand header to show all-day row * Expand header to show all-day row
*/ */
addToAllDay(dayHeader: HTMLElement): void { addToAllDay(dayHeader: HTMLElement): void {
const root = document.documentElement; const { currentHeight } = this.calculateAllDayHeight(0);
const currentHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0');
if (currentHeight === 0) { if (currentHeight === 0) {
// Find the calendar header element to animate // Find the calendar header element to animate
@ -47,7 +109,7 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer {
} }
checkAndAnimateAllDayHeight(): void { checkAndAnimateAllDayHeight(): void {
const container = document.querySelector('swp-allday-container'); const container = this.getAllDayContainer();
if (!container) return; if (!container) return;
const allDayEvents = container.querySelectorAll('swp-allday-event'); const allDayEvents = container.querySelectorAll('swp-allday-event');
@ -100,24 +162,21 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer {
* Animate all-day container to specific number of rows * Animate all-day container to specific number of rows
*/ */
animateToRows(targetRows: number): void { animateToRows(targetRows: number): void {
const root = document.documentElement; const { targetHeight, currentHeight, heightDifference } = this.calculateAllDayHeight(targetRows);
const targetHeight = targetRows * ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT;
const currentHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0');
if (targetHeight === currentHeight) return; // No animation needed if (targetHeight === currentHeight) return; // No animation needed
console.log(`🎬 All-day height animation starting: ${currentHeight}px → ${targetHeight}px (${Math.ceil(currentHeight / ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT)}${targetRows} rows)`); console.log(`🎬 All-day height animation starting: ${currentHeight}px → ${targetHeight}px (${Math.ceil(currentHeight / ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT)}${targetRows} rows)`);
// Find elements to animate // Get cached elements
const calendarHeader = document.querySelector('swp-calendar-header') as HTMLElement; const calendarHeader = this.getCalendarHeader();
const headerSpacer = document.querySelector('swp-header-spacer') as HTMLElement; const headerSpacer = this.getHeaderSpacer();
const allDayContainer = calendarHeader?.querySelector('swp-allday-container') as HTMLElement; const allDayContainer = this.getAllDayContainer();
if (!calendarHeader || !allDayContainer) return; if (!calendarHeader || !allDayContainer) return;
// Get current parent height for animation // Get current parent height for animation
const currentParentHeight = parseFloat(getComputedStyle(calendarHeader).height); const currentParentHeight = parseFloat(getComputedStyle(calendarHeader).height);
const heightDifference = targetHeight - currentHeight;
const targetParentHeight = currentParentHeight + heightDifference; const targetParentHeight = currentParentHeight + heightDifference;
const animations = [ const animations = [
@ -133,6 +192,7 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer {
// Add spacer animation if spacer exists // Add spacer animation if spacer exists
if (headerSpacer) { if (headerSpacer) {
const root = document.documentElement;
const currentSpacerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')) + currentHeight; const currentSpacerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')) + currentHeight;
const targetSpacerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')) + targetHeight; const targetSpacerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')) + targetHeight;
@ -150,6 +210,7 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer {
// Update CSS variable after animation // Update CSS variable after animation
Promise.all(animations.map(anim => anim.finished)).then(() => { Promise.all(animations.map(anim => anim.finished)).then(() => {
const root = document.documentElement;
root.style.setProperty('--all-day-row-height', `${targetHeight}px`); root.style.setProperty('--all-day-row-height', `${targetHeight}px`);
eventBus.emit('header:height-changed'); eventBus.emit('header:height-changed');
}); });
@ -163,9 +224,17 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer {
// Create simple all-day container (initially hidden) // Create simple all-day container (initially hidden)
container = document.createElement('swp-allday-container'); container = document.createElement('swp-allday-container');
calendarHeader.appendChild(container); calendarHeader.appendChild(container);
} else { // Clear cache since DOM structure changed
this.clearCache();
} }
} }
/**
* Public cleanup method for cached elements
*/
public destroy(): void {
this.clearCache();
}
} }
/** /**