Canvas интеграция

Atomico — это библиотека для создания веб-компонентов на JavaScript с реактивной моделью данных. Одной из её сильных сторон является гибкая интеграция с нативными DOM-элементами, включая <canvas>. Использование Canvas в Atomico требует внимательного подхода к жизненному циклу компонента и управлению состоянием.

Создание компонента с Canvas

Компонент в Atomico определяется функцией, возвращающей шаблон с использованием html:

import { c, html, useRef, useEffect } from "atomico";

function CanvasComponent() {
    const canvasRef = useRef();

    useEffect(() => {
        const canvas = canvasRef.current;
        const ctx = canvas.getContext("2d");

        ctx.fillStyle = "lightblue";
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        ctx.fillStyle = "red";
        ctx.font = "20px Arial";
        ctx.fillText("Atomico Canvas", 50, 50);
    }, []);

    return html`
        <canvas
            ${canvasRef}
            width="400"
            height="200"
            style="border: 1px solid black;"
        ></canvas>
    `;
}

export const canvasComponent = c(CanvasComponent);

Ключевые моменты:

  • Использование useRef() позволяет получить ссылку на элемент <canvas> после его рендера.
  • useEffect() используется для инициализации и отрисовки на Canvas. Этот хук гарантирует, что доступ к DOM возможен только после монтирования компонента.
  • Стиль и размеры Canvas задаются через атрибуты и CSS.

Реактивная отрисовка

Для динамического изменения графики используется реактивное состояние:

import { c, html, useRef, useEffect, useState } from "atomico";

function ReactiveCanvas() {
    const canvasRef = useRef();
    const [angle, setAngle] = useState(0);

    useEffect(() => {
        const canvas = canvasRef.current;
        const ctx = canvas.getContext("2d");

        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.save();
            ctx.translate(canvas.width / 2, canvas.height / 2);
            ctx.rotate(angle);
            ctx.fillStyle = "green";
            ctx.fillRect(-50, -50, 100, 100);
            ctx.restore();
        }

        draw();
    }, [angle]);

    return html`
        <canvas
            ${canvasRef}
            width="300"
            height="300"
            style="border: 1px solid black;"
        ></canvas>
        <button oncl ick=${() => setAngle(angle + 0.1)}>Повернуть</button>
    `;
}

export const reactiveCanvas = c(ReactiveCanvas);

Особенности реактивного обновления:

  • Хук useState позволяет хранить состояние, влияющее на отрисовку.
  • Второй аргумент useEffect([angle]) гарантирует перерисовку при изменении значения.
  • Canvas полностью очищается перед новой отрисовкой через ctx.clearRect().

Анимация с requestAnimationFrame

Для создания плавной анимации интеграция Canvas с Atomico использует requestAnimationFrame в useEffect:

function AnimatedCanvas() {
    const canvasRef = useRef();
    const [time, setTime] = useState(0);

    useEffect(() => {
        const canvas = canvasRef.current;
        const ctx = canvas.getContext("2d");
        let animationFrame;

        function animate(timestamp) {
            setTime(timestamp / 1000);
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            ctx.beginPath();
            ctx.arc(
                150 + 100 * Math.cos(time),
                150 + 100 * Math.sin(time),
                20,
                0,
                2 * Math.PI
            );
            ctx.fillStyle = "orange";
            ctx.fill();

            animationFrame = requestAnimationFrame(animate);
        }

        animationFrame = requestAnimationFrame(animate);

        return () => cancelAnimationFrame(animationFrame);
    }, [time]);

    return html`<canvas ${canvasRef} width="300" height="300"></canvas>`;
}

export const animatedCanvas = c(AnimatedCanvas);

Важные аспекты анимации:

  • requestAnimationFrame синхронизирует обновления с частотой обновления экрана.
  • Отписка через cancelAnimationFrame предотвращает утечки памяти при размонтировании компонента.
  • Использование реактивного состояния позволяет интегрировать анимацию с другими реактивными данными Atomico.

События и взаимодействие с Canvas

Atomico позволяет легко добавлять обработчики событий к Canvas. Например, обработка клика:

function InteractiveCanvas() {
    const canvasRef = useRef();

    function handleClick(event) {
        const rect = canvasRef.current.getBoundingClientRect();
        const x = event.clientX - rect.left;
        const y = event.clientY - rect.top;
        console.log("Клик по координатам:", x, y);
    }

    return html`
        <canvas
            ${canvasRef}
            width="400"
            height="400"
            style="border: 1px solid black;"
            oncl ick=${handleClick}
        ></canvas>
    `;
}

export const interactiveCanvas = c(InteractiveCanvas);

Особенности работы с событиями:

  • Обработчик получает координаты относительно Canvas через getBoundingClientRect.
  • Можно комбинировать с реактивными состояниями для изменения графики по взаимодействию пользователя.

Поддержка HiDPI и масштабирование

Для корректного отображения на дисплеях с высоким разрешением важно масштабировать Canvas:

function HiDPICanvas() {
    const canvasRef = useRef();

    useEffect(() => {
        const canvas = canvasRef.current;
        const ctx = canvas.getContext("2d");
        const dpr = window.devicePixelRatio || 1;

        canvas.width = 300 * dpr;
        canvas.height = 300 * dpr;
        canvas.style.width = "300px";
        canvas.style.height = "300px";

        ctx.scale(dpr, dpr);
        ctx.fillStyle = "purple";
        ctx.fillRect(0, 0, 300, 300);
    }, []);

    return html`<canvas ${canvasRef}></canvas>`;
}

export const hiDpiCanvas = c(HiDPICanvas);

Принципы масштабирования:

  • Фактический размер Canvas умножается на devicePixelRatio.
  • Стили ширины и высоты остаются стандартными для правильного отображения на странице.
  • ctx.scale обеспечивает корректное масштабирование отрисовки.

Использование Canvas вместе с Shadow DOM

Atomico использует Shadow DOM по умолчанию. Canvas в Shadow DOM работает идентично обычному DOM, но стоит помнить:

  • Стили внутри Shadow DOM не влияют на внешний DOM.
  • Можно передавать атрибуты через свойства компонента и использовать их для настройки Canvas.
  • Shadow DOM обеспечивает изоляцию стилей, что полезно при сложной графике.

Интеграция с другими библиотеками

Canvas-компоненты Atomico могут интегрироваться с библиотеками для графики, например, three.js или pixi.js. Основной подход:

  1. Создать Canvas через useRef.
  2. Инициализировать библиотеку внутри useEffect.
  3. Отписывать ресурсы при размонтировании компонента.

Такой подход позволяет сочетать реактивный рендер Atomico с мощными движками для 2D и 3D-графики.


Atomico предоставляет полный контроль над Canvas через рефы и хуки, обеспечивая реактивность, управление жизненным циклом и поддержку сложных анимаций и интерактивности. Эта комбинация делает библиотеку удобным инструментом для интеграции динамической графики в современные веб-компоненты.