题目比较长,先提供一个图片看看效果
也可以自己查看预览图 在线预览http://mubanmao.top/content/preview?id=686f9976-3c54-45b1-9e51-1a81389d5fbb
主要的js代码
let accuracy = 5,
clothX = 60,
clothY = 25,
spacing = 8,
tearDist = spacing * 4,
friction = 0.99,
bounce = 0.5,
gravity = 400,
modePull = true;
const canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
ctx.strokeStyle = "#555";
const w = canvas.width = Math.min(700, window.innerWidth),
h = canvas.height = 400;
const mouse = {
cut: spacing,
influence: spacing * 2,
down: false,
//button: 1,
x: 0,
y: 0,
px: 0,
py: 0,
};
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
this.px = x;
this.py = y;
this.vx = 0;
this.vy = 0;
this.pinX = null;
this.pinY = null;
this.constraints = [];
}
update(delta) {
if (this.pinX && this.pinY) { return this; }
if (mouse.down) {
let dx = this.x - mouse.x;
let dy = this.y - mouse.y;
let dist = Math.sqrt(dx * dx + dy * dy);
if (modePull && dist < mouse.influence) {
this.px = this.x - (mouse.x - mouse.px);
this.py = this.y - (mouse.y - mouse.py);
} else if (dist < mouse.cut) {
this.constraints = [];
}
}
this.addForce(0, gravity);
let nx = this.x + (this.x - this.px) * friction + this.vx * delta;
let ny = this.y + (this.y - this.py) * friction + this.vy * delta;
this.px = this.x;
this.py = this.y;
this.x = nx;
this.y = ny;
this.vy = this.vx = 0;
if (this.x >= w) {
this.px = w + (w - this.px) * bounce;
this.x = w;
} else if (this.x <= 0) {
this.px *= -1 * bounce;
this.x = 0;
}
if (this.y >= h) {
this.py = h + (h - this.py) * bounce;
this.y = h;
} else if (this.y <= 0) {
this.py *= -1 * bounce;
this.y = 0;
}
return this;
}
draw() {
let i = this.constraints.length;
while (i--) this.constraints[i].draw();
}
resolve() {
if (this.pinX && this.pinY) {
this.x = this.pinX;
this.y = this.pinY;
return;
}
this.constraints.forEach(constraint => constraint.resolve());
}
attach(point) {
this.constraints.push(new Constraint(this, point));
}
free(constraint) {
this.constraints.splice(this.constraints.indexOf(constraint), 1);
}
addForce(x, y) {
this.vx += x;
this.vy += y;
}
pin(pinx, piny) {
this.pinX = pinx;
this.pinY = piny;
}
}
class Constraint {
constructor(p1, p2) {
this.p1 = p1;
this.p2 = p2;
this.length = spacing;
}
resolve() {
let dx = this.p1.x - this.p2.x;
let dy = this.p1.y - this.p2.y;
let dist = Math.sqrt(dx * dx + dy * dy);
if (dist < this.length) return;
let diff = (this.length - dist) / dist;
if (dist > tearDist) this.p1.free(this);
let mul = diff * 0.5 * (1 - this.length / dist);
let px = dx * mul;
let py = dy * mul;
!this.p1.pinX && (this.p1.x += px);
!this.p1.pinY && (this.p1.y += py);
!this.p2.pinX && (this.p2.x -= px);
!this.p2.pinY && (this.p2.y -= py);
return this;
}
draw() {
ctx.moveTo(this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
}
}
class Cloth {
constructor(free) {
this.points = [];
let startX = w / 2 - clothX * spacing / 2;
for (let y = 0; y <= clothY; y++) {
for (let x = 0; x <= clothX; x++) {
let point = new Point(startX + x * spacing, 20 + y * spacing);
!free && y === 0 && point.pin(point.x, point.y);
x !== 0 && point.attach(this.points[this.points.length - 1]);
y !== 0 && point.attach(this.points[x + (y - 1) * (clothX + 1)]);
this.points.push(point);
}
}
}
update(delta) {
let i = accuracy;
while (i--) {
this.points.forEach(point => {
point.resolve();
});
}
ctx.beginPath();
this.points.forEach(point => {
point.update(delta * delta).draw();
});
ctx.stroke();
}
}
dragTracker({
container: canvas,
callbackDragStart: (_, pos) => {
mouse.down = true;
mouse.px = mouse.x = pos[0];
mouse.py = mouse.y = pos[1];
},
callback: (_, pos) => {
mouse.px = mouse.x;
mouse.py = mouse.y;
mouse.x = pos[0];
mouse.y = pos[1];
},
callbackDragEnd: () => {
mouse.down = false;
},
});
let cloth = new Cloth();
//Animation loop
(function update(time) {
ctx.clearRect(0, 0, w, h);
cloth.update(0.016);
requestAnimationFrame(update);
})();
let _g;
function zeroG() {
if(gravity) {
_g = gravity;
gravity = 0;
}
else {
gravity = _g;
}
}
function setMode(pull) {
modePull = pull;
}
function reset() {
cloth = new Cloth(!gravity);
}
// window.requestAnimFrame =
// window.requestAnimationFrame ||
// window.webkitRequestAnimationFrame ||
// window.mozRequestAnimationFrame ||
// window.oRequestAnimationFrame ||
// window.msRequestAnimationFrame ||
// function (callback) {
// window.setTimeout(callback, 1e3 / 60)
// }
// let accuracy = 5
// let gravity = 400
// let clothY = 24
// let clothX = 50
// let spacing = 8
// let tearDist = 60
// let friction = 0.99
// let bounce = 0.5
// let canvas = document.getElementById('canvas')
// let ctx = canvas.getContext('2d')
// canvas.width = Math.min(700, window.innerWidth)
// canvas.height = 400
// ctx.strokeStyle = '#555'
// let mouse = {
// cut: 8,
// influence: 36,
// down: false,
// button: 1,
// x: 0,
// y: 0,
// px: 0,
// py: 0
// }
// class Point {
// constructor (x, y) {
// this.x = x
// this.y = y
// this.px = x
// this.py = y
// this.vx = 0
// this.vy = 0
// this.pinX = null
// this.pinY = null
// this.constraints = []
// }
// update (delta) {
// if (this.pinX && this.pinY) return this
// if (mouse.down) {
// let dx = this.x - mouse.x
// let dy = this.y - mouse.y
// let dist = Math.sqrt(dx * dx + dy * dy)
// if (mouse.button === 1 && dist < mouse.influence) {
// this.px = this.x - (mouse.x - mouse.px)
// this.py = this.y - (mouse.y - mouse.py)
// } else if (dist < mouse.cut) {
// this.constraints = []
// }
// }
// this.addForce(0, gravity)
// let nx = this.x + (this.x - this.px) * friction + this.vx * delta
// let ny = this.y + (this.y - this.py) * friction + this.vy * delta
// this.px = this.x
// this.py = this.y
// this.x = nx
// this.y = ny
// this.vy = this.vx = 0
// if (this.x >= canvas.width) {
// this.px = canvas.width + (canvas.width - this.px) * bounce
// this.x = canvas.width
// } else if (this.x <= 0) {
// this.px *= -1 * bounce
// this.x = 0
// }
// if (this.y >= canvas.height) {
// this.py = canvas.height + (canvas.height - this.py) * bounce
// this.y = canvas.height
// } else if (this.y <= 0) {
// this.py *= -1 * bounce
// this.y = 0
// }
// return this
// }
// draw () {
// let i = this.constraints.length
// while (i--) this.constraints[i].draw()
// }
// resolve () {
// if (this.pinX && this.pinY) {
// this.x = this.pinX
// this.y = this.pinY
// return
// }
// this.constraints.forEach((constraint) => constraint.resolve())
// }
// attach (point) {
// this.constraints.push(new Constraint(this, point))
// }
// free (constraint) {
// this.constraints.splice(this.constraints.indexOf(constraint), 1)
// }
// addForce (x, y) {
// this.vx += x
// this.vy += y
// }
// pin (pinx, piny) {
// this.pinX = pinx
// this.pinY = piny
// }
// }
// class Constraint {
// constructor (p1, p2) {
// this.p1 = p1
// this.p2 = p2
// this.length = spacing
// }
// resolve () {
// let dx = this.p1.x - this.p2.x
// let dy = this.p1.y - this.p2.y
// let dist = Math.sqrt(dx * dx + dy * dy)
// if (dist < this.length) return
// let diff = (this.length - dist) / dist
// if (dist > tearDist) this.p1.free(this)
// let mul = diff * 0.5 * (1 - this.length / dist)
// let px = dx * mul
// let py = dy * mul
// !this.p1.pinX && (this.p1.x += px)
// !this.p1.pinY && (this.p1.y += py)
// !this.p2.pinX && (this.p2.x -= px)
// !this.p2.pinY && (this.p2.y -= py)
// return this
// }
// draw () {
// ctx.moveTo(this.p1.x, this.p1.y)
// ctx.lineTo(this.p2.x, this.p2.y)
// }
// }
// class Cloth {
// constructor (free) {
// this.points = []
// let startX = canvas.width / 2 - clothX * spacing / 2
// for (let y = 0; y <= clothY; y++) {
// for (let x = 0; x <= clothX; x++) {
// let point = new Point(startX + x * spacing, 20 + y * spacing)
// !free && y === 0 && point.pin(point.x, point.y)
// x !== 0 && point.attach(this.points[this.points.length - 1])
// y !== 0 && point.attach(this.points[x + (y - 1) * (clothX + 1)])
// this.points.push(point)
// }
// }
// }
// update (delta) {
// let i = accuracy
// while (i--) {
// this.points.forEach((point) => {
// point.resolve()
// })
// }
// ctx.beginPath()
// this.points.forEach((point) => {
// point.update(delta * delta).draw()
// })
// ctx.stroke()
// }
// }
// function setMouse (e) {
// let rect = canvas.getBoundingClientRect()
// mouse.px = mouse.x
// mouse.py = mouse.y
// mouse.x = e.clientX - rect.left
// mouse.y = e.clientY - rect.top
// }
// canvas.onmousedown = (e) => {
// mouse.button = e.which
// mouse.down = true
// setMouse(e)
// }
// canvas.onmousemove = setMouse
// canvas.onmouseup = () => (mouse.down = false)
// canvas.oncontextmenu = (e) => e.preventDefault()
// let cloth = new Cloth()
// function zeroG() {
// gravity = 0
// cloth = new Cloth(true)
// }
// ;(function update (time) {
// ctx.clearRect(0, 0, canvas.width, canvas.height)
// cloth.update(0.016)
// window.requestAnimFrame(update)
// })(0)
<head>
<link href="style.scss" rel="stylesheet" type="text/css">
</head>
<body>
<canvas id="canvas"></canvas>
<div>
<label><input type="radio" name="dragmode" onchange="setMode(true)" checked /> Pull</label>
<label><input type="radio" name="dragmode" onchange="setMode(false)" /> Cut</label>
<label><input type="checkbox" onchange="zeroG()" /> Zero G</label>
<p>
<button onclick="reset()">Reset</button>
<!--button onclick="zeroG()">Zero G!</button-->
</p>
</div>
<script type="text/javascript" src="drag-tracker.js"></script>
<script type="text/javascript" src="Cloth.js"></script>
</body>