10 CSS Parallax Effects 08 / 10

CSS Parallax Card Hover Effect

Six product/profile cards where three independent layers — gradient background, geometric SVG decoration, text content — shift on mouse position at 8px, 14px, and 22px depth respectively.

CSS + JS MIT licensed
Live Demo Open in tab

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

Open in playground

The code

<div class="plx-08">

  <header class="plx-08__header">
    <p class="plx-08__label">CSS Parallax · Mouse-Driven Tilt</p>
    <h1>Hover over a card<br><span>— any card.</span></h1>
  </header>

  <div class="plx-08__grid">

    <div class="plx-08__card">
      <div class="plx-08__card-bg"></div>
      <div class="plx-08__card-light"></div>
      <div class="plx-08__card-noise"></div>
      <div class="plx-08__card-geo">
        <svg viewBox="0 0 300 440" xmlns="http://www.w3.org/2000/svg">
          <circle cx="250" cy="80" r="120" stroke="white" stroke-width="1" fill="none"/>
          <circle cx="250" cy="80" r="70" stroke="white" stroke-width="0.5" fill="none"/>
          <line x1="0" y1="440" x2="300" y2="0" stroke="white" stroke-width="0.4"/>
        </svg>
      </div>
      <div class="plx-08__card-content">
        <div class="plx-08__card-icon">🌌</div>
        <p class="plx-08__card-tag">01 · Astrophysics</p>
        <h3 class="plx-08__card-title">The Observable Universe</h3>
        <p class="plx-08__card-body">93 billion light-years of space. Everything we know, everything we can ever know, fits inside this sphere.</p>
        <span class="plx-08__card-cta">Explore</span>
      </div>
    </div>

    <div class="plx-08__card">
      <div class="plx-08__card-bg"></div>
      <div class="plx-08__card-light"></div>
      <div class="plx-08__card-noise"></div>
      <div class="plx-08__card-geo">
        <svg viewBox="0 0 300 440" xmlns="http://www.w3.org/2000/svg">
          <polygon points="150,20 280,380 20,380" stroke="white" stroke-width="1" fill="none"/>
          <polygon points="150,80 240,340 60,340" stroke="white" stroke-width="0.5" fill="none"/>
        </svg>
      </div>
      <div class="plx-08__card-content">
        <div class="plx-08__card-icon">💎</div>
        <p class="plx-08__card-tag">02 · Mineralogy</p>
        <h3 class="plx-08__card-title">Crystalline Structures</h3>
        <p class="plx-08__card-body">Carbon under immense pressure becomes diamond. Chaos compressed into the most ordered thing in nature.</p>
        <span class="plx-08__card-cta">Discover</span>
      </div>
    </div>

    <div class="plx-08__card">
      <div class="plx-08__card-bg"></div>
      <div class="plx-08__card-light"></div>
      <div class="plx-08__card-noise"></div>
      <div class="plx-08__card-geo">
        <svg viewBox="0 0 300 440" xmlns="http://www.w3.org/2000/svg">
          <path d="M0,220 Q75,100 150,220 Q225,340 300,220" stroke="white" stroke-width="1" fill="none"/>
          <path d="M0,220 Q75,140 150,220 Q225,300 300,220" stroke="white" stroke-width="0.5" fill="none"/>
          <path d="M0,220 Q75,180 150,220 Q225,260 300,220" stroke="white" stroke-width="0.3" fill="none"/>
        </svg>
      </div>
      <div class="plx-08__card-content">
        <div class="plx-08__card-icon">🌊</div>
        <p class="plx-08__card-tag">03 · Oceanography</p>
        <h3 class="plx-08__card-title">Deep Currents</h3>
        <p class="plx-08__card-body">The ocean's thermohaline circulation moves water around the entire planet over hundreds of years.</p>
        <span class="plx-08__card-cta">Dive in</span>
      </div>
    </div>

    <div class="plx-08__card">
      <div class="plx-08__card-bg"></div>
      <div class="plx-08__card-light"></div>
      <div class="plx-08__card-noise"></div>
      <div class="plx-08__card-geo">
        <svg viewBox="0 0 300 440" xmlns="http://www.w3.org/2000/svg">
          <rect x="30" y="30" width="240" height="380" rx="20" stroke="white" stroke-width="1" fill="none"/>
          <rect x="70" y="70" width="160" height="300" rx="10" stroke="white" stroke-width="0.5" fill="none"/>
          <rect x="110" y="110" width="80" height="220" rx="5" stroke="white" stroke-width="0.3" fill="none"/>
        </svg>
      </div>
      <div class="plx-08__card-content">
        <div class="plx-08__card-icon">🏙️</div>
        <p class="plx-08__card-tag">04 · Architecture</p>
        <h3 class="plx-08__card-title">Vertical Cities</h3>
        <p class="plx-08__card-body">When land is scarce and ambition isn't, we build upward. The skyline is a city's autobiography.</p>
        <span class="plx-08__card-cta">Ascend</span>
      </div>
    </div>

    <div class="plx-08__card">
      <div class="plx-08__card-bg"></div>
      <div class="plx-08__card-light"></div>
      <div class="plx-08__card-noise"></div>
      <div class="plx-08__card-geo">
        <svg viewBox="0 0 300 440" xmlns="http://www.w3.org/2000/svg">
          <circle cx="150" cy="220" r="160" stroke="white" stroke-width="1" fill="none" stroke-dasharray="8 6"/>
          <circle cx="150" cy="220" r="100" stroke="white" stroke-width="0.5" fill="none" stroke-dasharray="5 4"/>
          <circle cx="150" cy="220" r="40" stroke="white" stroke-width="0.3" fill="none"/>
        </svg>
      </div>
      <div class="plx-08__card-content">
        <div class="plx-08__card-icon">⚡</div>
        <p class="plx-08__card-tag">05 · Physics</p>
        <h3 class="plx-08__card-title">Electromagnetic Fields</h3>
        <p class="plx-08__card-body">Invisible forces that hold atoms together, power every device, and fill every cubic inch of space around you.</p>
        <span class="plx-08__card-cta">Charge up</span>
      </div>
    </div>

    <div class="plx-08__card">
      <div class="plx-08__card-bg"></div>
      <div class="plx-08__card-light"></div>
      <div class="plx-08__card-noise"></div>
      <div class="plx-08__card-geo">
        <svg viewBox="0 0 300 440" xmlns="http://www.w3.org/2000/svg">
          <path d="M20,400 Q80,200 150,100 Q220,0 280,100 Q350,200 150,400" stroke="white" stroke-width="1" fill="none"/>
          <path d="M60,400 Q100,250 150,160 Q200,80 250,200 Q280,300 150,400" stroke="white" stroke-width="0.4" fill="none"/>
        </svg>
      </div>
      <div class="plx-08__card-content">
        <div class="plx-08__card-icon">🌿</div>
        <p class="plx-08__card-tag">06 · Botany</p>
        <h3 class="plx-08__card-title">Fractal Growth</h3>
        <p class="plx-08__card-body">A fern leaf, a river delta, a bolt of lightning — nature repeats its favourite shapes at every scale.</p>
        <span class="plx-08__card-cta">Grow</span>
      </div>
    </div>

  </div>

  <p class="plx-08__footer">Hover any card · 3 independent parallax layers · CSS transform + JS cursor</p>

</div>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* Fall back to system mono if Geist unavailable */
.plx-08 {
  --bg: #0c0c10;
  --surface: #14141c;
  --border: rgba(255,255,255,0.08);
  --text: #e8e8f0;
  --muted: rgba(232,232,240,0.45);
  --card-h: 440px;
  background: var(--bg);
  color: var(--text);
  font-family: 'Geist', system-ui, sans-serif;
  min-height: 100vh;
  padding: 60px 40px;
}

/* Header */
.plx-08__header {
  text-align: center;
  margin-bottom: 80px;
}

.plx-08__label {
  display: inline-block;
  font-size: 10px;
  font-family: 'Geist Mono', monospace;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 16px;
}

.plx-08__header h1 {
  font-size: clamp(32px, 5vw, 60px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.0;
}

.plx-08__header h1 span {
  opacity: 0.3;
}

/* Grid */
.plx-08__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 24px;
  max-width: 1200px;
  margin: 0 auto;
}

/* === THE CARD === */
.plx-08__card {
  position: relative;
  height: var(--card-h);
  border-radius: 16px;
  border: 1px solid var(--border);
  overflow: hidden;
  cursor: pointer;
  transform-style: preserve-3d;
  will-change: transform;
  /* 3D perspective is set inline via JS */
  transform: perspective(800px) rotateX(0deg) rotateY(0deg);
  transition: transform 0.1s ease, box-shadow 0.3s ease;
}

.plx-08__card:not(:hover) {
  transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.6s ease;
}

/* Background gradient layer — moves slower than card tilt */
.plx-08__card-bg {
  position: absolute;
  inset: -10%;
  border-radius: 24px;
  will-change: transform;
  transition: transform 0.1s ease;
  transform: translateX(0px) translateY(0px);
}

.plx-08__card:not(:hover) .plx-08__card-bg {
  transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Card accent/color themes */
.plx-08__card:nth-child(1) .plx-08__card-bg { background: radial-gradient(ellipse at 40% 40%, rgba(99,102,241,0.6) 0%, rgba(30,27,75,0.9) 60%); }
.plx-08__card:nth-child(2) .plx-08__card-bg { background: radial-gradient(ellipse at 60% 40%, rgba(236,72,153,0.5) 0%, rgba(75,10,50,0.9) 60%); }
.plx-08__card:nth-child(3) .plx-08__card-bg { background: radial-gradient(ellipse at 40% 60%, rgba(16,185,129,0.5) 0%, rgba(5,50,30,0.9) 60%); }
.plx-08__card:nth-child(4) .plx-08__card-bg { background: radial-gradient(ellipse at 50% 40%, rgba(245,158,11,0.5) 0%, rgba(60,30,5,0.9) 60%); }
.plx-08__card:nth-child(5) .plx-08__card-bg { background: radial-gradient(ellipse at 60% 60%, rgba(14,165,233,0.5) 0%, rgba(5,30,60,0.9) 60%); }
.plx-08__card:nth-child(6) .plx-08__card-bg { background: radial-gradient(ellipse at 40% 40%, rgba(168,85,247,0.5) 0%, rgba(40,10,70,0.9) 60%); }

/* Shimmer/light layer */
.plx-08__card-light {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 50%, rgba(255,255,255,0.12) 0%, transparent 60%);
  will-change: background;
  pointer-events: none;
  transition: background 0.1s ease;
  border-radius: inherit;
}

/* Noise texture */
.plx-08__card-noise {
  position: absolute;
  inset: 0;
  opacity: 0.04;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  pointer-events: none;
}

/* Geometric decorative layer — moves opposite to BG */
.plx-08__card-geo {
  position: absolute;
  inset: 0;
  will-change: transform;
  transform: translateX(0px) translateY(0px);
  transition: transform 0.1s ease;
  pointer-events: none;
}

.plx-08__card:not(:hover) .plx-08__card-geo {
  transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.plx-08__card-geo svg {
  width: 100%; height: 100%;
  opacity: 0.18;
}

/* Content layer — moves even more than card tilt (depth pop) */
.plx-08__card-content {
  position: absolute;
  inset: 0;
  padding: 32px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  will-change: transform;
  transform: translateX(0px) translateY(0px) translateZ(0px);
  transition: transform 0.1s ease;
  z-index: 5;
}

.plx-08__card:not(:hover) .plx-08__card-content {
  transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.plx-08__card-icon {
  width: 48px;
  height: 48px;
  border-radius: 12px;
  border: 1px solid rgba(255,255,255,0.15);
  background: rgba(255,255,255,0.08);
  display: grid;
  place-items: center;
  font-size: 22px;
  margin-bottom: auto;
}

.plx-08__card-tag {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  opacity: 0.4;
  margin-bottom: 10px;
}

.plx-08__card-title {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
  margin-bottom: 10px;
}

.plx-08__card-body {
  font-size: 13px;
  font-weight: 300;
  line-height: 1.65;
  opacity: 0.55;
  margin-bottom: 24px;
}

.plx-08__card-cta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 500;
  font-family: 'Geist Mono', monospace;
  letter-spacing: 0.08em;
  opacity: 0.7;
  transition: opacity 0.2s;
}
.plx-08__card:hover .plx-08__card-cta { opacity: 1; }
.plx-08__card-cta::after {
  content: '→';
  transition: transform 0.2s;
}
.plx-08__card:hover .plx-08__card-cta::after { transform: translateX(4px); }

/* Shadow ring on hover */
.plx-08__card:hover {
  box-shadow:
    0 20px 60px rgba(0,0,0,0.5),
    0 0 0 1px rgba(255,255,255,0.12);
}

/* Footer note */
.plx-08__footer {
  text-align: center;
  margin-top: 60px;
  font-family: 'Geist Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.15em;
  color: var(--muted);
}

@media (max-width: 600px) {
  .plx-08 { padding: 40px 20px; }
  .plx-08__grid { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .plx-08__card { transition: none !important; transform: none !important; }
  .plx-08__card-bg, .plx-08__card-geo, .plx-08__card-content { transition: none !important; transform: none !important; }
}
(() => {
  const root = document.querySelector('.plx-08');
  if (!root) return;

  const cards = Array.from(root.querySelectorAll('.plx-08__card'));

  const TILT_MAX = 18; // degrees
  const BG_SPEED = 0.3;
  const GEO_SPEED = -0.5;
  const CONTENT_SPEED = 0.7;

  cards.forEach(card => {
    const bg = card.querySelector('.plx-08__card-bg');
    const light = card.querySelector('.plx-08__card-light');
    const geo = card.querySelector('.plx-08__card-geo');
    const content = card.querySelector('.plx-08__card-content');

    function onMove(e) {
      const rect = card.getBoundingClientRect();
      // Normalized position: -1 to 1
      const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;
      const ny = ((e.clientY - rect.top) / rect.height) * 2 - 1;

      // Card tilt
      const rotX = -ny * TILT_MAX;
      const rotY = nx * TILT_MAX;
      card.style.transform = `perspective(800px) rotateX(${rotX}deg) rotateY(${rotY}deg)`;

      // BG drifts slowly in same direction
      const bgX = nx * 20 * BG_SPEED;
      const bgY = ny * 20 * BG_SPEED;
      if (bg) bg.style.transform = `translateX(${bgX}px) translateY(${bgY}px)`;

      // Light follows cursor
      if (light) light.style.background = `radial-gradient(circle at ${(nx+1)/2*100}% ${(ny+1)/2*100}%, rgba(255,255,255,0.15) 0%, transparent 55%)`;

      // Geo drifts in opposite direction (depth separation)
      const geoX = nx * 20 * GEO_SPEED;
      const geoY = ny * 20 * GEO_SPEED;
      if (geo) geo.style.transform = `translateX(${geoX}px) translateY(${geoY}px)`;

      // Content pops forward
      const contentX = nx * 12 * CONTENT_SPEED;
      const contentY = ny * 12 * CONTENT_SPEED;
      if (content) content.style.transform = `translateX(${contentX}px) translateY(${contentY}px) translateZ(20px)`;
    }

    function onLeave() {
      card.style.transform = 'perspective(800px) rotateX(0deg) rotateY(0deg)';
      if (bg) bg.style.transform = 'translateX(0px) translateY(0px)';
      if (geo) geo.style.transform = 'translateX(0px) translateY(0px)';
      if (content) content.style.transform = 'translateX(0px) translateY(0px) translateZ(0px)';
      if (light) light.style.background = 'radial-gradient(circle at 50% 50%, rgba(255,255,255,0.12) 0%, transparent 60%)';
    }

    // Touch support
    function onTouch(e) {
      const t = e.touches[0];
      onMove({ clientX: t.clientX, clientY: t.clientY });
    }

    card.addEventListener('mousemove', onMove);
    card.addEventListener('mouseleave', onLeave);
    card.addEventListener('touchmove', onTouch, { passive: true });
    card.addEventListener('touchend', onLeave);
  });
})();

Search CodeFronts

Loading…