console
var Rope = (function() {
function Rope(texture, pointBetween) {
this.texture = texture;
this.pointBetween = pointBetween;
this.points = [];
}
Rope.prototype = {
points: null,
pointBetween: 1,
_rootIndex: 0,
setRootIndex: function(index) { this._rootIndex = index; },
getRootIndex: function() { return this._rootIndex; },
rootPoint: function() { return this.points[this._rootIndex]; },
addPoint: function(x, y) {
this.points.push(new Point(this, x, y, this.points[this.points.length - 1]));
},
removePoint: function() {
if (!this.points.length) return;
this.points.splice(this.points.length - 1, 1);
},
render: function(ctx) {
var points = this.points,
texture = this.texture,
pointBetween = this.pointBetween,
p0, p1,
h, r, w, s, sx, angle,
i, len;
this.rootPoint().updateBoth();
if (texture.width === 0) return;
h = texture.height;
r = h * 0.5;
w = pointBetween + r * 2;
sw = texture.width - w;
sx = 0;
for (i = 0, len = points.length - 1; i < len; i++) {
p0 = points[i];
p1 = points[i + 1];
angle = Math.atan2(p1.y - p0.y, p1.x - p0.x);
ctx.save();
ctx.translate(p0.x, p0.y);
ctx.rotate(angle);
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2, false);
ctx.rect(0, -r, pointBetween, h);
ctx.arc(pointBetween, 0, r, 0, Math.PI * 2, false);
ctx.closePath();
ctx.clip();
ctx.drawImage(texture, sx % sw, 0, w, h, -r, -r, w, h);
ctx.restore();
sx += pointBetween;
}
}
};
function Point(rope, x, y, prev) {
this.rope = rope;
this.x = x || 0;
this.y = y || 0;
this.prev = prev || null;
if (this.prev) this.prev.next = this;
}
Point.prototype = {
rope: null,
x: 0,
y: 0,
prev: null,
next: null,
updateBoth: function() {
this.updateNext();
this.updatePrev();
},
updatePrev: function() {
if (this.prev) {
this._update(this.prev);
this.prev.updatePrev();
}
},
updateNext: function() {
if (this.next) {
this._update(this.next);
this.next.updateNext();
}
},
_update: function(neighbor) {
var angle = Math.atan2(neighbor.y - this.y, neighbor.x - this.x);
neighbor.x = this.x + Math.cos(angle) * this.rope.pointBetween;
neighbor.y = this.y + Math.sin(angle) * this.rope.pointBetween;
}
};
return Rope;
})();
(function() {
var TEXTURE_IMG_URL = 'http://jsrun.it/assets/f/e/Q/3/feQ3C.png',
POINT_NUM = 200;
var canvas, context,
rope,
isDrag = false;
window.addEventListener('load', function() {
var i, px, py;
canvas = document.getElementById('c');
context = canvas.getContext('2d');
resize(null);
rope = new Rope(new Image(), 3);
for (i = 0; i < POINT_NUM; i++) {
if (i === 0)
rope.addPoint(300, 300);
else
rope.addPoint(canvas.width * 0.5 * Math.random(), canvas.height * 0.5 * Math.random());
}
rope.texture.addEventListener('load', function() {
window.addEventListener('resize', resize, false);
document.addEventListener('mousemove', mouseMove, false);
document.addEventListener('mousedown', mouseDown, false);
document.addEventListener('mouseup', mouseUp, false);
rope.render(context);
}, false);
rope.texture.src = TEXTURE_IMG_URL;
}, false);
function resize(e) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
function mouseMove(e) {
if (isDrag) {
var drag = rope.rootPoint();
drag.x = e.clientX;
drag.y = e.clientY;
context.clearRect(0, 0, canvas.width, canvas.height);
rope.render(context);
}
}
function mouseDown(e) {
var points = rope.points,
p,
hit = false,
hitIndex = rope.getRootIndex(),
hitNear = 100,
mx = e.clientX,
my = e.clientY,
dx, dy, distSq,
i, len;
for (i = 0, len = points.length; i < len; i++) {
p = points[i];
dx = mx - p.x;
dy = my - p.y;
distSq = dx * dx + dy * dy;
if (distSq < 100 && distSq < hitNear) {
hit = true;
hitIndex = i;
}
}
rope.setRootIndex(hitIndex);
isDrag = hit;
}
function mouseUp(e) {
isDrag = false;
}
})();
<script id="jquery_183" type="text/javascript" class="library" src="http://sandbox.runjs.cn/"></script>
<canvas id='c'></canvas>
body {
font-family: sans-serif;
padding: 0;
margin: 0;
background: #f9f9f2;
}
canvas {
position: absolute;
top: 0;
left: 0;
}