Guillermo Esteves's profile picture

Guillermo Esteves

The Star Wars opening crawl in HTML & CSS

Screenshot of the Star Wars opening crawl in a Safari window.
I’m very proud of this technological terror I’ve constructed.

I’m done: the Star Wars opening crawl, built using only HTML & CSS. Caveats: It only works in Safari 5 and the WebKit Nightly. Nothing else supports the CSS and 3D transforms and animations I used (yet), but I just wanted to see if it could be done. Here’s how it works:

The first step is setting up the stage where the opening crawl will render:

body {
  background: black url("https://s3.amazonaws.com/csstarwars/background.png");
}

#stage {
  height: 600px;
  width: 1000px;
  overflow: hidden;
  margin: 0 auto;
  -webkit-perspective-origin: center 300px;
  -webkit-perspective: 800;
}

Most of it should be fairly self-explanatory: I’m setting a background image (the stars) on the body of the page, and defining a container of fixed dimensions for the crawl.

The critical properties here are -webkit-perspective-origin and -webkit-perspective. The former defines the point of view of the observer, making them watch from the horizontal center of the container and 300px down from the vertical center. The latter defines the distance of the observer along the Z axis. The actual values were a bit of guesswork to match the perspective seen in the movie.

Next up is the “a long time ago, in a galaxy far, far away” text, which is styled as follows:

#far-far-away {
  color: rgb(75,213,238);
  font-family: "FranklinGothicBookRegular", sans-serif;
  font-size: 48px;
  line-height: 1.5;
  position: absolute;
  top: 200px;
  left: 190px;
  opacity: 0;
  -webkit-animation: fade-in-out 6s linear;
  -webkit-animation-iteration-count: 1;
  -webkit-animation-delay: 5s;
}

@-webkit-keyframes fade-in-out {
  0%   { opacity:0; }
  16%  { opacity:1; }
  84%  { opacity:1; }
  100% { opacity:0; }
}

I’m using the @-webkit-keyframes rule to fine-tune the effect of the text fading in and out, to closely match its appearance in the movie. Specifically, the entire effect lasts 6 seconds, with the text fully visible 16% in (about a second) and starting to fade out again at 84% (or 5 seconds in). The animation starts playing on a 5-second delay, and only happens once.

Now, this is where the fun begins: Animating the Star Wars logo, followed by the opening crawl. Unfortunately, there’s no “Star Wars” font I could use, so the logo is simply an image, animated like so:

img {
  opacity: 0;
  position: absolute;
  top: 100px;
  width: 1000px;
  -webkit-transform-origin: center center;
  -webkit-animation: logo 25s linear;
  -webkit-animation-iteration-count: 1;
  -webkit-animation-delay: 12s;
  -webkit-animation-timing-function: ease-in;
}

@-webkit-keyframes logo {
  0%   { -webkit-transform: translateZ(0);         opacity:0;  }
  0.1% { -webkit-transform: translateZ(0);         opacity:1;  }
  50%  { -webkit-transform: translateZ(-50000px);  opacity:1;  }
  60%  { -webkit-transform: translateZ(-60000px);  opacity:0;  }
  100% { -webkit-transform: translateZ(-100000px); opacity:0;  }
}

The logo animation is on a 12-second delay so it starts at the appropriate time, taking 25 seconds in total and playing only once. The keyframes are fine-tuned to replicate the movie, where it appears to fade in and accelerate quickly, and fade out slowly when the logo is way out in the distance (this took a lot of trial and error, as you can imagine).

As for the crawl itself:

#crawl {
  color: rgb(252,223,43);
  font-family: "FranklinGothicDemiRegular", sans-serif;
  text-align: center;
  font-size: 36px;
  opacity: 0;
  -webkit-animation: crawl 120s linear;
  -webkit-animation-iteration-count: 1;
  -webkit-animation-delay: 16s;
  -webkit-transform-style: preserve-3d;
}

#crawl p.title {
  font-family: "FranklinGothicMediumCondRegul", sans-serif;
  text-transform: uppercase;
  font-size: 96px;
  -webkit-transform: scaleX(0.6);
}

#crawl p {
  white-space: pre;
}

@-webkit-keyframes crawl {
  0%   { -webkit-transform: rotateX(80deg) translateZ(200px) translateY(1100px);  opacity:1; }
  40%  { -webkit-transform: rotateX(80deg) translateZ(200px) translateY(-340px);  opacity:1; }
  80%  { -webkit-transform: rotateX(80deg) translateZ(200px) translateY(-1780px); opacity:0; }
  100% { -webkit-transform: rotateX(80deg) translateZ(200px) translateY(-2500px); opacity:0; }
}

Similar to the previous animation, I’m setting appropriate delay and duration lengths to ensure the crawl appears on the screen and disappears into the distance at the correct time. The key here is the translateY properties, which, combined with the perspective properties I had previously set on the container, ensure that the crawl’s vanishing point is in the correct place. The -webkit-transform-style set to preserve-3d allows me to rotate the crawl correctly in 3D space, and not have it be flattened on its plane.

And with that, the Star Wars opening crawl starts playing, and should match pretty closely the theatrical version. But there’s one thing missing: John Williams’s legendary score! Hilariously, despite doing everything else in pure HTML and CSS, we’ll need a single line of JS to ensure the music in the audio element on the page starts playing at the correct time, 12 seconds after the page loads:

setTimeout("document.getElementById('audio').play()", 12000);

(Update: I’ve removed the audio file to prevent any legal complications).

And that’s it! It’s pretty impressive how much you can accomplish these days with nothing but standards-based HTML and CSS. Here’s hoping these new CSS3 features make it to other browsers soon enough.


Update: After almost ten years hosting this on my website, I’ve moved it over to CodePen, go check it out there.