Created
July 9, 2025 02:48
-
-
Save baslie/b443f7b7b85222e44c9215c81d4f4ce2 to your computer and use it in GitHub Desktop.
Анимация появления текста при скролле (для Тильды)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- ********************************************************************** --> | |
| <!-- Анимация появления текста при скролле --> | |
| <!-- ********************************************************************** --> | |
| <!-- Скрипт создаёт эффектную анимацию поочерёдного появления строк текста --> | |
| <!-- снизу вверх при прокрутке страницы. Использует библиотеку GSAP --> | |
| <!-- для плавных переходов и автоматически разбивает контент на строки --> | |
| <!-- для создания каскадного эффекта появления. --> | |
| <!-- ********************************************************************** --> | |
| <!-- Инструкция: --> | |
| <!-- 1. Добавить в Zero Block текстовые элементы --> | |
| <!-- 2. Присвоить им класс «uc-text-reveal» --> | |
| <!-- 3. Вставить этот код в блок T123 --> | |
| <!-- ********************************************************************** --> | |
| <style> | |
| .uc-text-reveal { | |
| visibility: hidden; | |
| } | |
| .uc-line { | |
| position: relative; | |
| overflow: hidden; | |
| display: flex; | |
| } | |
| .uc-words { | |
| display: inline-block; | |
| } | |
| /* Добавлен стиль для плавного появления */ | |
| .uc-text-reveal.loaded { | |
| visibility: visible; | |
| } | |
| </style> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/gsap.min.js"></script> | |
| <script src='https://unpkg.com/[email protected]/dist/splitting.min.js'></script> | |
| <script src='https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollTrigger.min.js'></script> | |
| <script> | |
| window.addEventListener("load", function() { | |
| // ============= НАСТРОЙКИ АНИМАЦИИ ============= | |
| // Основные селекторы | |
| const SETTINGS = { | |
| // Главный селектор для элементов с анимацией | |
| mainSelector: '.uc-text-reveal .tn-atom', | |
| // Класс для контейнера текста | |
| containerClass: 'uc-text-reveal', | |
| // Классы для элементов | |
| lineClass: 'uc-line', | |
| wordsClass: 'words', | |
| wordClass: 'word', | |
| whitespaceClass: 'whitespace', | |
| hyphenClass: 'hyphen' | |
| }; | |
| // Параметры анимации | |
| const ANIMATION = { | |
| // Длительность анимации (в секундах) | |
| duration: 1, | |
| // Задержка перед началом анимации (в секундах) | |
| delay: 0.2, | |
| // Задержка между строками (в секундах) | |
| stagger: 0.25, | |
| // Начальное смещение по Y (в процентах) | |
| yPercent: 100, | |
| // Тип easing анимации | |
| ease: 'power2.out' | |
| }; | |
| // Настройки поведения триггера | |
| const TRIGGER_BEHAVIOR = { | |
| // Режим срабатывания: 'once' - один раз, 'repeat' - постоянно | |
| mode: 'once', // 'once' или 'repeat' | |
| // Поведение для однократного срабатывания | |
| onceToggleActions: 'play none none none', | |
| // Поведение для повторяющегося срабатывания | |
| repeatToggleActions: 'restart none none reset' | |
| }; | |
| // Настройки производительности | |
| const PERFORMANCE = { | |
| // Отключить анимацию на мобильных устройствах (ширина экрана меньше указанной) | |
| disableOnMobile: false, | |
| mobileBreakpoint: 768, | |
| // Использовать requestAnimationFrame для оптимизации | |
| useRAF: true, | |
| // Debounce задержка для resize событий (в мс) | |
| resizeDebounce: 150 | |
| }; | |
| // ============= ОСНОВНОЙ КОД ============= | |
| // Проверка поддержки необходимых API | |
| if (!window.gsap || !window.ScrollTrigger) { | |
| return; | |
| } | |
| // Проверка на мобильные устройства | |
| if (PERFORMANCE.disableOnMobile && window.innerWidth < PERFORMANCE.mobileBreakpoint) { | |
| // Показываем текст без анимации на мобильных | |
| document.querySelectorAll(SETTINGS.containerClass).forEach(el => { | |
| el.style.visibility = 'visible'; | |
| }); | |
| return; | |
| } | |
| // Улучшенная функция разбивки слов | |
| const splitWords = function(selector) { | |
| const elements = document.querySelectorAll(selector); | |
| elements.forEach(function(el) { | |
| // Сохраняем оригинальный текст | |
| el.dataset.splitText = el.textContent; | |
| // Разбиваем на слова с улучшенной обработкой | |
| el.innerHTML = el.textContent | |
| .trim() | |
| .split(/\s+/) // Используем регулярное выражение для множественных пробелов | |
| .filter(word => word.length > 0) // Убираем пустые строки | |
| .map(function(word) { | |
| return word | |
| .split("-") | |
| .map(function(part) { | |
| return `<span class="${SETTINGS.wordClass}">${part}</span>`; | |
| }) | |
| .join(`<span class="${SETTINGS.hyphenClass}">-</span>`); | |
| }) | |
| .join(`<span class="${SETTINGS.whitespaceClass}"> </span>`); | |
| }); | |
| }; | |
| // Улучшенная функция разбивки строк | |
| const splitLines = function(selector) { | |
| const elements = document.querySelectorAll(selector); | |
| if (elements.length === 0) { | |
| return; | |
| } | |
| splitWords(selector); | |
| elements.forEach(function(el) { | |
| const lines = getLines(el); | |
| if (lines.length === 0) { | |
| return; | |
| } | |
| let wrappedLines = ""; | |
| lines.forEach(function(wordsArr) { | |
| wrappedLines += `<span class="${SETTINGS.lineClass}"><span class="${SETTINGS.wordsClass}">`; | |
| wordsArr.forEach(function(word) { | |
| wrappedLines += word.outerHTML; | |
| }); | |
| wrappedLines += "</span></span>"; | |
| }); | |
| el.innerHTML = wrappedLines; | |
| }); | |
| }; | |
| // Оптимизированная функция получения строк | |
| const getLines = function(el) { | |
| const lines = []; | |
| let line = []; | |
| const words = el.querySelectorAll("span"); | |
| let lastTop = -1; | |
| for (let i = 0; i < words.length; i++) { | |
| const word = words[i]; | |
| const currentTop = word.offsetTop; | |
| if (currentTop !== lastTop) { | |
| // Не начинаем с пробелов | |
| if (!word.classList.contains(SETTINGS.whitespaceClass)) { | |
| if (line.length > 0) { | |
| lines.push(line); | |
| } | |
| lastTop = currentTop; | |
| line = []; | |
| } | |
| } | |
| line.push(word); | |
| } | |
| // Добавляем последнюю строку | |
| if (line.length > 0) { | |
| lines.push(line); | |
| } | |
| return lines; | |
| }; | |
| // Функция создания анимации с поддержкой настройки срабатывания | |
| const createAnimation = (element) => { | |
| const lines = element.querySelectorAll(`.${SETTINGS.wordsClass}`); | |
| if (lines.length === 0) { | |
| return null; | |
| } | |
| // Определяем настройки ScrollTrigger в зависимости от режима | |
| const scrollTriggerConfig = { | |
| trigger: element, | |
| // Настройки области срабатывания ScrollTrigger | |
| start: "top 100%", | |
| end: "bottom 0%" | |
| }; | |
| // Применяем настройки в зависимости от выбранного режима | |
| if (TRIGGER_BEHAVIOR.mode === 'once') { | |
| // Однократное срабатывание | |
| scrollTriggerConfig.once = true; | |
| scrollTriggerConfig.toggleActions = TRIGGER_BEHAVIOR.onceToggleActions; | |
| } else { | |
| // Повторяющееся срабатывание | |
| scrollTriggerConfig.toggleActions = TRIGGER_BEHAVIOR.repeatToggleActions; | |
| } | |
| const tl = gsap.timeline({ | |
| scrollTrigger: scrollTriggerConfig | |
| }); | |
| // Устанавливаем видимость | |
| tl.set(element, { autoAlpha: 1 }); | |
| // Создаем анимацию появления | |
| tl.from(lines, { | |
| duration: ANIMATION.duration, | |
| yPercent: ANIMATION.yPercent, | |
| ease: ANIMATION.ease, | |
| stagger: ANIMATION.stagger, | |
| delay: ANIMATION.delay | |
| }); | |
| return tl; | |
| }; | |
| // Debounce функция для оптимизации | |
| const debounce = (func, wait) => { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| }; | |
| // Функция инициализации | |
| const initTextReveal = () => { | |
| // Регистрируем плагин ScrollTrigger | |
| gsap.registerPlugin(ScrollTrigger); | |
| // Разбиваем текст на строки | |
| splitLines(SETTINGS.mainSelector); | |
| // Получаем все элементы для анимации | |
| const revealElements = document.querySelectorAll(SETTINGS.mainSelector); | |
| if (revealElements.length === 0) { | |
| return; | |
| } | |
| // Создаем анимации для каждого элемента | |
| const animations = []; | |
| revealElements.forEach((element) => { | |
| const animation = createAnimation(element); | |
| if (animation) { | |
| animations.push(animation); | |
| element.classList.add('loaded'); | |
| } | |
| }); | |
| // Обработка изменения размера окна | |
| if (PERFORMANCE.useRAF) { | |
| const handleResize = debounce(() => { | |
| ScrollTrigger.refresh(); | |
| }, PERFORMANCE.resizeDebounce); | |
| window.addEventListener('resize', handleResize); | |
| } | |
| return animations; | |
| }; | |
| // Запуск инициализации | |
| initTextReveal(); | |
| }); | |
| </script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment