UNPKG

animflow

Version:

A lightweight, high-performance animation library for creating smooth, responsive web animations

252 lines (208 loc) 8.84 kB
/*! * SVG Draw Animation v2.0.0 * A simple and lightweight SVG path drawing animation library * (c) 2025 AnimFlow Team * MIT License */ (function(global) { 'use strict'; class SVGAnimator { constructor(options = {}) { this.options = { duration: 10000, // 10 seconds by default easing: 'ease-in-out', delay: 0, autoStart: true, ...options }; this.pathCache = new WeakMap(); this.initialized = false; if (this.options.autoStart) { this.init(); } } // Initialize the animator init() { if (this.initialized) return; // Add CSS for animations if not already added this.addStyles(); // Wait for the DOM to be ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.setup()); } else { this.setup(); } this.initialized = true; } // Add required styles addStyles() { if (document.getElementById('svg-animator-styles')) return; const style = document.createElement('style'); style.id = 'svg-animator-styles'; style.textContent = ` [data-anim*="draw-path"] { opacity: 0; animation: none; animation-fill-mode: forwards; } @keyframes drawPath { from { stroke-dashoffset: var(--path-length); opacity: 1; } to { stroke-dashoffset: 0; opacity: 1; } } `; document.head.appendChild(style); } // Setup the animator setup() { // Find all elements with data-anim="draw-path" const elements = document.querySelectorAll('[data-anim*="draw-path"]'); // Initialize each element elements.forEach(element => { this.initElement(element); }); // Setup intersection observer for lazy loading this.setupIntersectionObserver(); // Setup mutation observer to watch for new elements this.setupMutationObserver(); } // Setup intersection observer for lazy loading setupIntersectionObserver() { if (!('IntersectionObserver' in window)) return; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const element = entry.target; if (!this.pathCache.has(element)) { this.initElement(element); } observer.unobserve(element); } }); }, { rootMargin: '50px 0px', threshold: 0.01 }); // Observe all current and future elements const observeElements = () => { document.querySelectorAll('[data-anim*="draw-path"]:not([data-observed])').forEach(element => { if (!this.pathCache.has(element)) { element.setAttribute('data-observed', 'true'); observer.observe(element); } }); }; observeElements(); // Re-observe when new elements are added const mutationObserver = new MutationObserver(observeElements); mutationObserver.observe(document.body, { childList: true, subtree: true }); } // Setup mutation observer to watch for new elements setupMutationObserver() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { // Element node if (node.matches && node.matches('[data-anim*="draw-path"]')) { this.initElement(node); } const elements = node.querySelectorAll ? node.querySelectorAll('[data-anim*="draw-path"]') : []; elements.forEach(element => this.initElement(element)); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); } // Initialize a single element initElement(element) { if (this.pathCache.has(element)) return; // Calculate path length if not already set const length = element.getTotalLength(); this.pathCache.set(element, length); // Set initial styles element.style.setProperty('--path-length', length + 'px'); element.style.strokeDasharray = length; element.style.strokeDashoffset = length; // Get animation options from data attributes const duration = element.getAttribute('data-anim-duration') || this.options.duration; const easing = element.getAttribute('data-anim-easing') || this.options.easing; const delay = element.getAttribute('data-anim-delay') || this.options.delay; // Start the animation this.animatePath(element, { duration: parseInt(duration, 10), easing: easing, delay: parseInt(delay, 10) }); } // Animate the path animatePath(path, options = {}) { const { duration = this.options.duration, easing = this.options.easing, delay = this.options.delay } = options; // Reset any existing animations path.style.animation = 'none'; // Force reflow path.offsetHeight; // Start the animation path.style.animation = `drawPath ${duration}ms ${easing} ${delay}ms forwards`; } // Reset the path to its initial state resetPath(path) { if (this.pathCache.has(path)) { const length = this.pathCache.get(path); // Reset styles path.style.animation = 'none'; path.style.strokeDasharray = length; path.style.strokeDashoffset = length; path.style.opacity = '0'; // Force reflow path.offsetHeight; } } // Reset and redraw a path redrawPath(path, options = {}) { this.resetPath(path); // Force reflow path.offsetHeight; // Redraw with new options this.animatePath(path, { duration: options.duration || path.getAttribute('data-anim-duration') || this.options.duration, easing: options.easing || path.getAttribute('data-anim-easing') || this.options.easing, delay: options.delay || path.getAttribute('data-anim-delay') || this.options.delay }); } } // Auto-initialize if not in a module environment if (typeof window !== 'undefined' && window.document) { // Create a global instance window.SVGAnimator = new SVGAnimator(); } // Export for different module systems if (typeof define === 'function' && define.amd) { // AMD define([], () => SVGAnimator); } else if (typeof exports !== 'undefined') { // CommonJS if (typeof module !== 'undefined' && module.exports) { module.exports = SVGAnimator; } } else if (typeof global !== 'undefined') { // Browser global global.SVGAnimator = SVGAnimator; } return SVGAnimator; })(typeof window !== 'undefined' ? window : this);