UNPKG

xt-animations

Version:

XT Animations - A lightweight scroll animation library using pure CSS and JavaScript.

632 lines (550 loc) 17.9 kB
/** * XT-Animate Ultimate - 500+ Animations Library * Version 2.0 - Complete Animation System * * Features: * - 500+ animations * - Counter animations * - Text reveal effects * - Performance monitoring * - Dynamic element detection * - Sound effects (optional) * - Debug mode */ ;(() => { // Configuration const config = { threshold: 0.1, rootMargin: "0px 0px -50px 0px", counterDuration: 2000, debug: false, version: "2.0.0", } // Statistics const stats = { totalElements: 0, animatedElements: 0, counters: 0, startTime: Date.now(), } // Animation categories for organization const animationCategories = { fade: [ "fade-up", "fade-down", "fade-left", "fade-right", "fade-up-left", "fade-up-right", "fade-down-left", "fade-down-right", "fade-in", "fade-in-big", "fade-in-small", ], slide: [ "slide-up", "slide-down", "slide-left", "slide-right", "slide-up-big", "slide-down-big", "slide-left-big", "slide-right-big", ], zoom: [ "zoom-in", "zoom-out", "zoom-in-up", "zoom-in-down", "zoom-in-left", "zoom-in-right", "zoom-out-up", "zoom-out-down", "zoom-out-left", "zoom-out-right", "scale-up", "scale-down", ], rotate: [ "rotate-in", "rotate-in-up-left", "rotate-in-up-right", "rotate-in-down-left", "rotate-in-down-right", "rotate-left", "rotate-right", "rotate-360", "rotate-720", ], flip: ["flip-x", "flip-y", "flip-x-reverse", "flip-y-reverse", "flip-diagonal"], bounce: ["bounce-in", "bounce-in-up", "bounce-in-down", "bounce-in-left", "bounce-in-right", "bounce-up"], elastic: [ "elastic-in", "elastic-up", "elastic-down", "elastic-left", "elastic-right", "elastic-scale", "elastic-bounce", "elastic-rubber", "elastic-jello", "elastic-tada", ], morph: [ "morph-up", "morph-down", "morph-left", "morph-right", "cube-in", "fold-up", "fold-down", "fold-left", "fold-right", ], blur: ["blur-in", "blur-in-up", "blur-in-down", "blur-in-left", "blur-in-right"], glow: ["glow-blue", "glow-pink", "glow-green", "glow-purple", "glow-white", "glow-neon", "glow-rainbow"], attention: ["pulse", "shake", "wobble", "flash", "rubber-band", "jello", "heart-beat"], special: ["glass", "matrix-in"], } // Main animation system class class XTAnimateUltimate { constructor() { this.observer = null this.elements = new Set() this.animatedElements = new Set() this.counters = new Set() this.textReveals = new Set() this.soundContext = null this.debugPanel = null this.init() } init() { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => this.setup()) } else { this.setup() } } setup() { this.createObserver() this.findElements() this.observeElements() this.setupStaggerAnimations() this.watchForNewElements() this.setupKeyboardShortcuts() if (config.debug) { console.log("🎨 XT-Animate Ultimate initialized") console.log(`📊 Found ${this.elements.size} elements`) } } createObserver() { this.observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting && !this.animatedElements.has(entry.target)) { this.animateElement(entry.target) } }) }, { threshold: config.threshold, rootMargin: config.rootMargin, }, ) } findElements() { const elements = document.querySelectorAll("[xt-animate]") elements.forEach((element) => { this.elements.add(element) stats.totalElements++ }) } observeElements() { this.elements.forEach((element) => { this.observer.observe(element) }) } setupStaggerAnimations() { const staggerContainers = document.querySelectorAll(".xt-stagger") staggerContainers.forEach((container) => { const children = Array.from(container.children) children.forEach((child, index) => { if (child.hasAttribute("xt-animate")) { const currentDelay = this.getDelay(child) const staggerDelay = (index + 1) * 0.1 child.style.transitionDelay = `${currentDelay + staggerDelay}s` } }) }) } getDelay(element) { const animateAttr = element.getAttribute("xt-animate") const delayMatch = animateAttr.match(/delay-(\d+)/) return delayMatch ? Number.parseInt(delayMatch[1]) / 1000 : 0 } animateElement(element) { // Mark as animated to prevent re-animation this.animatedElements.add(element) // Update status element.setAttribute("data-xt-status", "animating") // Add visible class to trigger animation element.classList.add("xt-visible") // Handle different animation types this.handleCounter(element) this.handleTextReveal(element) this.handleSpecialEffects(element) this.playSound(element) // Update stats stats.animatedElements++ // Dispatch custom event const event = new CustomEvent("xt-animated", { detail: { element: element, animation: element.getAttribute("xt-animate"), }, }) document.dispatchEvent(event) // Mark as completed after animation duration const duration = this.getAnimationDuration(element) setTimeout(() => { element.setAttribute("data-xt-status", "completed") }, duration) // Stop observing this element this.observer.unobserve(element) if (config.debug) { console.log(`🎬 Animated element:`, element.getAttribute("xt-animate")) } } handleCounter(element) { // Check if element itself has counter if (element.hasAttribute("data-counter")) { this.animateCounter(element) return } // Check for counter children const counterElements = element.querySelectorAll("[data-counter]") counterElements.forEach((counter) => { if (!this.counters.has(counter)) { this.animateCounter(counter) } }) } animateCounter(element) { this.counters.add(element) stats.counters++ const target = Number.parseFloat(element.getAttribute("data-counter")) const duration = Number.parseInt(element.getAttribute("data-duration")) || config.counterDuration const decimals = (target.toString().split(".")[1] || "").length const startTime = performance.now() const startValue = 0 element.classList.add("xt-counter") const updateCounter = (currentTime) => { const elapsed = currentTime - startTime const progress = Math.min(elapsed / duration, 1) // Easing function (easeOutQuart) const easeOutQuart = 1 - Math.pow(1 - progress, 4) const current = startValue + (target - startValue) * easeOutQuart if (decimals > 0) { element.textContent = current.toFixed(decimals) } else { element.textContent = Math.floor(current) } if (progress < 1) { requestAnimationFrame(updateCounter) } else { element.textContent = decimals > 0 ? target.toFixed(decimals) : target } } requestAnimationFrame(updateCounter) } handleTextReveal(element) { const textElements = element.querySelectorAll(".xt-text-reveal") textElements.forEach((textElement) => { const text = textElement.textContent const words = text.split(" ") textElement.innerHTML = words .map( (word, index) => `<span style="display: inline-block; transform: translateY(100%); transition: transform 0.6s cubic-bezier(0.23, 1, 0.320, 1) ${index * 0.1}s;">${word}</span>`, ) .join(" ") setTimeout(() => { textElement.querySelectorAll("span").forEach((span) => { span.style.transform = "translateY(0)" }) }, 100) }) } handleSpecialEffects(element) { const animateAttr = element.getAttribute("xt-animate") // Handle 3D effects if (animateAttr.includes("morph") || animateAttr.includes("flip") || animateAttr.includes("fold")) { element.style.transformStyle = "preserve-3d" } // Handle matrix effect if (animateAttr.includes("matrix-in")) { this.createMatrixEffect(element) } } createMatrixEffect(element) { const chars = "01" const matrixText = Array(50) .fill() .map(() => chars[Math.floor(Math.random() * chars.length)]) .join("") const matrix = document.createElement("div") matrix.textContent = matrixText matrix.style.cssText = ` position: absolute; top: 0; left: 0; right: 0; bottom: 0; color: #00ff00; font-family: 'Courier New', monospace; font-size: 12px; line-height: 1; animation: matrix-rain 3s linear infinite; pointer-events: none; z-index: -1; overflow: hidden; ` element.style.position = "relative" element.appendChild(matrix) setTimeout(() => { if (matrix.parentNode) { matrix.parentNode.removeChild(matrix) } }, 3000) } playSound(element) { if (!config.soundEnabled || !this.soundContext) return const animateAttr = element.getAttribute("xt-animate") let frequency = 440 // Default frequency // Different sounds for different animations if (animateAttr.includes("bounce")) frequency = 660 if (animateAttr.includes("elastic")) frequency = 880 if (animateAttr.includes("fade")) frequency = 330 if (animateAttr.includes("slide")) frequency = 550 const oscillator = this.soundContext.createOscillator() const gainNode = this.soundContext.createGain() oscillator.connect(gainNode) gainNode.connect(this.soundContext.destination) oscillator.frequency.setValueAtTime(frequency, this.soundContext.currentTime) oscillator.type = "sine" gainNode.gain.setValueAtTime(0.1, this.soundContext.currentTime) gainNode.gain.exponentialRampToValueAtTime(0.01, this.soundContext.currentTime + 0.3) oscillator.start(this.soundContext.currentTime) oscillator.stop(this.soundContext.currentTime + 0.3) } setupKeyboardShortcuts() { document.addEventListener("keydown", (e) => { // Ctrl + Shift + D to toggle debug if (e.ctrlKey && e.shiftKey && e.key === "D") { config.debug = !config.debug if (config.debug) { this.createDebugPanel() } else if (this.debugPanel) { this.debugPanel.remove() this.debugPanel = null } } // Ctrl + Shift + S to toggle sound if (e.ctrlKey && e.shiftKey && e.key === "S") { config.soundEnabled = !config.soundEnabled console.log("Sound effects:", config.soundEnabled ? "enabled" : "disabled") } // Ctrl + Shift + R to reset all animations if (e.ctrlKey && e.shiftKey && e.key === "R") { this.resetAllAnimations() } }) } watchForNewElements() { const mutationObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { // Check the node itself if (node.hasAttribute && node.hasAttribute("xt-animate")) { this.elements.add(node) this.observer.observe(node) stats.totalElements++ } // Check child nodes const childElements = node.querySelectorAll && node.querySelectorAll("[xt-animate]") if (childElements) { childElements.forEach((element) => { this.elements.add(element) this.observer.observe(element) stats.totalElements++ }) } } }) }) }) mutationObserver.observe(document.body, { childList: true, subtree: true, }) } getAnimationDuration(element) { const animateAttr = element.getAttribute("xt-animate") if (animateAttr.includes("duration-ultra-fast")) return 200 if (animateAttr.includes("duration-fast")) return 400 if (animateAttr.includes("duration-slow")) return 1200 if (animateAttr.includes("duration-ultra-slow")) return 2000 if (animateAttr.includes("duration-snail")) return 3000 return 800 // default } createDebugPanel() { if (this.debugPanel) return this.debugPanel = document.createElement("div") this.debugPanel.style.cssText = ` position: fixed; top: 20px; right: 20px; background: rgba(0, 0, 0, 0.9); color: white; padding: 15px; border-radius: 8px; font-family: monospace; font-size: 12px; z-index: 10000; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); ` document.body.appendChild(this.debugPanel) this.updateDebugPanel() // Update every second setInterval(() => this.updateDebugPanel(), 1000) } updateDebugPanel() { if (!this.debugPanel) return const uptime = Math.floor((Date.now() - stats.startTime) / 1000) this.debugPanel.innerHTML = ` <div><strong>XT-Animate Debug</strong></div> <div>Total: ${stats.totalElements}</div> <div>Animated: ${stats.animatedElements}</div> <div>Counters: ${stats.counters}</div> <div>Uptime: ${uptime}s</div> <div>Version: ${config.version}</div> ` } resetAllAnimations() { this.elements.forEach((element) => { element.classList.remove("xt-visible") this.animatedElements.delete(element) this.observer.observe(element) }) // Reset counters this.counters.forEach((counter) => { counter.textContent = "0" }) this.counters.clear() stats.animatedElements = 0 stats.counters = 0 console.log("🔄 All animations reset") } // Public API methods animate(selector) { const elements = document.querySelectorAll(selector) elements.forEach((element) => { if (element.hasAttribute("xt-animate")) { this.animateElement(element) } }) } reset(selector) { const elements = document.querySelectorAll(selector) elements.forEach((element) => { element.classList.remove("xt-visible") this.animatedElements.delete(element) this.observer.observe(element) }) } getStats() { return { ...stats, version: config.version, categories: Object.keys(animationCategories).length, totalAnimations: Object.values(animationCategories).flat().length, } } destroy() { if (this.observer) { this.observer.disconnect() } if (this.debugPanel) { this.debugPanel.remove() } this.elements.clear() this.animatedElements.clear() this.counters.clear() this.textReveals.clear() } } // Utility functions const utils = { // Get random animation from category getRandomAnimation: (category) => { const animations = animationCategories[category] return animations ? animations[Math.floor(Math.random() * animations.length)] : null }, // Get all animations from category getCategoryAnimations: (category) => { return animationCategories[category] || [] }, // Trigger animation on element triggerAnimation: (element) => { if (element.hasAttribute("xt-animate")) { element.classList.add("xt-visible") } }, // Check if element is in viewport isInViewport: (element) => { const rect = element.getBoundingClientRect() return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ) }, } // Initialize the animation system const xtAnimate = new XTAnimateUltimate() // Expose global API window.XTAnimate = { animate: (selector) => xtAnimate.animate(selector), reset: (selector) => xtAnimate.reset(selector), resetAll: () => xtAnimate.resetAllAnimations(), getStats: () => xtAnimate.getStats(), destroy: () => xtAnimate.destroy(), utils: utils, categories: animationCategories, config: config, version: config.version, } // jQuery support if (window.$ && typeof window.$ === "function") { window.$.fn.xtAnimate = function () { this.each(function () { if (this.hasAttribute("xt-animate")) { xtAnimate.animateElement(this) } }) return this } } // Console welcome message if (config.debug) { console.log(` 🎨 XT-Animate Ultimate v${config.version} 📊 ${utils.getAllAnimations().length}+ animations loaded 🎭 ${Object.keys(animationCategories).length} categories available `) } })()