frontblocks-shape-animation-option.js
7 months ago
frontblocks-shape-animations.css
7 months ago
frontblocks-shape-animations.js
7 months ago
frontblocks-shape-animations.js
235 lines
| 1 | /** |
| 2 | * FrontBlocks Custom SVG Animations |
| 3 | * Frontend JavaScript for handling custom SVG animations from JSON and Lottie |
| 4 | */ |
| 5 | |
| 6 | (function() { |
| 7 | 'use strict'; |
| 8 | |
| 9 | /** |
| 10 | * Initialize custom SVG animations on page load. |
| 11 | */ |
| 12 | function initCustomSvgAnimations() { |
| 13 | const animatedShapes = document.querySelectorAll('.frbl-custom-svg-animation'); |
| 14 | |
| 15 | if (!animatedShapes.length) { |
| 16 | return; |
| 17 | } |
| 18 | |
| 19 | animatedShapes.forEach(function(shape) { |
| 20 | const trigger = shape.dataset.shapeTrigger; |
| 21 | |
| 22 | // Set up Intersection Observer for on-load animations. |
| 23 | if (trigger === 'load') { |
| 24 | setupIntersectionObserver(shape); |
| 25 | } |
| 26 | |
| 27 | // Hover animations are handled by CSS, no JS needed. |
| 28 | }); |
| 29 | } |
| 30 | |
| 31 | /** |
| 32 | * Initialize Lottie animations on page load. |
| 33 | */ |
| 34 | function initLottieAnimations() { |
| 35 | const lottieContainers = document.querySelectorAll('.frbl-lottie-animation'); |
| 36 | |
| 37 | if (!lottieContainers.length || typeof lottie === 'undefined') { |
| 38 | return; |
| 39 | } |
| 40 | |
| 41 | lottieContainers.forEach(function(container) { |
| 42 | try { |
| 43 | const lottieData = JSON.parse(container.dataset.lottieJson); |
| 44 | const loop = container.dataset.loop === 'true'; |
| 45 | const autoplay = container.dataset.autoplay === 'true'; |
| 46 | const speed = parseFloat(container.dataset.speed) || 1; |
| 47 | |
| 48 | // Pre-apply color via CSS variable BEFORE animation loads. |
| 49 | const color = getComputedStyle(container).getPropertyValue('--lottie-color').trim(); |
| 50 | if (color) { |
| 51 | container.style.setProperty('--lottie-color', color); |
| 52 | } |
| 53 | |
| 54 | const animation = lottie.loadAnimation({ |
| 55 | container: container, |
| 56 | renderer: 'svg', |
| 57 | loop: loop, |
| 58 | autoplay: autoplay, |
| 59 | animationData: lottieData |
| 60 | }); |
| 61 | |
| 62 | animation.setSpeed(speed); |
| 63 | |
| 64 | // Store animation instance for potential controls. |
| 65 | container.lottieInstance = animation; |
| 66 | |
| 67 | // Apply color IMMEDIATELY after creating animation. |
| 68 | applyColorToLottie(container); |
| 69 | |
| 70 | // Apply color immediately after SVG is added to DOM. |
| 71 | animation.addEventListener('DOMLoaded', function() { |
| 72 | applyColorToLottie(container); |
| 73 | }); |
| 74 | |
| 75 | // Apply color on EVERY SINGLE FRAME (most aggressive approach). |
| 76 | animation.addEventListener('enterFrame', function() { |
| 77 | applyColorToLottie(container); |
| 78 | }); |
| 79 | |
| 80 | // Apply color multiple times immediately for instant coverage. |
| 81 | setTimeout(function() { applyColorToLottie(container); }, 0); |
| 82 | setTimeout(function() { applyColorToLottie(container); }, 10); |
| 83 | setTimeout(function() { applyColorToLottie(container); }, 20); |
| 84 | setTimeout(function() { applyColorToLottie(container); }, 50); |
| 85 | setTimeout(function() { applyColorToLottie(container); }, 100); |
| 86 | } catch (error) { |
| 87 | console.error('FrontBlocks: Error initializing Lottie animation', error); |
| 88 | } |
| 89 | }); |
| 90 | } |
| 91 | |
| 92 | /** |
| 93 | * Set up Intersection Observer for load animations. |
| 94 | * |
| 95 | * @param {HTMLElement} element The shape element to observe. |
| 96 | */ |
| 97 | function setupIntersectionObserver(element) { |
| 98 | // Check if IntersectionObserver is supported. |
| 99 | if (!('IntersectionObserver' in window)) { |
| 100 | return; |
| 101 | } |
| 102 | |
| 103 | const options = { |
| 104 | root: null, |
| 105 | rootMargin: '0px', |
| 106 | threshold: 0.1 |
| 107 | }; |
| 108 | |
| 109 | const observer = new IntersectionObserver(function(entries) { |
| 110 | entries.forEach(function(entry) { |
| 111 | if (entry.isIntersecting) { |
| 112 | // Element is in viewport, ensure animation plays. |
| 113 | entry.target.style.animationPlayState = 'running'; |
| 114 | |
| 115 | // Check if it's an infinite animation. |
| 116 | const computedStyle = getComputedStyle(entry.target); |
| 117 | const iterationCount = computedStyle.animationIterationCount; |
| 118 | |
| 119 | // If not infinite, unobserve after first play. |
| 120 | if (iterationCount !== 'infinite') { |
| 121 | observer.unobserve(entry.target); |
| 122 | } |
| 123 | } else { |
| 124 | // If infinite animation, pause when out of viewport for performance. |
| 125 | const computedStyle = getComputedStyle(entry.target); |
| 126 | const iterationCount = computedStyle.animationIterationCount; |
| 127 | |
| 128 | if (iterationCount === 'infinite') { |
| 129 | entry.target.style.animationPlayState = 'paused'; |
| 130 | } |
| 131 | } |
| 132 | }); |
| 133 | }, options); |
| 134 | |
| 135 | observer.observe(element); |
| 136 | } |
| 137 | |
| 138 | /** |
| 139 | * Apply color to Lottie animation SVG elements - AGGRESSIVELY. |
| 140 | * |
| 141 | * @param {HTMLElement} container The Lottie container element. |
| 142 | */ |
| 143 | function applyColorToLottie(container) { |
| 144 | if (!container) return; |
| 145 | |
| 146 | const color = getComputedStyle(container).getPropertyValue('--lottie-color').trim(); |
| 147 | if (!color) return; |
| 148 | |
| 149 | // Find ALL SVG elements - be very aggressive. |
| 150 | const elements = container.querySelectorAll('svg *, [fill], [stroke]'); |
| 151 | |
| 152 | elements.forEach(function(element) { |
| 153 | // Get current fill and stroke. |
| 154 | const currentFill = element.getAttribute('fill'); |
| 155 | const currentStroke = element.getAttribute('stroke'); |
| 156 | const computedFill = window.getComputedStyle(element).fill; |
| 157 | |
| 158 | // Apply to anything that has color. |
| 159 | if (currentFill && currentFill !== 'none' && currentFill !== 'transparent') { |
| 160 | element.setAttribute('fill', color); |
| 161 | element.style.setProperty('fill', color, 'important'); |
| 162 | } |
| 163 | |
| 164 | // Also check computed style. |
| 165 | if (computedFill && computedFill !== 'none' && computedFill !== 'transparent' && computedFill !== 'rgba(0, 0, 0, 0)') { |
| 166 | element.style.setProperty('fill', color, 'important'); |
| 167 | } |
| 168 | |
| 169 | // Also handle stroke if it exists. |
| 170 | if (currentStroke && currentStroke !== 'none' && currentStroke !== 'transparent') { |
| 171 | element.setAttribute('stroke', color); |
| 172 | element.style.setProperty('stroke', color, 'important'); |
| 173 | } |
| 174 | }); |
| 175 | |
| 176 | // Also try to set fill on the SVG itself. |
| 177 | const svg = container.querySelector('svg'); |
| 178 | if (svg) { |
| 179 | svg.style.setProperty('fill', color, 'important'); |
| 180 | } |
| 181 | |
| 182 | // Mark container as color-applied to make it visible. |
| 183 | container.classList.add('frbl-color-applied'); |
| 184 | } |
| 185 | |
| 186 | /** |
| 187 | * Initialize all animations. |
| 188 | */ |
| 189 | function initAllAnimations() { |
| 190 | initCustomSvgAnimations(); |
| 191 | initLottieAnimations(); |
| 192 | } |
| 193 | |
| 194 | /** |
| 195 | * Initialize on DOM ready. |
| 196 | */ |
| 197 | if (document.readyState === 'loading') { |
| 198 | document.addEventListener('DOMContentLoaded', initAllAnimations); |
| 199 | } else { |
| 200 | initAllAnimations(); |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * Re-initialize when new content is loaded dynamically. |
| 205 | * Useful for infinite scroll, AJAX loading, etc. |
| 206 | */ |
| 207 | if (window.MutationObserver) { |
| 208 | const bodyObserver = new MutationObserver(function(mutations) { |
| 209 | let shouldReinit = false; |
| 210 | |
| 211 | mutations.forEach(function(mutation) { |
| 212 | mutation.addedNodes.forEach(function(node) { |
| 213 | if (node.nodeType === 1) { // Element node. |
| 214 | if (node.classList && (node.classList.contains('frbl-custom-svg-animation') || node.classList.contains('frbl-lottie-animation'))) { |
| 215 | shouldReinit = true; |
| 216 | } else if (node.querySelector && (node.querySelector('.frbl-custom-svg-animation') || node.querySelector('.frbl-lottie-animation'))) { |
| 217 | shouldReinit = true; |
| 218 | } |
| 219 | } |
| 220 | }); |
| 221 | }); |
| 222 | |
| 223 | if (shouldReinit) { |
| 224 | initAllAnimations(); |
| 225 | } |
| 226 | }); |
| 227 | |
| 228 | bodyObserver.observe(document.body, { |
| 229 | childList: true, |
| 230 | subtree: true |
| 231 | }); |
| 232 | } |
| 233 | |
| 234 | })(); |
| 235 |