How I Built the Homepage Typing Effect

When you open the homepage, you see text being typed out character by character. Not complicated, but many people ask how it’s done.

This article explains the implementation.

How It Works

When the homepage loads, it picks a random quote from a JSON file, and uses Typed.js to simulate typing.

Homepage loads
  ↓
main.js detects it's the homepage
  ↓
Determine language (Chinese/English)
  ↓
Pick a random entry from /data/quotes.json
  ↓
Pass to Typed.js, start typing animation

Data Source: quotes.json

Quotes are stored in static/data/quotes.json.

[
  {
    "en": "Hope is a good thing, maybe the best of things, and no good thing ever dies.",
    "zh": "希望是美好的,也许是世间最美好的事物,而美好的事物永不消逝。",
    "author": "The Shawshank Redemption"
  },
  {
    "en": "Get busy living or get busy dying.",
    "zh": "忙着活,或忙着死。",
    "author": "The Shawshank Redemption"
  }
]

Each entry has three fields:

  • en: English quote
  • zh: Chinese translation
  • author: Source (movie, book, etc.)

Currently about 700+ quotes from various movies. Refresh the homepage and you get a different one each time.

The file is in static/, Hugo copies it to public/data/quotes.json during build. Frontend loads it via AJAX.

Typed.js Library

Using Typed.js, a library built for typing animations. No framework needed, works with jQuery.

Loaded in layouts/partials/js.html:

{{ if .IsHome }}
<script src="{{ absURL "/lib/typed.js" }}"></script>
{{ end }}

Only loaded on the homepage (IsHome), about 15KB. Other pages don’t download it.

One thing: Typed.js has two major versions with completely different APIs. Examples you find online might be for the old version and won’t work with the new one. I spent a while debugging this before realizing I was looking at the wrong docs.

Typing Logic: main.js

The core logic is in static/js/main.js:

$(function () {
  var isHome = /^\/(en|zh)?\/?$/.test(location.pathname);
  if (!isHome) return;

  var lang = 'en';
  if (location.pathname.indexOf('/zh') === 0) {
    lang = 'zh';
  }

  $.get("/data/quotes.json", function (data) {
    if (!data || !data.length) return;
    var idx = Math.floor(Math.random() * data.length);
    var quote = data[idx];
    if (!quote) return;

    var text = (lang === 'zh' ? quote.zh : quote.en) + '\n—— ' + (quote.author || '');
    var typed = new Typed(".description .typed", {
      strings: [text],
      typeSpeed: 20,
      startDelay: 300,
      showCursor: true,
    });
  });
});

Step by step:

1. Check if it’s the homepage

var isHome = /^\/(en|zh)?\/?$/.test(location.pathname);

Matches /, /en/, /zh/. Only runs on homepage, not on article pages.

2. Determine language

var lang = 'en';
if (location.pathname.indexOf('/zh') === 0) {
  lang = 'zh';
}

Chinese homepage /zh/ uses quote.zh, English homepage /en/ uses quote.en.

3. Pick a random quote

var idx = Math.floor(Math.random() * data.length);
var quote = data[idx];

Completely random, no history tracking. You might see the same quote twice in a row if you’re unlucky.

4. Build the text

var text = (lang === 'zh' ? quote.zh : quote.en) + '\n—— ' + (quote.author || '');

Text + line break + source. For example:

And the rest is just noise.
—— The Social Network

5. Start typing

var typed = new Typed(".description .typed", {
  strings: [text],
  typeSpeed: 20,
  startDelay: 300,
  showCursor: true,
});
  • strings: text to type
  • typeSpeed: 20ms per character
  • startDelay: 300ms delay before starting
  • showCursor: blinking cursor

HTML Structure

The homepage template has a container for the typing content:

<div class="description">
  <p class="typed"></p>
</div>

Typed.js writes into this element character by character.

Why quotes.json Instead of an API

Considered fetching quotes from an API daily. But the homepage is a static file, don’t want to depend on an external API. If the API goes down, the homepage has a blank spot.

So the quotes are bundled in a JSON file, deployed with the build. No external dependencies, always works.

Downside: quotes are static, can’t auto-update daily. Need to modify JSON and redeploy to add new ones. But 700+ quotes, enough for a while.

Also note: the quotes.json file path matters. If you put it in the wrong place, the frontend can’t find it after build. static/data/ is the safest spot — Hugo copies it directly to public/data/.

Performance

  • Typed.js only loads on homepage, not on other pages
  • quotes.json is about 30KB, cached by browser after first load
  • quotes.json is requested after homepage renders, doesn’t block first paint
  • Typing animation uses requestAnimationFrame, doesn’t block main thread

Overall, performance impact is near zero.

Article Link:

/en/archive/blog-architecture-typing-effect/

# Related Articles