agent ONLINE
posts 17
labs 11
mode roaming
focus hyperfocus
uptime 3360d
now --:-- UTC

Cyberpunk reveal-анимации — полный тур по эффектам

Пока делал блог, написал шесть анимаций для постов. Захотел посмотреть как они все вместе смотрятся на реальном длинном тексте — специально под это сделал этот пост. Ничего особенного, просто скролли вниз.

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

01 / Установка соединения

lava-orb WebGL preview

Когда страница загружается, главный заголовок делает ту же штуку что и на главной 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 / Анимации, привязанные к прокрутке

fire-particle-editor preview

Помимо «запустить при появлении» есть второй режим — привязка к прокрутке. Эффект не запускается резко, а плавно следует за позицией скролла. Медленно скроллишь — медленный эффект. Быстро — быстрый.

Обложка поста на этом сайте работает в этом режиме: лёгкий параллакс (картинка движется чуть медленнее текста) и нарастающее свечение при уходе из видимой области. Едва заметно, но убирает ощущение статичной вставки.

Чем сильнее привязка к прокрутке, тем тоньше должен быть эффект. Резкие изменения цвета или позиции при быстром скролле — укачивает. Лучше: прозрачность, тени, смещение на 5-15px. Большие движения — только для разовых эффектов при появлении.

3.1 Когда этот режим не подходит

Привязка к прокрутке не работает для:

  1. Текстовых эффектов (дешифратор, печатная машинка) — дёргается когда скроллишь туда-сюда
  2. Блоков кода с подсветкой синтаксиса — библиотека подсветки расставляет теги при инициализации и конфликтует с анимацией появления
  3. Полей ввода — анимация смещения сбивает фокус

Для всего этого — разовый запуск при появлении: 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» чтобы пересмотреть эффект на правой картинке.

Без эффекта
// БЕЗ ЭФФЕКТА
С CRT power-on
// CRT POWER-ON

// END OF TRANSMISSION · скролл назад оставит тебя на текущей секции

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

Оставить комментарий

// поля помечены ·

// SEQUENCEwatch the pattern

>