Paper.js и React

Paper.js — это мощная библиотека для векторной графики и работы с канвасом в браузере на JavaScript. В контексте React её применение требует особого подхода, поскольку Paper.js управляет своим собственным сценическим графом, а React оперирует виртуальным DOM. Основная задача — обеспечить согласованное взаимодействие между жизненным циклом компонентов React и Paper.js.


Настройка канваса

Для корректной работы Paper.js необходимо создать <canvas> элемент и привязать его к Paper.js с помощью paper.setup(canvas). В React рекомендуется использовать ref, чтобы получить доступ к DOM-элементу:

import React, { useRef, useEffect } from 'react';
import paper from 'paper';

function PaperCanvas() {
    const canvasRef = useRef(null);

    useEffect(() => {
        paper.setup(canvasRef.current);

        const circle = new paper.Path.Circle({
            center: [80, 50],
            radius: 30,
            fillColor: 'red'
        });

        paper.view.draw();

        return () => {
            paper.project.remove(); // очистка проекта при размонтировании
        };
    }, []);

    return <canvas ref={canvasRef} width={400} height={400} />;
}

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

  • useRef обеспечивает доступ к DOM-элементу канваса.
  • paper.setup привязывает Paper.js к конкретному канвасу.
  • paper.project.remove() освобождает ресурсы и предотвращает утечки памяти.

Управление жизненным циклом объектов

Paper.js создаёт объекты в проекте, которые остаются в памяти до их явного удаления. В React это требует внимательного контроля:

  • Добавление объектов: через конструкторы Path, Shape, Raster.
  • Обновление объектов: напрямую меняя свойства объектов Paper.js (например, position, fillColor).
  • Удаление объектов: вызов object.remove() или project.clear().

Пример динамического обновления позиции круга с использованием useState:

import React, { useRef, useEffect, useState } from 'react';
import paper from 'paper';

function AnimatedCircle() {
    const canvasRef = useRef(null);
    const [position, setPosition] = useState({ x: 50, y: 50 });

    useEffect(() => {
        paper.setup(canvasRef.current);
        const circle = new paper.Path.Circle({
            center: [position.x, position.y],
            radius: 30,
            fillColor: 'blue'
        });

        paper.view.onFr ame = () => {
            circle.position.x += 1;
            paper.view.draw();
        };

        return () => {
            paper.project.remove();
        };
    }, []);

    return <canvas ref={canvasRef} width={400} height={400} />;
}

Работа с событиями

Paper.js поддерживает свои события (onMouseDown, onMouseDrag, onKeyDown). В React лучше регистрировать их после paper.setup и удалять при размонтировании:

useEffect(() => {
    paper.setup(canvasRef.current);

    const path = new paper.Path();
    path.strokeColor = 'black';

    paper.view.onMouseD own = (event) => {
        path.add(event.point);
    };

    return () => {
        paper.view.onMouseD own = null;
        paper.project.remove();
    };
}, []);

Совет: не использовать одновременно React-события (onClick) и Paper.js события на одном канвасе, чтобы избежать конфликтов.


Анимации и requestAnimationFrame

Paper.js интегрирует анимации через view.onFrame. React перерисовывает виртуальный DOM отдельно, поэтому важно избегать состояния React для каждой анимационной итерации, чтобы не вызывать лишние ререндеры:

paper.view.onFr ame = (event) => {
    circle.rotate(3); // вращение круга
    paper.view.draw();
};
  • Использование useRef для хранения объектов Paper.js предотвращает лишние пересоздания при каждом ререндере компонента.

Структурирование проекта

В больших проектах рекомендуется разделять:

  1. Компонент канваса – управление <canvas> и Paper.js.
  2. Логика объектов – функции создания и анимации фигур.
  3. Состояние приложения – React State хранит данные, необходимые для взаимодействия с Paper.js, но не для постоянного ререндеринга канваса.

Пример структуры:

// PaperObjects.js
export const createCircle = (center, color) => {
    return new paper.Path.Circle({
        center,
        radius: 20,
        fillColor: color
    });
};
// CanvasComponent.jsx
import { createCircle } from './PaperObjects';

useEffect(() => {
    paper.setup(canvasRef.current);
    const circle = createCircle([100, 100], 'green');

    paper.view.onFr ame = () => circle.rotate(2);

    return () => paper.project.remove();
}, []);

Работа с Paper.js Layers

Paper.js поддерживает слои (Layer). Для React важно:

  • Создавать слой один раз при монтировании.
  • Добавлять объекты к определённым слоям.
  • Управлять порядком слоёв через insertAbove, insertBelow.
const layer1 = new paper.Layer();
const layer2 = new paper.Layer();

const circle = new paper.Path.Circle({ center: [50, 50], radius: 20 });
layer1.addChild(circle);

const square = new paper.Path.Rectangle({ point: [70, 70], size: [50, 50] });
layer2.addChild(square);

Интеграция с React Hooks

  • useEffect – для инициализации Paper.js и очистки.
  • useRef – для хранения канваса и объектов.
  • useState – только для данных, которые влияют на React-компоненты вне канваса.
  • useMemo – для вычислений объектов, которые не должны пересоздаваться при каждом ререндере.

Практические рекомендации

  • Каждый Paper.js проект должен быть самодостаточным: очистка и удаление объектов обязательны.
  • Анимации выполняются внутри Paper.js (view.onFrame), а не через React setInterval или requestAnimationFrame.
  • Все объекты Paper.js лучше хранить в useRef, чтобы избежать лишних пересозданий при ререндере.
  • Для сложных сцен использовать модули и функции, чтобы структура оставалась читаемой и расширяемой.

Эта методология обеспечивает стабильную работу Paper.js внутри React, позволяет создавать интерактивные анимации, векторные фигуры и графические интерфейсы без конфликта с виртуальным DOM.