258 lines
7.5 KiB
JavaScript
258 lines
7.5 KiB
JavaScript
// Region animation configuration and lifecycle helpers.
|
|
|
|
function normalizePlayerAnimationConfig(value) {
|
|
var raw = value;
|
|
if (typeof raw === 'string') {
|
|
var text = String(raw || '').trim();
|
|
if (!text) {
|
|
raw = null;
|
|
} else {
|
|
try {
|
|
raw = JSON.parse(text);
|
|
} catch (_error) {
|
|
raw = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
raw = {};
|
|
}
|
|
|
|
return {
|
|
intro: normalizePlayerAnimationStep(raw.intro, 'none'),
|
|
outro: normalizePlayerAnimationStep(raw.outro !== undefined ? raw.outro : raw.out, 'none'),
|
|
loop: normalizePlayerAnimationStep(raw.loop, 'none')
|
|
};
|
|
}
|
|
|
|
function normalizePlayerAnimationStep(value, fallbackPreset) {
|
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
return {
|
|
preset: String(value.preset || fallbackPreset || 'none').trim(),
|
|
duration_ms: Number.isFinite(Number(value.duration_ms)) && Number(value.duration_ms) > 0 ? Math.round(Number(value.duration_ms)) : null,
|
|
delay_ms: Number.isFinite(Number(value.delay_ms)) && Number(value.delay_ms) >= 0 ? Math.round(Number(value.delay_ms)) : null,
|
|
iterations: Number.isFinite(Number(value.iterations)) && Number(value.iterations) > 0 ? Math.round(Number(value.iterations)) : null
|
|
};
|
|
}
|
|
|
|
return {
|
|
preset: String(typeof value === 'string' ? value : fallbackPreset || 'none').trim(),
|
|
duration_ms: null,
|
|
delay_ms: null,
|
|
iterations: null
|
|
};
|
|
}
|
|
|
|
function getAnimationStepTimingMs(step) {
|
|
if (!step) {
|
|
return 0;
|
|
}
|
|
|
|
var preset = String(step.preset || 'none').trim();
|
|
if (!preset || preset === 'none') {
|
|
return 0;
|
|
}
|
|
|
|
var durationMs = Number(step.duration_ms);
|
|
if (!Number.isFinite(durationMs) || durationMs <= 0) {
|
|
durationMs = 1000;
|
|
}
|
|
|
|
var delayMs = Number(step.delay_ms);
|
|
if (!Number.isFinite(delayMs) || delayMs < 0) {
|
|
delayMs = 0;
|
|
}
|
|
|
|
var iterations = Number(step.iterations);
|
|
if (!Number.isFinite(iterations) || iterations < 1) {
|
|
iterations = 1;
|
|
}
|
|
|
|
return delayMs + (durationMs * iterations);
|
|
}
|
|
|
|
function getRegionAnimationPhaseTimings(root, phase) {
|
|
if (!root) {
|
|
return [];
|
|
}
|
|
|
|
var normalizedPhase = String(phase || 'intro').trim().toLowerCase();
|
|
if (normalizedPhase !== 'intro' && normalizedPhase !== 'outro') {
|
|
normalizedPhase = 'intro';
|
|
}
|
|
|
|
var timings = [];
|
|
Array.prototype.forEach.call(root.querySelectorAll('[data-animation-json]'), function (element) {
|
|
if (!element) {
|
|
return;
|
|
}
|
|
|
|
var config;
|
|
try {
|
|
config = normalizePlayerAnimationConfig(element.getAttribute('data-animation-json') || '{}');
|
|
} catch (_error) {
|
|
config = null;
|
|
}
|
|
|
|
if (!config) {
|
|
return;
|
|
}
|
|
|
|
var timingMs = getAnimationStepTimingMs(normalizedPhase === 'outro' ? config.outro : config.intro);
|
|
if (timingMs > 0) {
|
|
timings.push({
|
|
element: element,
|
|
timingMs: timingMs
|
|
});
|
|
}
|
|
});
|
|
|
|
return timings;
|
|
}
|
|
|
|
function getRegionAnimationPhaseTimingMs(root, phase) {
|
|
return getRegionAnimationPhaseTimings(root, phase).reduce(function (maxTimingMs, entry) {
|
|
return Math.max(maxTimingMs, Number(entry && entry.timingMs || 0));
|
|
}, 0);
|
|
}
|
|
|
|
function clearRegionAnimationClasses(root) {
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
var elements = [];
|
|
if (typeof root.matches === 'function' && root.matches('[data-animation-json]')) {
|
|
elements.push(root);
|
|
}
|
|
Array.prototype.forEach.call(root.querySelectorAll('[data-animation-json]'), function (element) {
|
|
elements.push(element);
|
|
});
|
|
|
|
elements.forEach(function (element) {
|
|
if (!element) {
|
|
return;
|
|
}
|
|
element.classList.remove('animate__animated', 'animate__infinite');
|
|
element.classList.remove('animate__repeat-1', 'animate__repeat-2', 'animate__repeat-3');
|
|
Array.prototype.slice.call(element.classList || []).forEach(function (className) {
|
|
if (String(className || '').indexOf('animate__') === 0) {
|
|
element.classList.remove(className);
|
|
}
|
|
});
|
|
element.style.removeProperty('--animate-duration');
|
|
element.style.removeProperty('--animate-delay');
|
|
element.style.removeProperty('--animate-repeat');
|
|
element.style.removeProperty('animation-delay');
|
|
});
|
|
}
|
|
|
|
function isAttentionSeekerAnimation(preset) {
|
|
return ['bounce', 'flash', 'pulse', 'rubberBand', 'shakeX', 'shakeY', 'headShake', 'swing', 'tada', 'wobble', 'jello', 'heartBeat'].indexOf(String(preset || '').trim()) !== -1;
|
|
}
|
|
|
|
function applyAnimationStep(element, step, phase) {
|
|
if (!element || !step) {
|
|
return;
|
|
}
|
|
|
|
var preset = String(step.preset || 'none').trim();
|
|
if (!preset || preset === 'none') {
|
|
return;
|
|
}
|
|
|
|
element.style.setProperty('--animate-duration', String(Math.max(1, Number(step.duration_ms || 0) || 1000)) + 'ms');
|
|
var delayMs = phase === 'outro' ? 0 : Math.max(0, Number(step.delay_ms || 0));
|
|
if (delayMs > 0) {
|
|
element.style.setProperty('--animate-delay', String(delayMs) + 'ms');
|
|
element.style.setProperty('animation-delay', String(delayMs) + 'ms');
|
|
} else {
|
|
element.style.removeProperty('--animate-delay');
|
|
element.style.removeProperty('animation-delay');
|
|
}
|
|
element.classList.add('animate__animated', 'animate__' + preset);
|
|
|
|
if (phase === 'loop') {
|
|
var repeatCount = Number(step.iterations);
|
|
if (!Number.isFinite(repeatCount) || repeatCount < 1) {
|
|
repeatCount = 1;
|
|
}
|
|
if (repeatCount > 1) {
|
|
element.classList.add('animate__repeat-1');
|
|
}
|
|
element.style.setProperty('--animate-repeat', String(repeatCount));
|
|
return;
|
|
}
|
|
|
|
if (isAttentionSeekerAnimation(preset) && Number(step.iterations || 0) > 1) {
|
|
element.style.setProperty('--animate-repeat', String(Math.max(1, Number(step.iterations || 1))));
|
|
}
|
|
}
|
|
|
|
function playRegionAnimation(element, phase) {
|
|
if (!element) {
|
|
return;
|
|
}
|
|
|
|
var normalizedPhase = String(phase || 'intro').trim().toLowerCase();
|
|
if (normalizedPhase !== 'intro' && normalizedPhase !== 'outro') {
|
|
normalizedPhase = 'intro';
|
|
}
|
|
|
|
var config;
|
|
try {
|
|
config = normalizePlayerAnimationConfig(element.getAttribute('data-animation-json') || '{}');
|
|
} catch (_error) {
|
|
config = null;
|
|
}
|
|
|
|
if (!config) {
|
|
return;
|
|
}
|
|
|
|
element.dataset.animationPhase = normalizedPhase;
|
|
clearRegionAnimationClasses(element);
|
|
|
|
if (normalizedPhase === 'outro') {
|
|
applyAnimationStep(element, config.outro, 'outro');
|
|
return;
|
|
}
|
|
|
|
if (config.intro && String(config.intro.preset || '').trim() && String(config.intro.preset || '').trim() !== 'none') {
|
|
applyAnimationStep(element, config.intro, 'intro');
|
|
if (config.loop && String(config.loop.preset || '').trim() && String(config.loop.preset || '').trim() !== 'none') {
|
|
element.addEventListener('animationend', function handleAnimationEnd(event) {
|
|
if (event.target !== element) {
|
|
return;
|
|
}
|
|
if (String(element.dataset.animationPhase || '').trim() !== 'intro') {
|
|
return;
|
|
}
|
|
element.removeEventListener('animationend', handleAnimationEnd);
|
|
clearRegionAnimationClasses(element);
|
|
applyAnimationStep(element, config.loop, 'loop');
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
applyAnimationStep(element, config.loop, 'loop');
|
|
}
|
|
|
|
function playRegionAnimations(root, phase) {
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
var normalizedPhase = String(phase || 'intro').trim().toLowerCase();
|
|
if (normalizedPhase !== 'intro' && normalizedPhase !== 'outro') {
|
|
normalizedPhase = 'intro';
|
|
}
|
|
|
|
var elements = Array.prototype.slice.call(root.querySelectorAll('[data-animation-json]'));
|
|
elements.forEach(function (element) {
|
|
playRegionAnimation(element, normalizedPhase);
|
|
});
|
|
}
|