Дизеринг-градиенты на канвасе
Как устроен фон этого сайта — крошечный буфер ImageData, матрица Байера и одно CSS-свойство.
Фон этого сайта — не 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 и останавливается само, когда вкладка скрыта или в системе включён режим уменьшенного движения.