console
var canvas;
var context;
var screenWidth;
var screenHeight;
var PI2 = Math.PI * 2;
var bgColor = '#262422';
var rope;
var gravity = 0.2;
var ropes = [];
var movementRamp = 0.984;
var windFactor = 0.02;
var windDirection = 1;
var windValue = 0;
var step = 0;
var cutting = false;
var blur = 0.6;
var gui;
var colors = ['#565853', '#5E9190', '#DCD9CD', '#BD4A61'];
window.onload = function()
{
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
window.onresize = function()
{
screenWidth = window.innerWidth;
screenHeight = window.innerHeight;
canvas.width = screenWidth;
canvas.height = screenHeight;
context.fillStyle = bgColor;
context.fillRect(0, 0, screenWidth, screenHeight);
};
window.onresize();
init();
guiSetup();
loop();
};
function init()
{
generateRopes();
canvas.addEventListener('mousemove', function(e)
{
if(cutting) checkRopesIntersection(e.clientX, e.clientY);
});
canvas.addEventListener('mousedown', function(e)
{
cutting = true;
});
canvas.addEventListener('mouseup', function(e)
{
cutting = false;
});
}
function guiSetup()
{
var controls =
{
blur:blur,
gravity:gravity,
windFactor:windFactor,
movementRamp:movementRamp,
reset:reset
};
gui = new dat.GUI();
gui.add(controls, 'blur', 0.0, 1.0).onChange(function(value){blur = value;});
gui.add(controls, 'gravity', -1.0, 1.0).onChange(function(value){gravity = value;});
gui.add(controls, 'windFactor', 0.0, 1.0).onChange(function(value){windFactor = value;});
gui.add(controls, 'movementRamp', 0.8, 1.0).onChange(function(value){movementRamp = value;});
gui.add(controls, 'reset');
}
function reset()
{
ropes = [];
generateRopes();
}
function generateRopes()
{
var r1 = new VRope(new VPoint(100, 0), new VPoint(screenWidth - 100, 0), 26, getRandomColor());
var r2 = new VRope(r1.points[4], r1.points[20], 18, getRandomColor());
var r3 = new VRope(r2.points[3], r2.points[20], 8, getRandomColor());
var r4 = new VRope(r1.points[1], r1.points[2], 15, getRandomColor());
var r5 = new VRope(r3.points[1], r3.points[2], 6, getRandomColor());
var r6 = new VRope(r3.points[r3.points.length - 6], r2.points[8], 15, getRandomColor());
var r7 = new VRope(r6.points[r6.points.length - 3], r1.points[28], 30, getRandomColor());
var r8 = new VRope(r7.points[32], r1.points[r1.points.length - 4], 22, getRandomColor());
var r9 = new VRope(r7.points[10], r1.points[r1.points.length - 10], 28, getRandomColor());
r3.segments[0].constrainable = false;
r4.segments[r4.segments.length - 1].constrainable = false;
ropes.push(r1);
ropes.push(r2);
ropes.push(r3);
ropes.push(r4);
ropes.push(r5);
ropes.push(r6);
ropes.push(r7);
ropes.push(r8);
ropes.push(r9);
}
function getRandomColor()
{
return colors[(Math.random() * colors.length) >> 0];
}
function checkRopeIntersection(rope, x, y)
{
var i = rope.points.length - 1;
for(i; i > -1; --i)
{
var point = rope.points[i];
var vx = x - point.x;
var vy = y - point.y;
var length = Math.sqrt(vx * vx + vy * vy);
if(length < 10)
{
var segment = getSegmentFromPoint(point, rope);
segment.constrainable = false;
}
}
};
function checkRopesIntersection(x, y)
{
var i = ropes.length - 1;
for(i; i > -1; --i)
{
var rope = ropes[i];
checkRopeIntersection(rope, x, y);
}
}
function getSegmentFromPoint(point, rope)
{
var i = rope.segments.length - 1;
for(i; i > -1; --i)
{
var segment = rope.segments[i];
if(segment.a == point || segment.b == point)
{
return segment;
break;
}
}
}
window.getAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback)
{
window.setTimeout(callback, 16.6);
};
function loop()
{
context.globalAlpha = 1 - blur;
context.fillStyle = bgColor;
context.fillRect(0, 0, screenWidth, screenHeight);
context.globalAlpha = 1;
updateWind();
updateRopes();
drawRopes();
ropes[0].x += 1;
step += 0.06;
getAnimationFrame(loop);
}
function updateWind()
{
windValue = Math.sin(step * Math.cos(step * 0.02) * Math.sin(step * 0.1) * 0.1) * windFactor * windDirection;
}
function updateRopes()
{
var i = ropes.length - 1;
for(i; i > -1; --i)
{
var rope = ropes[i];
rope.update();
}
}
function drawRopes()
{
var i = ropes.length - 1;
for(i; i > -1; --i)
{
var rope = ropes[i];
drawRope(rope, '#FFF', 4);
}
}
function Vector2(x, y)
{
this.x = x || 0;
this.y = y || 0;
}
Vector2.prototype =
{
constructor:Vector2,
angle :function()
{
return Math.atan2(this.y, this.x);
},
setAngle:function(value)
{
var length = this.length();
this.x = Math.cos(value) * length;
this.y = Math.sin(value) * length;
},
length:function()
{
return Math.sqrt(this.x * this.x + this.y * this.y);
},
setLength:function(value)
{
var angle = this.angle();
this.x = Math.cos(angle) * value;
this.y = Math.sin(angle) * value;
},
dx:function()
{
return this.x / this.length();
},
dy:function()
{
return this.y / this.length();
},
ln:function()
{
return new Vector2(this.y, -this.x);
},
rn:function()
{
return new Vector2(-this.y, this.x);
},
};
function VPoint(x, y)
{
this.x = x || 0;
this.y = y || 0;
this.prevX = this.x;
this.prevY = this.y;
}
VPoint.prototype =
{
constructor:VPoint,
setPos:function(x, y)
{
this.prevX = this.x = x;
this.prevY = this.y = y;
},
update:function()
{
var tx = this.x;
var ty = this.y;
this.x += (this.x - this.prevX) * movementRamp;
this.y += (this.y - this.prevY) * movementRamp;
this.prevX = tx;
this.prevY = ty;
}
};
function VSegment(pointA, pointB)
{
this.a = pointA;
this.b = pointB;
this.constrainable = true;
this.length = Math.sqrt((this.b.x - this.a.x) * (this.b.x - this.a.x) + (this.b.y - this.a.y) * (this.b.y - this.a.y));
}
VSegment.prototype =
{
constructor:VSegment,
constrain:function()
{
var vx = this.b.x - this.a.x;
var vy = this.b.y - this.a.y;
var t = Math.sqrt(vx * vx + vy * vy);
var diff = this.length - t;
var offsetX = ((vx / t) * diff) * 0.5;
var offsetY = ((vy / t) * diff) * 0.5;
this.a.x -= offsetX;
this.a.y -= offsetY;
this.b.x += offsetX;
this.b.y += offsetY;
},
update:function()
{
this.a.x += windValue;
this.b.x += windValue;
this.a.y += gravity;
this.b.y += gravity;
this.a.update();
this.b.update();
if(this.constrainable) this.constrain();
}
};
function VRope(pa, pb, segs, color)
{
this.a = pa;
this.b = pb;
this.segments = [];
this.points = [];
this.color = color || '#F00';
this.lineWidth = (Math.random() * 8 + 4) >> 0;
var vx = this.b.x - this.a.x;
var vy = this.b.y - this.a.y;
var t = Math.sqrt(vx * vx + vy * vy);
var segmentWidth = t / segs;
var i = 0;
var l = segs;
for(i; i < l; ++i)
{
var pointA = (this.points.length > 0) ? this.points[this.points.length - 1] : new VPoint(segmentWidth * i + pa.x, pa.y);
var pointB = new VPoint(segmentWidth * (i + 1) + pa.x, pa.y);
this.points.push(pointA);
this.points.push(pointB);
var segment = new VSegment(pointA, pointB);
this.segments.push(segment);
}
}
VRope.prototype =
{
constructor:VRope,
update:function()
{
var i = this.segments.length -1;
for(i; i > -1; --i)
{
var segment = this.segments[i];
segment.update();
}
this.points[0].setPos(this.a.x, this.a.y);
this.points[this.points.length - 1].setPos(this.b.x, this.b.y);
}
}
function drawRope(rope, color, lineWidth)
{
var i = rope.segments.length - 1;
var c = color || '#FFF';
for(i; i > -1; --i)
{
var segment = rope.segments[i];
context.strokeStyle = rope.color;
context.lineWidth = rope.lineWidth;
context.beginPath();
context.moveTo(segment.a.x, segment.a.y);
context.lineTo(segment.b.x, segment.b.y);
if(segment.constrainable) context.stroke();
}
}
function Point(x, y)
{
this.x = x || 0;
this.y = y || 0;
}
Point.prototype =
{
constructor:Point
};
function norm(value, min, max)
{
return (value - min) / (max - min);
};
function lerp(norm, min, max)
{
return (max - min) * norm + min;
};
function map(value, smin, smax, omin, omax)
{
return this.lerp(norm(value, smin, smax), omin, omax);
};
function dotProduct(v1, v2)
{
return v1.dx() * v2.dx() + v1.dy() * v2.dy();
};
function unitRandom()
{
return 1 - Math.random() * 2;
};
<script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
<canvas id="canvas"></canvas>
body {
margin: 0;
padding: 0;
background: #FFF;
};
canvas {
position: absolute;
width: 100%;
height: 100%;
}