CSS 타이핑 효과 9종 — 한 줄 복붙으로
CSS 타이핑 효과는 자바스크립트 라이브러리 없이 CSS만으로 문장이 한 글자씩 나타나거나 지워지는 것처럼 보이게 만드는 짧은 애니메이션입니다. 아래 아홉 개는 각각 다른 CSS 속성 하나에 기대고 있고, 필요한 건 격자에서 눈으로 고른 뒤 zip으로 통째로 가져가면 됩니다.
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
- 01 커서 깜빡임
- 02 steps() 타자 효과
- 03 다국어 글자 조합 타이핑
- 04 여러 줄 타이핑
- 05 지우고 다시 타이핑
- 06 AI 답변 스트리밍 느낌
- 07 터미널 프롬프트
- 08 SVG 손글씨 효과
- 09 단어 교체 타이핑
아홉 개는 인기 순이 아니라 브라우저가 실제로 타이핑을 표현하는 방법 순으로 놓았습니다. 커서만 깜빡이는 것(01)에서 시작해 폭을 단계별로 늘리는 기본기(02), 그 기본기가 다국어에서 깨지는 지점을 고치는 방법(03), 여러 줄(04)과 삭제 후 재입력(05)으로 응용을 넓히고, AI 응답(06)과 터미널(07) 같은 실제 화면, SVG 선 그리기(08)와 단어 교체(09)로 마무리됩니다. 코드는 각 항목의 규칙만 남겼고, 마크업까지 다 붙은 완성 파일은 zip 안에 들어 있습니다.
01커서 깜빡임
타이핑이 끝난 자리에 문자 폭만한 막대를 두고 opacity를 두 값 사이에서 뚝뚝 끊어 전환하면 실제 텍스트 커서처럼 보입니다. 폭이나 위치는 전혀 건드리지 않아 레이아웃 재계산이 없습니다.
.line__caret {
width: 10px;
height: 1.3em;
background: #2f6df6;
animation: caret-blink 300ms steps(2, jump-none) infinite;
}
@keyframes caret-blink {
from { opacity: 1; }
to { opacity: 0; }
}
02steps() 타자 효과
width를 0에서 글자 수만큼의 ch로 steps()를 이용해 한 글자씩 끊어 늘리면 실제 타자기가 찍는 것과 같은 리듬이 나옵니다. 자간과 글자 폭이 완전히 고정폭이 아니라면 마지막 한두 글자가 살짝 밀려 보일 수 있습니다.
.type__text {
overflow: hidden;
white-space: nowrap;
width: 0;
border-right: .12em solid #17141a;
animation: type-loop 2s linear infinite;
}
@keyframes type-loop {
0%, 6% { width: 0; animation-timing-function: steps(10, end); }
58% { width: 10ch; }
88% { width: 10ch; }
94%, 100% { width: 0; }
}
03다국어 글자 조합 타이핑
steps()와 ch는 고정폭 글꼴을 전제해서 한글 자모 조합이나 이모지가 섞이면 한 스텝에 반 글자만 드러나는 경우가 생깁니다. 캔버스로 각 글자의 실제 픽셀 폭을 재서 그 경계에서만 clip-path가 멈추게 하면 어떤 언어가 섞여도 글자 중간에서 잘리지 않습니다.
var w = 0, cum = [0];
for (var i = 0; i < s.length; i++) {
w += ctx.measureText(s[i]).width;
cum.push(w);
}
var kf = cum.map(function (px) {
return { clipPath: 'inset(0 ' + (100 - px / w * 100) + '% 0 0)' };
});
el.animate(kf, { duration: 2000, iterations: Infinity });
04여러 줄 타이핑
줄마다 따로 keyframes를 두고 각자의 정지 구간과 steps() 구간을 퍼센트로 배치하면, 위 줄이 다 찍히는 시점에 맞춰 다음 줄이 이어서 시작하는 것처럼 보입니다. 세 줄 모두 같은 2초 루프를 공유하기 때문에 별도의 지연 값을 손으로 맞출 필요가 없습니다.
@keyframes l2-type {
0%, 10% { width: 0; animation-timing-function: steps(16, end); }
34% { width: 16ch; }
88% { width: 16ch; }
96%, 100% { width: 0; }
}
.lines__text--2 { animation: l2-type 2s ease-out infinite; }
05지우고 다시 타이핑
width가 0에서 끝까지 갔다가 다시 0으로 돌아오는 keyframes 하나에 타이핑과 삭제를 같이 담으면 자바스크립트 없이도 검색창 placeholder 같은 반복 문구를 만들 수 있습니다. 타이핑 구간과 삭제 구간에 각각 다른 지점에서 steps()를 다시 걸어야 두 방향 모두 끊어지는 느낌이 유지됩니다.
@keyframes search-loop {
0%, 6% { width: 0; animation-timing-function: steps(11, end); }
46% { width: 11ch; }
62% { width: 11ch; animation-timing-function: steps(11, end); }
96%, 100% { width: 0; }
}
06AI 답변 스트리밍 느낌
고정 간격 steps() 대신 글자마다 30에서 90ms 사이 무작위 값을 뽑아 누적한 뒤 그 비율을 offset으로 넘기면, 재생될 때마다 조금씩 다른 리듬으로 글자가 나타나 실제 토큰 스트리밍처럼 보입니다. 옆의 작은 원은 opacity와 transform만으로 따로 맥동해 응답이 아직 오고 있다는 신호를 더합니다.
var acc = 0, kf = [{ clipPath: 'inset(0 100% 0 0)', offset: 0 }];
for (var i = 0; i < n; i++) {
acc += delays[i];
kf.push({
clipPath: 'inset(0 ' + (100 - acc / sum * 78) + '% 0 0)',
offset: acc / sum * .78
});
}
el.animate(kf, { duration: 2000, iterations: Infinity });
07터미널 프롬프트
명령어 줄은 steps()로 찍히고, 그 애니메이션이 끝나는 지점 이후 구간에서만 응답 줄의 opacity가 0에서 1로 바뀌도록 같은 keyframes 퍼센트 안에 순서를 담으면 두 단계가 자연스럽게 이어집니다. 두 애니메이션이 같은 2초 루프를 쓰기 때문에 지연 값 없이도 명령어와 응답의 순서가 항상 지켜집니다.
@keyframes resp-fade {
0%, 44% { opacity: 0; }
50% { opacity: 1; }
82% { opacity: 1; }
90%, 100% { opacity: 0; }
}
.term__resp { animation: resp-fade 2s ease-out infinite; }
08SVG 손글씨 효과
path의 stroke-dasharray를 선 전체 길이보다 넉넉하게 두고 stroke-dashoffset을 그 값에서 0으로 트랜지션하면, 획이 순서대로 그려지는 손글씨 느낌이 납니다. 정확한 길이를 몰라도 되기 때문에 복잡한 곡선을 다시 그릴 때마다 값을 재는 수고가 없습니다.
path {
stroke-dasharray: 520;
stroke-dashoffset: 520;
animation: draw 2s ease-out infinite;
}
@keyframes draw {
0%, 6% { stroke-dashoffset: 520; }
55% { stroke-dashoffset: 0; }
80% { stroke-dashoffset: 0; }
96%, 100% { stroke-dashoffset: 520; }
}
09단어 교체 타이핑
같은 자리에 단어 세 개를 겹쳐 두고 각자의 keyframes에 자기 차례의 구간만 내주면, 문장의 나머지는 그대로 둔 채 그 구간에서만 폭 0에서 시작해 타이핑되고 다시 지워집니다. 텍스트 노드를 자바스크립트로 바꿔치기하지 않기 때문에 리액트에서도 동일한 keyframes 세 개만 그대로 옮기면 됩니다.
@keyframes w2 {
0%, 40% { width: 0; animation-timing-function: steps(3, end); }
46% { width: 3ch; }
68% { width: 3ch; animation-timing-function: steps(3, end); }
74%, 100% { width: 0; }
}
.swap__word--2 { position: absolute; left: 0; animation: w2 2s linear infinite; }
어디서 깨지는가 — 함정 하나
width를 max-content 같은 고유 크기 키워드로 트랜지션하면 언어별로 텍스트 길이가 달라져도 잘리지 않을 거라 기대하기 쉽지만, 실측해 보면 값이 계단식으로 나뉘지 않고 시작 값에서 끝 값으로 한 번에 건너뜁니다. 렌더 측정에서 스물세 프레임 중 단 두 프레임만 바뀐 것으로 잡혔고, 화면으로 봐도 타이핑이 아니라 텍스트가 순간 나타나는 것처럼 보였습니다. width와 max-content 사이의 보간은 interpolate-size 관련 MDN 문서가 다루는 영역이고, 이 값을 켜지 않은 브라우저에서는 이산 애니메이션으로 처리됩니다. 아직 지원이 갈리기 때문에(caniuse의 interpolate-size 지원표) 아홉 개 모두 자바스크립트로 실제 렌더 폭을 캔버스에서 재서 CSS 변수에 넣고, 그 변수를 픽셀 길이로 트랜지션하는 방식으로 고쳤습니다. 이렇게 고친 아홉 개 파일의 압축 비밀번호는 erv8c6eb이고, 아래 버튼으로 받을 수 있습니다.
두 번째로 짚을 것은 03의 clip-path 방식입니다. steps() 대신 캔버스로 잰 실제 글자 폭을 offset으로 직접 넘기기 때문에, 폰트가 아직 로드되지 않은 시점에 측정하면 좁은 폭 기준으로 어긋난 값이 그대로 굳어 버립니다. 폰트 로드가 끝난 뒤에 측정하도록 순서를 맞추는 것이 이 기법의 전제입니다.
접근성
아홉 개 전부 prefers-reduced-motion: reduce에서 트랜지션과 keyframes를 꺼서 완성된 문장이나 획만 즉시 보여주고, 자바스크립트로 만든 03·06의 clip-path 애니메이션도 같은 미디어 쿼리 안에서 el.animate() 호출 자체를 건너뛰도록 처리했습니다. 커서나 원처럼 장식만 담당하는 요소는 애니메이션을 끄는 대신 opacity를 고정값으로 남겨 정보가 사라지지 않게 했습니다. 세부 값 조합은 MDN의 prefers-reduced-motion 문서에 정리돼 있습니다.
@media (prefers-reduced-motion: reduce) {
.type__text { animation: none; width: var(--full, 10ch); border-right-color: transparent; }
}
CSS 텍스트·문자 관련 다른 주제는 CSS 애니메이션 카테고리 안에도 있고, 이 블로그가 데모를 어떻게 만들고 검증하는지는 소개 페이지에 적어 뒀습니다.
FAQ
CSS만으로 타이핑 효과를 만들 수 있습니까?
네, 01·02·04·05·07·08은 자바스크립트 없이 CSS keyframes만으로 동작합니다. 다만 텍스트 길이가 바뀔 때마다 완성 폭 값도 같이 바꿔야 끝까지 정확히 드러납니다.
03과 06은 왜 자바스크립트가 필요합니까?
03은 언어마다 다른 실제 글자 폭을 측정해야 하고, 06은 무작위 간격을 미리 계산해 el.animate()에 keyframes로 넘겨야 하기 때문입니다. 두 경우 모두 CSS 값만으로는 얻을 수 없는 수치입니다.
타이핑 속도를 늦추거나 빠르게 하려면 어디를 고칩니까?
각 항목의 SCSS 맨 위에 있는 $duration 변수 하나만 바꾸면 됩니다. steps() 개수는 텍스트 길이에 맞춰져 있어 속도를 바꿔도 끊어지는 리듬 자체는 유지됩니다.