Paper.js предоставляет гибкую систему расширения за счёт прототипного
наследования и динамического добавления функциональности. Плагины в
контексте Paper.js — это наборы методов, свойств или модификаций,
расширяющих стандартные классы библиотеки: Path,
Group, Item, View,
Project и другие.
Расширение может происходить на нескольких уровнях:
Ключевая особенность — доступ к внутренней структуре объектов сцены и возможность вмешательства в процесс отрисовки.
Paper.js построен на прототипной системе JavaScript, что позволяет напрямую добавлять методы в классы.
paper.Path.prototype.toCenter = function () {
this.position = paper.view.center;
return this;
};
Теперь любой объект Path получает новый метод:
var circle = new paper.Path.Circle({
center: [50, 50],
radius: 20
});
circle.toCenter();
Все графические элементы наследуются от Item, поэтому
добавление методов сюда делает их доступными для всех типов
объектов:
paper.Item.prototype.fadeOut = function (duration = 60) {
let frame = 0;
let item = this;
function animate() {
if (frame < duration) {
item.opacity -= 1 / duration;
frame++;
requestAnimationFrame(animate);
}
}
animate();
};
Чтобы избежать конфликтов, плагины оформляются через собственное пространство имён.
var MyPlugin = {};
Или с привязкой к Paper.js:
paper.MyPlugin = {};
Пример:
paper.MyPlugin.randomColor = function () {
return new paper.Color(Math.random(), Math.random(), Math.random());
};
Хороший плагин не просто добавляет функции, а структурирует поведение.
paper.MyPlugin.ShapeFactory = {
createStar: function (center, points, radius1, radius2) {
return new paper.Path.Star(center, points, radius1, radius2);
},
createCircle: function (center, radius) {
return new paper.Path.Circle(center, radius);
}
};
Современный подход предполагает использование модульной структуры.
export function addPathExtensions(paper) {
paper.Path.prototype.doubleScale = function () {
this.scale(2);
return this;
};
}
Импорт:
import { addPathExtensions } from './plugin.js';
addPathExtensions(paper);
Плагины могут вмешиваться в систему событий Paper.js.
paper.Item.prototype.onClickHighli ght = function () {
this.onCl ick = function () {
this.fillColor = 'red';
};
};
View отвечает за отрисовку и взаимодействие с
canvas.
paper.View.prototype.enableGrid = function (size = 20) {
var grid = new paper.Group();
for (let x = 0; x < this.size.width; x += size) {
let line = new paper.Path.Line({
from: [x, 0],
to: [x, this.size.height],
strokeColor: '#eee'
});
grid.addChild(line);
}
for (let y = 0; y < this.size.height; y += size) {
let line = new paper.Path.Line({
from: [0, y],
to: [this.size.width, y],
strokeColor: '#eee'
});
grid.addChild(line);
}
return grid;
};
Плагины часто расширяют математические возможности.
paper.Point.prototype.distanceTo = function (point) {
return this.getDistance(point);
};
Или более сложный пример:
paper.Path.prototype.getBoundingCircle = function () {
var bounds = this.bounds;
var center = bounds.center;
var radius = Math.max(bounds.width, bounds.height) / 2;
return new paper.Path.Circle(center, radius);
};
Удобство использования плагинов повышается за счёт возврата
this.
paper.Path.prototype.setRed = function () {
this.fillColor = 'red';
return this;
};
paper.Path.prototype.moveRight = function (value) {
this.position.x += value;
return this;
};
Использование:
circle.setRed().moveRight(50).scale(1.5);
Project управляет сценой.
paper.Project.prototype.clearAll = function () {
this.activeLayer.removeChildren();
};
Иногда требуется изменить стандартное поведение.
const originalCircle = paper.Path.Circle;
paper.Path.Circle = function (...args) {
console.log('Создан круг:', args);
return new originalCircle(...args);
};
Важно сохранять оригинальные методы, чтобы не ломать библиотеку.
Плагин может выступать адаптером между Paper.js и сторонними инструментами.
paper.Item.prototype.animatePosition = function (to, duration = 1000) {
let start = this.position.clone();
let startTime = Date.now();
let item = this;
function animate() {
let elapsed = Date.now() - startTime;
let progress = Math.min(elapsed / duration, 1);
item.position = start.add(to.subtract(start).multiply(progress));
if (progress < 1) {
requestAnimationFrame(animate);
}
}
animate();
};
Хорошо организованный плагин включает:
var MyPlugin = (function () {
function init(paper) {
extendPath(paper);
extendItem(paper);
}
function extendPath(paper) {
paper.Path.prototype.blink = function () {
let visible = true;
let item = this;
setInterval(() => {
item.visible = visible = !visible;
}, 300);
};
}
function extendItem(paper) {
paper.Item.prototype.centerIt = function () {
this.position = paper.view.center;
};
}
return {
init: init
};
})();
Использование:
MyPlugin.init(paper);
При разработке плагинов важно учитывать:
if (!paper.Path.prototype.rotateAround) {
paper.Path.prototype.rotateAround = function (point, angle) {
this.rotate(angle, point);
};
}
Каждое расширение должно сопровождаться описанием:
/**
* Перемещает объект в центр холста
* @returns {Item}
*/
paper.Item.prototype.toCenter = function () {
this.position = paper.view.center;
return this;
};
Проверка плагинов включает:
Плагины могут распространяться:
Пример структуры npm-пакета:
paperjs-plugin/
├── index.js
├── package.json
├── README.md
При разработке плагинов необходимо учитывать:
Плохая практика:
for (let i = 0; i < 1000; i++) {
path.bounds; // дорого
}
Хорошая практика:
let bounds = path.bounds;
for (let i = 0; i < 1000; i++) {
bounds;
}
Вместо изменения прототипов можно использовать композицию:
function FancyPath(path) {
this.path = path;
}
FancyPath.prototype.highlight = function () {
this.path.strokeColor = 'yellow';
};
Плагины могут формировать pipeline обработки объектов:
paper.Path.prototype.process = function (...steps) {
steps.forEach(step => step(this));
return this;
};
Использование:
circle.process(
p => p.scale(2),
p => p.rotate(45),
p => p.fillColor = 'blue'
);
paper.Layer.prototype.lockAll = function () {
this.children.forEach(child => child.locked = true);
};
Плагин может хранить состояние:
paper.MyPlugin.state = {
selectedItems: []
};
paper.Path.prototype.getCachedLength = function () {
if (!this._cachedLength) {
this._cachedLength = this.length;
}
return this._cachedLength;
};
function withShadow(item) {
item.shadowColor = new paper.Color(0, 0, 0, 0.5);
item.shadowBlur = 10;
return item;
}
paper.MyPlugin.presets = {
button: {
fillColor: '#3498db',
radius: 8
}
};
Применение:
Object.assign(path, paper.MyPlugin.presets.button);