Skip to content

Instantly share code, notes, and snippets.

@baslie
Created July 9, 2025 02:48
Show Gist options
  • Select an option

  • Save baslie/b443f7b7b85222e44c9215c81d4f4ce2 to your computer and use it in GitHub Desktop.

Select an option

Save baslie/b443f7b7b85222e44c9215c81d4f4ce2 to your computer and use it in GitHub Desktop.
Анимация появления текста при скролле (для Тильды)
<!-- ********************************************************************** -->
<!-- Анимация появления текста при скролле -->
<!-- ********************************************************************** -->
<!-- Скрипт создаёт эффектную анимацию поочерёдного появления строк текста -->
<!-- снизу вверх при прокрутке страницы. Использует библиотеку 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