О чем этот пример

По умолчанию Phaser сам создаёт элемент <canvas> и встраивает его в DOM. Но иногда вам нужно взять под полный контроль над контейнером игры — например, для интеграции с другими библиотеками, кастомного CSS-оформления или сложной вёрстки. В этой статье мы разберём, как передать Phaser уже существующий элемент <canvas> и настроить его под свои нужды. Этот подход открывает возможность тонкой настройки окружения игры.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('pic', 'assets/pics/baal-loader.png');
    }

    create ()
    {
        this.add.image(400, 300, 'pic');
    }
}


const myCustomCanvas = document.createElement('canvas');

myCustomCanvas.id = 'myCustomCanvas';
myCustomCanvas.style = 'border: 8px solid red';

document.body.appendChild(myCustomCanvas);

// const myCustomContext = myCustomCanvas.getContext('2d');

const config = {
    type: Phaser.CANVAS,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    canvas: document.getElementById('myCustomCanvas'),
    scene: Example
};

const game = new Phaser.Game(config);

Зачем нужен кастомный canvas?

Стандартное поведение Phaser удобно для быстрого старта: вы задаёте parent в конфиге, и движок сам создаёт и размещает элемент. Однако в реальных проектах часто требуется:

- Вручную задать id, class или стили для точного позиционирования. - Встроить игру в сложную, уже существующую структуру DOM. - Использовать один canvas для Phaser и других графических операций (например, рисование поверх игры). - Контролировать момент создания элемента (например, после загрузки определённых данных).

Передав свой canvas, вы получаете полную власть над этим элементом до инициализации игры.

Создание и подготовка своего элемента <canvas>

Всё начинается с создания элемента через DOM API. Мы можем задать ему атрибуты и стили, а затем добавить в тело документа (или любой другой контейнер).

const myCustomCanvas = document.createElement('canvas');

myCustomCanvas.id = 'myCustomCanvas';
myCustomCanvas.style = 'border: 8px solid red';

document.body.appendChild(myCustomCanvas);

Здесь мы создаём элемент, присваиваем ему идентификатор myCustomCanvas и добавляем красную рамку толщиной 8 пикселей для наглядности. После этого элемент становится частью DOM-дерева. Важно сделать это *до* создания экземпляра игры Phaser.Game, иначе движок не найдёт его по id.

Настройка конфига Phaser для работы с кастомным canvas

Ключевой момент — указать созданный canvas в объекте конфигурации игры. Для этого используется свойство canvas. Одновременно мы задаём type: Phaser.CANVAS, чтобы явно указать рендерер.

const config = {
    type: Phaser.CANVAS,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    canvas: document.getElementById('myCustomCanvas'),
    scene: Example
};

Обратите внимание: мы находим элемент по id, который задали ранее. Свойство parent здесь всё ещё можно указать — оно будет использоваться для расчёта масштабирования и выравнивания, но сам canvas создан не будет. Phaser возьмёт переданный элемент и начнёт отрисовку в него.

Что происходит внутри сцены?

Код сцены остаётся абсолютно неизменным. Phaser работает с переданным canvas как с родным. В примере загружается и отображается изображение.

class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('pic', 'assets/pics/baal-loader.png');
    }

    create ()
    {
        this.add.image(400, 300, 'pic');
    }
}

Метод this.add.image выводит картинку в координаты (400, 300) относительно холста. Все операции отрисовки будут применены к нашему кастомному элементу с красной рамкой. Это демонстрирует, что интеграция происходит прозрачно для игровой логики.

Важные технические детали и ограничения

1. **Порядок операций**: Создайте и добавьте canvas в DOM *до* вызова new Phaser.Game(config). Иначе document.getElementById вернёт null. 2. **Контекст рендеринга**: В исходнике есть закомментированная строка // const myCustomContext = myCustomCanvas.getContext('2d');. Получив контекст до инициализации Phaser, вы можете нарушить внутреннюю работу движка. Если нужен гибридный рендеринг, планируйте его аккуратно. 3. **Размеры**: Ширина и высота в конфиге (width, height) определяют внутреннее разрешение игры. Физические размеры canvas на странице можно дополнительно контролировать через CSS, но это может привести к растяжению. Лучше задавать размеры в JavaScript или следить за соотношением сторон. 4. **Рендерер**: Мы используем Phaser.CANVAS. Для WebGL-рендеринга (Phaser.WEBGL) подход аналогичен, но убедитесь, что браузер поддерживает WebGL в данном canvas.

Что попробовать дальше

Использование своего элемента <canvas> в Phaser — мощный приём для продвинутой интеграции игры в веб-страницу. Вы получаете полный контроль над DOM-элементом, сохраняя всю функциональность движка. **Идеи для экспериментов:** - Попробуйте динамически менять размеры canvas после создания игры, используя game.scale.resize(). - Встройте несколько независимых экземпляров Phaser в разные canvas на одной странице. - Навесьте на canvas обработчики событий мыши или клавиатуры поверх игровых. - Используйте кастомный canvas внутри Shadow DOM для изоляции стилей.