Developing the Star Wars opening crawl in HTML/CSS

Jan 15, 2018

To celebrate Star Wars: The Last Jedi coming to theaters I decided to implement a fun easter egg on my personal website.

I wanted to display the saga's famous opening crawl when a user inputs the Konami Code.

This blog post will show every step that was necessary to achieve it, from building it in HTML/CSS, to implementing it in an Angular project and adding final touches such as John Williams' famous track.

First step: Catching the Konami Code

I decided to catch my user's input from the root component of my website: the AppComponent.

I headed over to app.component.ts and added the following:

// The Konami Code itself
konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];

// Our user's position in the code
konamiCodePosition = 0;

// True once the code is valid, false otherwise
validCode = false;

// Catch keydown events
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
  var requiredKey = this.konamiCode[this.konamiCodePosition];

  // If the input equals to the required key
  if (event.key == requiredKey) {
    // Go to the next step of the Konami code
    this.konamiCodePosition++;

    // If we are at the end of it, validate the code and reset the position to 0
    if (this.konamiCodePosition == this.konamiCode.length) {
      this.validCode = true;
      this.konamiCodePosition = 0;
    }
  } else {
    // The input was false, set the validity to false and the position to 0
    this.validCode = false;
    this.konamiCodePosition = 0;
  }
}

Pretty simple, huh? This could be done in many other and better ways, but that will do the trick.

Second step: Displaying the right component

Once the code has been validated, I wish to display the component that will contain the crawl itself.

Firstly, I create the new component:
ng g component star-wars

Then I use the Angular directive *ngIf to hide or display it.

In app.component.html:

<div class="parent" *ngIf="!validCode">
  <router-outlet></router-outlet>
</div>

<app-star-wars *ngIf="validCode"></app-star-wars>

As you can see, if the code is invalid we display the website's usual content (the router-outlet) otherwise we display our newly created component !

Third step: Creating the crawl

This was tricky at first, but once you break it down into several parts it becomes easy to understand.

The crawl contains 5 essential elements:

  • The background
  • The intro phrase
  • The logo
  • The text
  • The music

All of these steps take place in the newly created StarWarsComponent.

Creating the background

Let's do something a bit more original than adding a picture of space. Let's generate stars randomly !

In star-wars.component.html:

<div id="space"></div>

And in star-wars.component.ts:

// Sets the number of stars we wish to display
readonly numStars = 100;

// Inject what we need to access the native document variable
constructor(@Inject(DOCUMENT) private document: any) { }

ngOnInit() {
  // For every star we want to display
  for (let i = 0; i < this.numStars; i++) {
    // Create a new element
    let star = this.document.createElement("div");
    // Set its style to resemble a star
    star.style.position = "absolute";
    star.style.width = "1px";
    star.style.height = "1px";
    star.style.backgroundColor = "white";
    // Get random positions on the screen and set them
    var xy = this.getRandomPosition();
    star.style.top = xy[0] + 'px';
    star.style.left = xy[1] + 'px';
    // Append our new star
    this.document.getElementById("space").append(star);
  }
}

// Gets random x, y values based on the size of the container
getRandomPosition() {
  var y = window.innerWidth;
  var x = window.innerHeight;
  var randomX = Math.floor(Math.random()*x);
  var randomY = Math.floor(Math.random()*y);
  return [randomX,randomY];
}

And finally in star-wars.component.sass:

#space
    background-color: black
    width: 100%
    height: 100%
    position: absolute

Tada ! We now have a beautiful background to display the crawl on.

It looks like this (note that the stars might not be visible on these pictures as they are a single pixel wide):

Adding the famous intro phrase

A long time ago in a galaxy far, far away....

Everyone has already heard, seen or whispered this phrase in their lifetime, so let's add it to our component (with the necessary effects).

In star-wars.component.html:

...

<section class="intro">
  A long time ago, in a galaxy far,<br />
  far away....
</section>

In star-wars.component.sass:

...

// Center the section element
section
    position: absolute
    top: 45%
    left: 50%
    z-index: 1

// Set the animation, color, size and hide the text
.intro
    animation: intro 6s ease-out 1s
    margin: 0 0 0 (- 15em / 2)
    color: rgb(75, 213, 238)
    font-weight: 400
    font-size: 300%
    width: 15em
    opacity: 0

@keyframes intro
    0%
        opacity: 0
    20%
        opacity: 1
    90%
        opacity: 1
    100%
        opacity: 0

Result:

The logo is vital to this opening sequence, here's how I added it.

In star-wars.component.html:

...

<section class="logo">
  <!-- SVG GOES HERE -->
</section>

The SVG being a very long file, I have uploaded it here for you to copy and paste.

In star-wars.component.sass:

...

// Set the animation & hide the logo
.logo
    animation: logo 9s ease-out 9s
    margin: 0 0 0 (- 18em / 2)
    opacity: 0

    svg
        width: inherit

// Scale the logo down and maintain it centered
@keyframes logo
    0%
        width: 18em
        margin: (- 18em / 2) 0 0 (- 18em / 2)
        transform: scale(2.75)
        opacity: 1
    50%
        opacity: 1
        width: 18em
        margin: (- 18em / 2) 0 0 (- 18em / 2)
    100%
        opacity: 0
        transform: scale(0.1)
        width: 18em
        margin: (- 18em / 2) 0 0 (- 18em / 2)

And there we go, our beautifully animated logo:

Adding the scrolling text

It's probably the most essential part of the crawl but it's rather easy to implement.

In star-wars.component.html:

...

<!-- Change the text to your liking -->
<div id="board">
  <div id="content">
    <p id="title">Episode I</p>
    <p id="subtitle">THE CODER'S MENACE</p>
    <br />
    <!-- And make it cheesy ! -->
    <p>
      Turmoil has engulfed the Galactic Republic as Christopher Kade finishes
      studying to become a master in his trade.
    </p>
    <p>
      Hoping to resolve the matter with side-projects and research, he retreated
      to the small planet of Ireland for the coming year.
    </p>
    <p>
      As his skills keep on evolving through constant learnings, his passion for
      open-source technologies grows with it...
    </p>
  </div>
</div>

In star-wars.component.sass:

...

p
  color: #FFFF82

// Set the font, lean the board, position it
#board
  font-family: Century Gothic, CenturyGothic, AppleGothic, sans-serif
  transform: perspective(300px) rotateX(25deg)
  transform-origin: 50% 100%
  text-align: justify
  position: absolute
  margin-left: -9em
  font-weight: bold
  overflow: hidden
  font-size: 350%
  height: 50em
  width: 18em
  bottom: 0
  left: 50%
  &:after
    background-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, transparent 100%)
    pointer-events: none
    position: absolute
    content: ' '
    bottom: 60%
    left: 0
    right: 0
    top: 0

// Set the scrolling animation and position it
#content
  animation: scroll 100s linear 16s
  position: absolute
  top: 100%

#title, #subtitle
  text-align: center

@keyframes scroll
    0%
        top: 100%
    100%
        top: -170%

And there we go !

Final touch: the music

What would Star Wars be without its music?

Since we have timed our animations in advance, it should be a piece of cake !

First, download the following .mp3 file and add it to your project's assets.

Then, in our html file, add:

<audio preload="auto" autoplay>
  <source
    src="./../../assets/audio/Star_Wars_original_opening_crawl_1977.mp3"
    type="audio/mpeg"
  />
</audio>

Which preloads the music as the page is loaded and plays it automatically.

And voilĂ , everything should work as expected.

Final thoughts

You can check out the final product on my website by inputing the Konami Code (OUTDATED). It really was a blast to develop and I hope it shows how much possibilities you have with such a basic kit.

Thanks for reading,
@christo_kade