---
title: "Пиксель-арт в браузере без размытия: image-rendering, целочисленное масштабирование и devicePixelRatio"
description: "Почему пиксель-арт размывается в браузерной игре и как это исправить: imageSmoothingEnabled в Canvas2D, CSS image-rendering pixelated и crisp-edges, формула целочисленного масштаба, учёт devicePixelRatio на экранах 125% и 150%, таблица масштабов для 320×180, 384×216, 480×270 и 640×360."
slug: piksel-art-v-brauzere-bez-razmytiya
date: 2026-11-02
updated: 2026-11-02
scheduled_at: "2026-11-02T15:00:00+03:00"
rubric: ustrojstvo-2d-igr
tags: ["piksel-art", "canvas", "masshtabirovanie", "brauzernye-igry", "profi"]
reading_time: 8
draft: false
summary: "Пиксель-арт в браузере размывается в трёх местах: при рисовании спрайта с масштабом (сглаживание Canvas2D включено по умолчанию), при растягивании самого холста через CSS и при дробном масштабе на экранах с devicePixelRatio 1,25 или 1,5. Лечится тремя правилами: imageSmoothingEnabled = false после каждого изменения размера холста, image-rendering: pixelated для элемента canvas и целочисленный масштаб, посчитанный в физических пикселях, а не в CSS-пикселях."
---

## Коротко

Пиксель-арт размывается, когда браузер увеличивает картинку со сглаживанием или на дробный коэффициент. Чтобы пиксели остались квадратными, нужны три вещи: `imageSmoothingEnabled = false` в контексте Canvas2D (заново после каждого изменения размера холста), `image-rendering: pixelated` на элементе canvas и целочисленный масштаб, посчитанный в физических пикселях с учётом `devicePixelRatio`. Ниже — формулы, готовая функция подгонки холста и таблица масштабов для популярных базовых разрешений.

## Содержание

1. Где именно появляется размытие
2. Правило 1: отключить сглаживание в Canvas2D
3. Правило 2: image-rendering для самого холста
4. Правило 3: целочисленный масштаб
5. Правило 4: считать масштаб в физических пикселях
6. Рисовать в низком разрешении или сразу в большом
7. Рабочий сценарий: игра 320×180 на любом экране
8. Когда вам это не подойдёт

## Где именно появляется размытие

Размытие появляется в трёх местах, и каждое нужно закрыть отдельно. Первое — рисование спрайта с увеличением через `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). Поэтому код «один раз выключил при старте» ломается при первом же изменении размера окна.

```js
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`** — ближайший сосед до ближайшего целого кратного исходного размера, а оставшаяся часть до итогового размера догоняется плавной интерполяцией. Так сохраняется пиксельный вид без артефактов, когда итоговый размер не кратен исходному.

```css
canvas.game {
  image-rendering: pixelated;
}
```

Свойство действует только тогда, когда картинка действительно масштабируется: если CSS-размер холста совпадает с размером его растра в физических пикселях, ничего не меняется. Это и есть идеальная цель — масштабировать самим, целым коэффициентом, а `pixelated` держать как страховку.

## Правило 3: целочисленный масштаб

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

```text
масштаб = max(1, floor(min(доступная_ширина / базовая_ширина,
                           доступная_высота / базовая_высота)))

итоговый размер = базовый размер × масштаб
поля            = (доступный размер − итоговый размер) / 2
```

Пример для базы `320×180` и экрана `1366×768`: `1366 / 320 = 4,27`, `768 / 180 = 4,27`, масштаб `4`, кадр `1280×720`, поля `43` пикселя по бокам и `24` сверху и снизу.

Таблица: Целые масштабы для базовых разрешений 16:9 (наш расчёт по формуле выше)

| База | `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`.

```text
Расчёт в 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
```

Готовая функция подгонки, которая собирает все четыре правила:

```js
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 раза, каждый пиксель исходника состоит из одинакового квадрата экранных точек.

### Чек-лист

1. **Выберите базу** по таблице выше: `320×180`, если важны все четыре разрешения без полей.
2. **Сделайте фон страницы того же цвета, что и поля**, чтобы поля читались как рамка, а не как ошибка.
3. **Подключите `fitCanvas`** при старте, на `resize` и на смену `devicePixelRatio`.
4. **Добавьте `image-rendering: pixelated`** холсту как страховку от дробного остатка.
5. **Округляйте координаты** спрайтов и камеры перед каждым `drawImage`.
6. **Проверьте три экрана:** `devicePixelRatio` `1`, `1,25` и `1,5` (в Chrome DevTools это можно эмулировать в режиме устройства) и масштаб страницы `110%`.
7. **Проверьте шрифт интерфейса:** пиксельный шрифт рисуйте тем же масштабом, обычный — отдельным слоем в полном разрешении.

## Когда вам это не подойдёт

Целочисленное масштабирование нужно только для пиксель-арта и только там, где поля по краям приемлемы. Случаи, когда лучше выбрать другое:

- **Рисованная HD-графика или вектор.** Сглаживание таким картинкам полезно; отключение даст ступенчатые края.
- **Спрайты вращаются или плавно масштабируются.** Ближайший сосед при повороте даёт рваные края; для таких объектов оставляют сглаживание или готовят повёрнутые кадры заранее.
- **Портретный телефон.** При ширине `390` CSS-пикселей база `320×180` помещается только в масштабе `×1` (с учётом `devicePixelRatio` — немного больше), и игра занимает узкую полосу. Для телефона нужна отдельная вертикальная база или дробный масштаб с `pixelated`.
- **Отрисовка через WebGL или WebGPU.** CSS-правила те же, но сглаживание текстур задаётся фильтром самой текстуры (режим ближайшего соседа), а не свойством Canvas2D.

Мы разрабатываем **J2DRU** — браузерный 2D игровой движок; он в разработке и публично пока не открыт, заявку можно оставить на [странице раннего доступа](/early-access?utm_source=blog&utm_medium=article&utm_campaign=piksel-art-v-brauzere-bez-razmytiya) (подача заявки не гарантирует приглашение).

**Источники (проверено: ноябрь 2026):** MDN — *image-rendering CSS property*; MDN — *CanvasRenderingContext2D: imageSmoothingEnabled property*; MDN — *Window: devicePixelRatio property*; WHATWG HTML Standard — раздел *The canvas element* (установка размеров растра и состояние контекста). Таблица масштабов и примеры — наш расчёт по формулам статьи.

**Читайте также:**
- [WebGPU и WebGL2: как работает отрисовка 2D-игры](/blog/webgpu-i-webgl2-kak-rabotaet-otrisovka-2d-igry)
- [Тайловые сетки и устройство 2D-миров](/blog/tajlovye-setki-i-ustrojstvo-2d-mirov)
