CSS & JS로 사이버펑크 글리치 UI 구축 (소스 코드)

발행: (2026년 1월 2일 오전 01:49 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

사이버펑크 미학에는 부인할 수 없는 멋이 있습니다. 네온 조명과 하이‑테크 분위기가 미래를 외칩니다. 이번 튜토리얼에서는 Cyberpunk Glitch Card를 만들어볼 텐데, 마치 SF 게임 UI에서 바로 뽑아낸 듯한 느낌입니다.

결과는? 마우스 움직임에 반응하고, 들쭉날쭉한 글리치 텍스트 애니메이션을 특징으로 하는 3‑D 인터랙티브 프로필 카드입니다.

사이버펑크 글리치 카드를 만드는 이유?

이것은 단순한 정적 이미지가 아닙니다. 카드가 특별한 이유는 다음과 같습니다:

  • 홀로그램 글리치 텍스트 – CSS keyframes + clip이 텍스트에 오작동 같은 모습을 부여합니다.
  • 3‑D 틸트 효과 – 카드는 커서를 따라 3‑D 공간에서 움직입니다.
  • 각진 UIclip‑path가 모서리를 잘라 날카롭고 산업적인 SF 느낌을 줍니다.

Demo →

소스 코드

아래 코드를 복사‑붙여넣기하여 자신의 포트폴리오나 프로젝트에서 이 카드를 사용할 수 있습니다.

HTML 구조

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Cyberpunk Glitch Card</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <div class="card" id="cyber-card">
            <div class="glitch-wrapper">
                <img class="avatar" src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?q=80&w=1000" alt="Cyber Avatar">
            </div>

            <h2 class="cyber-title" data-text="ALEX_MERCER">ALEX_MERCER</h2>
            <p class="cyber-role">SYSTEM_ARCHITECT // LVL.99</p>

            <div class="stats">
                <div class="stat-box">
                    <span class="label">SPEED</span>
                    <span class="value">98%</span>
                </div>
                <div class="stat-box">
                    <span class="label">HACK</span>
                    <span class="value">100%</span>
                </div>
                <div class="stat-box">
                    <span class="label">STEALTH</span>
                    <span class="value">85%</span>
                </div>
            </div>

            <button class="cyber-btn">INITIALIZE **</button>

            <!-- Decorative corners -->
            <div class="corner top-left"></div>
            <div class="corner top-right"></div>
            <div class="corner bottom-left"></div>
            <div class="corner bottom-right"></div>
        </div>
    </div>
</body>
</html>

CSS (마법)

@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Share+Tech+Mono&display=swap');

:root {
    --primary:   #00f3ff; /* Cyan Neon */
    --secondary: #ff0055; /* Pink Neon */
    --bg:        #050505;
    --card-bg:   #111;
}

/* Reset & basic layout */
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}
body {
    background-color: var(--bg);
    background-image:
        linear-gradient(rgba(0, 243, 255, 0.03) 1px, transparent 1px),
        linear-gradient(90deg, rgba(0, 243, 255, 0.03) 1px, transparent 1px);
    background-size: 30px 30px;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    font-family: 'Share Tech Mono', monospace;
    overflow: hidden;
}
.container {
    perspective: 1000px;
}

/* Card */
.card {
    width: 350px;
    padding: 40px 30px;
    background: var(--card-bg);
    border: 1px solid rgba(0, 243, 255, 0.2);
    position: relative;
    transform-style: preserve-3d;
    cursor: pointer;
    box-shadow: 0 0 20px rgba(0, 243, 255, 0.1);
    clip-path: polygon(
        0 0,
        100% 0,
        100% 85%,
        90% 100%,
        0 100%
    );
}

/* Avatar & glitch wrapper */
.glitch-wrapper {
    width: 120px;
    height: 120px;
    margin: 0 auto 20px;
    position: relative;
}
.avatar {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border: 2px solid var(--primary);
    filter: grayscale(100%);
    transition: .3s;
}
.card:hover .avatar {
    filter: grayscale(0%);
    border-color: var(--secondary);
}

/* Typography */
.cyber-title {
    font-family: 'Orbitron', sans-serif;
    color: #fff;
    text-align: center;
    font-size: 1.8rem;
    margin-bottom: 5px;
    position: relative;
    letter-spacing: 2px;
}

/* Glitch text effect */
.cyber-title::before,
.cyber-title::after {
    content: attr(data-text);
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: var(--card-bg);
}
.cyber-title::before {
    left: 2px;
    text-shadow: -1px 0 var(--secondary);
    clip: rect(0, 900px, 0, 0);
    animation: glitch-anim 2s infinite linear alternate-reverse;
}
.cyber-title::after {
    left: -2px;
    text-shadow: 1px 0 var(--primary);
    clip: rect(0, 900px, 0, 0);
    animation: glitch-anim 2s infinite linear alternate-reverse;
}

/*  */
/* Glitch animation */
@keyframes glitch-anim {
    0%   { clip: rect(42px, 9999px, 44px, 0); }
    10%  { clip: rect(12px, 9999px, 14px, 0); }
    20%  { clip: rect(62px, 9999px, 64px, 0); }
    30%  { clip: rect(22px, 9999px, 24px, 0); }
    40%  { clip: rect(72px, 9999px, 74px, 0); }
    50%  { clip: rect(32px, 9999px, 34px, 0); }
    60%  { clip: rect(82px, 9999px, 84px, 0); }
    70%  { clip: rect(42px, 9999px, 44px, 0); }
    80%  { clip: rect(12px, 9999px, 14px, 0); }
    90%  { clip: rect(62px, 9999px, 64px, 0); }
    100% { clip: rect(22px, 9999px, 24px, 0); }
}

/* Role text */
.cyber-role {
    text-align: center;
    color: var(--primary);
    font-size: .9rem;
    margin-bottom: 20px;
}

/* Stats */
.stats {
    display: flex;
    justify-content: space-between;
    margin-bottom: 20px;
}
.stat-box {
    text-align: center;
}
.stat-box .label {
    display: block;
    font-size: .7rem;
    color: #777;
}
.stat-box .value {
    font-size: 1.1rem;
    color: #fff;
}

/* Button */
.cyber-btn {
    width: 100%;
    padding: 10px;
    background: var(--primary);
    border: none;
    color: #000;
    font-weight: bold;
    cursor: pointer;
    transition: background .3s;
}
.cyber-btn:hover {
    background: var(--secondary);
}

/* Angled corners (decorative) */
.corner {
    position: absolute;
    width: 20px;
    height: 20px;
    border: 2px solid var(--primary);
}
.top-left    { top: -2px; left: -2px; border-right: none; border-bottom: none; }
.top-right   { top: -2px; right: -2px; border-left: none; border-bottom: none; }
.bottom-left { bottom: -2px; left: -2px; border-right: none; border-top: none; }
.bottom-right{ bottom: -2px; right: -2px; border-left: none; border-top: none; }

JavaScript (옵션 틸트 효과)

카드가 마우스를 따라 움직이게 하려면, 닫는 </body> 태그 바로 앞에 다음 스크립트를 추가하세요:

<script>
const card = document.getElementById('cyber-card');

card.addEventListener('mousemove', (e) => {
    const rect = card.getBoundingClientRect();
    const x = e.clientX - rect.left - rect.width / 2;
    const y = e.clientY - rect.top - rect.height / 2;
    const rotateX = (y / rect.height) * 20;
    const rotateY = -(x / rect.width) * 20;
    card.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
});

card.addEventListener('mouseleave', () => {
    card.style.transform = 'rotateX(0) rotateY(0)';
});
</script>

이제 완전한 기능을 갖춘 Cyberpunk Glitch Card를 프로젝트에 바로 적용할 수 있습니다. 네온 빛을 즐겨보세요!

CSS – 카드 스타일링 및 글리치 효과

/* --- Card Container --- */
.cyber-card {
  width: 350px;
  height: 500px;
  background: rgba(0, 0, 0, 0.6);
  border: 2px solid var(--primary);
  border-radius: 12px;
  overflow: hidden;
  position: relative;
  perspective: 1000px;
  transform-style: preserve-3d;
}

/* --- Title with Glitch --- */
.cyber-title {
  position: relative;
  font-family: 'Orbitron', sans-serif;
  font-size: 2rem;
  color: var(--primary);
  text-align: center;
  margin-top: 30px;
  text-shadow: 0 0 5px var(--primary);
}
.cyber-title::before,
.cyber-title::after {
  content: attr(data-text);
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.cyber-title::before {
  left: 2px;
  text-shadow: -1px 0 var(--secondary);
  clip: rect(24px, 550px, 90px, 0);
  animation: glitch-anim-2 3s infinite linear alternate-reverse;
}
.cyber-title::after {
  left: -2px;
  text-shadow: -1px 0 var(--primary);
  clip: rect(85px, 550px, 140px, 0);
  animation: glitch-anim 2.5s infinite linear alternate-reverse;
}

/* --- Role / Subtitle --- */
.cyber-role {
  text-align: center;
  color: var(--primary);
  font-size: 0.9rem;
  margin-bottom: 30px;
  opacity: 0.8;
}

/* --- Stats Grid --- */
.stats {
  display: flex;
  justify-content: space-between;
  margin-bottom: 30px;
  border-top: 1px solid rgba(255,255,255,0.1);
  border-bottom: 1px solid rgba(255,255,255,0.1);
  padding: 15px 0;
}
.stat-box {
  text-align: center;
}
.stat-box .label {
  display: block;
  font-size: 0.7rem;
  color: #888;
  margin-bottom: 5px;
}
.stat-box .value {
  font-size: 1.1rem;
  color: #fff;
  font-weight: bold;
}

/* --- Button --- */
.cyber-btn {
  width: 100%;
  padding: 15px;
  background: transparent;
  border: 1px solid var(--primary);
  color: var(--primary);
  font-family: 'Orbitron', sans-serif;
  font-weight: bold;
  letter-spacing: 2px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: 0.3s;
}
.cyber-btn::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: var(--primary);
  transition: 0.3s;
  z-index: -1;
}
.cyber-btn:hover {
  color: #000;
  box-shadow: 0 0 15px var(--primary);
}
.cyber-btn:hover::before {
  left: 0;
}

/* --- Decorative Corners --- */
.corner {
  position: absolute;
  width: 10px;
  height: 10px;
  border: 2px solid var(--primary);
  transition: 0.3s;
}
.top-left {
  top: -2px;
  left: -2px;
  border-right: none;
  border-bottom: none;
}
.top-right {
  top: -2px;
  right: -2px;
  border-left: none;
  border-bottom: none;
}
.bottom-left {
  bottom: -2px;
  left: -2px;
  border-right: none;
  border-top: none;
}
.bottom-right {
  bottom: -2px;
  right: -2px;
  border-left: none;
  border-top: none;
  width: 20px;
  border-color: var(--secondary);
}
.card:hover .corner {
  width: 100%;
  height: 100%;
  opacity: 0.1;
}

/* --- Keyframes --- */
@keyframes glitch-anim {
  0%   { clip: rect(10px, 9999px, 30px, 0);  transform: skew(0.5deg); }
  10%  { clip: rect(50px, 9999px, 70px, 0);  transform: skew(0.2deg); }
  20%  { clip: rect(20px, 9999px, 60px, 0);  transform: skew(0.8deg); }
  100% { clip: rect(80px, 9999px, 100px, 0); transform: skew(0.1deg); }
}
@keyframes glitch-anim-2 {
  0%   { clip: rect(60px, 9999px, 80px, 0);  transform: skew(0.6deg); }
  10%  { clip: rect(10px, 9999px, 30px, 0);  transform: skew(0.3deg); }
  100% { clip: rect(90px, 9999px, 100px, 0); transform: skew(0.1deg); }
}

JavaScript – 상호작용

const card = document.getElementById('cyber-card');
Back to Blog

관련 글

더 보기 »

v2: GSAP가 문제가 되지 않을 때

내 포트폴리오 v1을 출시했을 때, 나는 신뢰성이 야망보다 더 중요했기 때문에 단순화된 모션을 선택했다. 나는 GSAP을 존중했지만 아직 완전히 이해하지 못했다—i...