TypeScript и Paper.js

Использование TypeScript при работе с библиотекой Paper.js позволяет получить строгую типизацию, улучшенную читаемость кода и более безопасную разработку графических приложений. Paper.js изначально ориентирован на JavaScript, однако благодаря наличию деклараций типов (type definitions) он хорошо интегрируется с TypeScript-проектами.


Установка и настройка окружения

Для начала необходимо установить саму библиотеку и типы:

npm install paper
npm install --save-dev @types/paper

Типы могут быть встроены в пакет (в зависимости от версии), но в большинстве случаев используется отдельный пакет @types/paper.

В tsconfig.json важно учитывать следующие параметры:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "ESNext",
    "strict": true,
    "esModuleInterop": true,
    "types": ["paper"]
  }
}

Подключение Paper.js в TypeScript

Существует два основных способа подключения:

1. Через импорт модуля

import paper from 'paper';

paper.setup(document.getElementById('canvas') as HTMLCanvasElement);

2. Использование PaperScope

Paper.js использует концепцию PaperScope — изолированного контекста, содержащего все объекты библиотеки.

import { PaperScope } from 'paper';

const scope = new PaperScope();
scope.setup(document.getElementById('canvas') as HTMLCanvasElement);

const circle = new scope.Path.Circle({
    center: [100, 100],
    radius: 50,
    fillColor: 'blue'
});

Типизация позволяет точно определить, какие методы и свойства доступны в каждом объекте.


Типы и интерфейсы Paper.js

Paper.js предоставляет множество классов, которые в TypeScript представлены как типы:

  • Path
  • Point
  • Size
  • Rectangle
  • Color
  • Project
  • Layer

Пример использования типов

const point: paper.Point = new paper.Point(50, 50);
const size: paper.Size = new paper.Size(100, 100);
const rect: paper.Rectangle = new paper.Rectangle(point, size);

Работа с графическими объектами

Создание и типизация пути

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

path.add(new paper.Point(0, 0));
path.add(new paper.Point(100, 100));

TypeScript обеспечивает проверку типов при добавлении сегментов и свойств.


Расширение объектов (Augmentation)

Иногда требуется добавить собственные свойства к объектам Paper.js. Для этого используется расширение интерфейсов.

declare module 'paper' {
    interface Path {
        customId?: string;
    }
}

Теперь можно безопасно использовать:

const path = new paper.Path();
path.customId = 'line-1';

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

Paper.js активно использует событийную модель.

const tool = new paper.Tool();

tool.onMouseD own = (event: paper.ToolEvent) => {
    console.log(event.point);
};

Тип ToolEvent включает:

  • point
  • event
  • modifiers

TypeScript гарантирует корректный доступ к этим свойствам.


Типизация анимации

Paper.js поддерживает анимацию через onFrame:

paper.view.onFr ame = (event: paper.IViewOnFrameEvent) => {
    console.log(event.delta);
};

Тип IViewOnFrameEvent содержит:

  • delta — время между кадрами
  • time — общее время
  • count — номер кадра

Работа с коллекциями

Многие методы возвращают массивы или коллекции объектов:

const paths: paper.PathItem[] = paper.project.activeLayer.children as paper.PathItem[];

При строгой типизации может потребоваться явное приведение типов.


Generics и кастомные утилиты

TypeScript позволяет создавать обобщённые функции для работы с объектами Paper.js:

function cloneItem<T extends paper.Item>(item: T): T {
    return item.clone() as T;
}

Null и undefined в Paper.js

Некоторые методы могут возвращать null:

const hitResult = paper.project.hitTest(new paper.Point(50, 50));

if (hitResult) {
    console.log(hitResult.item);
}

Строгий режим TypeScript (strictNullChecks) требует обязательной проверки.


Использование enum и union типов

Paper.js использует строковые значения для некоторых свойств:

type BlendMode = 
    | 'normal'
    | 'multiply'
    | 'screen';

const path = new paper.Path.Circle({
    center: [50, 50],
    radius: 20,
    blendMode: 'multiply' as BlendMode
});

Работа с координатами и математикой

TypeScript помогает избежать ошибок при работе с координатами:

function movePoint(point: paper.Point, dx: number, dy: number): paper.Point {
    return point.add(new paper.Point(dx, dy));
}

Организация проекта

Рекомендуется разделять код:

/src
  /graphics
    shapes.ts
    animation.ts
  /utils
    math.ts
  main.ts

Интеграция с DOM

Canvas элемент должен быть явно типизирован:

const canvas = document.getElementById('canvas') as HTMLCanvasElement;
paper.setup(canvas);

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

const layer: paper.Layer = new paper.Layer();
layer.activate();

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

Типизация пользовательских инструментов

class CustomTool {
    tool: paper.Tool;

    constructor() {
        this.tool = new paper.Tool();
        this.tool.onMouseM ove = this.onMouseMove.bind(this);
    }

    onMouseMove(event: paper.ToolEvent): void {
        console.log(event.point);
    }
}

Использование строгого режима

Включение strict режима выявляет:

  • неправильные типы
  • отсутствие проверок
  • потенциальные ошибки времени выполнения

Оптимизация и производительность

TypeScript не влияет напрямую на производительность, но позволяет:

  • избегать лишних преобразований
  • минимизировать ошибки
  • улучшать архитектуру

Работа с внешними библиотеками

При интеграции с другими библиотеками (например, React):

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

function Canvas() {
    const canvasRef = useRef<HTMLCanvasElement>(null);

    useEffect(() => {
        if (!canvasRef.current) return;

        paper.setup(canvasRef.current);

        new paper.Path.Circle({
            center: [50, 50],
            radius: 25,
            fillColor: 'green'
        });
    }, []);

    return <canvas ref={canvasRef} />;
}

Частые проблемы типизации

1. Потеря контекста this

tool.onMouseD own = function(event) {
    // this может быть не тем, что ожидается
};

Решение — использовать стрелочные функции:

tool.onMouseD own = (event) => {};

2. Неявные типы

const point = [10, 20]; // ошибка

Правильно:

const point: paper.Point = new paper.Point(10, 20);

3. Приведение типов

const item = paper.project.activeLayer.children[0] as paper.Path;

Расширение возможностей через декларации

Можно описывать собственные структуры:

interface Drawable {
    draw(): void;
}

И комбинировать с Paper.js:

class CircleShape implements Drawable {
    private path: paper.Path.Circle;

    constructor(center: paper.Point, radius: number) {
        this.path = new paper.Path.Circle(center, radius);
    }

    draw(): void {
        this.path.fillColor = new paper.Color('blue');
    }
}

Архитектурные подходы

При использовании TypeScript удобно применять:

  • MVC (разделение логики и отображения)
  • ECS (Entity Component System)
  • модульную архитектуру

Отладка

TypeScript облегчает отладку:

  • автодополнение
  • предупреждения компилятора
  • безопасный рефакторинг

Компиляция и сборка

Используются инструменты:

  • tsc
  • webpack
  • vite

Пример команды:

tsc --watch

Итоговая структура типов в Paper.js

Основные иерархии:

Item
 ├── Path
 ├── Shape
 ├── Group
 └── Raster

Каждый из этих типов имеет собственные свойства и методы, которые строго контролируются TypeScript.


Практический пример

import paper from 'paper';

const canvas = document.getElementById('canvas') as HTMLCanvasElement;
paper.setup(canvas);

const center = new paper.Point(200, 200);

const circle = new paper.Path.Circle({
    center,
    radius: 80,
    strokeColor: new paper.Color('black')
});

paper.view.onFr ame = (event) => {
    circle.rotate(1);
};

Типизация гарантирует:

  • корректность параметров
  • правильное использование методов
  • безопасность при масштабировании проекта