Skip to content

Instantly share code, notes, and snippets.

@yinheli
Created April 9, 2026 15:41
Show Gist options
  • Select an option

  • Save yinheli/f2812f57a290f17f1639330387f75f6c to your computer and use it in GitHub Desktop.

Select an option

Save yinheli/f2812f57a290f17f1639330387f75f6c to your computer and use it in GitHub Desktop.
Greeting animation effect - per-character blur/fade/scale transition, inspired by https://cai.im/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Greeting Animation</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: #fafafa;
color: #1a1a1a;
}
.greeting-container {
position: relative;
height: 1.5em;
display: flex;
align-items: center;
font-size: 3rem;
font-weight: 600;
}
.greeting-word {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
display: flex;
pointer-events: none;
visibility: hidden;
z-index: 1;
}
.greeting-word.active {
visibility: visible;
z-index: 2;
}
.greeting-word.previous {
visibility: visible;
z-index: 1;
}
.greeting-char {
display: inline-block;
transition: all 800ms cubic-bezier(0.23, 1, 0.32, 1);
opacity: 0;
filter: blur(12px);
transform: translateY(-10px) scale(0.9);
transition-delay: 0ms;
}
.greeting-word.active .greeting-char {
opacity: 1;
filter: blur(0);
transform: translateY(0) scale(1.1);
}
</style>
</head>
<body>
<div class="greeting-container" id="greeting"></div>
<script>
const texts = ["Hello!", "Hola!", "你好!", "Bonjour!", "こんにちは!", "안녕!", "Ciao!"];
const INTERVAL = 3000;
const CHAR_DELAY = 40;
const container = document.getElementById("greeting");
let current = 0;
let previous = -1;
// Build DOM: one div per word, one span per character
const wordEls = texts.map((text) => {
const div = document.createElement("div");
div.className = "greeting-word";
text.split("").forEach((char) => {
const span = document.createElement("span");
span.className = "greeting-char";
span.textContent = char === " " ? "\u00A0" : char;
div.appendChild(span);
});
container.appendChild(div);
return div;
});
function update() {
wordEls.forEach((el, i) => {
el.classList.toggle("active", i === current);
el.classList.toggle("previous", i === previous);
// Set staggered delay only on active word's chars
const chars = el.querySelectorAll(".greeting-char");
chars.forEach((ch, ci) => {
ch.style.transitionDelay = i === current ? `${ci * CHAR_DELAY}ms` : "0ms";
});
});
}
update();
setInterval(() => {
previous = current;
current = (current + 1) % texts.length;
update();
}, INTERVAL);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment