UNPKG

@erosnicolau/animated-text

Version:

Advanced React animated text component with comprehensive animation effects

345 lines (344 loc) 12.5 kB
// Transform values for string-based animation effects const getStringTransformValues = (effect) => { const transforms = { fadeIn: { from: '', to: '' }, 'slide-up': { from: 'translateY(20px)', to: 'translateY(0)' }, 'slide-down': { from: 'translateY(-20px)', to: 'translateY(0)' }, 'slide-left': { from: 'translateX(20px)', to: 'translateX(0)' }, 'slide-right': { from: 'translateX(-20px)', to: 'translateX(0)' }, 'slide-up-left': { from: 'translate(20px, 20px)', to: 'translate(0, 0)' }, 'slide-up-right': { from: 'translate(-20px, 20px)', to: 'translate(0, 0)' }, 'slide-down-left': { from: 'translate(20px, -20px)', to: 'translate(0, 0)' }, 'slide-down-right': { from: 'translate(-20px, -20px)', to: 'translate(0, 0)' } }; return transforms[effect] || { from: '', to: '' }; }; // Transform values for object-based animation effects const getObjectTransformValues = (effect) => { switch (effect.type) { case 'scale': return { from: `scale(${effect.from})`, to: `scale(${effect.to})` }; case 'rotate': return { from: `rotate(${effect.from}deg)`, to: `rotate(${effect.to}deg)` }; case 'slide': { const fromX = effect.x || 0; const fromY = effect.y || 0; return { from: `translate(${fromX}px, ${fromY}px)`, to: `translate(0, 0)` }; } case 'color': // Color effects don't use transform return { from: '', to: '' }; case 'backgroundColor': // Background color effects don't use transform return { from: '', to: '' }; case 'textShadow': // Text shadow effects don't use transform return { from: '', to: '' }; case 'fontSize': // Font size effects don't use transform return { from: '', to: '' }; case 'letterSpacing': // Letter spacing effects don't use transform return { from: '', to: '' }; case 'padding': case 'paddingTop': case 'paddingRight': case 'paddingBottom': case 'paddingLeft': case 'margin': case 'marginTop': case 'marginRight': case 'marginBottom': case 'marginLeft': case 'borderRadius': // Layout effects don't use transform return { from: '', to: '' }; case 'backdropFilter': case 'boxShadow': case 'filter': // Visual effects don't use transform return { from: '', to: '' }; case 'lineHeight': case 'fontWeight': case 'border': case 'borderWidth': case 'borderColor': case 'borderStyle': case 'width': case 'minWidth': case 'maxWidth': case 'height': case 'minHeight': case 'maxHeight': // Advanced property effects don't use transform return { from: '', to: '' }; default: return { from: '', to: '' }; } }; // Unified transform values processing const getTransformValues = (effect) => { if (typeof effect === 'string') { return getStringTransformValues(effect); } else { return getObjectTransformValues(effect); } }; // Compose multiple effects into combined transform, opacity, color, text shadow, font size, and letter spacing values const getComposedEffectValues = (effects) => { const effectsArray = Array.isArray(effects) ? effects : [effects]; let hasFadeIn = false; let combinedTransformFrom = ''; let combinedTransformTo = ''; const colorEffects = []; const backgroundColorEffects = []; const textShadowEffects = []; const fontSizeEffects = []; const letterSpacingEffects = []; const layoutEffects = []; const visualEffects = []; const advancedEffects = []; effectsArray.forEach(effect => { if (typeof effect === 'string') { if (effect === 'fadeIn') { hasFadeIn = true; } else { const transforms = getTransformValues(effect); if (transforms.from) { combinedTransformFrom += transforms.from + ' '; } if (transforms.to) { combinedTransformTo += transforms.to + ' '; } } } else { // Object effect if (effect.type === 'color') { colorEffects.push(effect); } else if (effect.type === 'backgroundColor') { backgroundColorEffects.push(effect); } else if (effect.type === 'textShadow') { textShadowEffects.push(effect); } else if (effect.type === 'fontSize') { fontSizeEffects.push(effect); } else if (effect.type === 'letterSpacing') { letterSpacingEffects.push(effect); } else if ([ 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'borderRadius' ].includes(effect.type)) { layoutEffects.push(effect); } else if (['backdropFilter', 'boxShadow', 'filter'].includes(effect.type)) { visualEffects.push(effect); } else if ([ 'lineHeight', 'fontWeight', 'border', 'borderWidth', 'borderColor', 'borderStyle', 'width', 'minWidth', 'maxWidth', 'height', 'minHeight', 'maxHeight' ].includes(effect.type)) { advancedEffects.push(effect); } else { const transforms = getTransformValues(effect); if (transforms.from) { combinedTransformFrom += transforms.from + ' '; } if (transforms.to) { combinedTransformTo += transforms.to + ' '; } } } }); return { hasFadeIn, transformFrom: combinedTransformFrom.trim(), transformTo: combinedTransformTo.trim(), colorEffects, backgroundColorEffects, textShadowEffects, fontSizeEffects, letterSpacingEffects, layoutEffects, visualEffects, advancedEffects }; }; // Convert easing to CSS timing function const getTimingFunction = (easing) => { const timings = { ease: 'ease', 'ease-in': 'ease-in', 'ease-out': 'ease-out', 'ease-in-out': 'ease-in-out', linear: 'linear' }; return timings[easing]; }; // ONE SINGLE ROBUST ANIMATION FUNCTION - handles everything including color, text shadow, font size, and letter spacing effects export const generateAnimationStyles = (effects, duration, delayBefore = 0, easing = 'ease-out', isAnimated) => { const { hasFadeIn, transformFrom, transformTo, colorEffects, backgroundColorEffects, textShadowEffects, fontSizeEffects, letterSpacingEffects, layoutEffects, visualEffects, advancedEffects } = getComposedEffectValues(effects); const timingFunction = getTimingFunction(easing); if (isAnimated) { // ANIMATED STATE const style = {}; // Handle opacity transitions if (hasFadeIn) { style.opacity = 1; } // Handle transform transitions - CRITICAL FIX: Always apply transform when present if (transformTo) { style.transform = transformTo; } // Handle color transitions colorEffects.forEach(colorEffect => { const property = colorEffect.property || 'color'; style[property] = colorEffect.to; }); // Handle background color transitions backgroundColorEffects.forEach(backgroundColorEffect => { style.backgroundColor = backgroundColorEffect.to; }); // Handle text shadow transitions textShadowEffects.forEach(textShadowEffect => { style.textShadow = textShadowEffect.to; }); // Handle font size transitions fontSizeEffects.forEach(fontSizeEffect => { style.fontSize = fontSizeEffect.to; }); // Handle letter spacing transitions letterSpacingEffects.forEach(letterSpacingEffect => { style.letterSpacing = letterSpacingEffect.to; }); // Handle layout transitions layoutEffects.forEach(layoutEffect => { ; style[layoutEffect.type] = layoutEffect.to; }); // Handle visual effects transitions visualEffects.forEach(visualEffect => { ; style[visualEffect.type] = visualEffect.to; }); // Handle advanced property transitions advancedEffects.forEach(advancedEffect => { if (advancedEffect.type === 'lineHeight') { style.lineHeight = advancedEffect.to; } else { ; style[advancedEffect.type] = advancedEffect.to; } }); // Build transition string - use simple "all" to catch everything if (hasFadeIn || transformTo || colorEffects.length > 0 || backgroundColorEffects.length > 0 || textShadowEffects.length > 0 || fontSizeEffects.length > 0 || letterSpacingEffects.length > 0 || layoutEffects.length > 0 || visualEffects.length > 0 || advancedEffects.length > 0) { style.transition = `all ${duration}ms ${timingFunction} ${delayBefore}ms`; } return style; } else { // INITIAL STATE const style = { transition: 'none' }; // Set initial opacity if (hasFadeIn) { style.opacity = 0; } // Set initial transform - CRITICAL FIX: Always apply initial transform when present if (transformFrom) { style.transform = transformFrom; } // Set initial colors colorEffects.forEach(colorEffect => { const property = colorEffect.property || 'color'; style[property] = colorEffect.from; }); // Set initial background colors backgroundColorEffects.forEach(backgroundColorEffect => { style.backgroundColor = backgroundColorEffect.from; }); // Set initial text shadows textShadowEffects.forEach(textShadowEffect => { style.textShadow = textShadowEffect.from; }); // Set initial font sizes fontSizeEffects.forEach(fontSizeEffect => { style.fontSize = fontSizeEffect.from; }); // Set initial letter spacing letterSpacingEffects.forEach(letterSpacingEffect => { style.letterSpacing = letterSpacingEffect.from; }); // Set initial layout properties layoutEffects.forEach(layoutEffect => { ; style[layoutEffect.type] = layoutEffect.from; }); // Set initial visual properties visualEffects.forEach(visualEffect => { ; style[visualEffect.type] = visualEffect.from; }); // Set initial advanced properties advancedEffects.forEach(advancedEffect => { if (advancedEffect.type === 'lineHeight') { style.lineHeight = advancedEffect.from; } else { ; style[advancedEffect.type] = advancedEffect.from; } }); return style; } }; // Wrapper for block animations (uses same function) export const generateBlockAnimationStyles = (effects, duration, easing, shouldAnimate) => { return generateAnimationStyles(effects, duration, 0, easing, shouldAnimate); };