Subscriptions
The subscription system provides efficient event management for requestAnimationFrame and window resize events with priority-based ordering.
Overview
Section titled “Overview”The subscription system provides:
- Raf Service: RequestAnimationFrame with GSAP ticker integration
- Resize Service: Debounced window resize events
- Priority System: Priority-based subscription ordering
- Memory Management: Automatic cleanup and resource management
- Performance Optimization: Efficient event handling and debouncing
Core Services
Section titled “Core Services”Raf Service
Section titled “Raf Service”The Raf service provides a centralized requestAnimationFrame loop using GSAP’s optimized ticker:
import { Raf } from "@lib/subs";
// Subscribe to animation frame updatesconst unsubscribe = Raf.add(({ deltaTime, time }) => { // deltaTime: time since last frame in seconds // time: total time since start (scaled down by 0.01)
// Update animations element.style.transform = `translateX(${Math.sin(time) * 50}px)`;});
// Clean up subscriptionunsubscribe();Resize Service
Section titled “Resize Service”The Resize service provides debounced window resize events:
import { Resize } from "@lib/subs";
// Subscribe to resize eventsconst unsubscribe = Resize.add(({ width, height }) => { // Handle resize console.log(`Window resized to: ${width}x${height}`);
// Update responsive behavior if (width < 768) { element.classList.add("mobile"); } else { element.classList.remove("mobile"); }});
// Clean up subscriptionunsubscribe();Configuration
Section titled “Configuration”Raf Configuration
Section titled “Raf Configuration”The Raf service uses GSAP’s ticker for optimal performance:
class _Raf extends Subscribable { constructor() { super(); gsap.ticker.add(this.update.bind(this)); }
update(deltaTime: number, time: number) { this.notify({ deltaTime, time: time * 0.01 }); }}Resize Configuration
Section titled “Resize Configuration”The Resize service includes debouncing and dimension tracking:
class _Resize extends Subscribable { width = window.innerWidth; height = window.innerHeight; private timeoutId: number | null = null; private readonly debounceDelay = 100; // 100ms debounce
constructor() { super(); window.addEventListener("resize", this.update.bind(this)); }}Priority System
Section titled “Priority System”Both Raf and Resize services support priority-based subscription ordering:
Priority Levels
Section titled “Priority Levels”- High Priority (negative numbers): Execute first
- Normal Priority (0): Default priority
- Low Priority (positive numbers): Execute last
Usage Examples
Section titled “Usage Examples”// High priority - critical animationsRaf.add(updateCriticalAnimation, -1);
// Normal priority - standard animationsRaf.add(updateStandardAnimation, 0);
// Low priority - background effectsRaf.add(updateBackgroundEffect, 1);
// Resize prioritiesResize.add(updateCriticalLayout, -1);Resize.add(updateStandardLayout, 0);Resize.add(updateBackgroundLayout, 1);Advanced Usage Patterns
Section titled “Advanced Usage Patterns”Component Integration
Section titled “Component Integration”export default function (element: HTMLElement, dataset: DOMStringMap) { // Raf subscription for smooth animations const rafUnsubscribe = Raf.add(({ deltaTime, time }) => { // Smooth rotation element.style.transform = `rotate(${time * 50}deg)`;
// Frame-rate independent animation const currentOpacity = parseFloat(element.style.opacity) || 0; element.style.opacity = Math.min( 1, currentOpacity + deltaTime * 2, ).toString(); });
// Resize subscription for responsive behavior const resizeUnsubscribe = Resize.add(({ width, height }) => { // Responsive font sizing element.style.fontSize = width < 768 ? "14px" : "18px";
// Responsive positioning if (width < 1024) { element.style.left = "10px"; } else { element.style.left = "50px"; } });
// Clean up subscriptions onDestroy(() => { rafUnsubscribe(); resizeUnsubscribe(); });}Performance Optimization
Section titled “Performance Optimization”export default function (element: HTMLElement, dataset: DOMStringMap) { let isActive = false; let lastUpdate = 0; const updateInterval = 16; // ~60fps
// Conditional Raf subscription const rafUnsubscribe = Raf.add(({ time }) => { // Only update when active and at appropriate intervals if (!isActive || time - lastUpdate < updateInterval) return;
lastUpdate = time;
// Perform expensive calculations const newPosition = calculateComplexPosition(time); element.style.transform = `translate(${newPosition.x}px, ${newPosition.y}px)`; });
// Activate only when in view const observer = onView(element, { callback: ({ isIn }) => { isActive = isIn; }, });
onDestroy(() => { rafUnsubscribe(); });}Multiple Subscriptions
Section titled “Multiple Subscriptions”export default function (element: HTMLElement, dataset: DOMStringMap) { const subscriptions: (() => void)[] = [];
// Multiple Raf subscriptions with different priorities subscriptions.push( Raf.add(updatePosition, -1), // High priority Raf.add(updateRotation, 0), // Normal priority Raf.add(updateOpacity, 1), // Low priority );
// Multiple Resize subscriptions subscriptions.push( Resize.add(updateLayout, -1), // Critical layout updates Resize.add(updateAnimations, 0), // Animation adjustments Resize.add(updateBackground, 1), // Background effects );
// Clean up all subscriptions onDestroy(() => { subscriptions.forEach((unsubscribe) => unsubscribe()); });}Data Structures
Section titled “Data Structures”Raf Data
Section titled “Raf Data”interface RafData { deltaTime: number; // Time since last frame in seconds time: number; // Total time since start (scaled by 0.01)}Resize Data
Section titled “Resize Data”interface ResizeData { width: number; // Current window width height: number; // Current window height}Best Practices
Section titled “Best Practices”Raf Usage
Section titled “Raf Usage”- Frame Rate Independence: Use
deltaTimefor frame-rate independent animations - Performance: Keep Raf callbacks lightweight and efficient
- Conditional Updates: Only update when necessary
- Cleanup: Always unsubscribe to prevent memory leaks
Resize Usage
Section titled “Resize Usage”- Debouncing: The service automatically debounces, but avoid heavy operations
- Responsive Design: Use resize events for responsive behavior
- Layout Updates: Prioritize critical layout updates
- Performance: Limit DOM queries and heavy calculations
General Guidelines
Section titled “General Guidelines”- Priority Management: Use priorities to ensure critical updates run first
- Memory Management: Always clean up subscriptions in
onDestroy - Error Handling: Handle errors gracefully in subscription callbacks
- Testing: Test performance with multiple subscriptions
Performance Considerations
Section titled “Performance Considerations”Raf Performance
Section titled “Raf Performance”// Good - Lightweight operationsRaf.add(({ time }) => { element.style.transform = `translateX(${time * 10}px)`;});
// Bad - Heavy operations every frameRaf.add(({ time }) => { // Expensive DOM queries const allElements = document.querySelectorAll(".heavy"); allElements.forEach((el) => { // Complex calculations const rect = el.getBoundingClientRect(); const distance = calculateDistance(rect, time); el.style.transform = `translate(${distance.x}px, ${distance.y}px)`; });});Resize Performance
Section titled “Resize Performance”// Good - Efficient responsive updatesResize.add(({ width }) => { element.classList.toggle("mobile", width < 768); element.style.fontSize = width < 768 ? "14px" : "18px";});
// Bad - Heavy operations on every resizeResize.add(({ width, height }) => { // Expensive recalculations const allElements = document.querySelectorAll(".recalculate"); allElements.forEach((el) => { const newLayout = calculateComplexLayout(el, width, height); applyComplexLayout(el, newLayout); });});Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”- Memory Leaks: Ensure all subscriptions are cleaned up
- Performance Issues: Reduce number of subscriptions or optimize callbacks
- Priority Conflicts: Use appropriate priorities for different update types
- Debounce Issues: Resize is automatically debounced, but check for conflicts
Debug Mode
Section titled “Debug Mode”// Debug Raf subscriptionsRaf.add((data) => { console.log("Raf Debug:", data);}, -999); // Very high priority
// Debug Resize subscriptionsResize.add((data) => { console.log("Resize Debug:", data);}, -999); // Very high priorityAPI Reference
Section titled “API Reference”Raf Service
Section titled “Raf Service”Raf.add(fn, priority?, id?)- Subscribe to animation frame updatesRaf.remove(id)- Remove specific subscriptionRaf.notify(data)- Manually trigger notifications (internal use)
Resize Service
Section titled “Resize Service”Resize.add(fn, priority?, id?)- Subscribe to resize eventsResize.remove(id)- Remove specific subscriptionResize.width- Current window widthResize.height- Current window heightResize.notify(data)- Manually trigger notifications (internal use)
Base Subscribable Class
Section titled “Base Subscribable Class”add(fn, priority?, id?)- Add subscriber with priorityremove(id)- Remove subscriber by IDnotify(data)- Notify all subscribers with data
Return Values
Section titled “Return Values”All add methods return an unsubscribe function:
const unsubscribe = Raf.add(callback);// ... later ...unsubscribe(); // Clean up subscriptionIntegration Examples
Section titled “Integration Examples”With Lifecycle Hooks
Section titled “With Lifecycle Hooks”export default function (element: HTMLElement, dataset: DOMStringMap) { const rafUnsubscribe = Raf.add(updateAnimation); const resizeUnsubscribe = Resize.add(updateResponsive);
onDestroy(() => { rafUnsubscribe(); resizeUnsubscribe(); });}With Observer System
Section titled “With Observer System”export default function (element: HTMLElement, dataset: DOMStringMap) { let isActive = false;
// Only animate when in view const observer = onView(element, { callback: ({ isIn }) => { isActive = isIn; }, });
// Conditional animation const rafUnsubscribe = Raf.add(({ time }) => { if (!isActive) return;
element.style.transform = `translateY(${Math.sin(time) * 10}px)`; });
onDestroy(() => { rafUnsubscribe(); });}