via dev.to
타자기로 한 글자씩 입력하는 효과(Typewriter Effect)는 이미 수 많은 라이브러리가 있지만, setInterval
타이머 API를 이용해서 직접 구현할 수 있다. 원래 문장(텍스트)을 한 글자씩 자른 후 가장 앞에 글자부터 하나씩 이어 붙이는 방식이다.
<div>
<span class="content"></span>
<span class="blink" />
</div>
const $content = document.querySelector(".content");
function typewriter(target, sentence, speed = 200) {
const split = sentence.split("");
let text = "";
let i = 0;
const timer = setInterval(() => {
if (i < split.length) {
text += split[i++]; // ++는 후위 증감(증가하기전 값 반환)
target.textContent = text;
} else {
clearInterval(timer);
}
}, speed);
};
typewriter($content, 'hello world', 400);
깜빡이는 커서 효과는 |
콘텐츠를 갖는 <span>
태그에, step-end
step-start
같은 애니메이션을 추가한다. :after
수도 클래스를 사용하지 않고 <span>|</span>
형태로 입력해도 된다.
.blink::after {
content: "|";
}
.blink {
animation: blink 1s step-end infinite;
font-weight: bold;
margin-left: 1px;
}
@keyframes blink {
50% {
opacity: 0;
}
}
React에 적용할 때도 setInterval
을 이용해 위와 비슷한 로직으로 작성하면 된다.
index
를 1씩 더하는 setInterval
내부 함수가 sec
초 간격으로 실행된다.index
를 초과하면 안되므로 content.length - 1
미만까지만 실행한다.index
상태가 변경되면 2번째 useEffect
가 실행돼서 현재 index
에 해당하는 글자를 이어 붙인다.export default function useTypeWriter({ content, sec = 200, hasBlink = false }) {
const [displayedContent, setDisplayedContent] = useState('');
const [index, setIndex] = useState(0);
useEffect(() => {
const animKey = setInterval(() => {
setIndex(index => {
if (index < content.length - 1) {
return index + 1;
}
clearInterval(animKey);
return index;
});
}, sec);
return () => clearInterval(animKey);
}, []);
useEffect(() => {
setDisplayedContent(displayedContent => displayedContent + content[index]);
}, [index]);
return (
<>
{displayedContent}
{hasBlink && <span className="blink" />}
</>
);
}
작성한 Hook은 아래처럼 원하는 조건(깜빡임 커서 여부, 입력 속도)에 맞춰 실행할 수 있다.
import useTypeWriter from '@/hooks/useTypeWriter';
export default function Component() {
const typingText = useTypeWriter({
content: 'Hello World',
sec: 200,
hasBlink: true,
});
return <p>{typewriterText}</p>;
}