Create a Star Wars-Like Crawl Using CSS & JavaScript
Tags: typography, frontend, css, javascript
Published: 2023-01-26 13:20:12

If you're a web developer (or want to be), then you're probably a geek and you like Star Wars. Well, how would you like to have the Star Wars opening crawl as a background for your next website? 😁

The answer, of course, is YAAAAAS! Read on....

I tried many iterations of this (because I really really wanted it). First I tried using a stock video of traveling through space, but the file size was too large and Google Lighthouse complained. I tried compressing to `webm` format but that only works on some devices (not iOS) and looks terrible and pixelated.

After hours of playing around, I decided to use the HTML5 canvas element and JavaScript to render the stars and CSS keyframes to render the 3D crawling text. As for the font, I downloaded a mock-Star Wars font called "Deathstar". You can find those [here](https://www.fontspace.com/category/star-wars).

Check out [this working example](https://designly3.vercel.app/examples/starwars/index.html).

And now for the code:

```js
// stars.js

// Get canvas element by its ID
var field = document.getElementById("field");

// Make sure field canvas exists
var f = (typeof field.getContext === 'function') ? field.getContext("2d") : null;

// Config
var stars = {};
var starIndex = 0;
var numStars = 0;
var acceleration = 1;
var starsToDraw = (field.width * field.height) / 200;

function Star() {
    this.X = field.width / 2;
    this.Y = field.height / 2;

    this.SX = Math.random() * 10 - 5;
    this.SY = Math.random() * 10 - 5;

    var start = 0;

    if (field.width > field.height)
        start = field.width;
    else
        start = field.height;

    this.X += this.SX * start / 10;
    this.Y += this.SY * start / 10;

    this.W = 1;
    this.H = 1;

    this.age = 0;
    this.dies = 500;

    starIndex++;
    stars[starIndex] = this;

    this.ID = starIndex;
    this.C = "#ffffff";
}

Star.prototype.Draw = function () {
    if (!f) {
        console.log('Could not load canvas element');
        return;
    }
    this.X += this.SX;
    this.Y += this.SY

    this.SX += this.SX / (50 / acceleration);
    this.SY += this.SY / (50 / acceleration);

    this.age++;

    if (this.age == Math.floor(50 / acceleration) | this.age == Math.floor(150 / acceleration) | this.age == Math.floor(300 / acceleration)) {
        this.W++;
        this.H++;
    }

    if (this.X + this.W < 0 | this.X > field.width |
        this.Y + this.H < 0 | this.Y > field.height) {
        delete stars[this.ID];
        numStars--;
    }

    f.fillStyle = this.C;
    f.fillRect(this.X, this.Y, this.W, this.H);
}

field.width = window.innerWidth;
field.height = window.innerHeight;

function draw() {
    if (!f) {
        console.log('Could not load canvas element');
        return;
    }

    if (field.width != window.innerWidth)
        field.width = window.innerWidth;
    if (field.height != window.innerHeight)
        field.height = window.innerHeight;

    // The alpha value can be adjusted to create a stream effect
    f.fillStyle = "rgba(0, 0, 0, 0.6)";

    f.fillRect(0, 0, field.width, field.height);

    for (var i = numStars; i < starsToDraw; i++) {
        new Star();
        numStars++;
    }

    for (var star in stars) {
        stars[star].Draw();
    }
}

// Modify interval to adjust speed
if (f) setInterval(draw, 40);
```

And the CSS:

```css
/* global.css */

body {
    margin: 0;
    padding: 0;
    background-color: black;
}

main {
    width: 100vw;
    height: 100vh;
}

#field {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: -3;
}

#crawl {
    position: fixed;
    top: 0;
    bottom: 0;
    z-index: -2;
    font-family: Deathstar, Verdana, Geneva, Tahoma, sans-serif;
    text-align: center;
    color: white;
    font-size: 4rem;
    perspective: 200px;
}

#crawl p {
    transform: rotateX(20deg);
    animation: starwars 30s ease-in;
    animation-iteration-count: infinite;
    margin: 0 auto;
}

#crawl p span {
    font-size: 150%;
}

#overlay {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: -1;
    background-color: rgba(0, 0, 0, 0.4);
}

@keyframes starwars {
    0% {
        transform: rotateX(20deg) translateY(750px);
    }

    100% {
        transform: rotateX(20deg) translateY(-1500px);
    }
}

@keyframes starwars1200 {
    0% {
        transform: rotateX(20deg) translateY(565px);
    }

    100% {
        transform: rotateX(20deg) translateY(-1500px);
    }
}

@media only screen and (max-width: 1200px) {
    #crawl {
        font-size: 1rem;
    }

    #crawl p {
        animation: starwars1200 30s ease-in;
    }
}

@font-face {
    font-family: 'Deathstar';
    font-style: normal;
    font-weight: 100;
    font-display: swap;
    src: url('https://cdn.designly.biz/fonts/deathstar/death_star-webfont.eot');
    src: local(''),
        url('https://cdn.designly.biz/fonts/deathstar/death_star-webfont.eot?#iefix') format('embedded-opentype'),
        url('https://cdn.designly.biz/fonts/deathstar/death_star-webfont.woff2') format('woff2'),
        url('https://cdn.designly.biz/fonts/deathstar/death_star-webfont.woff') format('woff'),
        url('https://cdn.designly.biz/fonts/deathstar/deathstar.otf') format('opentype'),
        url('https://cdn.designly.biz/fonts/deathstar/death_star-webfont.svg') format('svg');
}
```

The CSS could definitely use some fine tuning, including additional breakpoints. The downside to this code is that you have to adjust the CSS if you change the length of the text. But if your text is static then this should work nicely.

And last but not least, the HTML:

```html
<html lang="en">

<head>
    <meta name="robots" content="index, follow" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=yes" />
    <link rel="stylesheet" href="css/global.css" />
</head>

<body>
    <main>
        <canvas id="field"></canvas>
        <div id="crawl">
            <p>
                <span>Episode IV</span>
                <br /><br />
                A New Hope
                <br /><br />
                It is a period of civil war.
                Rebel spaceships, striking
                from a hidden base, have won
                their first victory against
                the evil Galactic Empire.
                <br /><br />
                During the battle, Rebel
                spies managed to steal secret
                plans to the Empire's
                ultimate weapon, the DEATH
                STAR, an armored space
                station with enough power to
                destroy an entire planet.
                <br /> <br />
                Pursued by the Empire's
                sinister agents, Princess
                Leia races home aboard her
                starship, custodian of the
                stolen plans that can save
                her people and restore
                freedom to the galaxy....
            </p>
        </div>
        <div id="overlay"></div>
    </main>
</body>

<script src="js/stars.js"></script>

</html>
```

I hope you found this article helpful. You're welcome to use my copy of the Deathstar font from my CDN for non-commercial purposes only. Otherwise, please download and host your own. If you're not sure how to do that, check out [this article](https://designly.biz/blog/post/how-to-use-google-fonts-in-a-way-google-likes-seo).

You can find the code for this example on [GitHub](https://github.com/designly1/starwars-example). Feel free to download or clone it.

For more great information about web dev and systems administration, please read the [Designly Blog](https://designly.biz/blog). 🙏