xt-animations
Version:
XT Animations - A lightweight scroll animation library using pure CSS and JavaScript.
632 lines (550 loc) • 17.9 kB
JavaScript
/**
* 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
`)
}
})()