UNPKG

@erosnicolau/animated-text

Version:

Advanced React animated text component with comprehensive animation effects

139 lines (138 loc) 4.73 kB
// Text processing and tokenization utilities // Allowed HTML tags for security const ALLOWED_HTML_TAGS = ['br', 'strong', 'i', 'b', 'em', 'italic']; // HTML sanitization function export const sanitizeHtml = (content) => { if (!content) return ''; const tempDiv = document.createElement('div'); tempDiv.innerHTML = content; const cleanNode = (node) => { if (node.nodeType === Node.ELEMENT_NODE) { const element = node; const tagName = element.tagName.toLowerCase(); if (!ALLOWED_HTML_TAGS.includes(tagName)) { const textNode = document.createTextNode(element.textContent || ''); element.parentNode?.replaceChild(textNode, element); return; } Array.from(element.attributes).forEach(attr => { element.removeAttribute(attr.name); }); Array.from(element.childNodes).forEach(cleanNode); } }; Array.from(tempDiv.childNodes).forEach(cleanNode); return tempDiv.innerHTML; }; // Tokenize content into words export const tokenizeContent = (content) => { const tokens = []; let wordIndex = 1; const regex = /(<[^>]+>)|(\S+)|(\s+)/g; let match; while ((match = regex.exec(content)) !== null) { if (match[1]) { tokens.push({ type: 'html', content: match[1] }); } else if (match[2]) { tokens.push({ type: 'word', content: match[2], wordIndex: wordIndex++ }); } else if (match[3]) { tokens.push({ type: 'space', content: match[3] }); } } return tokens; }; // Tokenize content into phrases separated by "|" export const tokenizePhrases = (content) => { const phrases = content.split('|'); const tokens = []; phrases.forEach((phrase, phraseIndex) => { const regex = /(<[^>]+>)|([^<]+)/g; let match; while ((match = regex.exec(phrase)) !== null) { if (match[1]) { tokens.push({ type: 'html', content: match[1] }); } else if (match[2]) { const trimmedContent = match[2].trim(); if (trimmedContent) { tokens.push({ type: 'phrase', content: trimmedContent, phraseIndex: phraseIndex + 1 }); } } } if (phraseIndex < phrases.length - 1) { tokens.push({ type: 'space', content: ' ' }); } }); return tokens; }; // Tokenize content into individual words for sequence animation export const tokenizeWords = (content) => { const tokens = []; let wordIndex = 1; const words = content.trim().split(/\s+/); words.forEach((word, index) => { if (word.trim()) { tokens.push({ type: 'word', content: word, wordIndex: wordIndex++ }); // Add space after each word except the last one if (index < words.length - 1) { tokens.push({ type: 'space', content: ' ', wordIndex: 0 }); } } }); return tokens; }; // Tokenize content into individual letters for typewriter animation export const tokenizeLetters = (content) => { const tokens = []; let letterIndex = 1; for (const char of content) { if (char === ' ') { tokens.push({ type: 'space', content: ' ', letterIndex: 0 }); } else { tokens.push({ type: 'letter', content: char, letterIndex: letterIndex++ }); } } return tokens; }; // Tokenize content for word-typewriter animation (words with individual letters) export const tokenizeWordTypewriter = (content) => { const tokens = []; let letterIndex = 1; // Split content into words and spaces while preserving spaces const parts = content.split(/(\s+)/); for (const part of parts) { if (/^\s+$/.test(part)) { // It's whitespace (spaces, tabs, etc.) tokens.push({ type: 'space', content: part, letterIndex: 0, isWordBoundary: true }); } else if (part.length > 0) { // It's a word - create word token with individual letters const letters = []; for (const char of part) { letters.push({ content: char, letterIndex: letterIndex++ }); } tokens.push({ type: 'word', content: part, letters: letters }); } } return tokens; };