CHRISTOPHER KADE

Developing the Star Wars opening crawl in HTML/CSS

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:


Displaying the logo

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