Назад в блог

Дизеринг-градиенты на канвасе

Как устроен фон этого сайта — крошечный буфер ImageData, матрица Байера и одно CSS-свойство.

  • canvas
  • графика
  • ретро

Фон этого сайта — не CSS-градиент. Это градиент, отрисованный в буфер шириной 180 пикселей, сведённый дизерингом к двум цветам и растянутый на весь экран. Именно последний шаг и делает его похожим на демку 1994 года, а не на современную «шапку» лендинга.

Коротко про упорядоченный дизеринг

Упорядоченный дизеринг сравнивает значение каждого пикселя с порогом, который меняется внутри маленькой повторяющейся плитки. Классическая плитка — матрица Байера:

const BAYER = [
  [0, 32, 8, 40],
  [48, 16, 56, 24],
  [12, 44, 4, 36],
  [60, 28, 52, 20],
].map((row) => row.map((v) => (v + 0.5) / 64));

const on = value > BAYER[y & 3][x & 3];

С двумя цветами и плиткой 8×8 получается 65 воспринимаемых уровней — более чем достаточно для плавного градиента, и всё это на целочисленных сравнениях.

Почему буфер такой маленький

Попиксельный JavaScript тормозит только тогда, когда пикселей много. При 180×101 их около 18 000 — это ничто. Весь фокус в том, чтобы вообще не рисовать в полном разрешении:

canvas {
  width: 100vw;
  height: 100svh;
  image-rendering: pixelated;
}

Браузер растягивает буфер методом ближайшего соседа, поэтому узор дизеринга остаётся чётким и «крупным», а не размывается.

Реакция на курсор

Мышь двигает центр мягкого радиального слагаемого в функции градиента. Но если следовать за курсором напрямую, фон начинает дёргаться и перетягивать внимание с текста. Лечится сильным запаздыванием:

currentX += (targetX - currentX) * 0.02;

На 30 кадрах в секунду догон занимает пару секунд, и это читается как фон тебя заметил, а не фон за тобой следит.


Всё вместе — примерно 150 строк, работает на 30 fps и останавливается само, когда вкладка скрыта или в системе включён режим уменьшенного движения.