Коротко
Пиксель-арт размывается, когда браузер увеличивает картинку со сглаживанием или на дробный коэффициент. Чтобы пиксели остались квадратными, нужны три вещи: imageSmoothingEnabled = false в контексте Canvas2D (заново после каждого изменения размера холста), image-rendering: pixelated на элементе canvas и целочисленный масштаб, посчитанный в физических пикселях с учётом devicePixelRatio. Ниже — формулы, готовая функция подгонки холста и таблица масштабов для популярных базовых разрешений.
Содержание
- Где именно появляется размытие
- Правило 1: отключить сглаживание в Canvas2D
- Правило 2: image-rendering для самого холста
- Правило 3: целочисленный масштаб
- Правило 4: считать масштаб в физических пикселях
- Рисовать в низком разрешении или сразу в большом
- Рабочий сценарий: игра 320×180 на любом экране
- Когда вам это не подойдёт
Где именно появляется размытие
Размытие появляется в трёх местах, и каждое нужно закрыть отдельно. Первое — рисование спрайта с увеличением через drawImage: по умолчанию контекст сглаживает картинку. Второе — растягивание всего холста через CSS: если размер элемента на странице больше его внутреннего растра, браузер масштабирует его по правилу image-rendering, а значение по умолчанию auto оставляет алгоритм на усмотрение браузера (MDN приводит пример: Gecko использует билинейную интерполяцию). Третье — дробный коэффициент: при увеличении в 4,5 раза одни пиксели исходника становятся шириной 4 экранных, другие — 5, и даже без сглаживания картинка выглядит неровной.
| Где | Что видно | Что чинит |
|---|---|---|
drawImage с масштабом | Мягкие края у спрайта | ctx.imageSmoothingEnabled = false |
| CSS-размер холста больше растра | Мыло на всём кадре | image-rendering: pixelated |
Дробный масштаб (×4,5) | Пиксели разной ширины, «дрожь» при движении | Целый масштаб, посчитанный в физических пикселях |
Правило 1: отключить сглаживание в Canvas2D
Сглаживание в Canvas2D отключается свойством контекста imageSmoothingEnabled. По MDN значение по умолчанию — true, и при увеличении картинки алгоритм по умолчанию размывает пиксели; для игр с пиксель-артом свойство рекомендуют ставить в false (MDN, проверено: ноябрь 2026).
Главная ловушка — сброс. По стандарту HTML установка размеров растра холста (свойства width и height) сначала сбрасывает контекст рисования в состояние по умолчанию, а imageSmoothingEnabled входит в это состояние (HTML Standard, раздел о canvas, проверено: ноябрь 2026). Поэтому код «один раз выключил при старте» ломается при первом же изменении размера окна.
function applyPixelSettings(ctx) {
// вызывать после КАЖДОГО изменения canvas.width или canvas.height
ctx.imageSmoothingEnabled = false;
}
canvas.width = 1920; // контекст сброшен, сглаживание снова включено
canvas.height = 1080;
applyPixelSettings(ctx);
Правило 2: image-rendering для самого холста
Для элемента canvas, который растягивается стилями, нужно CSS-свойство image-rendering. MDN описывает два подходящих значения (проверено: ноябрь 2026):
crisp-edges— масштабирование алгоритмом вроде ближайшего соседа, без размытия и смешивания цветов; предназначено для пиксель-арта и штриховых рисунков.pixelated— ближайший сосед до ближайшего целого кратного исходного размера, а оставшаяся часть до итогового размера догоняется плавной интерполяцией. Так сохраняется пиксельный вид без артефактов, когда итоговый размер не кратен исходному.
canvas.game {
image-rendering: pixelated;
}
Свойство действует только тогда, когда картинка действительно масштабируется: если CSS-размер холста совпадает с размером его растра в физических пикселях, ничего не меняется. Это и есть идеальная цель — масштабировать самим, целым коэффициентом, а pixelated держать как страховку.
Правило 3: целочисленный масштаб
Целочисленный масштаб — это наибольшее целое число, при котором базовый кадр игры помещается в доступную область. Оставшееся место заполняется полями.
масштаб = max(1, floor(min(доступная_ширина / базовая_ширина,
доступная_высота / базовая_высота)))
итоговый размер = базовый размер × масштаб
поля = (доступный размер − итоговый размер) / 2
Пример для базы 320×180 и экрана 1366×768: 1366 / 320 = 4,27, 768 / 180 = 4,27, масштаб 4, кадр 1280×720, поля 43 пикселя по бокам и 24 сверху и снизу.
| База | 1280×720 | 1920×1080 | 2560×1440 | 3840×2160 |
|---|---|---|---|---|
320×180 | ×4 без полей | ×6 без полей | ×8 без полей | ×12 без полей |
384×216 | ×3 (1152×648) | ×5 без полей | ×6 (2304×1296) | ×10 без полей |
480×270 | ×2 (960×540) | ×4 без полей | ×5 (2400×1350) | ×8 без полей |
640×360 | ×2 без полей | ×3 без полей | ×4 без полей | ×6 без полей |
Из таблицы видно, почему базу 320×180 и 640×360 выбирают часто: они делят все четыре популярных разрешения без остатка. База 384×216 даёт больше игрового поля, но на 1280×720 и 2560×1440 оставляет поля.
Правило 4: считать масштаб в физических пикселях
Масштаб нужно считать в физических пикселях экрана, а не в CSS-пикселях. window.devicePixelRatio — отношение физических пикселей к CSS-пикселям: 1 соответствует классическому экрану 96 DPI, 2 — HiDPI/Retina; значение меняется при масштабировании страницы и при переносе окна на другой монитор (MDN, проверено: ноябрь 2026). На ноутбуках с масштабом системы 125% или 150% оно равно 1,25 или 1,5.
Почему это важно, видно на примере: окно 1100×650 CSS-пикселей, devicePixelRatio = 1,5, база 320×180.
Расчёт в CSS-пикселях (ошибка):
floor(min(1100/320, 650/180)) = floor(min(3,44; 3,61)) = 3
холст 960×540 CSS-пикселей = 1440×810 физических
1440 / 320 = 4,5 → пиксели исходника шириной то 4, то 5 точек
Расчёт в физических пикселях (верно):
доступно 1650×975 физических
floor(min(1650/320, 975/180)) = floor(min(5,16; 5,42)) = 5
растр холста 1600×900, CSS-размер 1600/1,5 × 900/1,5 ≈ 1066,67×600
Готовая функция подгонки, которая собирает все четыре правила:
const BASE_W = 320;
const BASE_H = 180;
function fitCanvas(canvas, ctx) {
const dpr = window.devicePixelRatio || 1;
const availW = Math.floor(window.innerWidth * dpr);
const availH = Math.floor(window.innerHeight * dpr);
const scale = Math.max(1, Math.floor(Math.min(availW / BASE_W, availH / BASE_H)));
canvas.width = BASE_W * scale; // растр в физических пикселях
canvas.height = BASE_H * scale;
canvas.style.width = (canvas.width / dpr) + "px"; // размер на странице
canvas.style.height = (canvas.height / dpr) + "px";
ctx.imageSmoothingEnabled = false; // контекст только что сброшен
return scale;
}
window.addEventListener("resize", () => fitCanvas(canvas, ctx));
Для отслеживания смены devicePixelRatio без изменения размера окна (перенос на другой монитор, масштаб страницы) MDN предлагает медиазапрос matchMedia("(resolution: " + dpr + "dppx)") с подпиской на событие change. CSS-размер холста при этом может быть дробным; проверяйте результат на реальных экранах с масштабом 125% и 150%, потому что округление дробных CSS-размеров до физических точек остаётся за браузером.
Рисовать в низком разрешении или сразу в большом
Есть два рабочих способа, и выбирать их нужно по тому, как движется камера. Первый — рисовать весь кадр во внеэкранный холст 320×180, а затем одним вызовом drawImage растягивать его на видимый холст в целое число раз. Второй — рисовать сразу на большом холсте, умножая координаты на масштаб и округляя их до целых.
| Буфер в базовом разрешении | Рисование сразу в итоговом размере | |
|---|---|---|
| Пиксели всегда одного размера | Да | Только если координаты округлены и спрайты не вращаются |
| Плавная камера с шагом меньше игрового пикселя | Нет, камера «шагает» по 1 пикселю базы | Да |
| Текст интерфейса в высоком разрешении | Нужен отдельный слой поверх | Можно рисовать на том же холсте |
| Стоимость | Один лишний drawImage за кадр | Без лишнего прохода |
Правило для обоих способов одно: координаты спрайтов и камеры перед рисованием округляются (Math.round), иначе соседние кадры дают смещение на доли пикселя, и при imageSmoothingEnabled = false спрайт начинает «дрожать» на 1 пиксель при медленном движении.
Рабочий сценарий: игра 320×180 на любом экране
Задача: платформер с базой 320×180 должен выглядеть одинаково чётко на мониторе 1920×1080, ноутбуке 1366×768 и ноутбуке с масштабом системы 150%. Проверяемый результат — на скриншоте, увеличенном в 4 раза, каждый пиксель исходника состоит из одинакового квадрата экранных точек.
Чек-лист
- Выберите базу по таблице выше:
320×180, если важны все четыре разрешения без полей. - Сделайте фон страницы того же цвета, что и поля, чтобы поля читались как рамка, а не как ошибка.
- Подключите
fitCanvasпри старте, наresizeи на сменуdevicePixelRatio. - Добавьте
image-rendering: pixelatedхолсту как страховку от дробного остатка. - Округляйте координаты спрайтов и камеры перед каждым
drawImage. - Проверьте три экрана:
devicePixelRatio1,1,25и1,5(в Chrome DevTools это можно эмулировать в режиме устройства) и масштаб страницы110%. - Проверьте шрифт интерфейса: пиксельный шрифт рисуйте тем же масштабом, обычный — отдельным слоем в полном разрешении.
Когда вам это не подойдёт
Целочисленное масштабирование нужно только для пиксель-арта и только там, где поля по краям приемлемы. Случаи, когда лучше выбрать другое:
- Рисованная HD-графика или вектор. Сглаживание таким картинкам полезно; отключение даст ступенчатые края.
- Спрайты вращаются или плавно масштабируются. Ближайший сосед при повороте даёт рваные края; для таких объектов оставляют сглаживание или готовят повёрнутые кадры заранее.
- Портретный телефон. При ширине
390CSS-пикселей база320×180помещается только в масштабе×1(с учётомdevicePixelRatio— немного больше), и игра занимает узкую полосу. Для телефона нужна отдельная вертикальная база или дробный масштаб сpixelated. - Отрисовка через WebGL или WebGPU. CSS-правила те же, но сглаживание текстур задаётся фильтром самой текстуры (режим ближайшего соседа), а не свойством Canvas2D.
Мы разрабатываем J2DRU — браузерный 2D игровой движок; он в разработке и публично пока не открыт, заявку можно оставить на странице раннего доступа (подача заявки не гарантирует приглашение).
Источники (проверено: ноябрь 2026): MDN — image-rendering CSS property; MDN — CanvasRenderingContext2D: imageSmoothingEnabled property; MDN — Window: devicePixelRatio property; WHATWG HTML Standard — раздел The canvas element (установка размеров растра и состояние контекста). Таблица масштабов и примеры — наш расчёт по формулам статьи.
Читайте также: