<canvas id="drawing_canvas"></canvas>
body {
background-color: #222;
margin: 0;
}
#drawing_canvas {
position: absolute;
margin: auto;
width: 512px;
height: 512px;
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 1px solid #555
}
// canvas settings
var viewWidth = 512,
viewHeight = 512,
centerX = viewWidth * 0.5,
centerY = viewHeight * 0.5,
drawingCanvas = document.getElementById("drawing_canvas"),
ctx,
timeStep = (1/30),
time = 0;
var ribbon,
frontGradient,
backGradient;
function initDrawingCanvas() {
drawingCanvas.width = viewWidth;
drawingCanvas.height = viewHeight;
ctx = drawingCanvas.getContext('2d');
ctx.translate(0.5, 0.5);
frontGradient = ctx.createLinearGradient(0, 0, viewWidth, 0);
frontGradient.addColorStop(0.0, '#999');
frontGradient.addColorStop(0.5, '#fff');
frontGradient.addColorStop(1.0, '#999');
backGradient = ctx.createLinearGradient(0, 0, viewWidth, 0);
backGradient.addColorStop(0.0, '#555');
backGradient.addColorStop(0.5, '#777');
backGradient.addColorStop(1.0, '#555');
}
function initCurves() {
var curves = [],
rotations = 12,
dy = (viewHeight + 84) / rotations;
for (var i = 0; i <= rotations; i++) {
var c,
startX = centerX,
startY = viewHeight - dy * i + 84,
controlX = (i % 2) * viewWidth,
controlY = startY;
c = new Curve(
new Point(startX, startY),
new Point(controlX, controlY),
new Point(controlX, controlY - dy),
new Point(startX, startY - dy)
);
curves.push(c);
}
ribbon = new BezierRibbon(curves, 42);
}
function update() {
// ribbon.updateTime(timeStep);
ribbon.currentTime = Math.min(time, ribbon.totalTime);
ribbon.startOffset = Math.max(time - 1.5, 0);
time %= (ribbon.totalTime + 1.5);
// console.log(time % (ribbon.totalTime));
// ribbon.currentTime = Math.min(time, ribbon.totalTime);
// ribbon.startOffset = Math.max(ribbon.currentTime - 3, 0);
}
function draw() {
// ctx.fillStyle = '#000';
ctx.clearRect(0, 0, viewWidth, viewHeight);
ribbon.draw();
}
window.onload = function() {
initDrawingCanvas();
initCurves();
requestAnimationFrame(loop);
};
function loop() {
update();
draw();
time += timeStep;
requestAnimationFrame(loop);
}
////////////////////////////
// class and utils
////////////////////////////
BezierRibbon = function(curves, width) {
this.curves = curves;
this.width = width;
this.halfWidth = this.width * 0.5;
this.totalTime = curves.length; // 1.0 per curve
this.currentTime = 0;
this.startOffset = 0;
// point cache
this.tp0 = new Point();
this.tp1 = new Point();
this.tp2 = new Point();
this.tp3 = new Point();
};
BezierRibbon.prototype = {
draw:function() {
// debug draw
// this.curves.forEach(function(c) {
// c.draw();
// });
var step = 1e-2,
index = 0,
dx,
curve;
for (var t = this.startOffset; t < this.currentTime; t += step) {
index = t | 0;
curve = this.curves[index];
curve.interpolate(t - index, this.tp0);
curve.interpolate(t + step - index, this.tp1);
this.tp0.translate(0, -this.halfWidth);
this.tp1.translate(0, -this.halfWidth);
this.tp2.copy(this.tp1).translate(0, this.width);
this.tp3.copy(this.tp0).translate(0, this.width);
dx = this.tp1.x - this.tp0.x;
ctx.strokeStyle = ctx.fillStyle = dx >= 0 ? frontGradient : backGradient;
ctx.globalCompositeOperation = dx >= 0 ? 'source-over' : 'destination-over';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(this.tp0.x, this.tp0.y);
ctx.lineTo(this.tp1.x, this.tp1.y);
ctx.lineTo(this.tp2.x, this.tp2.y);
ctx.lineTo(this.tp3.x, this.tp3.y);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
}
};
Curve = function(p0, p1, p2, p3) {
this.p0 = p0;
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
};
Curve.prototype = {
interpolate:function(t, p) {
p = p || new Point();
var nt = (1 - t);
p.x = nt * nt * nt * this.p0.x + 3 * nt * nt * t * this.p1.x + 3 * nt * t * t * this.p2.x + t * t * t * this.p3.x;
p.y = nt * nt * nt * this.p0.y + 3 * nt * nt * t * this.p1.y + 3 * nt * t * t * this.p2.y + t * t * t * this.p3.y;
return p;
},
draw:function() {
ctx.strokeStyle = "#f00";
ctx.beginPath();
ctx.moveTo(this.p0.x, this.p0.y);
ctx.bezierCurveTo(this.p1.x, this.p1.y, this.p2.x, this.p2.y, this.p3.x, this.p3.y);
ctx.stroke();
},
clone:function(offsetX, offsetY) {
offsetX = offsetX || 0;
offsetY = offsetY || 0;
return new Curve(
this.p0.clone(offsetX, offsetY),
this.p1.clone(offsetX, offsetY),
this.p2.clone(offsetX, offsetY),
this.p3.clone(offsetX, offsetY)
);
}
};
Point = function(x, y) {
this.x = x || 0;
this.y = y || 0;
};
Point.prototype = {
clone:function(offsetX, offsetY) {
offsetX = offsetX || 0;
offsetY = offsetY || 0;
return new Point(this.x + offsetX, this.y + offsetY);
},
copy:function(p) {
this.x = p.x;
this.y = p.y;
return this;
},
translate:function(dx, dy) {
dx = dx || 0;
dy = dy || 0;
this.x += dx;
this.y += dy;
return this;
}
};
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.