From 77592278d37c4412775fd2f566c532ef29430c62 Mon Sep 17 00:00:00 2001 From: Janus Knudsen Date: Wed, 3 Sep 2025 00:25:05 +0200 Subject: [PATCH] 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. --- src/renderers/HeaderRenderer.ts | 93 ++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/src/renderers/HeaderRenderer.ts b/src/renderers/HeaderRenderer.ts index 34647f2..9f1eaa2 100644 --- a/src/renderers/HeaderRenderer.ts +++ b/src/renderers/HeaderRenderer.ts @@ -19,14 +19,76 @@ export interface HeaderRenderer { * Base class with shared addToAllDay implementation */ 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; + /** + * 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 */ addToAllDay(dayHeader: HTMLElement): void { - const root = document.documentElement; - const currentHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0'); + const { currentHeight } = this.calculateAllDayHeight(0); if (currentHeight === 0) { // Find the calendar header element to animate @@ -47,7 +109,7 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer { } checkAndAnimateAllDayHeight(): void { - const container = document.querySelector('swp-allday-container'); + const container = this.getAllDayContainer(); if (!container) return; 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 */ animateToRows(targetRows: number): void { - 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 { targetHeight, currentHeight, heightDifference } = this.calculateAllDayHeight(targetRows); 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)`); - // Find elements to animate - const calendarHeader = document.querySelector('swp-calendar-header') as HTMLElement; - const headerSpacer = document.querySelector('swp-header-spacer') as HTMLElement; - const allDayContainer = calendarHeader?.querySelector('swp-allday-container') as HTMLElement; + // Get cached elements + const calendarHeader = this.getCalendarHeader(); + const headerSpacer = this.getHeaderSpacer(); + const allDayContainer = this.getAllDayContainer(); if (!calendarHeader || !allDayContainer) return; // Get current parent height for animation const currentParentHeight = parseFloat(getComputedStyle(calendarHeader).height); - const heightDifference = targetHeight - currentHeight; const targetParentHeight = currentParentHeight + heightDifference; const animations = [ @@ -133,6 +192,7 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer { // Add spacer animation if spacer exists if (headerSpacer) { + const root = document.documentElement; const currentSpacerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')) + currentHeight; const targetSpacerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')) + targetHeight; @@ -150,6 +210,7 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer { // Update CSS variable after animation Promise.all(animations.map(anim => anim.finished)).then(() => { + const root = document.documentElement; root.style.setProperty('--all-day-row-height', `${targetHeight}px`); eventBus.emit('header:height-changed'); }); @@ -163,9 +224,17 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer { // Create simple all-day container (initially hidden) container = document.createElement('swp-allday-container'); calendarHeader.appendChild(container); - } else { + // Clear cache since DOM structure changed + this.clearCache(); } } + + /** + * Public cleanup method for cached elements + */ + public destroy(): void { + this.clearCache(); + } } /**