How I Built the Homepage Typing Effect
When I open the homepage, I 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 animationData 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. Refreshing the homepage shows 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. Most examples online are 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. The same quote can appear twice in a row if 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 Network5. Start typing
var typed = new Typed(".description .typed", {
strings: [text],
typeSpeed: 20,
startDelay: 300,
showCursor: true,
});strings: text to typetypeSpeed: 20ms per characterstartDelay: 300ms delay before startingshowCursor: 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 it’s 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.