Hey there
How it’s made

If you’re wondering how some thing on this webpage are made, no need to to inspect and gues what’s going on, I’ll explain it right here.

Click on the section you’re interested in and I’ll show you how it’s done!

Site structure

NodeJS, ExpressJS, JS ES6

This portfolio is running on a NodeJs + Express server generating html from EJS templates based on a JSON file. That’s about as many tech terms I think of jammed in one sentence describing my backend setup. While this is a cool and fast setup it’s not that fancy or high tech, and I’m relying on a lot of libraries to make it do what it has to do. For my front end it’s a different situation, the only library I use is GSAP for the animation. Everything else is custom build.

Libraries
For most things on the frontend I prefer to avoid libraries. Most of the time you don’t use them to their full potential just because they want support all use cases. For this reason I wanted to use as little libraries as possible. Don’t get me wrong I agree that there are plenty of use cases to use big libraries but for a simple website like a personal portfolio I see little excuses.
Next up I wanted to see if I would be able to write my own frontend router and see how it would end up. So without further ado, this is what I came up with.

close
minimize
fullscreen
  • 1
new portfolio()
  • 1
new portfolio()

The shape
Since I have no intention to make this lab item a 2 hour read I will simplify most in here. I’m a big fan of JS classes so this setup uses them all over the place. Since I wanted cool transitions between pages I wanted the app to do page changes with AJAX requests. This ment that after the first load I had to initiate some stuff:
- router()
- link()
- transitionRegion()

And I also wanted one of those fancy intro animations. This gives me the following, simplified, main.js file.

close
minimize
fullscreen
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
class portfolio { constructor() { this.ui = new Ui() this.intro = new IntroAnim(); this.router = new Router(); this.links = new MenuLinks( this.router ); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
class portfolio { constructor() { this.ui = new Ui() this.intro = new IntroAnim(); this.router = new Router(); this.links = new MenuLinks( this.router ); } }

In here you can see that the router is setup first and afterwards the links get setup to listen to attempted page changes. When one of those events fires up I intercept it, get the data url, retrieve data, parse it into a new view and initiate a transition chain inside of the transitionRegion.

close
minimize
fullscreen
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
newPage.onRender( appendedData, () => { lastPage.animateOut( newPage, () => { lastPage.onDestroy( newPage, () => { currentView.remove(); window.scrollTo( 0, 0 ); newPage.animateIn( lastPage, () => { } ); } ); } ); } );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
newPage.onRender( appendedData, () => { lastPage.animateOut( newPage, () => { lastPage.onDestroy( newPage, () => { currentView.remove(); window.scrollTo( 0, 0 ); newPage.animateIn( lastPage, () => { } ); } ); } ); } );

The animation calls in the views, newPage and lastPage, are situated in the view template. This is a JS class that gets extended for every page. By default this template sets up event listeners in each page and creates some basic in and out animations. This makes adding templates a bit more DRY and less cumbersome.

close
minimize
fullscreen
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
export class View { name: String container: Element router: any links: MenuLinks constructor( name: String, router: any ) { this.name = name; this.router = router; } onRender( viewData: Element, cb: Function ) { console.log(`OnRender: ${this.name}`); this.container = viewData; this.links = new MenuLinks( this.router, this.container ) cb && cb(); } animateIn( lastPage, cb: Function ) {… } animateOut( newPage, cb: Function ) {… } onDestroy( lastPage, cb: Function ) {… } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
export class View { name: String container: Element router: any links: MenuLinks constructor( name: String, router: any ) { this.name = name; this.router = router; } onRender( viewData: Element, cb: Function ) { console.log(`OnRender: ${this.name}`); this.container = viewData; this.links = new MenuLinks( this.router, this.container ) cb && cb(); } animateIn( lastPage, cb: Function ) {… } animateOut( newPage, cb: Function ) {… } onDestroy( lastPage, cb: Function ) {… } }

In addition to each template I create two more classes: Ui & Events. These two classes are more for my sanity then they are for performance. However using the Ui class makes it easy for myself to make sure I never use different selectors to get the same element and all my selectors fire at the initialisation of the template. Furthermore it makes it easy to see later on that I’m getting data from a element:

this.ui.aboutImage

But then again all of these things can be done in a different way.

close
minimize
fullscreen
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
class Ui {… } class Events {… } export class Home extends View {… }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
class Ui {… } class Events {… } export class Home extends View {… }

Final words
Should you build your own frontend router / state manager? Imo, no. Except if you want to learn. Setups like React/VueJS do a lot of this stuf for you in a performant way with a lot of flexibility. And they even give you all the goodies like a virtual dom and a huge community. But then again building it yourself will teach you way better then any blog post can tell you what those libraries do for you. Just don’t expect the same result 😀