CSS 텍스트 효과 9종 — 해킹처럼 풀리는 디코드 연출
CSS 텍스트 효과는 제목이나 문장이 화면에 나타날 때 함께 움직이는 연출로, 코드를 몰라도 파일을 그대로 붙이면 바로 쓸 수 있습니다. 이 아홉 가지는 해킹 화면처럼 문장이 풀리는 순간을 노려, 헥스 코드나 이진수·암호가 뒤섞였다가 제자리를 찾아가는 과정을 보여줍니다.
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
- 01 헥스 코드 정착
- 02 이진수 비트 스왑
- 03 알파벳 순환 암호
- 04 비밀번호 점 모핑
- 05 스캔라인 마스크 스윕
- 06 기밀 해제 바 리프트
- 07 체크섬 카운트다운
- 08 네거티브 반전 플래시
- 09 레이더 스윕 채움
아홉 개는 인기 순이 아니라 감춰진 정보가 드러나는 순서를 따라갑니다. 먼저 무작위 코드가 진짜 글자로 맞춰지는 연출(01~03)을 지나, 점이나 검은 막대로 가려진 정보가 벗겨지는 연출(04~06)을 거쳐, 마지막은 검증·발각의 순간을 연출하는 효과(07~09)로 끝납니다.
01헥스 코드 정착
글자 자리마다 무작위 16진수 한 글자가 빠르게 반짝이다가, 오른쪽부터 하나씩 실제 글자로 멈춰 섭니다. 정착 전엔 주황, 정착 후엔 초록으로 색도 함께 바뀝니다. CSS만으로는 글자 내용을 바꿀 수 없어서 짧은 자바스크립트로 처리했습니다.
var target = "ACCESS", hex = "0123456789ABCDEF";
var spans = document.querySelectorAll(".hb-c");
var n = 0;
setInterval(function () {
n++;
var settled = Math.max(0, Math.floor((n % 44) / 5) - 1);
spans.forEach(function (s, i) {
var on = i >= target.length - settled;
s.textContent = on ? target[i] : hex[Math.random() * 16 | 0];
s.className = on ? "hb-c hb-on" : "hb-c hb-off";
});
}, 90);
02이진수 비트 스왑
글자가 처음엔 01001000 같은 이진수 여덟 자리로 보이다가, 순간적으로 사라지며 실제 글자가 겹쳐 떠오릅니다. 왼쪽부터 순서대로 바뀌는데, 이 스태거는 자바스크립트가 아니라 부모 요소의 인라인 animation-delay를 자식이 inherit로 물려받아 만듭니다.
.bn-ch {
position: absolute;
inset: 0;
opacity: 0;
transform: scale(.85);
}
@keyframes bn-ch-in {
0%, 40% { opacity: 0; transform: scale(.85); }
55%, 90% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(.85); }
}
.bn-word.is-demo .bn-ch {
animation: bn-ch-in 2s ease-out infinite;
animation-delay: inherit;
}
03알파벳 순환 암호
각 글자가 알파벳 몇 칸 뒤에서 출발해 한 칸씩 되감기며 원래 글자를 찾아갑니다. 자물쇠 다이얼을 맞추는 느낌으로, 글자마다 정해둔 오프셋 값을 주기적으로 되풀이합니다.
var offs = [5, 6, 4, 7, 5, 6], cyc = 40;
var n = 0;
setInterval(function () {
n++;
spans.forEach(function (s, i) {
var t = ((n - i * 2) % cyc + cyc) % cyc;
var left = Math.max(0, offs[i] - Math.floor(t / 2));
var base = target.charCodeAt(i) - 65;
s.textContent = String.fromCharCode(65 + (base + left) % 26);
});
}, 130);
04비밀번호 점 모핑
비밀번호 입력창처럼 동그란 점들로 가려져 있던 글자가, 살짝 커지며 점이 사라지고 실제 글자가 드러납니다. 점 레이어와 글자 레이어를 겹쳐 두고 opacity와 scale만 반대로 움직이는 크로스페이드입니다.
.pd-real {
position: absolute;
inset: 0;
opacity: 0;
transform: scale(.9);
}
@keyframes pd-show {
0%, 18% { opacity: 0; transform: scale(.9); }
48%, 78% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(.9); }
}
.pd-wrap.is-demo .pd-real { animation: pd-show 2s cubic-bezier(.2, .8, .2, 1) infinite; }
05스캔라인 마스크 스윕
지지직거리는 노이즈 블록이 글자를 덮고 있다가, 밝은 스캔라인이 위에서 아래로 훑고 지나가면 그 자리부터 또렷한 글자가 드러납니다. 노이즈 레이어의 clip-path를 위에서 아래로 넓혀가며 걷어내는 방식입니다.
.sl-noise {
position: absolute;
inset: 0;
background-image: repeating-linear-gradient(45deg, rgba(255, 255, 255, .4) 0 2px, transparent 2px 4px);
mix-blend-mode: screen;
}
@keyframes sl-clear {
0%, 8% { clip-path: inset(0 0 0 0); }
55%, 78% { clip-path: inset(100% 0 0 0); }
100% { clip-path: inset(0 0 0 0); }
}
.sl-wrap.is-demo .sl-noise { animation: sl-clear 2s ease-out infinite; }
06기밀 해제 바 리프트
기밀 문서처럼 검정 막대가 단어를 하나씩 가리고 있다가, 오른쪽부터 걷히며 순서대로 글자가 드러납니다. 두 단어 사이에 지연 시간을 살짝 줘서 왼쪽 단어부터 차례로 해제됩니다.
.rb-bar {
position: absolute;
inset: -8% -6%;
background: $stage-ink;
transform-origin: right center;
}
@keyframes rb-lift {
0%, 12% { transform: scaleX(1); }
50%, 78% { transform: scaleX(0); }
100% { transform: scaleX(1); }
}
.rb-wrap.is-demo .rb-word .rb-bar { animation: rb-lift 2s ease-out infinite; }
.rb-wrap.is-demo .rb-word--2 .rb-bar { animation-delay: .3s; }
07체크섬 카운트다운
처음엔 밑줄만 나열돼 있다가 옆의 진행률 숫자가 올라가는 것과 맞춰 왼쪽부터 글자가 하나씩 채워지고, 채워질 때마다 살짝 튕깁니다. 채워진 글자 수를 그대로 퍼센트로 계산해 보여줍니다.
var n = 0;
setInterval(function () {
n++;
var filled = Math.min(target.length, Math.floor((n % 50) / 5));
spans.forEach(function (s, i) {
s.textContent = i < filled ? target[i] : "_";
s.classList.toggle("cc-pop", i === filled - 1);
});
pct.textContent = Math.round(filled / target.length * 100) + "%";
}, 90);
08네거티브 반전 플래시
색이 반전된 네거티브 필름 같은 글자가 두세 번 깜빡이다가 사라지면, 그 아래 정상 색의 진짜 글자가 남습니다. 반전 레이어 하나를 원래 글자 위에 겹치고 opacity만 여러 번 껐다 켭니다.
.ni-negative {
position: absolute;
inset: 0;
color: $stage-paper;
background: $color;
}
@keyframes ni-flash {
0%, 4% { opacity: 1; }
9% { opacity: 0; }
14% { opacity: 1; }
19% { opacity: 0; }
24% { opacity: 1; }
32%, 100% { opacity: 0; }
}
.ni-wrap.is-demo .ni-negative { animation: ni-flash 2s ease-out infinite; }
09레이더 스윕 채움
레이더 화면처럼 밝은 부채꼴 선이 글자 위를 한 바퀴 돌면서 지나간 자리의 글자만 순서대로 선명해집니다. 글자마다 스윕이 지나가는 타이밍에 맞춰 animation-delay를 SCSS 반복문으로 나눠 줬습니다.
.rs-sweep {
position: absolute;
inset: -60%;
background: conic-gradient(from 0deg, transparent 0deg, rgba(61, 220, 132, .5) 10deg, transparent 34deg);
mix-blend-mode: screen;
}
@keyframes rs-lock {
0%, 4% { opacity: .16; }
14%, 88% { opacity: 1; }
100% { opacity: .16; }
}
@for $i from 1 through 8 {
.rs-wrap.is-demo .rs-text span:nth-child(#{$i}) {
animation-delay: calc(2s / 8 * #{$i - 1});
}
}
CSS 텍스트 효과는 어디서 깨지는가
가장 먼저 걸려 넘어진 건 04번이었습니다. 점과 글자를 크로스페이드로 겹쳐 두면서 전환 구간을 전체 주기의 42~58%로 좁게 잡았더니, 24프레임 중 실제로 픽셀이 바뀌는 프레임이 5장뿐이라 최소 기준(6장)에 못 미쳤습니다. 겉보기엔 멀쩡히 도는 것 같았지만 프레임을 하나씩 열어 보니 대부분의 프레임이 완전히 같은 모양이었습니다. 전환 구간을 18~48%로 넓히고 나서야 24프레임 중 10장이 움직임을 담아 기준을 통과했습니다. 렌더러가 프레임 사이 픽셀 차이로 "움직였는가"를 판단하기 때문에, 순간적으로 끝나는 크로스페이드는 눈에는 자연스러워 보여도 측정값으로는 정지 화면과 구별이 안 될 수 있다는 뜻입니다. 같은 원리로 08번 네거티브 반전 플래시도 깜빡이는 구간이 짧아 움직인 프레임이 8장으로 아홉 개 중 가장 적었지만, 그래도 최소 기준(6장)은 넉넉히 넘겼습니다. 반대로 09번 레이더 스윕 채움은 23장 모두가 움직여, 01번 헥스 코드 정착과 함께 아홉 개 중 가장 오래 움직이는 항목이었습니다. 이 zip을 열 때 쓰는 비밀번호는 8y6rrjcm 이고, 아홉 번째 항목까지 다 읽고 나면 자연스럽게 마주치게 됩니다.
접근성 — 아홉 개가 reduced-motion에서 남기는 것
화면에서 움직임을 줄이도록 설정해 둔 사람은 이 아홉 개 전부를 prefers-reduced-motion: reduce 조건 아래 정지된 최종 모습으로 봅니다. 02·04·05·06·08·09번처럼 SCSS @keyframes로 움직이는 항목은 최종 모습(글자가 다 드러나고, 막대가 걷히고, 스윕이 끝난 상태)을 !important로 고정해 곧바로 다 보이게 했습니다. 01·03·07번은 CSS가 아니라 자바스크립트로 움직이기 때문에, 스크립트 안에서 matchMedia로 직접 검사해 무작위로 섞지 않고 완성된 단어를 바로 보여줍니다.
비슷한 등장 효과가 궁금하다면 CSS 텍스트 애니메이션 9종 글도 참고할 만하고, 타이핑 커서가 궁금하다면 CSS 타이핑 텍스트 효과 글을 보면 됩니다. 이 아홉 개 말고 다른 주제의 모음도 함께 훑어보고 싶다면 CSS 애니메이션 카테고리 페이지에서 둘러볼 수 있습니다. 겹친 레이어를 잘라내는 clip-path 속성의 세부 값은 MDN 레퍼런스에 정리돼 있습니다.
FAQ — 자주 묻는 질문
CSS 텍스트 효과에 자바스크립트가 꼭 필요한가요?
대부분은 아닙니다. 아홉 개 중 여섯 개는 SCSS @keyframes와 opacity·transform·clip-path만으로 동작하고, 01·03·07번 세 개만 글자 내용 자체를 무작위로 바꾸거나 채워야 해서 짧은 자바스크립트가 들어갑니다.
01번 헥스 코드 정착과 03번 알파벳 순환 암호 중 어느 걸 써야 하나요?
로그인 성공이나 보안 인증처럼 "코드가 풀린다"는 느낌이 중요하면 01번이 더 기술적으로 보이고, 퍼즐이나 잠금 해제처럼 "차례로 맞춰진다"는 느낌이 중요하면 03번의 자물쇠 다이얼 방식이 더 잘 어울립니다.
zip 안의 vanilla와 react 코드가 다른가요?
움직임 자체는 vanilla와 React 두 버전이 동일하게 만들어 냅니다. 차이는 재생 방식뿐인데, vanilla 데모는 is-demo 클래스로 끊임없이 반복시키지만 React 컴포넌트는 IntersectionObserver를 써서 화면에 들어온 순간 딱 한 번만 움직이도록 짜여 있습니다.