07Lắc nhẹ nhấn mạnh
Cả cụm chữ xoay qua lại rất nhẹ giữa rotate 1-2 độ và translateX 2px, yếu hơn hẳn một cú rung báo lỗi, chỉ đủ kéo mắt liếc qua một lần. 60% đầu vòng lặp đứng yên hoàn toàn, chữ chỉ lắc trong khoảng 66-90%.
Dùng choChữ gợi ý cạnh badge mới, kéo chú ý tới nhãn giảm giá
rotate 1~2degtranslateX 2px짧은 왕복

<main class="stage">
<p class="wiggle" data-split>Hàng mới về</p>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop;
$easing: $ease-out;
$angle: 2deg; // 회전 폭 — 에러 흔들림보다 훨씬 작다
@include stage($stage-orange);
.wiggle {
margin: 0;
font-weight: 800; font-size: clamp(26px, 8vw, 40px); line-height: 1.2; font-family: inherit;
color: $subject-cream;
text-align: center;
animation: wiggle $duration $easing infinite;
}
.wiggle .letter {
display: inline-block;
animation: letter-blur-in $duration $easing infinite;
animation-delay: calc(var(--i) * 35ms + var(--n) * 60ms);
}
@keyframes wiggle {
0%, 60%, 100% { transform: rotate(0) translateX(0); }
66% { transform: rotate(-#{$angle}) translateX(-2px); }
72% { transform: rotate(#{$angle}) translateX(2px); }
78% { transform: rotate(-#{$angle}) translateX(-2px); }
84% { transform: rotate(#{$angle}) translateX(2px); }
90% { transform: rotate(0) translateX(0); }
}
@keyframes letter-blur-in {
0%, 4% { filter: blur(8px); opacity: .3; }
22%, 100% { filter: blur(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.wiggle { animation: none; transform: none; }
.wiggle .letter { animation: none; filter: none; opacity: 1; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.wiggle {
margin: 0;
font-weight: 800;
font-size: clamp(26px, 8vw, 40px);
line-height: 1.2;
font-family: inherit;
color: #fff7e6;
text-align: center;
animation: wiggle 2s ease-out infinite;
}
.wiggle .letter {
display: inline-block;
animation: letter-blur-in 2s ease-out infinite;
animation-delay: calc(var(--i) * 35ms + var(--n) * 60ms);
}
@keyframes wiggle {
0%, 60%, 100% {
transform: rotate(0) translateX(0);
}
66% {
transform: rotate(-2deg) translateX(-2px);
}
72% {
transform: rotate(2deg) translateX(2px);
}
78% {
transform: rotate(-2deg) translateX(-2px);
}
84% {
transform: rotate(2deg) translateX(2px);
}
90% {
transform: rotate(0) translateX(0);
}
}
@keyframes letter-blur-in {
0%, 4% {
filter: blur(8px);
opacity: 0.3;
}
22%, 100% {
filter: blur(0);
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.wiggle {
animation: none;
transform: none;
}
.wiggle .letter {
animation: none;
filter: none;
opacity: 1;
}
}(function(){
function splitLetters(el){
var text = el.textContent;
el.textContent = "";
Array.from(text).forEach(function(ch, i){
var span = document.createElement("span");
span.className = "letter";
var seed = Math.sin((i + 1) * 12.9898) * 43758.5453;
var noise = seed - Math.floor(seed);
span.style.setProperty("--i", i);
span.style.setProperty("--n", noise.toFixed(3));
span.textContent = ch === " " ? " " : ch;
el.appendChild(span);
});
}
document.querySelectorAll("[data-split]").forEach(splitLetters);
})();import type { CSSProperties } from "react";
import styles from "./WiggleEmphasis.module.scss";
type Vars = CSSProperties & { "--i"?: number; "--n"?: number };
function noise(i: number): number {
const s = Math.sin((i + 1) * 12.9898) * 43758.5453;
return Number((s - Math.floor(s)).toFixed(3));
}
export type WiggleEmphasisProps = {
text: string;
/** 왕복 한 주기 (기본 2000ms) */
duration?: number;
color?: string;
};
export default function WiggleEmphasis({ text, duration = 2000, color = "#fff7e6" }: WiggleEmphasisProps) {
const vars = { "--duration": `${duration}ms`, "--color": color } as CSSProperties;
const chars = Array.from(text);
return (
<p className={styles.wiggle} style={vars}>
{chars.map((ch, i) => (
<span key={i} className={styles.letter} style={{ "--i": i, "--n": noise(i) } as Vars}>
{ch === " " ? " " : ch}
</span>
))}
</p>
);
}07 Lắc nhẹ nhấn mạnh — Diện tích đo được 4.14% · khung hình chuyển động 15/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 CSS Text Animation Effects — dán 1 dòng là chạy
Bài viết chứa demo này
9 CSS Text Animation Effects — dán 1 dòng là chạyCSS text animation effects là các chuyển động chữ dựng thuần bằng CSS, không cần JavaScript, giúp tiêu đề hay câu chữ xu Xem tất cả demo trong bài này


