В основе векторной графики лежит представление кривых с помощью
сегментов и управляющих точек. В библиотеке Paper.js каждая кривая
строится из объектов Segment, которые содержат не только
координаты самой точки, но и два вектора — handleIn и
handleOut. Именно они определяют форму кривой между
сегментами.
Каждый сегмент (Segment) включает три ключевых
компонента:
var segment = new Segment({
point: [100, 100],
handleIn: [-50, 0],
handleOut: [50, 0]
});
Векторы handleIn и handleOut задаются
относительно точки point, а не в абсолютных
координатах.
Handles управляют направлением и «силой» кривизны:
Если оба вектора равны [0, 0], сегмент соединяется с
соседними прямой линией.
Paper.js использует кубические кривые Безье. Каждый отрезок между двумя сегментами определяется четырьмя точками:
point)handleOuthandleInpoint)Это позволяет точно контролировать форму кривой.
Пример создания простой кривой:
var path = new Path();
path.strokeColor = 'black';
path.add(new Segment({
point: [100, 100],
handleOut: [100, 0]
}));
path.add(new Segment({
point: [300, 100],
handleIn: [-100, 0]
}));
Здесь формируется плавная дуга между двумя точками.
Когда handleIn и handleOut равны по длине и
противоположны по направлению:
segment.handleIn = [-50, 0];
segment.handleOut = [50, 0];
Такая конфигурация обеспечивает плавный переход (smooth point).
Handles можно задавать независимо:
segment.handleIn = [-30, -20];
segment.handleOut = [80, 40];
Это позволяет создавать острые изгибы и асимметричные кривые.
Paper.js предоставляет метод smooth() для автоматической
настройки handles:
path.smooth();
Алгоритм анализирует соседние сегменты и выставляет управляющие точки так, чтобы кривая выглядела максимально плавной.
Варианты:
path.smooth({
type: 'continuous'
});
Типы сглаживания:
continuous — плавный переход с сохранением формыasymmetric — допускает асимметриюgeometric — учитывает геометрию сегментовvar segment = path.segments[0];
segment.handleOut = new Point(50, 0);
segment.handleIn = new Point(-50, 0);
segment.handleIn = new Point(0, 0);
segment.handleOut = new Point(0, 0);
Превращает участок в прямую линию.
Для отладки полезно отображать управляющие точки:
path.fullySelected = true;
Это включает отображение:
Handles можно изменять в реальном времени, например, при анимации:
function onFrame(event) {
var segment = path.segments[0];
segment.handleOut.x = Math.sin(event.time * 2) * 100;
}
Это создаёт эффект «живой» кривой.
Handles автоматически трансформируются при изменении пути:
path.scale(2);
path.rotate(45);
Все управляющие точки изменяются вместе с сегментами, сохраняя форму кривой.
Даже если путь изначально состоит из прямых, можно добавить handles:
var segment = path.segments[1];
segment.handleIn = new Point(-50, 0);
segment.handleOut = new Point(50, 0);
Это мгновенно превращает прямой участок в кривую.
Пример сложной кривой:
var path = new Path({
strokeColor: 'black'
});
path.add(new Segment([100, 100], null, [50, 0]));
path.add(new Segment([200, 150], [-50, 0], [50, 50]));
path.add(new Segment([300, 100], [-50, -50], null));
Каждый сегмент влияет на форму всей кривой.
По умолчанию сглаженные точки могут вести себя как связанные. Для полного контроля:
segment.smooth({ type: 'asymmetric' });
Или вручную задавать значения, игнорируя симметрию.
1. Неправильное понимание координат handles Handles задаются относительно точки, а не в глобальных координатах.
2. Слишком длинные handles Могут приводить к неожиданным изгибам и самопересечениям.
3. Отсутствие балансировки между сегментами Резкие переходы возникают, если handles соседних сегментов не согласованы.
for (var i = 0; i < path.segments.length; i++) {
var segment = path.segments[i];
segment.handleIn = new Point(-30, 0);
segment.handleOut = new Point(30, 0);
}
segment.handleIn = new Point(0, 0);
segment.handleOut = new Point(0, 0);
Handles — это обычные векторы, значит можно использовать:
segment.handleOut.length = 100;
segment.handleOut.angle = 45;
Это даёт удобный способ контролировать форму через длину и угол.
При создании редакторов или интерактивных инструментов:
Пример:
function onMouseDrag(event) {
var segment = path.segments[0];
segment.handleOut = event.point.subtract(segment.point);
}