@@ -10,3 +10,169 @@ document.querySelectorAll('nav a').forEach(link => {
1010
1111const footer = document . querySelector ( 'footer p' ) ;
1212footer . 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+ } ) ;
0 commit comments