5 шокирующих способов создать «текучую ленту с текстом» в Three.js: от простого UV‑скроллинга до живого рендеринга на canvas

19 декабря 2025 г.

Вступление

Веб‑разработчики и дизайнеры постоянно ищут новые способы привлечь внимание пользователя. Один из самых зрелищных приёмов – анимированный текст, «нарисованный» на гибкой ленте, которая изгибается, крутится и при этом остаётся полностью читаемой. Такой эффект часто встречается в рекламных роликах, интерактивных заставках и даже в видеоиграх. На первый взгляд задача кажется простой: «нужно, чтобы текст шел по волнистой линии». На практике же появляется целый набор вопросов: как хранить текстуру, как синхронизировать её с геометрией, как обеспечить плавность при деформации и при этом не перегрузить GPU?

В Reddit‑сообществе r/threejs пользователь задал вопрос о том, какой подход считается «чистым» для реализации этого эффекта. Ниже – подробный разбор всех предложенных решений, мнения экспертов и практический пример, который поможет вам быстро стартовать.

Японский хокку, отражающий суть задачи:

Лента в ветре —
Текст скользит, не теряя
Слова в изгибе.

Пересказ оригинального Reddit‑поста

Автор поста ищет способ воспроизвести анимацию, где текст выглядит как будто его «нарисовали» на движущейся ленте. При этом лента может изгибаться и крутиться, но текст остаётся читаемым. Вопрос звучит так: какой «чистый» подход в Three.js предпочтительнее – использовать меш‑ленту с повторяющейся текстурой текста и просто прокручивать UV‑координаты, или же каждый кадр рендерить живой текст на canvas‑текстуру?

Суть проблемы и «хакерский» подход

  • Геометрия ленты – обычно реализуется как TubeGeometry или кастомный RibbonGeometry, где вершины образуют длинную полосу.
  • Текстурирование – два пути:
    1. Статическая текстура с заранее подготовленным надписью, повторяющаяся по длине ленты. Прокрутка достигается изменением offset у UV‑координат.
    2. Динамическая canvas‑текстура, в которую каждый кадр рисуется нужный фрагмент текста. Позволяет менять содержание «на лету».
  • Синхронизация – важно, чтобы движение текстуры совпадало с изгибом ленты, иначе текст будет «скользить» по поверхности неправильно.

Детальный разбор проблемы с разных сторон

1. Производительность

Статическая текстура с UV‑скроллингом почти не нагружает процессор: GPU просто смещает координаты. Динамический canvas‑рендеринг требует перерисовки текстуры каждый кадр, что может стать узким местом, особенно на мобильных устройствах.

2. Гибкость контента

Если текст меняется часто (например, в рекламных баннерах с переменной информацией), canvas‑подход выигрывает, потому что менять текст в готовой текстуре без пересборки геометрии невозможно.

3. Качество отображения

При масштабировании ленты статическая текстура может потерять чёткость, если её разрешение недостаточно велико. Canvas‑текстура позволяет задавать произвольное разрешение и использовать devicePixelRatio для ретина‑дисплеев.

4. Сложность реализации

UV‑скроллинг – простая настройка материала. Canvas‑текстура требует написания кода, который будет правильно позиционировать текст, учитывать отступы и обновлять needsUpdate у текстуры.

5. Совместимость с другими эффектами

Если планируется добавить шейдерные эффекты (например, световые блики, шум, искажения), статическая текстура проще интегрировать в шейдерную цепочку. Canvas‑текстуру тоже можно использовать, но потребуется дополнительный шаг – передать её в шейдер как uniform.

Практические примеры и кейсы

Рассмотрим два типовых сценария.

Сценарий A: Статический рекламный баннер

Текст фиксированный («Скидка 30 %», «Новинка 2025»). Требуется высокая частота кадров, минимум нагрузки. Выбираем UV‑скроллинг. Текстуру готовим в графическом редакторе (например, Photoshop) с повторяющимся паттерном, задаём wrapS = RepeatWrapping и меняем offset.x в анимационном цикле.

Сценарий B: Интерактивный UI с меняющимся содержимым

Текст меняется в зависимости от действий пользователя (например, ввод имени). Здесь удобнее canvas‑текстура. Каждый раз, когда пользователь вводит новый символ, мы перерисовываем canvas и обновляем texture.needsUpdate = true. При этом геометрия ленты остаётся неизменной.

Экспертные мнения из комментариев

I'm sure there's some psychopath with the world supply of autism who could make this with nothing but 15,000 divs. But you're right something like ThreeJS is probably the easy answer. Though I'd be interested to see Lottie take a crack at this, might have to give that a play.

— Ralliare

Ralliare подчёркивает, что «чистый» подход – использовать Three.js, а не пытаться собрать всё из HTML‑элементов. Он также упоминает Lottie как потенциальный альтернативный инструмент.

Three.js if you need it to be dynamic, otherwise I’d just make it in after effects or blender and use a video element.

— berky93

berky93 советует использовать Three.js только в случае динамического контента, а для статических анимаций лучше подготовить видеоролик в After Effects или Blender.

Slap the gif onto the page and call it a day.

— not-halsey

not‑halsey предлагает самый простой путь – просто вставить готовый GIF. Это, конечно, не решает задачу интерактивности, но иногда «быстро и грязно» – лучший вариант.

Возможные решения и рекомендации

  1. Для статических надписей – создайте CanvasTexture один раз, задайте её в MeshStandardMaterial и анимируйте material.map.offset.x. Установите repeat так, чтобы текст покрывал всю длину ленты.
  2. Для динамических надписей – используйте отдельный HTMLCanvasElement, рисуйте текст через fillText, а затем передавайте его в Three.js как Texture. Не забывайте вызывать texture.needsUpdate = true после каждой перерисовки.
  3. Оптимизация – ограничьте размер canvas (например, 1024 × 256) и используйте requestAnimationFrame только при изменении текста.
  4. Шейдерный контроль – если нужен более сложный визуал (например, «текучесть» текста), добавьте в материал пользовательский шейдер, где по UV‑координатам будет применяться шум или искажение.
  5. Кроссплатформенность – проверяйте работу на мобильных браузерах, где WebGL может иметь ограничения по размеру текстур.

Прогноз развития

С ростом популярности WebGL‑фреймворков (Three.js, Babylon.js) и появлением новых API (WebGPU), создание сложных анимаций станет ещё более доступным. Ожидается, что в ближайшие 2‑3 года появятся готовые «плагины» для анимированных текстовых лент, а инструменты вроде Lottie Web будут поддерживать 3‑D‑примитивы, что упростит процесс до уровня «перетащи‑и‑отпусти».

Практический пример (Python)

Ниже – простой скрипт на Python, который генерирует последовательность изображений с текстом, имитируя движение текста по ленте. Полученные кадры можно импортировать в Three.js как DataTexture или собрать в видеоролик.


import numpy as np
from PIL import Image, ImageDraw, ImageFont

# Параметры генерации
WIDTH, HEIGHT = 1024, 256          # Размер кадра (соответствует размеру canvas‑текстуры)
TEXT = "ТЕКСТ НА ЛЕНТЕ"            # Текст, который будет «скользить»
FONT_SIZE = 64                     # Размер шрифта
FPS = 30                           # Частота кадров
DURATION = 5                       # Длительность в секундах
TOTAL_FRAMES = FPS * DURATION

# Создаём шрифт (путь к .ttf может отличаться)
font = ImageFont.truetype("arial.ttf", FONT_SIZE)

# Вычисляем ширину текста в пикселях
dummy_img = Image.new("RGB", (1, 1))
dummy_draw = ImageDraw.Draw(dummy_img)
text_width, _ = dummy_draw.textsize(TEXT, font=font)

# Функция генерации одного кадра
def generate_frame(offset):
    """Создаёт изображение с текстом, смещённым на offset пикселей."""
    img = Image.new("RGB", (WIDTH, HEIGHT), (0, 0, 0))
    draw = ImageDraw.Draw(img)

    # Вычисляем позицию начала текста (циклически)
    x_start = -offset % text_width
    # Рисуем несколько копий текста, чтобы покрыть всю ширину кадра
    while x_start < WIDTH:
        draw.text((x_start, HEIGHT // 2 - FONT_SIZE // 2), TEXT, font=font, fill=(255, 255, 255))
        x_start += text_width

    return img

# Генерация всех кадров и сохранение в папку "frames"
for i in range(TOTAL_FRAMES):
    offset = int((i / TOTAL_FRAMES) * text_width)  # Смещение от 0 до ширины текста
    frame = generate_frame(offset)
    frame.save(f"frames/frame_{i:04d}.png")

# После генерации можно собрать кадры в видео с помощью ffmpeg:
# ffmpeg -r 30 -i frames/frame_%04d.png -c:v libx264 -pix_fmt yuv420p ribbon_text.mp4

Скрипт создаёт набор PNG‑изображений, где текст плавно «скользит» слева направо. Эти изображения легко импортировать в Three.js как последовательность текстур или собрать в видеоролик, который затем можно отобразить на плоскости в сцене.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE