Пока делал блог, написал шесть анимаций для постов. Захотел посмотреть как они все вместе смотрятся на реальном длинном тексте — специально под это сделал этот пост. Ничего особенного, просто скролли вниз.
Шесть эффектов: символьный дешифратор на главном заголовке, цветовые помехи на подзаголовках, сканирующая линия на блоках кода, включение ЭЛТ-монитора на картинках, прерывистое появление параграфов и прилипающие заголовки при прокрутке. Почти каждый раздел ниже показывает что-то новое.
01 / Установка соединения

Когда страница загружается, главный заголовок делает ту же штуку что и на главной androman.pro — символы из мусорной кучи (█▓▒░#@$%&*+=!?>) по одному встают на свои места, и каждый зафиксированный символ на мгновение вспыхивает голубым. Через ~1.5 секунды заголовок устаканивается с лёгким свечением.
Мета-строка сверху (дата, тег, время чтения) при загрузке расширяется по расстоянию между буквами — как на печатной машинке, не плавное появление. Потом обложка (если есть) делает включение старого ЭЛТ-монитора: сжатая в полоску картинка за 1.6 секунды разворачивается в полный размер с вспышкой яркости.
1.1 Что под капотом
Сами эффекты — связка из двух частей: GSAP ScrollTrigger (библиотека анимаций + модуль отслеживания прокрутки) следит когда элемент въезжает в видимую область страницы, и навешивает CSS-класс. А CSS-анимации с steps() делают саму картинку. Анимации выполняются на видеокарте и не подвисают при нагрузке на страницу.
// Helper: trigger CSS class on scroll-into-view
function triggerOnScroll(selector, className, opts){
gsap.utils.toArray(selector).forEach(function(el){
ScrollTrigger.create({
trigger: el,
start: opts.start || 'top 88%',
once: opts.once !== false,
onEnter: function(){
el.classList.remove(className);
void el.offsetWidth; // reflow gimmick
el.classList.add(className);
}
});
});
}
// h2/h3 — RGB glitch shift
triggerOnScroll('.post-content > h2, .post-content > h3', 'cyber-glitch');
// Code blocks — scanline reveal
triggerOnScroll('.post-content > pre', 'cyber-scan');
Ключевой трюк — steps(N) в CSS-анимации. Обычное плавное движение (ease-out) даёт современный мягкий переход, а steps(5) или steps(10) — прерывистое, покадровое. Как анимация в старом мультике, не как iOS.
02 / Киберпанк-приёмы
Если разобрать что отделяет «киберпанк» от «современная анимация в блоге», то приёмы вполне конкретные. Не один универсальный, а набор:
- RGB shift — красный, зелёный и синий каналы текста чуть съезжают в разные стороны, как помехи на плохом сигнале
- Scanline — горизонтальная светящаяся линия проходит сверху вниз и «открывает» элемент
- Decoder — символы из мусорной кучи по одному встают на свои места, каждый вспыхивает при фиксации
- CRT power-on — как включается старый ЭЛТ-монитор: сначала тонкая полоска, потом резкое разворачивание с вспышкой яркости
- Stutter — покадровое, прерывистое движение вместо плавного
- Flicker — прозрачность дёргается 0 → 0.4 → 0 → 0.7 → 1, имитация плохого сигнала
Те же приёмы я уже использовал в своих WebGL-экспериментах (lava-orb, orb-2048, fire-particle-editor) — теперь перенёс в посты блога.
2.1 Прилипающий заголовок раздела
Самый полезный трюк для длинных постов — когда подзаголовок доезжает до верха страницы, он там «прилипает» и остаётся на виду пока читаешь раздел. Как только следующий подзаголовок подъезжает снизу — предыдущий отпускается. Всегда видно в каком разделе находишься.
gsap.utils.toArray('.post-content > h2').forEach(function(h2, idx, arr){
var nextH2 = arr[idx + 1] || null;
ScrollTrigger.create({
trigger: h2,
start: 'top 60px',
endTrigger: nextH2 || '.post-content',
end: 'top 60px',
pin: h2,
pinSpacing: false,
pinType: 'fixed'
});
});
Работает только если подзаголовки идут один за другим — что норма для длинного поста. На короткой странице с одним подзаголовком просто ничего не происходит.
03 / Анимации, привязанные к прокрутке

Помимо «запустить при появлении» есть второй режим — привязка к прокрутке. Эффект не запускается резко, а плавно следует за позицией скролла. Медленно скроллишь — медленный эффект. Быстро — быстрый.
Обложка поста на этом сайте работает в этом режиме: лёгкий параллакс (картинка движется чуть медленнее текста) и нарастающее свечение при уходе из видимой области. Едва заметно, но убирает ощущение статичной вставки.
Чем сильнее привязка к прокрутке, тем тоньше должен быть эффект. Резкие изменения цвета или позиции при быстром скролле — укачивает. Лучше: прозрачность, тени, смещение на 5-15px. Большие движения — только для разовых эффектов при появлении.
3.1 Когда этот режим не подходит
Привязка к прокрутке не работает для:
- Текстовых эффектов (дешифратор, печатная машинка) — дёргается когда скроллишь туда-сюда
- Блоков кода с подсветкой синтаксиса — библиотека подсветки расставляет теги при инициализации и конфликтует с анимацией появления
- Полей ввода — анимация смещения сбивает фокус
Для всего этого — разовый запуск при появлении: toggleActions: 'play none none reverse' или once: true.
04 / Производительность
GSAP вместе с модулем прокрутки весит ~30 КБ в сжатом виде и загружается с внешнего CDN — сети серверов по всему миру, откуда файлы отдаются быстро и кэшируются в браузере. Для повторного посетителя загрузка нулевая: файл уже лежит в кэше с прошлого раза.
На остальных страницах (главная, архив, лаборатория) GSAP не подгружается вообще — в теме стоит проверка, что библиотека нужна только на страницах отдельных постов. Поэтому показатели скорости главной страницы не страдают.
if ( is_singular( 'post' ) ) {
wp_enqueue_style( 'androman-single-cyber', /* ... */ );
wp_enqueue_script( 'gsap-core', /* CDN */ );
wp_enqueue_script( 'gsap-scrolltrigger', /* CDN */ );
wp_enqueue_script( 'androman-single-anim', /* local */ );
}
4.1 Режим без анимаций
В Windows, macOS и Android есть системная настройка «уменьшить анимации» — для людей, которым мигание и движение на экране некомфортно. В эффектах это учтено. В CSS прописан override для этого режима:
@media (prefers-reduced-motion: reduce) {
.cyber-glitch, .cyber-scan, .cyber-crt,
.cyber-stutter-p, .cyber-stutter-x,
.cyber-meta-in, .cyber-flicker {
animation: none !important;
opacity: 1 !important;
transform: none !important;
clip-path: none !important;
filter: none !important;
}
}
И в JavaScript: если включён этот режим, скрипт анимаций сразу завершает работу не запуская ничего. Все элементы которые должны были «появляться» — сразу видны. Контент доступен, анимаций нет.
05 / Что дальше
Тестовый пост — для разовой проверки. Пожил на длинном тексте, выглядит как надо.
Следующее что хочу попробовать — звук: короткий клик когда символ дешифратора встаёт на место, тихий гул при CRT-включении. Плюс пасхалка: ↑↑↓↓←→←→BA включает «режим старого ЭЛТ» на всём сайте — полосы сканирования, насыщенность +40%, чуть больше контраста. Ещё есть идея с магнитным курсором (~30 строк) и 3D-наклоном карточек лаборатории при наведении. Каждое — на час. Пока в списке, не в расписании.
06 / Сравнение CRT vs default
Слева — картинка без эффекта (помечена class="no-anim"). Справа — с CRT power-on: scaleY 0.02→1 (как ЭЛТ-монитор включается из тонкой линии), brightness peak 2.2, saturate 0→1.2→1, hue-rotate 20°→0, blur 2px→0. Длительность 1.6s.
Кликни «▷ REPLAY» чтобы пересмотреть эффект на правой картинке.


// END OF TRANSMISSION · скролл назад оставит тебя на текущей секции
Работает как надо — живёт в теме. Большую часть времени потратил не на написание, а на подбор тайминга: на коротком тексте эти вещи не видны, а на длинном сразу понятно где слишком резко, а где не хватает. Этот пост и был для этого.