PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / 1.3.0
FrontBlocks for Gutenberg/GeneratePress v1.3.0
trunk 0.2.0 0.2.1 0.2.2 0.2.3 0.2.4 0.2.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 ci-artifacts
frontblocks / assets / shape-animations / frontblocks-shape-animations.js
frontblocks / assets / shape-animations Last commit date
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