Как сделать анимированный баннер на JavaScript

Как сделать анимированный баннер на JavaScript Полезное

Анимированный баннер — это рекламный или промо-блок фиксированного размера, движение в котором задано кодом, а не заранее отрисованным видео. Сегодня такие баннеры собирают из HTML, CSS и JavaScript вместо устаревшего Flash. В статье разберу три способа анимации (CSS, DOM плюс JavaScript, canvas), дам минимальный рабочий пример на canvas и requestAnimationFrame, покажу, почему он лучше setInterval, и как удержать баннер легким и плавным.

Три подхода к анимации баннера

Выбор техники зависит от сложности движения. Ниже — ориентир, что брать под задачу.

Подход Когда подходит Плюсы Ограничения
CSS-анимации и transitions простое движение готовых элементов (сдвиг, затухание, масштаб) не нужен JavaScript, браузер сам считает кадры на GPU нет сложной логики, тяжело управлять по ходу
DOM-элементы плюс JavaScript нужна логика, реакция на клик, смена сцен полный контроль, легко читать разметку много узлов тормозят перерисовку
Canvas 2D плюс requestAnimationFrame частицы, множество объектов, попиксельная отрисовка одна поверхность вместо сотен узлов, высокая производительность все рисуем руками, нет готовых DOM-событий на фигурах

Общее правило: если движение раскладывается на пару CSS-свойств — берите CSS. Если объектов десятки и они пересчитываются каждый кадр — берите canvas. DOM плюс JavaScript — это середина для интерактивных сцен из нескольких блоков.

Когда хватает чистого CSS

Самый легкий баннер вообще не требует JavaScript. Движение задается через @keyframes и свойство transform, а браузер сам синхронизирует кадры с частотой экрана.

<div class="banner">
  <div class="box"></div>
</div>
.banner {
  position: relative;
  width: 300px;
  height: 250px;
  overflow: hidden;
  background: #f1f3f4;
}
.box {
  position: absolute;
  top: 100px;
  width: 50px;
  height: 50px;
  background: #1a73e8;
  animation: slide 2s linear infinite;
}
@keyframes slide {
  from { transform: translateX(0); }
  to   { transform: translateX(250px); }
}

Результат: в браузере синий квадрат едет слева направо по серому баннеру 300×250 и зацикливается каждые 2 секунды. JavaScript тут не нужен вовсе. Важно, что анимируется transform, а не свойство left — об этой разнице отдельный раздел ниже.

Разница между transition и animation: transition проигрывает переход между двумя состояниями по событию (наведение, смена класса), а animation с @keyframes крутит заданную последовательность сама, в том числе бесконечно. Для баннера, который анимируется без действий пользователя, нужен именно animation.

Рабочий пример на canvas и requestAnimationFrame

Когда объектов много или нужна попиксельная отрисовка, DOM становится дорогим: каждый узел браузер размещает и перерисовывает. Canvas — это одна поверхность, на которой мы сами рисуем каждый кадр. Вот минимальный полный баннер, который можно вставить в пустую HTML-страницу и открыть в браузере.

<canvas id="banner" width="300" height="250"></canvas>
const canvas = document.getElementById('banner');
const ctx = canvas.getContext('2d');
let x = 0;

function frame() {
  ctx.clearRect(0, 0, canvas.width, canvas.height); // стираем прошлый кадр
  ctx.fillStyle = '#1a73e8';
  ctx.fillRect(x, 100, 50, 50);                      // рисуем квадрат
  x += 2;                                            // сдвигаем на 2 px
  if (x > canvas.width) x = -50;                     // ушел за край - вернули влево
  requestAnimationFrame(frame);                      // просим следующий кадр
}
requestAnimationFrame(frame);

Результат: в браузере синий квадрат едет слева направо и, уйдя за правый край, появляется снова слева. Разберу по шагам:

  • getContext('2d') дает объект для рисования 2D-фигур на canvas.
  • clearRect стирает предыдущий кадр целиком — без этого квадрат оставлял бы шлейф.
  • fillRect(x, 100, 50, 50) рисует квадрат 50×50 в текущей позиции x.
  • x += 2 меняет состояние, а requestAnimationFrame(frame) планирует следующий вызов перед очередной перерисовкой экрана.

У этого примера есть скрытая граница: шаг x += 2 привязан к кадру, а не ко времени. На экране 60 Гц квадрат едет со скоростью 120 px/с, а на 144 Гц — вдвое быстрее. Чтобы скорость была одинаковой на любом мониторе, считайте сдвиг от прошедшего времени.

const canvas = document.getElementById('banner');
const ctx = canvas.getContext('2d');
const speed = 120; // пикселей в секунду
let x = 0;
let last = 0;

function frame(now) {
  const dt = last ? (now - last) / 1000 : 0; // секунд с прошлого кадра
  last = now;
  x += speed * dt;
  if (x > canvas.width) x = -50;
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#1a73e8';
  ctx.fillRect(x, 100, 50, 50);
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

Результат: тот же едущий квадрат, но теперь его скорость — ровно 120 пикселей в секунду и не зависит от частоты экрана. Аргумент now в колбэк передает сам requestAnimationFrame — это метка времени текущего кадра в миллисекундах.

Почему requestAnimationFrame, а не setInterval

Раньше анимацию крутили через setInterval(frame, 16). Так делать не стоит по нескольким причинам.

  • setInterval не знает про частоту экрана и про перерисовку. Он может вызвать колбэк между кадрами — и часть отрисовок пропадет впустую или даст рывки.
  • requestAnimationFrame синхронизирован с циклом отрисовки браузера: колбэк вызывается один раз перед каждым обновлением экрана, обычно 60 раз в секунду.
  • Когда вкладка неактивна, браузер приостанавливает requestAnimationFrame — баннер не жжет процессор и батарею в фоне. setInterval продолжает срабатывать вслепую.

Вот тот же баннер на setInterval — как антипример, который дает рывки:

let x = 0;
setInterval(() => {          // фиксированные 16 мс, не привязаны к кадру
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillRect(x, 100, 50, 50);
  x += 2;
  if (x > canvas.width) x = -50;
}, 16);

Результат: квадрат тоже едет, но при нагрузке на страницу движение дергается, а в фоновой вкладке таймер продолжает работать вхолостую. Для новой анимации используйте requestAnimationFrame; setInterval уместен разве что для логики, не связанной с отрисовкой.

Библиотека GSAP, когда движения много

Писать вручную сложные тайминги (несколько объектов, задержки, плавности) утомительно. Для этого есть GSAP (GreenSock Animation Platform) — популярная библиотека анимации, которая работает и с DOM-элементами, и с canvas, и с SVG. Она берет на себя расчет промежуточных кадров и последовательностей (timeline).

// подключив gsap на страницу
gsap.to('.box', { x: 250, duration: 2, repeat: -1, ease: 'power1.inOut' });

Результат: элемент с классом box едет на 250 пикселей вправо за 2 секунды, бесконечно повторяя движение с плавным ускорением и замедлением. GSAP оправдан на сложных сценах; для простого сдвига квадрата хватит CSS или canvas без внешних зависимостей — лишняя библиотека утяжеляет баннер.

Оптимизация: не блокировать поток и рисовать на GPU

Плавность баннера — это стабильные кадры без пропусков. Два главных приема.

Первое: не блокировать основной поток. JavaScript в браузере однопоточный — тяжелый расчет в колбэке кадра задержит отрисовку и даст рывки. Внутри frame() держите только минимум: не создавайте новые объекты и массивы каждый кадр (это нагружает сборщик мусора), не читайте размеры DOM в цикле, тяжелые вычисления выносите заранее или в Web Worker.

Второе: анимировать свойства, которые считаются на видеокарте. Браузер рисует страницу слоями, и часть свойств композитор двигает на GPU без пересчета геометрии. Практическое правило для DOM-анимаций:

Свойство Что вызывает Для анимации
transform (translate, scale, rotate) только композицию слоя подходит, плавно
opacity только композицию слоя подходит, плавно
left, top, width, height пересчет разметки (reflow) и перерисовку избегать в анимации
box-shadow, background перерисовку (repaint) использовать осторожно

Поэтому в CSS-примере выше двигали transform: translateX(...), а не left. Подсказка will-change: transform заранее сообщает браузеру, что элемент будет анимироваться, и помогает вынести его на отдельный слой — но применять ее стоит точечно, лишние слои тоже стоят памяти.

Для canvas правило другое: там нет CSS-слоев, зато важно минимизировать площадь clearRect и число вызовов рисования за кадр, а статичный фон выносить на отдельный, редко перерисовываемый canvas.

Адаптивность и четкость на разных экранах

Баннер часто показывают на дисплеях разной плотности. Если задать canvas жесткие пиксели, на экране с высоким DPI (Retina) картинка будет мыльной. Решение — масштабировать буфер canvas на devicePixelRatio, а видимый размер задавать через CSS.

function setupCanvas(canvas) {
  const dpr = window.devicePixelRatio || 1;
  const rect = canvas.getBoundingClientRect(); // размер в CSS-пикселях
  canvas.width = rect.width * dpr;             // буфер под плотность экрана
  canvas.height = rect.height * dpr;
  const ctx = canvas.getContext('2d');
  ctx.scale(dpr, dpr);                          // рисуем в CSS-координатах
  return ctx;
}

Результат: на обычном мониторе картинка та же, а на Retina квадрат и текст остаются четкими, а не размытыми. Сам видимый размер баннера задавайте в CSS процентами или через max-width: 100%, чтобы он вписывался в контейнер на телефоне. Для DOM-баннера адаптивность проще — относительные единицы и медиазапросы. При смене размера окна пересчитывайте буфер по событию resize (желательно с задержкой-throttle, чтобы не дергать пересчет на каждый пиксель).

Вес баннера: почему это важно

Рекламные площадки ограничивают размер файла баннера, и тяжелый блок либо отклонят, либо он будет грузиться дольше самой страницы. Практические ориентиры на 2026 год:

  • Держите вес готового баннера небольшим — десятки, а не сотни килобайт. Точный лимит задает площадка, у Google Ads для HTML5-баннера действует ограничение около 150 КБ (значение уточняйте в справке площадки, оно менялось).
  • Не тащите крупные библиотеки ради одного эффекта — CSS или чистый canvas часто закрывают задачу без зависимостей.
  • Изображения сжимайте и берите современные форматы (WebP, AVIF), простую графику рисуйте кодом или через SVG, а не растровыми PNG.
  • Минифицируйте CSS и JavaScript при сборке готового баннера.

Легкий баннер быстрее показывается, лучше проходит модерацию сетей и меньше нагружает устройство пользователя — это напрямую влияет на то, увидит ли человек анимацию до того, как уйдет со страницы.

Выводы

  • Современный анимированный баннер собирают из HTML, CSS и JavaScript; Flash устарел и в рекламных сетях не используется.
  • Для простого движения хватает CSS с transform и @keyframes, для сложных сцен из многих объектов — canvas 2D.
  • Анимацию на canvas ведите через requestAnimationFrame, а сдвиг считайте от времени (delta), чтобы скорость не зависела от частоты экрана; setInterval для отрисовки не подходит.
  • Плавность дают два правила: не блокировать основной поток и анимировать transform/opacity, которые считаются на GPU.
  • Баннер держите легким и адаптивным: devicePixelRatio для четкости, сжатые ассеты и минимум зависимостей ради лимита площадки.

Где применяется / связь с практикой

Анимированные баннеры — частая задача фронтенд-разработчика и верстальщика: промо-блоки на сайте, рекламные креативы для сетей, интерактивные элементы лендингов. Все приемы из статьи (canvas, requestAnimationFrame, работа с transform) — это база JavaScript и работы с DOM, а не отдельная узкая тема. Чтобы уверенно писать такие вещи с нуля, нужно разобраться с типами, функциями, событиями и API браузера по порядку.

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

Системно освоить язык и довести до практики можно на курсе JavaScript Basic: там разбирают синтаксис, работу с DOM и анимацию на реальных задачах. Посмотреть формат и уровень преподавания перед стартом помогают бесплатные открытые уроки Otus — это разовые занятия по конкретным темам без оплаты.

FAQ

Можно ли сделать анимированный баннер вообще без JavaScript?
Да, для простого движения (сдвиг, затухание, поворот, масштаб) достаточно CSS с @keyframes и transform. JavaScript нужен, когда требуется логика, реакция на действия пользователя или отрисовка множества объектов на canvas.

Чем canvas отличается от SVG для баннера?
SVG — это векторные узлы в DOM: каждую фигуру видит браузер, ей можно повесить событие, она масштабируется без потери качества. Canvas — одна растровая поверхность, где мы рисуем каждый кадр сами; он быстрее при большом числе объектов, но событий на отдельных фигурах там нет. Для нескольких иконок берите SVG, для частиц и сложной динамики — canvas.

Почему баннер дергается в фоновой вкладке или на слабом телефоне?
Скорее всего, отрисовка привязана к setInterval или в кадре идет тяжелый расчет, блокирующий поток. Переведите анимацию на requestAnimationFrame, считайте движение от времени, уберите создание объектов внутри кадра и анимируйте transform/opacity вместо left/top.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)