11/03/21

A Simple Website using 11ty and Barba.js

11ty Barba Website

Eleventy (11ty) is a static site generator that I've been wanting to try out for a while. So I decided to use it for one of my latest projects and I really like it! I would definitely recommend it for websites like portfolios or blogs, especially if you don't want to get into a framework like React. 11ty doesn't need any configuration and runs out of the box. And you can simply use HTML, CSS and Vanilla JS.

Let's build a simple website like this together using Eleventy, Nunjucks as a template engine and Snowpack for module bundling. I will also add some basic page transitions using Barba.js and GSAP. Here's the code.

Eleventy Setup

Create a new folder and a package.json file to install Eleventy.
(You need to have Node and npm installed, I recommend Homebrew.)

npm init -y

Install and save 11ty into the project.

npm install --save-dev @11ty/eleventy

With 11ty you can choose from many templating languages. I use Nunjucks, but you can also use Markdown, Liquid or others.

Create a new file index.njk

<h1>Hello World</h1>

Run npx eleventy

This command generates a _site directory and the file structure is compiled to HTML files. You could use this output folder already to put the website online. Super simple!

Let's structure the code. I like to put the code I write in a src folder. Move index.njk in there and create two more folders, styles and scripts with a index.js and index.css file.

|- src
  |- styles
    |- index.css
  |-scripts
    |- index.js
  |- index.njk
|- package.json

I also want to generate the _site folder inside src. Create a new file in the root directory .eleventy.js. There you are able to configure other 11ty related things.

module.exports = {
    dir: {
      input: 'src',
      output: 'src/_site'
    }
};

When a website becomes more complex, you usually split the JavaScript into separate files. That's called JavaScript modules. To use this feature, we need to bundle the modules into one single file that the browser can execute. If you want to use Sass, it also needs to be compiled. Parcel is very common for module bundling, but I wanted to try Snowpack as I've heard a lot about it lately.

Snowpack

npm install snowpack

To start and build the project, configure these scripts inside package.json

"scripts": {
  "start": "snowpack dev",
  "build": "snowpack build"
}

We also need to create a new file in the root directory snowpack.config.js
You can specify the input/output there and which folders to compile.

module.exports = {
  mount: {
    'src/_site': {
      url: '/',
      static: true,
    },
    'src/scripts': '/',
    'src/styles': '/',
  },
  plugins: [
    [
      '@snowpack/plugin-run-script',
      {
        cmd: 'eleventy',
        watch: '$1 --watch',
      },
    ],
  ],
  optimize: {
    bundle: true,
    minify: true,
    target: 'es2018',
  },
};

Since we are now running snowpack dev instead of npx eleventy we need to specify this as well. We can do this with a plugin. Don't forget to install it!

npm i @snowpack/plugin-run-script

If you want to use Sass you need to add plugin-sass

npm i @snowpack/plugin-sass
  plugins: [
    '@snowpack/plugin-sass',
    [
      '@snowpack/plugin-run-script',
      {
        cmd: 'eleventy',
        watch: '$1 --watch',
      },
    ],
  ],

Let's build the website

Website Preview

Now, when you run npm start, you should have a server running at http://localhost:8080 and see an empty website!

To see the actual content on the website, we need to add a layout template.

Create a new folder _includes with a layout.njk file.

|- src
  |- _includes
    |- layout.njk
  |- styles
    |- index.css
  |-scripts
    |- index.js
  |- index.njk
|- .eleventy.js
|- package.json
|- snowpack.config.js

A layout template is a special kind of file because we can use it across multiple sites. Therefore, the entire base structure and components like header or footer are defined there, which then appear on each site.

layout.njk

---
title: Hello World
---

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }}</title>
    <link rel="stylesheet" href="/index.css">
  </head>
  <body>

    {% include "../components/nav.njk" %}

    <main>
      {% include "../components/transition.njk" %}

      {{ content | safe }}
    </main>

    <script type="module" src="/index.js"></script>
  </body>
</html>

Nunjucks also lets you use variables that can be specified in the front matter. I like to separate content into components and import them to keep the code organized. You can do this with the include syntax within Nunjucks.

To use the layout template inside index.njk, you only need to add it to the front matter.

---
layout: layout.njk
---

<h1>Hello World</h1>

If you reload the website now, Hello World should appear!

Navigation

Linking pages in Eleventy is really easy. It simply maps the file structure. For each page create a page.njk file. For example, about.njk and contact.njk.

For the navigation itself, create a new file inside components nav.njk. Then, link the page with /page.

nav.njk

<nav>
  <ul>
    <li>
      <a href="/about">
        <p>
          About
        </p>
      </a>
    </li>
    <li>
      <a href="/projects">
        <p>
          Projects
        </p>
      </a>
    </li>
    <li>
      <a href="/contact">
        <p>
          Contact
        </p>
      </a>
    </li>
  </ul>
</nav>

Don't forget to link the layout template in your newly created pages!

In this example, I positioned the navigation items at a random position with a few lines of JS.

const navItem = document.querySelectorAll('li');

navItem.forEach((item => {
  const xPos = window.innerHeight - item.clientHeight;
	const yPos = window.innerWidth - item.clientWidth;

  item.style.top = `${Math.floor(Math.random() * xPos)}px`;
  item.style.left = `${Math.floor(Math.random() * yPos)}px`;
}));


Styling

In all my websites I like to reset the CSS first so that headings, buttons and so on don't come with their default styling. You can use something like normalize.css. Also, I always add some basic styles which you can see here.

The styling is nothing crazy in this example, feel free to style it however you like!

@import './base/globalStyles.css';
@import './base/normalize.css';
@import './base/variables.css';

section {
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  overflow: hidden;
}

h1 {
  transform: translate3d(-12vw, 10vw, 0);
  line-height: var(--lh);
  letter-spacing: var(--ls);
}

span {
  font-size: 50vw;
}

nav {
  position: absolute;
  width: 100vw;
  height: 100vh;
  z-index: 1;
  overflow: hidden;
}

p {
  position: absolute;
  color: black;
  background: white;
  border-radius: 50%;
  height: 20vw;
  width: 20vw;
  display: flex;
  justify-content: center;
  align-items: center;
  transform: rotate(-45deg);
}

p:hover {
  background: black;
  color: white;
  transform: rotate(-405deg);
  transition: transform 0.4s ease;
}

.transition-wrapper {
  position: absolute;
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 2;
  pointer-events: none;
}

.circle {
  background: white;
  width: 50vw;
  height: 50vw;
  border-radius: 50%;
  transform: scale(0);
}

Page Transitions

Page Transitioin

Barba.js is a great fit for page transitions.

npm install --save-dev @barba/core


For Barba to work, you must specify some additional attributes in layout.njk. Also, each page needs to have a unique name.

<body data-barba="wrapper">

<main data-barba="container" data-barba-namespace="{{ barbaTitle }}">

index.njk

---
layout: layout.njk
barbaTitle: Home
---

<section>
  <h1>
    <span>Hello</span>
    <span>World</span>
  </h1>
</section>

When a user changes pages, I want to display a circle that scales. This is the transition component.

transition.njk

<section class="transition-wrapper">
  <div class="circle"></div>
</section>

For the transition itself, create a new JS file transitions.js and import and call it in index.js

import {pageTransition} from './components/transitions'

pageTransition();

To animate our transitions, we use GSAP.

npm install gsap

Let's initialize Barba inside transitions.js

import gsap from 'gsap';
import barba from '@barba/core';

export const pageTransition = () => {
  barba.init({
    sync: true,
    transitions: [
      {
        async leave(data) {

        },
        async enter(data) {

        },
      },
    ],
  });
};

First, I want to animate the circle. For this we need two functions that define what happens when the page is left and entered.

When the user leaves a page, the circle scales very large and when the user enters the page, the circle is reset to a scale of zero, so it's invisible.

const circleOut = () =>
  gsap.to('.circle', {
    scale: 5,
    duration: 0.9,
  });
    
const circleIn = () => {
  const circle = document.querySelector('.circle');
  circle.style.transform = 'scale(0)';
}

We can also animate the entire content on the page. Make two new functions again and animate the main part.

const contentOut = () =>
  gsap.to('main', {
    scale: 1.5,
    duration: 0.6,
  });
    
const contentIn = () =>
  gsap.fromTo('main', {
    scale: 1.3,
  }, {
    scale: 1,
  });

Let's put everything together.

import gsap from 'gsap';
import barba from '@barba/core';

export const pageTransition = () => {
  const circleOut = () =>
    gsap.to('.circle', {
      scale: 5,
      duration: 0.9,
    });
    
  const circleIn = () => {
    const circle = document.querySelector('.circle');
    circle.style.transform = 'scale(0)';
  }
  
  const contentOut = () =>
    gsap.to('main', {
      scale: 1.5,
      duration: 0.6,
    });
    
  const contentIn = () =>
    gsap.fromTo('main', {
      scale: 1.3,
    }, {
      scale: 1,
    });


  barba.init({
    sync: true,
    transitions: [
      {
        async leave(data) {
          circleOut();
          await contentOut();
          data.current.container.remove();
        },
        async enter() {
          circleIn();
          await contentIn();
        },
      },
    ],
  });
};

We are done! If you want to deploy your website, just run npm run build. Snowpack will generate a build folder in your root directory. An easy way to get the site online is to use the drag and drop function with Netlify!