UNPKG

@thecoderzeus/scroll-reveal-fx

Version:

Uma suíte de animação de alta performance, modular e sem dependências, para criar interações ricas e elegantes baseadas no scroll e no mouse.

127 lines (110 loc) 4.64 kB
import { revealOnScroll } from './core.js'; import { getAnimation } from './utils.js'; const TYPEWRITER_EFFECT = 'typewriter'; /** * Animate text elements on scroll. * Can reveal text by characters or words using various effects, * or use a special typewriter effect. * * @param {object} [options={}] - The configuration options, extends revealOnScroll options. * @param {'chars' | 'words'} [options.splitType='chars'] - How to split the text. * @param {boolean} [options.randomOrder=false] - If true, reveals parts in a random order (not for typewriter). * @param {string} [options.effect='slide'] - Animation effect. Use 'typewriter' for a special typing effect. * @param {number} [options.typewriterSpeed=50] - Speed in ms for the typewriter effect. */ export function animateText(options = {}) { const defaults = { splitType: 'chars', randomOrder: false, typewriterSpeed: 50, effect: 'slide', direction: 'up', duration: 400, stagger: 30, once: true }; const config = { ...defaults, ...options }; const elements = document.querySelectorAll(config.selector); if (elements.length === 0) { console.warn(`AnimateText: No elements found with selector: "${config.selector}"`); return; } if (config.effect === TYPEWRITER_EFFECT) { elements.forEach(el => { if (el.dataset.typingInitialized === undefined) { el.dataset.originalText = el.textContent; el.dataset.typingInitialized = 'true'; } const originalText = el.dataset.originalText; el.innerHTML = ''; const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { if (el.dataset.isTyping === 'true') return; el.dataset.isTyping = 'true'; let i = 0; el.innerHTML = ''; const type = () => { if (i < originalText.length) { el.innerHTML += originalText.charAt(i); i++; setTimeout(type, config.typewriterSpeed); } else { delete el.dataset.isTyping; if (!config.once) { el.dataset.canReset = 'true'; } } }; type(); if (config.once) { observer.unobserve(el); } } else { if (!config.once && el.dataset.canReset === 'true') { el.innerHTML = ''; delete el.dataset.isTyping; } } }, { threshold: config.threshold || 0.75 }); observer.observe(el); }); return; } elements.forEach(el => { const originalText = el.textContent; const splitRegex = config.splitType === 'words' ? /(\s+)/ : ''; const parts = originalText.split(splitRegex); el.innerHTML = ''; el.style.opacity = 1; let partSpans = []; const tempContainer = document.createDocumentFragment(); parts.forEach(part => { if (part.trim() === '') { tempContainer.appendChild(document.createTextNode(part)); } else { const chunks = config.splitType === 'chars' ? part.split('') : [part]; chunks.forEach(chunk => { const span = document.createElement('span'); span.textContent = chunk; span.style.display = 'inline-block'; span.style.willChange = 'opacity, transform, filter'; span.classList.add('reveal-text-part'); partSpans.push(span); tempContainer.appendChild(span); }); } }); el.appendChild(tempContainer); if (config.randomOrder) { partSpans.sort(() => Math.random() - 0.5); } partSpans.forEach((span, index) => { span.dataset.revealIndex = index; }); }); const revealConfig = { ...config, selector: `${config.selector} .reveal-text-part` }; revealOnScroll(revealConfig); }