Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Interactive Bezier curves with Illustrator style handles
var Curve = function(points, canvas){
this.points = points.map( function(p){ return new Point(p); })
this.points[0].setFirst();
this.points[this.points.length-1].setLast();
// create the underlying beziers
this.beziers = [];
for(var n=0; n<this.points.length-1; n++){
this.beziers.push(
new CubicBezier(CANVAS, {
start : this.points[n],
c1 : this.points[n].handles.fore,
c2 : this.points[n+1].handles.aft,
end : this.points[n+1]
})
)
}
this.points.forEach( function(p){ p.addToCanvas(); })
this.render();
}
Curve.prototype = {
render : function(){
// canvas.a
this.beziers.forEach( function(b){ b.renderCurve(CANVAS); })
this.points.forEach( function(p){ p.render(); });
}
}
var Point = function(opts){
this.x = opts.x;
this.y = opts.y;
// console.log(this.x, this.y);
var OFFSET = 50;
this.handles = {
fore : new Handle(this.x+OFFSET, this.y+OFFSET, this, 'fore'),
aft : new Handle(this.x-OFFSET, this.y-OFFSET, this, 'aft')
}
this.render_el = new fabric.Circle({
radius: 5,
color: (opts.color || '#f00'),
top : this.y,
left: this.x,
hasControls : false,
hasBorder: false,
bound_to : this
})
// this.handle_line = new fabric.Polyline()
CANVAS.add(this.render_el);
}
Point.prototype = {
render : function(opts){
this.render_el.setLeft( this.x );
this.render_el.setTop( this.y );
this.renderHandles();
},
renderHandles : function(){
for( point in this.handles ){
this.handles[point].render({ color: '#f77' });
}
// if(this.handles.fore && this.handles.aft){
// var ctx = CANVAS.getContext();
// ctx.moveTo( this.handles.aft.x,this.handles.aft.y );
// ctx.lineTo( this.handles.fore.x,this.handles.fore.y );
// ctx.strokeStyle = "#05f";
// ctx.stroke();
// }
},
set : function(x, y){
var delta = new Vec2(x-this.x, y-this.y)
this.x = x; this.y = y;
this.centerDidUpdate(delta);
this.updateHandleLines();
},
setFirst : function(){
this.handles = { fore : this.handles.fore };
},
setLast : function(){
this.handles = { aft : this.handles.aft }
},
initHandleLine : function(){
if(this.handles.fore){
this.foreHandleLine = new fabric.Line([this.x, this.y, this.handles.fore.x, this.handles.fore.y] );
this.foreHandleLine.stroke = 'black';
this.foreHandleLine.selectable = false;
}
if(this.handles.aft){
this.aftHandleLine = new fabric.Line([ this.handles.aft.x, this.handles.aft.y,this.x, this.y] );
this.aftHandleLine.stroke = 'black';
this.aftHandleLine.selectable = false;
}
},
toString : Vec2.prototype.toString,
addToCanvas : function(){
this.initHandleLine()
if(this.handles.fore){
CANVAS.add( this.foreHandleLine,this.handles.fore.render_el );
CANVAS.sendToBack( this.foreHandleLine );
}
if(this.handles.aft){
CANVAS.add( this.aftHandleLine, this.handles.aft.render_el);
CANVAS.sendToBack( this.aftHandleLine );
}
CANVAS.add( this.render_el );
},
handleDidUpdate : function(type, delta){
// simplest: locked handles
// if a handle changes, change the other handle by -delta
if(type==='fore' && this.handles.aft){
this.handles.aft.set( this.handles.aft.x + delta.x, this.handles.aft.y+delta.y, true);
}
if(type==='aft' && this.handles.fore){
this.handles.fore.set( this.handles.fore.x + delta.x, this.handles.fore.y+delta.y, true);
}
this.updateHandleLines();
},
centerDidUpdate : function(delta){
if(this.handles.aft){
this.handles.aft.x += delta.x;
this.handles.aft.y += delta.y;
}
if(this.handles.fore){
this.handles.fore.x += delta.x;
this.handles.fore.y += delta.y;
}
},
updateHandleLines : function(){
if(this.handles.fore){
this.foreHandleLine.set('x2', this.handles.fore.x);
this.foreHandleLine.set('y2', this.handles.fore.y);
this.foreHandleLine.set('x1', this.x);
this.foreHandleLine.set('y1', this.y);
}
if(this.handles.aft){
this.aftHandleLine.set('x1', this.handles.aft.x);
this.aftHandleLine.set('y1', this.handles.aft.y);
this.aftHandleLine.set('x2', this.x);
this.aftHandleLine.set('y2', this.y);
}
}
}
var Handle = function(x, y, parent, type){
this.x = x;
this.y = y;
this.type = type;
this.parent = parent;
var OFFSET = 10;
this.render_el = new fabric.Circle({
radius: 5,
color: '#00f',
top : this.y,
left: this.x,
hasControls : false,
hasBorder: false,
bound_to : this
})
// CANVAS.add(this.render_el);
}
Handle.prototype = {
render : function(opts){
this.render_el.setLeft( this.x );
this.render_el.setTop( this.y );
this.render_el.setCoords();
},
toString : Vec2.prototype.toString,
set : function(x, y, dontAlertParent){
var delta = new Vec2(this.x-x, this.y-y)
this.x = x;
this.y = y;
if(!dontAlertParent) this.parent.handleDidUpdate(this.type, delta);
}
}
// Cubic Bezier and Vector class are from an older demo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment