Interactive ropes background (Verlet physics algorithm)
In this tutorial, we will implement Verlet physics algorithm using html css and JavaScript.
Verlet integration is a method for calculating the position and velocity of objects in a simulation. It is often used in physics engines for games and other interactive applications. Here is an example of how you could implement a simple Verlet physics algorithm using HTML, CSS, and JavaScript.
For additional HTML, CSS, and JavaScript projects, see our website.
Source Code:
HTML:
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>Interactive ropes background (Verlet physics algorithm)</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- partial:index.partial.html -->
<canvas></canvas>
<!-- glowing image base64 -->
<img class="asset-img" id="light-img" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAMAAADzN3VRAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACnVBMVEUAAAD40ED40D740Dv40En40Dr40FH40Df40D/40D340Dn40Dz40Dj40AD40Ff40Db40DT40EH40EL40ED40ED40Dv40D/4zz/4zz74zz/4zz/40D74zz/4zz/4zz/40UH40kD40UL40kH4zz/4zz340D34zz740kH41UT410f410j410j410f40D/40D/4zz/40ED40EH400T410f42Er4103410/411D41UT40kH40D/4z0D4zz/41UX410j42E7411L42Ff42Vv42Vz42En41kb40UH4zz/40Dz40ED41UT42Ej4107411b42l743WT432r4323432r410/42En4zz340Dn400H410f42mD432v44nf45IP45Yn45IT44nj432r411b400L40D/4zz/41UT42Er411P432v44oD46Zv47q/48Lj46Zr432n40UH410f42Ff44nf48r/499T4997499T48r744nf43WP40D741EH42Vv4+O34+PP4+O347q745IL432j40UD432345Yj48LX49934+PP4+Pj4993477T45Ib432z411D400D40D/40UH432n45IP47a7432j42Vv410/400H40Dr4zz340UD410b410346Zj48r343WP42Ff410f40D/41kT42Er42l744nv46Zf42l3411L410r41kX40ED410f42E7411b421/45IL45Ib432n421740UH40D340Dz4zz741EP410j410743WT432f432v43mj43GT42Ej41kT4zz740ED41EX411P42Ff42Fv411f410j41kX400P41034107411D42E340D740UH410j41kb41ET400H40D/40ED4z0D4zz74zz340D////+WdN8lAAAA3nRSTlMAAAAAAAAAAAAAAAAAAAAAAAAAAgMCBAcJDAkDCA0SFRsbGxMDBQscJSwwMS0SDAUFFyExQExUVyIYDQULJzlOZHeDhDonFwoDASE5VXGNpbO4tFM5BwEcMJa3zdjb2Mu2cBwDESM+Y7bU4ebo4bUUK3bL6vDy7+rKogIZgPb49ebXsBu02+jx9/jw6Nu0VRsBGbDX5q9/UhkCCRUqS+HqoHMqESI9i9Thi2E9IhEvTG+V1tq0kxsDAQchN1Ggr7SvoDghChUjYXJ/cjgnIUlSVEkFGi8qIhsFFAcICAaJi/ApAAAAAWJLR0Te6W7imwAAAAd0SU1FB+YMHBEUINvSacMAAAIKSURBVCjPY2AgBBgZmZiFRYQZgQBVnEVUTFxCUlJSSlxMlAVJjplVWkZWTl5BUUleWVZGhZkZYZKqmpy6hqaWtpaOhrqunj7MREZGA1lDI2MTUzNzM1MTYwtLK2uIFCOjiI2hrZ29g6OTs5Ojg72Lq5u7Bxs7SMZTXNfL28fXzz8gMMjfzzc4xEI3NIwVrEUyPMLeNzIqOiY2Lj4hMtE+Ikk2mYkRKCOekpqW7peRmZWdk52bmeeXnqYhJ84BkpHKLzAt9C/KKi4pLSvPragsNNXMl/JkZGCqUqvWMqsJiMkuqa2rL2tobKox06qWTOZg4EiWbNY2d25pbWvv6Ozo6u7pde7T6pf04GTgmGAzEahn0uSGsvqO+rIpjVOnTdeaoTaTmYGLfdbsOXMLKyvmzS/rKps/r2jBwrmL5KWAfmViEl+8ZGn6srzlK6Z0T1mxPG/lqtVr1oLdxp0sqbRu/YaNeUWbNm8q2rJ1w/p12yS38wDDh3PHTrldu/dsWLZ33/4DB5dt2HPosO7OMFBwM/KKHjl6bPf64yemOZ88dXz96TPyR6T5+EFByi6ganP07LrV585fmH5x6bqzs21UmZhg0XDJXe7ykjnrtK5cvXZdzv0GMycs6jiZpXdKLs532+Z2M0VSXAQ5KTAKeorcuu1uc+TO3e07hNDSCCMjNyczKz8nN8FkxgAAgH2hXtQzzA4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMTItMjhUMTc6MjA6MzIrMDA6MDA1MNVoAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTEyLTI4VDE3OjIwOjMyKzAwOjAwRG1t1AAAAABJRU5ErkJggg==">
<!-- partial -->
<script type="module" src="./script.js"></script>
</body>
</html>
——————————
📂 Important Links:
——————————
>> Learn Graphics Design & Make A Successful Profession.
>> Canva Makes Graphics Design Easy.
>> Start Freelancing Today & Earn Money.
>> Make Video Editing As Your Profession.
CSS:
html,
body,
canvas {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
background: #191919;
}
.asset-img {
display: none;
}
Another Article for you.
JavaScript
class Mouse {
constructor(canvas) {
this.pos = new Vector(-1000, -1000);
this.radius = 40;
canvas.onmousemove = (e) => this.pos.setXY(e.clientX, e.clientY);
canvas.ontouchmove = (e) =>
this.pos.setXY(e.touches[0].clientX, e.touches[0].clientY);
canvas.ontouchcancel = () => this.pos.setXY(-1000, -1000);
canvas.ontouchend = () => this.pos.setXY(-1000, -1000);
}
}
class Dot {
constructor(x, y) {
this.pos = new Vector(x, y);
this.oldPos = new Vector(x, y);
this.friction = 0.97;
this.gravity = new Vector(0, 0.6);
this.mass = 1;
this.pinned = false;
this.lightImg = document.querySelector("#light-img");
this.lightSize = 15;
}
update(mouse) {
if (this.pinned) return;
let vel = Vector.sub(this.pos, this.oldPos);
this.oldPos.setXY(this.pos.x, this.pos.y);
vel.mult(this.friction);
vel.add(this.gravity);
let { x: dx, y: dy } = Vector.sub(mouse.pos, this.pos);
const dist = Math.sqrt(dx * dx + dy * dy);
const direction = new Vector(dx / dist, dy / dist);
const force = Math.max((mouse.radius - dist) / mouse.radius, 0);
if (force > 0.6) this.pos.setXY(mouse.pos.x, mouse.pos.y);
else {
this.pos.add(vel);
this.pos.add(direction.mult(force));
}
}
drawLight(ctx) {
ctx.drawImage(
this.lightImg,
this.pos.x - this.lightSize / 2,
this.pos.y - this.lightSize / 2,
this.lightSize,
this.lightSize
);
}
draw(ctx) {
ctx.fillStyle = "#aaa";
ctx.fillRect(
this.pos.x - this.mass,
this.pos.y - this.mass,
this.mass * 2,
this.mass * 2
);
}
}
class Stick {
constructor(p1, p2) {
this.startPoint = p1;
this.endPoint = p2;
this.length = this.startPoint.pos.dist(this.endPoint.pos);
this.tension = 0.3;
}
update() {
const dx = this.endPoint.pos.x - this.startPoint.pos.x;
const dy = this.endPoint.pos.y - this.startPoint.pos.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const diff = (dist - this.length) / dist;
const offsetX = diff * dx * this.tension;
const offsetY = diff * dy * this.tension;
const m = this.startPoint.mass + this.endPoint.mass;
const m1 = this.endPoint.mass / m;
const m2 = this.startPoint.mass / m;
if (!this.startPoint.pinned) {
this.startPoint.pos.x += offsetX * m1;
this.startPoint.pos.y += offsetY * m1;
}
if (!this.endPoint.pinned) {
this.endPoint.pos.x -= offsetX * m2;
this.endPoint.pos.y -= offsetY * m2;
}
}
draw(ctx) {
ctx.beginPath();
ctx.strokeStyle = "#999";
ctx.moveTo(this.startPoint.pos.x, this.startPoint.pos.y);
ctx.lineTo(this.endPoint.pos.x, this.endPoint.pos.y);
ctx.stroke();
ctx.closePath();
}
}
class Rope {
constructor(config) {
this.x = config.x;
this.y = config.y;
this.segments = config.segments || 10;
this.gap = config.gap || 15;
this.color = config.color || "gray";
this.dots = [];
this.sticks = [];
this.iterations = 10;
this.create();
}
pin(index) {
this.dots[index].pinned = true;
}
create() {
for (let i = 0; i < this.segments; i++) {
this.dots.push(new Dot(this.x, this.y + i * this.gap));
}
for (let i = 0; i < this.segments - 1; i++) {
this.sticks.push(new Stick(this.dots[i], this.dots[i + 1]));
}
}
update(mouse) {
this.dots.forEach((dot) => {
dot.update(mouse);
});
for (let i = 0; i < this.iterations; i++) {
this.sticks.forEach((stick) => {
stick.update();
});
}
}
draw(ctx) {
this.dots.forEach((dot) => {
dot.draw(ctx);
});
this.sticks.forEach((stick) => {
stick.draw(ctx);
});
this.dots[this.dots.length - 1].drawLight(ctx);
}
}
class App {
static width = innerWidth;
static height = innerHeight;
static dpr = devicePixelRatio > 1 ? 2 : 1;
static interval = 1000 / 60;
constructor() {
this.canvas = document.querySelector("canvas");
this.ctx = this.canvas.getContext("2d");
this.mouse = new Mouse(this.canvas);
this.resize();
window.addEventListener("resize", this.resize.bind(this));
this.createRopes();
}
createRopes() {
this.ropes = [];
const TOTAL = App.width * 0.06;
for (let i = 0; i < TOTAL + 1; i++) {
const x = randomNumBetween(App.width * 0.3, App.width * 0.7);
const y = 0;
const gap = randomNumBetween(App.height * 0.05, App.height * 0.08);
const segments = 10;
const rope = new Rope({ x, y, gap, segments });
rope.pin(0);
this.ropes.push(rope);
}
}
resize() {
App.width = innerWidth;
App.height = innerHeight;
this.canvas.style.width = "100%";
this.canvas.style.height = "100%";
this.canvas.width = App.width * App.dpr;
this.canvas.height = App.height * App.dpr;
this.ctx.scale(App.dpr, App.dpr);
this.createRopes();
}
render() {
let now, delta;
let then = Date.now();
const frame = () => {
requestAnimationFrame(frame);
now = Date.now();
delta = now - then;
if (delta < App.interval) return;
then = now - (delta % App.interval);
this.ctx.clearRect(0, 0, App.width, App.height);
// draw here
this.ropes.forEach((rope) => {
rope.update(this.mouse);
rope.draw(this.ctx);
});
};
requestAnimationFrame(frame);
}
}
function randomNumBetween(min, max) {
return Math.random() * (max - min) + min;
}
window.addEventListener("load", () => {
const app = new App();
app.render();
});
export default class Vector {
constructor(x, y) {
this.x = x || 0;
this.y = y || 0;
}
static add(v1, v2) {
return new Vector(v1.x + v2.x, v1.y + v2.y);
}
static sub(v1, v2) {
return new Vector(v1.x - v2.x, v1.y - v2.y);
}
add(x, y) {
if (arguments.length === 1) {
this.x += x.x;
this.y += x.y;
} else if (arguments.length === 2) {
this.x += x;
this.y += y;
}
return this;
}
sub(x, y) {
if (arguments.length === 1) {
this.x -= x.x;
this.y -= x.y;
} else if (arguments.length === 2) {
this.x -= x;
this.y -= y;
}
return this;
}
mult(v) {
if (typeof v === "number") {
this.x *= v;
this.y *= v;
} else {
this.x *= v.x;
this.y *= v.y;
}
return this;
}
setXY(x, y) {
this.x = x;
this.y = y;
return this;
}
dist(v) {
const dx = this.x - v.x;
const dy = this.y - v.y;
return Math.sqrt(dx * dx + dy * dy);
}
}