Skip to content

Commit 76bc091

Browse files
committed
adding bubbles
1 parent 7d9e4a4 commit 76bc091

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed

assets/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,6 @@ <h2>Contact</h2>
7979
<p>© 2024 Joseph Lavoie</p>
8080
</footer>
8181
<script src="script.js"></script>
82+
<canvas id="bg"></canvas>
8283
</body>
8384
</html>

assets/script.js

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,169 @@ document.querySelectorAll('nav a').forEach(link => {
1010

1111
const footer = document.querySelector('footer p');
1212
footer.innerHTML = ${new Date().getFullYear()} Joseph Lavoie`;
13+
14+
15+
Matter.use('matter-wrap');
16+
17+
let floatyBubbles = {
18+
options: {
19+
canvasSelector: '', // to find <canvas> in DOM to draw on
20+
radiusRange: [50, 100], // random range of body radii
21+
xVarianceRange: [-0.5, 0.5], // random range of x velocity scaling on bodies
22+
yVarianceRange: [0.5, 1.5], // random range of y velocity scaling on bodies
23+
airFriction: 0.03, // air friction of bodies
24+
opacity: 1, // opacity of bodies
25+
collisions: true, // do bodies collide or pass through
26+
scrollVelocity: 0.025, // scaling of scroll delta to velocity applied to bodies
27+
pixelsPerBody: 50000, // viewport pixels required for each body added
28+
29+
// colors to cycle through to fill bodies
30+
colors: ['#e4e4cc', '#e1d2c4', '#d1e4df']
31+
},
32+
33+
// throttling intervals (in ms)
34+
scrollDelay: 100,
35+
resizeDelay: 400,
36+
37+
// throttling variables and timeouts
38+
lastOffset: undefined,
39+
scrollTimeout: undefined,
40+
resizeTimeout: undefined,
41+
42+
// Matter.js objects
43+
engine: undefined,
44+
render: undefined,
45+
runner: undefined,
46+
bodies: undefined,
47+
48+
// Starts the bubbles
49+
init(options) {
50+
// override default options with incoming options
51+
this.options = Object.assign({}, this.options, options);
52+
53+
let viewportWidth = document.documentElement.clientWidth;
54+
let viewportHeight = document.documentElement.clientHeight;
55+
56+
this.lastOffset = window.pageYOffset;
57+
this.scrollTimeout = null;
58+
this.resizeTimeout = null;
59+
60+
// engine
61+
this.engine = Matter.Engine.create();
62+
this.engine.world.gravity.y = 0;
63+
64+
// render
65+
this.render = Matter.Render.create({
66+
canvas: document.querySelector(this.options.canvasSelector),
67+
engine: this.engine,
68+
options: {
69+
width: viewportWidth,
70+
height: viewportHeight,
71+
wireframes: false,
72+
background: 'transparent'
73+
}
74+
});
75+
Matter.Render.run(this.render);
76+
77+
// runner
78+
this.runner = Matter.Runner.create();
79+
Matter.Runner.run(this.runner, this.engine);
80+
81+
// bodies
82+
this.bodies = [];
83+
let totalBodies = Math.round(viewportWidth * viewportHeight / this.options.pixelsPerBody);
84+
for (let i = 0; i <= totalBodies; i++) {
85+
let body = this.createBody(viewportWidth, viewportHeight);
86+
this.bodies.push(body);
87+
}
88+
Matter.World.add(this.engine.world, this.bodies);
89+
90+
// events
91+
window.addEventListener('scroll', this.onScrollThrottled.bind(this));
92+
window.addEventListener('resize', this.onResizeThrottled.bind(this));
93+
},
94+
95+
// stop all bubbles
96+
shutdown() {
97+
Matter.Engine.clear(this.engine);
98+
Matter.Render.stop(this.render);
99+
Matter.Runner.stop(this.runner);
100+
101+
window.removeEventListener('scroll', this.onScrollThrottled);
102+
window.removeEventListener('resize', this.onResizeThrottled);
103+
},
104+
105+
// random number generator
106+
randomize(range) {
107+
let [min, max] = range;
108+
return Math.random() * (max - min) + min;
109+
},
110+
111+
// create body with some random parameters
112+
createBody(viewportWidth, viewportHeight) {
113+
let x = this.randomize([0, viewportWidth]);
114+
let y = this.randomize([0, viewportHeight]);
115+
let radius = this.randomize(this.options.radiusRange);
116+
let color = this.options.colors[this.bodies.length % this.options.colors.length];
117+
118+
return Matter.Bodies.circle(x, y, radius, {
119+
render: {
120+
fillStyle: color,
121+
opacity: this.options.opacity
122+
},
123+
frictionAir: this.options.airFriction,
124+
collisionFilter: {
125+
group: this.options.collisions ? 1 : -1
126+
},
127+
plugin: {
128+
wrap: {
129+
min: { x: 0, y: 0 },
130+
max: { x: viewportWidth, y: viewportHeight }
131+
}
132+
}
133+
});
134+
},
135+
136+
// enforces throttling of scroll handler
137+
onScrollThrottled() {
138+
if (!this.scrollTimeout) {
139+
this.scrollTimeout = setTimeout(this.onScroll.bind(this), this.scrollDelay);
140+
}
141+
},
142+
143+
// applies velocity to bodies based on scrolling with some randomness
144+
onScroll() {
145+
this.scrollTimeout = null;
146+
147+
let delta = (this.lastOffset - window.pageYOffset) * this.options.scrollVelocity;
148+
this.bodies.forEach((body) => {
149+
Matter.Body.setVelocity(body, {
150+
x: body.velocity.x + delta * this.randomize(this.options.xVarianceRange),
151+
y: body.velocity.y + delta * this.randomize(this.options.yVarianceRange)
152+
});
153+
});
154+
155+
this.lastOffset = window.pageYOffset;
156+
},
157+
158+
// enforces throttling of resize handler
159+
onResizeThrottled() {
160+
if (!this.resizeTimeout) {
161+
this.resizeTimeout = setTimeout(this.onResize.bind(this), this.resizeDelay);
162+
}
163+
},
164+
165+
// restart everything with the new viewport size
166+
onResize() {
167+
this.shutdown();
168+
this.init();
169+
}
170+
};
171+
172+
// wait for DOM to load
173+
window.addEventListener('DOMContentLoaded', () => {
174+
// start floaty bubbles background
175+
Object.create(floatyBubbles).init({
176+
canvasSelector: '#bg'
177+
});
178+
});

assets/styles.css

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,29 @@ img {
135135
border-radius: 10px;
136136
margin: 10px 0;
137137
}
138+
139+
.wrapper {
140+
max-width: 800px;
141+
margin: 40px auto;
142+
padding: 20px;
143+
border: 1px solid #fff;
144+
background-color: rgba(255, 255, 255, 0.5);
145+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
146+
overflow: hidden;
147+
}
148+
149+
canvas {
150+
position: fixed;
151+
z-index: -1;
152+
top: 0;
153+
left: 0;
154+
}
155+
156+
@media screen and (min-width: 450px) {
157+
h1 {
158+
font-size: 4rem;
159+
}
160+
h2 {
161+
font-size: 2rem;
162+
}
163+
}

0 commit comments

Comments
 (0)