Threads 모바일 앱의 '반짝이 스포일러' 효과, 웹에서 100% 재현하는 프론트엔드 꿀팁!
2026. 4. 19.
Threads 모바일 앱의 '반짝이 스포일러' 효과, 웹에서 100% 재현하는 프론트엔드 꿀팁!
Meta의 Threads 앱을 모바일로 사용해 보셨다면, 아마 흥미로운 효과 하나를 발견하셨을 겁니다. 바로 텍스트를 반짝이는 베일 뒤에 숨기는 '스포일러 태그'죠. 이번 글에서는 이 매력적인 효과를 HTML, CSS, JavaScript를 활용해서 웹 브라우저에 어떻게 구현하는지 제 경험을 바탕으로 자세히 보여드리겠습니다.
참고: 글 전체를 읽을 시간이 없으시다면, 제일 아래로 스크롤해서 완성된 HTML, CSS, JavaScript 코드를 바로 확인하셔도 좋습니다.
Threads의 스포일러 효과, 한눈에 파악하기
'스포일러' 태그에 익숙하지 않은 분들을 위해 간략히 설명하자면, 이 기능은 사용자가 자신의 게시물 중 일부를 선택적으로 숨겨두었다가, 다른 사용자가 원할 때만 내용을 볼 수 있도록 하는 Threads의 기능입니다. 아래 예시에서 제가 "Highlight the word “hello” to mark as a spoiler!"라는 문장을 게시하려는 모습을 보세요.
"hello"라는 단어를 드래그하자 "Mark spoiler" 버튼이 나타납니다. 이 버튼을 클릭하고 게시물을 올리면, 데스크톱 브라우저에서는 이렇게 보입니다.
"hello"라는 단어가 회색 막대로 가려졌죠. 게시물을 읽는 사람이 숨겨진 단어를 보고 싶다면, 이 회색 막대를 클릭해야 합니다.
위 스크린샷은 독자가 회색 막대를 클릭한 후 보게 될 화면입니다. 이제 숨겨진 단어를 볼 수 있죠.
요약하자면, Threads의 "스포일러 표시" 기능은 저자가 특정 텍스트를 선택적으로 숨기고, 사용자가 의도적인 동작(클릭)을 통해 숨겨진 텍스트를 드러내는 방식입니다. 이 기능의 주요 목적은 이야기의 반전이나 결말에 대해 이야기할 때, 아직 그 부분을 보지 못한 독자들에게 '스포일러'를 하지 않기 위함입니다. 숨겨진 텍스트를 고의적으로 드러내도록 강제함으로써, 독자들이 의도치 않게 스포일러를 접하는 것을 방지하죠.
하지만 이 스포일러 바는 모바일 앱에서 좀 더 재미있습니다. 다음 스크린샷은 동일한 게시물이지만 모바일 Threads 앱에서 본 모습입니다.
회색 막대 대신 반짝이는 흐림 효과가 적용된 것을 볼 수 있습니다. 정말 멋지지 않나요! 바로 이 반짝이는 스포일러 효과를 이 글에서 구현하는 방법을 알려드리겠습니다. 제가 Threads 앱을 처음 접했을 때, 이 작은 디테일이 사용자 경험에 얼마나 큰 차이를 만드는지 새삼 깨달았습니다. 단순히 내용을 가리는 걸 넘어, 시각적으로 즐거운 경험을 제공하는 거죠.
잠깐! 진짜 스포일러 주의!
이제부터 스타워즈: 에피소드 V – 제국의 역습의 주요 반전에 대해 이야기할 겁니다. 영화는 1980년에 개봉했지만, 혹시 아직 보지 못하셨거나 내용을 전혀 모르는 분이라면 이 글이 여러분의 관람 경험을 망칠 수 있습니다!
계속 읽는 것은 본인의 책임입니다!
그렇다면 이 매력적인 효과, 어떻게 구현할까요?
먼저, 기술적인 세부 사항에 들어가기 전에 이 효과를 어떻게 구현할지 계획을 세워보겠습니다. 우리가 해야 할 일은 다음과 같습니다.
- 텍스트를 스포일러로 표시하기
- 반짝이 효과 만들기
- 적절하게 태그된 텍스트에 효과 적용하기
- 사용자가 클릭하면 효과 제거하기
자, 시작해봅시다!
스포일러 텍스트 표시하기
먼저 index.html이라는 HTML 파일을 만들고 표준 HTML 기본 코드를 채워 넣습니다.
<body>
<p>
The biggest plot twist of all time is:
<span class="spoiler">
Darth Vader is Luke Skywalker's father!
</span>
</p>
</body>
이것은 역사상 가장 큰 반전에 대한 텍스트를 추가한 기본적인 HTML 페이지입니다. 지금은 아주 단순한 상태라 브라우저에서는 이렇게 보일 겁니다.
Threads의 배경과 텍스트는 주로 어두운 테마이므로, 우리 HTML 페이지에도 이를 적용해봅시다. 스포일러 효과의 일부는 아니지만, 나중에 보기 좋게 만들 요소입니다. style.css라는 CSS 파일을 만들고 페이지를 좀 더 보기 편하게 수정해보겠습니다. 제가 작성한 코드는 다음과 같습니다.
:root {
--bg: #0f1115;
--text: #f4f7fb;
}
body {
min-height: 25vh;
display: grid;
place-items: center;
background: var(--bg);
color: var(--text);
}
먼저, :root 규칙을 만들어 몇 가지 CSS 변수를 저장했습니다. 지금은 bg와 text 두 가지 변수만 있지만, 프로젝트가 커지면 더 많은 변수가 생길 것이므로 한곳에 모아두는 것이 가장 좋습니다. 변수를 쓰는 습관은 프로젝트가 커질수록 유지보수성을 크게 높여주니, 미리 이렇게 세팅해두는 걸 추천합니다.
다음으로, 웹 페이지의 min-height, display, place-items 값을 설정했습니다. 위에 추가된 특정 값들은 텍스트가 브라우저 창의 중앙에서 약간 아래쪽에 나타나도록 합니다. 마지막으로, bg와 text 변수를 사용하여 웹 페이지의 배경과 글자색 속성을 설정했습니다. 이제 HTML 파일의 <title> 태그 바로 아래에 다음 줄을 추가합니다.
<link rel="stylesheet" href="style.css" />
이제 브라우저는 이렇게 보일 겁니다.
bg 및 text 변수를 조정하여 페이지가 여러분의 미적 기준에 맞게 보기 좋도록 설정해도 좋습니다.
이제 스포일러 텍스트를 어딘가에 표시해야 합니다. 일단은 스포일러 부분을 spoiler 클래스의 <span> 태그 안에 넣겠습니다. 이렇게 한다고 해서 아직 시각적으로 아무것도 변하지는 않습니다. 효과를 만들기 전에 스포일러 텍스트를 미리 표시해두는 것뿐입니다. HTML 파일의 <body> 태그는 이제 이렇게 보일 겁니다.
<body>
<p>
The biggest plot twist of all time is:
<span class="spoiler">
Darth Vader is Luke Skywalker's father!
</span>
</p>
</body>
이제 효과를 만들어봅시다!
효과 만들기
효과를 만들기 위해, 우선 태그된 텍스트를 흐리게 만드는 작은 CSS 코드부터 작성해보겠습니다. style.css 파일에 spoiler 클래스를 위한 규칙을 추가해야 합니다. 내장 CSS blur 함수를 사용하고 커서를 pointer로 만들 것입니다. spoiler 클래스에 대한 제 CSS는 다음과 같습니다.
.spoiler {
filter: blur(7px);
cursor: pointer;
position: relative;
}
이 CSS 코드에서 spoiler 클래스에 filter 속성을 blur(7px)로 설정하고, cursor 속성은 pointer로 설정했습니다. blur 함수에서 짐작할 수 있듯이, 이를 filter 값으로 사용하면 요소가 흐려집니다. 함수에 전달하는 숫자는 흐림의 반경을 정의합니다. 숫자가 높을수록 흐림 효과가 커집니다. cursor 속성을 pointer로 설정하는 이유는 사용자가 효과 위에 마우스를 올렸을 때 커서가 포인터로 변하여 클릭하면 어떤 동작이 일어날 것임을 알려주기 위함입니다.
또한, position 속성을 relative로 설정하여 <span>이 페이지의 다른 요소들과 간섭하지 않도록 했습니다. 여기서는 position: relative를 주는 건 뒤에 ✨반짝이✨들이 자유롭게 움직일 공간을 마련해 주는 중요한 준비 작업입니다.
새로운 CSS를 적용하면 브라우저 페이지는 이제 이렇게 보일 겁니다.
위 스크린샷은 filter: blur(7px) 덕분에 스포일러 텍스트가 흐려진 것을 보여줍니다.
반짝이 애니메이션과 토글 기능을 만들기 전에, HTML 구조를 약간 조정해야 합니다. 이 spoiler <span> 안에 두 개의 <span> 요소를 더 넣어야 합니다. 하나는 내용을 감싸는 용도이고, 다른 하나는 흐림 효과를 담는 용도입니다. 이 단계는 반짝이 애니메이션과 함께 토글 효과를 구현하는 것을 좀 더 쉽게 만들어 줄 것입니다.
spoiler-content라는 <span> 클래스와 spoiler-blur라는 또 다른 <span> 클래스를 만들겠습니다. spoiler-content <span>은 텍스트를 감쌀 것이고, spoiler-blur <span>은 아무것도 감싸지 않고 spoiler <span> 안에만 있을 것입니다. <body> 태그 안의 HTML은 이제 이렇게 보입니다.
<p>
The biggest plot twist of all time is:
<span class="spoiler" data-hidden="true">
<span class="spoiler-content">
Darth Vader is Luke Skywalker's father!
</span>
<span class="spoiler-blur" />
</span>
</p>
그리고 CSS는 이제 다음과 같이 조정되어야 합니다.
.spoiler {
cursor: pointer;
position: relative;
}
.spoiler[data-hidden="true"] .spoiler-content {
filter: blur(7px);
}
이 업데이트를 통해 data-hidden 속성을 spoiler <span>에 "true"로 설정하고, spoiler <span> 안에 있는 모든 spoiler-content <span>에 흐림 효과를 적용하는 CSS 규칙을 만들었습니다. 이 속성은 나중에 효과의 토글 기능을 만들 때 사용될 것입니다.
이제 반짝이 효과를 추가할 준비가 되었습니다.
반짝이 효과 추가하기
반짝이 효과를 추가하기 위해, 우리는 두 가지를 활용할 것입니다: CSS 키프레임과 약간의 JavaScript. 전반적인 전략은 다음과 같습니다.
- 스포일러 텍스트를 덮는 개별
sparkle-blur<span>을 만들기 위한 CSSspark클래스 생성 - JavaScript를 사용하여 여러
spark요소를spoiler-blur<span>의 자식으로 추가- 난수를 생성하는 함수를 만들 것입니다.
- 전체 "반짝이 효과"를 만들기 위해 CSS 애니메이션/키프레임에서 사용될 몇 가지 사용자 정의 속성을 (위에서 언급한 난수 함수에서 나온) 임의의 값으로 요소에 설정할 것입니다.
- 반짝이들이 깜빡이는 것처럼 보이게 하는
twinkleCSS 키프레임 생성 - 반짝이들이 움직이는 것처럼 보이게 하는
driftCSS 키프레임 생성
먼저, spark 클래스를 필요한 몇 가지 속성만으로 만들 것입니다. 저는 이렇게 정의했습니다.
.spark {
position: absolute;
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--spoiler-dot);
}
position: absolute 줄은 각 개별 반짝이가 다른 요소의 위치를 방해하지 않고 오버레이(spoiler <span>)에서 공간을 차지할 수 있도록 합니다. width, height, border-radius 세 줄은 반짝이를 완벽한 원으로 만듭니다. 사각형을 선호한다면 border-radius 줄을 제거할 수 있고, 수직선을 원한다면 width와 height 속성을 가지고 놀 수 있습니다. background는 :root 규칙에서 설정할 수 있는 CSS 변수를 사용하며, 저는 --spoiler-dot: rgba(255,255,255,0.85); 와 같이 흰색 계열로 설정했습니다. 제 경험상, 이 position: absolute는 작은 요소들을 부모 요소 내에서 자유롭게 배치할 때 정말 자주 쓰이는 속성입니다.
이 요소는 우리 반짝이 스포일러 효과의 각 개별 "반짝이"의 스타일을 정의합니다. 우리는 무작위 크기의 여러 sparkle 요소를 만들고 spoiler-blur <span> 요소의 무작위 위치에 추가해야 합니다. sparkle.js라는 JavaScript 파일을 만들고 HTML의 <body> 안에 다음과 같이 가져옵니다.
<script src="sparkle.js"></script>
참고: 이 줄은 HTML의 <body> 태그 안에 넣어야 합니다. <head> 태그에 넣으면 작동하지 않습니다.
이제 sparkle.js 파일에서, 반짝이 효과를 얻기 위해 무작위성에 의존할 것이므로, 무작위 값을 반환하는 편의 함수를 만드는 것으로 시작하겠습니다. 저는 random 함수가 rscale 변수를 Math.random 함수가 생성한 숫자로 곱하도록 작성했습니다.
function random(rscale) {
return Math.random() * rscale;
}
다음으로 buildSparkles라는 함수를 만들 것입니다. 이 함수는 <span> 요소와 만들 반짝이의 개수를 인수로 받습니다. 우리가 만들 각 반짝이는 시작 2차원 좌표(left 및 top 속성을 나타내는 x와 y), 끝 2차원 좌표, 반짝이가 두 좌표 사이를 이동하는 데 걸리는 시간, 반짝이가 "깜빡이는" 데 걸리는 시간, 그리고 크기를 제어하는 스케일 변수와 함께 생성될 것입니다. 이 모든 값은 각 반짝이에 대해 무작위로 생성됩니다.
각 sparkle 요소가 생성되면, 함수에 전달한 spoiler-blur <span>에 추가할 것입니다. 제가 작성한 함수는 다음과 같습니다.
function buildSparkles(spoilerBlurSpan, sparkleCount) {
for (let i = 0; i < sparkleCount; i++) {
const s = document.createElement("span");
s.className = "spark";
const x0 = random(100);
const y0 = random(100);
const x1 = Math.max(0, Math.min(100, x0 + random(18)));
const y1 = Math.max(0, Math.min(100, y0 + random(14)));
s.style.left = `${x0}%`;
s.style.top = `${y0}%`;
s.style.setProperty("--x0", "0px");
s.style.setProperty("--y0", "0px");
s.style.setProperty("--x1", `${x1 - x0}px`);
s.style.setProperty("--y1", `${y1 - y0}px`);
s.style.setProperty("--dur", `${random(60)}s`);
s.style.setProperty("--twinkle", `${random(1.2)}s`);
s.style.setProperty("--scale", `${random(1.5)}`);
spoilerBlurSpan.appendChild(s)
}
}
함수 시그니처에서 spoilerBlurSpan을 첫 번째 인수로 받습니다. 이것은 웹 페이지 어딘가에 스포일러 텍스트를 포함하고 있다고 표시된 <span>이 될 것입니다. 또한 sparkleCount를 받는데, 이는 하나의 <span>에 넣을 반짝이의 개수입니다. 그런 다음 for 루프에 들어갑니다.
for 루프의 각 반복은 하나의 spark 요소를 생성합니다. 처음 두 줄은 <span> 요소를 만들고 spark 클래스 이름을 할당합니다.
다음으로 x0, y0, x1, y1을 설정합니다. 이 변수들은 기본적으로 두 세트의 무작위 x, y 좌표입니다. 나중에 drift라는 CSS 애니메이션을 만들어 sparkle이 이 두 좌표 사이를 움직이도록 할 것입니다.
left와 top을 설정하는 줄은 방금 생성한 무작위 x, y 좌표에 <span> 요소를 배치하는 것입니다. 다음 네 줄의 setProperty는 방금 생성한 sparkle <span>에 x0, y0, x1, y1의 속성과 초기 값을 할당합니다. 이러한 속성이 필요한 이유는 잠시 후 CSS 애니메이션을 만들 때 명확해질 것입니다.
마지막으로, 마지막 세 줄을 사용하여 dur, twinkle, scale이라는 속성을 설정합니다. 이 속성들과 초기 값을 설정해야 우리가 곧 만들 CSS 애니메이션에서 사용할 수 있습니다. dur 속성은 sparkle이 두 x, y 좌표 사이를 이동하는 데 걸리는 시간을 제어하는 데 사용될 것입니다. twinkle 속성은 각 반짝이가 깜빡이는 "속도"(낮은 투명도와 높은 투명도 사이를 전환하는 속도)를 제어할 것입니다. 마지막으로 scale 속성은 각 반짝이의 크기에 영향을 미칠 것입니다.
이 모든 속성을 설정한 후, 내장 appendChild 메서드를 사용하여 새로 생성된 sparkle <span>을 함수 시그니처에서 받은 spoilerBlurSpan에 추가합니다. 제가 실무에서 이런 종류의 애니메이션을 구현할 때, Math.random()으로 동적인 요소를 추가하면 사용자 경험이 훨씬 풍부해진다는 걸 자주 느낍니다. 매번 똑같지 않고 새롭게 느껴지니까요.
마지막으로 sparkle.js 파일의 끝에 몇 줄을 더 추가합니다.
function wireSpoiler(spoilerBlurSpan) {
buildSparkles(spoilerBlurSpan, 75);
}
document.querySelectorAll(".spoiler-blur").forEach(wireSpoiler);
wireSpoiler라는 함수를 하나 더 만듭니다. 이 함수는 spoilerBlurSpan을 인수로 받아서 buildSparkles 함수와 함께 만들 반짝이의 개수(위 예시에서는 75개)를 전달합니다.
마지막으로 querySelectorAll을 사용하여 웹 페이지의 spoiler-blur 클래스를 가진 모든 <span>을 가져와 wireSpoiler 메서드에 전달합니다. 이렇게 하면 페이지에 spoiler <span>이 하나만 있더라도 모든 spoiler <span>이 반짝이 효과를 얻게 됩니다.
이제 CSS로 넘어갑니다.
먼저, @keyframes를 사용하여 애니메이션을 만들어봅시다. drift와 twinkle 두 가지를 만들 것입니다. 이 CSS를 style.css 파일의 하단에 추가합니다.
@keyframes drift {
from {
transform: translate(var(--x0), var(--y0)) scale(var(--scale));
}
to {
transform: translate(var(--x1), var(--y1)) scale(var(--scale));
}
}
@keyframes twinkle {
from { opacity: 0.25; }
to { opacity: 0.95; }
}
drift 키프레임은 CSS 함수 translate를 사용하는데, 이는 요소를 주어진 위치로 이동시킵니다. 나중에 애니메이션 속성에서 사용될 것이므로, 요소가 이동해야 하는 두 위치를 정의하기 위해 from 및 to 속성을 부여합니다. 이 경우 x0 및 y0 변수를 사용하여 sparkle의 시작점을 정의하고, x1 및 y1을 끝점으로 사용합니다. 기억하시겠지만, 이러한 값은 이전에 JavaScript 파일에서 무작위로 생성했으므로, spoiler-blur 요소에 연결하는 각 spark는 자체적인 시작점과 끝점을 가질 것입니다.
twinkle 키프레임은 spark 요소가 왔다 갔다 할 투명도 값을 정의합니다. 투명도는 요소의 투명도 수준이므로, 낮은 값과 높은 값 사이를 오가면 요소가 깜빡이는 듯한 착시를 줍니다.
이제 spark 요소의 CSS를 업데이트하여 이러한 키프레임을 애니메이션으로 포함시킵니다. 요소의 전체 CSS 규칙은 다음과 같습니다.
.spark {
position: absolute;
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--spoiler-dot);
animation:
drift var(--dur) linear infinite,
twinkle var(--twinkle) ease-in-out infinite alternate;
}
여기서의 변경 사항은 spark 클래스에 animation 속성을 추가하고 drift와 twinkle을 추가한 것입니다. drift의 경우, JavaScript에서 정의한 무작위로 생성된 dur 값을 전달합니다. 이 값은 스파크가 시작 좌표에서 끝 좌표로 이동하는 데 걸리는 시간을 정의합니다. 또한 linear 및 infinite 인수를 애니메이션에 전달하여, 이동이 직선으로 이루어지고 페이지가 로드되는 동안 반복되도록 합니다.
마찬가지로, 무작위로 생성된 twinkle 변수를 twinkle 키프레임 애니메이션에 전달합니다. 이 변수는 스파크가 시작 투명도에서 끝 투명도로 이동하는 데 걸리는 시간을 정의합니다. alternate 인수는 투명도 값이 끝 투명도에 도달한 후 "다시 시작"하지 않고 대신 시작 값으로 다시 돌아가도록 보장합니다.
이 마지막 추가로, 우리는 스포일러 콘텐츠 위에 멋진 반짝이는 베일을 갖게 될 것입니다. 제 페이지는 이렇게 보입니다.
많은 값을 무작위로 생성하기 때문에, 여러분의 예시가 제 것과 정확히 같지는 않을 수 있으며, 페이지를 새로고침하면 다시 다르게 보일 수 있다는 점에 유의하세요. 하지만 흐릿해진 스포일러 텍스트 위로 반짝이들이 깜빡이고 움직이는 것을 볼 수 있다면, 지금까지 모든 것을 제대로 한 것입니다!
이제 마지막으로 토글 효과를 추가할 차례입니다!
토글(Toggle) 효과 추가하기
스포일러 태그의 목적은 사람들이 아직 볼 준비가 되지 않은 것을 보는 것을 막는 것입니다. 하지만 미디어의 스포일러를 감수할 의향이 있는 사용자들을 위해, 반짝이는 베일을 제거하고 숨겨진 텍스트를 드러내는 기능을 추가해야 합니다. 이제 그 효과를 추가하겠습니다.
이를 구현하기 위한 전략은 다음과 같습니다.
spoiler<span>의data-hidden속성을 현재 값의 반대로 설정하는 함수 추가 (현재true이면false로, 그 반대도 마찬가지)- 이 함수를
spoiler<span>의 "click" 이벤트 리스너에 추가 data-hidden="false"속성을 가진spoiler<span>내부 속성의 CSS 업데이트
첫 번째와 두 번째 부분은 wireSpoiler 함수 내부에서 동시에 일어날 것입니다. 우리가 할 첫 번째 일은 spoiler-blur 요소의 부모 요소(이는 spoiler <span>이어야 합니다)를 가져오는 것입니다. 이는 다음 JavaScript 한 줄로 수행됩니다.
let spoilerSpan = spoilerBlurSpan.parentElement;
이제 그 줄 바로 아래에 toggle이라는 함수를 만들 것입니다. 이 함수는 spoiler의 data-hidden 속성을 가져와 hidden이라는 변수를 반대 값으로 설정합니다. 그런 다음 setAttribute 메서드를 사용하여 data-hidden을 새 hidden 변수의 값으로 설정합니다. 이것이 "토글링"이 의미하는 바입니다.
마지막으로, addEventListener 메서드를 사용하여 toggle 메서드를 해당 요소의 클릭 이벤트에 추가할 것입니다. 종합적으로 wireSpoiler 함수는 이제 이렇게 보일 것입니다.
function wireSpoiler(spoilerBlurSpan) {
buildSparkles(spoilerBlurSpan, 75);
let spoilerSpan = spoilerBlurSpan.parentElement;
function toggle() {
const hidden = spoilerSpan.getAttribute("data-hidden") === "true";
spoilerSpan.setAttribute("data-hidden", String(!hidden))
}
spoilerSpan.addEventListener("click", toggle);
}
위 코드에서 wireSpoiler 함수에 몇 줄을 추가했습니다. 먼저 spoilerBlur 요소의 부모 요소, 즉 spoiler <span>을 가져왔습니다. 그런 다음 toggle 함수를 만들었는데, 이 함수의 첫 번째 줄은 data-hidden 속성 값을 가져와 true인지 확인합니다. HTML에서 우리가 이 줄을 작성했으므로:
<span class="spoiler" data-hidden="true">
웹 페이지가 처음 로드될 때 값은 문자열 "true"가 될 것이고, 이는 hidden 변수의 값이 부울 true가 됨을 의미합니다.
다음 줄에서는 !hidden을 사용하여 해당 값의 반대 문자열 표현으로 속성을 설정합니다. ! 연산자는 그 옆에 있는 변수를 부정합니다. 따라서 !true("true가 아니다"라고 읽음)는 false가 되고 !false("false가 아니다"라고 읽음)는 true가 됩니다. 여기에 !hidden으로 반대 값을 넣어주는 방식은 토글 기능의 핵심이죠. 간단하지만 강력합니다.
마지막으로 toggle 함수 외부에서 addEventListener 메서드를 사용하여 toggle 함수를 click 이벤트에 추가했습니다. 이는 spoiler <span>이 클릭될 때마다 이 함수가 실행됨을 의미합니다.
현재로서는 spoiler 태그를 클릭하면 data-hidden 속성만 true와 false 사이를 전환합니다. 브라우저에서 요소를 확인하고 클릭할 때마다 HTML에서 data-hidden 값이 변하는 것을 볼 수 있습니다. 흐림 효과를 실제로 켜고 끄려면 CSS를 업데이트하여 data-hidden 속성 값이 true인 spoiler 요소 내부의 요소들이 특정 방식으로 작동하도록 해야 합니다.
이것은 data-hidden 속성 값이 false인 spoiler <span> 내부의 모든 spark 요소에 대한 규칙 블록을 만들어서 수행합니다. 다음 내용을 CSS 파일에 추가하여 이를 수행할 것입니다.
.spoiler[data-hidden="false"] .spark {
animation-play-state: paused;
display: none;
}
위 코드는 data-hidden 값이 "false"인 스포일러 요소 내부에 있는 spark 요소만 대상으로 합니다. 그런 다음 animation-play-state를 paused로 설정하는데, 이는 반짝이가 현재 위치에서 멈춘다는 것을 의미합니다. 또한 display 속성을 none으로 설정하여 반짝이를 사실상 숨깁니다.
spoiler 태그를 클릭하면 data-hidden 값이 true일 경우 false로 전환되므로, 위에 작성한 새로운 규칙이 모든 spark <span>에 적용되어 반짝이들을 숨기게 됩니다. 또한 spoiler-content 내부의 텍스트도 흐림이 풀리는데, 이는 우리가 filter 속성을 blur(7px)로 설정한 규칙이 data-hidden="true" 요소에만 적용되기 때문입니다.
이 마지막 업데이트를 통해 이제 웹 페이지에서 흐림 효과를 켜고 끌 수 있게 될 것입니다! 브라우저를 새로고침하고 흐릿한 스포일러 텍스트를 클릭하여 효과를 직접 확인해보세요.
직접 확장해보기
이제 스포일러 효과를 구현하는 기본적인 아이디어를 파악했으니, 코드를 확장하고 개선할 수 있는 많은 것들이 있습니다. 예를 들어, 위 코드는 toggle 함수를 spoiler <span>에 모든 반짝이마다 설정하기 때문에 비효율적입니다. 어떻게 고칠 수 있을까요? (힌트: spoiler 스팬 자체에 한 번만 이벤트 리스너를 붙이도록 리팩토링하는 것이 성능상 훨씬 좋습니다.) 또한, 각 스포일러 태그가 개별적으로 클릭해야 흐림이 풀리도록 하거나, 어떤 스포일러 태그든 클릭하면 모든 스포일러 태그가 흐림이 풀리도록 설정하는 등 다양한 실험을 해볼 수 있습니다. 애니메이션 스타일과 반짝이 모양을 변경할 수 있는 수많은 방법도 있습니다. 한번 가지고 놀아보세요!
마치며
이 글에서는 Threads의 스포일러 효과를 모방하는 방법을 배우고 HTML, CSS, 그리고 약간의 JavaScript를 사용하여 이를 구현했습니다. 키프레임을 사용하고 CSS 애니메이션에 대해 조금 알아보았죠. 이제 텍스트를 조건부로 숨기고 표시하는 기본적인 전략을 알게 되었으니, 이 효과를 자신의 웹사이트에서 다양한 목적으로 활용할 수 있을 겁니다. 이 기본적인 전략만 이해한다면, 단순히 스포일러 효과를 넘어 다양한 인터랙티브 UI를 만드는 데 응용할 수 있을 겁니다.
완성된 코드
이 글의 끝에는 index.html, style.css, sparkle.js 세 파일이 있습니다. 제 파일의 내용은 다음과 같습니다.
index.html:
<!doctype html>
<html>
<head>
<title>Sparkly Spoiler Effect</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<p>
The biggest plot twist of all time is:
<span class="spoiler" data-hidden="true">
<span class="spoiler-content">
Darth Vader is Luke Skywalker's father!
</span>
<span class="spoiler-blur" />
</span>
</p>
<script src="sparkle.js"></script>
</body>
</html>
style.css:
:root {
--bg: #0f1115;
--text: #f4f7fb;
--spoiler-dot: rgba(255,255,255,0.85);
}
body {
min-height: 25vh;
display: grid;
place-items: center;
background: var(--bg);
color: var(--text);
}
.spoiler {
cursor: pointer;
position: relative;
}
.spoiler[data-hidden="true"] .spoiler-content {
filter: blur(7px);
}
.spark {
position: absolute;
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--spoiler-dot);
animation:
drift var(--dur) linear infinite,
twinkle var(--twinkle) ease-in-out infinite alternate;
}
.spoiler[data-hidden="false"] .spark {
animation-play-state: paused;
display: none;
}
@keyframes drift {
from {
transform: translate(var(--x0), var(--y0)) scale(var(--scale));
}
to {
transform: translate(var(--x1), var(--y1)) scale(var(--scale));
}
}
@keyframes twinkle {
from { opacity: 0.25; }
to { opacity: 0.95; }
}
sparkle.js:
function random(rscale) {
return Math.random() * rscale;
}
function buildSparkles(spoilerBlurSpan, sparkleCount) {
for (let i = 0; i < sparkleCount; i++) {
const s = document.createElement("span");
s.className = "spark";
const x0 = random(100);
const y0 = random(100);
const x1 = Math.max(0, Math.min(100, x0 + random(18)));
const y1 = Math.max(0, Math.min(100, y0 + random(14)));
s.style.left = `${x0}%`;
s.style.top = `${y0}%`;
s.style.setProperty("--x0", "0px");
s.style.setProperty("--y0", "0px");
s.style.setProperty("--x1", `${x1 - x0}px`);
s.style.setProperty("--y1", `${y1 - y0}px`);
s.style.setProperty("--dur", `${random(60)}s`);
s.style.setProperty("--twinkle", `${random(1.2)}s`);
s.style.setProperty("--scale", `${random(1.5)}`);
spoilerBlurSpan.appendChild(s)
}
}
function wireSpoiler(spoilerBlurSpan) {
buildSparkles(spoilerBlurSpan, 75);
let spoilerSpan = spoilerBlurSpan.parentElement;
function toggle() {
const hidden = spoilerSpan.getAttribute("data-hidden") === "true";
spoilerSpan.setAttribute("data-hidden", String(!hidden))
}
spoilerSpan.addEventListener("click", toggle);
}
document.querySelectorAll(".spoiler-blur").forEach(wireSpoiler);
원문: https://dev.to/erikwhiting88/how-to-create-a-sparkly-spoiler-effect-like-the-one-in-threads-mobile-app-19nk 수집일: 2026-04-19 01:21:26