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