Back to CSS Scroll Animations Mountain Parallax Layers CSS + JS
Share
HTML
<div class="scroll-space">
  <div class="scene">
    <div class="sun" id="sun"></div>
    <div class="cloud c1" id="cl1"></div>
    <div class="cloud c2" id="cl2"></div>

    <div class="title" id="title">
      <div class="sub">A Parallax Ascent</div>
      <h1>Higher<br>Ground</h1>
    </div>

    <div class="layer l1" id="L1"><svg viewBox="0 0 1200 220" preserveAspectRatio="none"><path d="M0,220 L0,90 L180,30 L360,110 L560,20 L780,120 L980,50 L1200,100 L1200,220 Z"/></svg></div>
    <div class="layer l2" id="L2"><svg viewBox="0 0 1200 200" preserveAspectRatio="none"><path d="M0,200 L0,110 L220,50 L420,130 L640,60 L860,140 L1080,70 L1200,120 L1200,200 Z"/></svg></div>
    <div class="layer l3" id="L3"><svg viewBox="0 0 1200 180" preserveAspectRatio="none"><path d="M0,180 L0,130 L260,80 L500,150 L720,90 L960,160 L1200,100 L1200,180 Z"/></svg></div>
    <div class="layer l4" id="L4"><svg viewBox="0 0 1200 150" preserveAspectRatio="none"><path d="M0,150 L0,120 L300,90 L600,130 L900,95 L1200,125 L1200,150 Z"/></svg></div>
    <div class="layer l5" id="L5"><svg viewBox="0 0 1200 110" preserveAspectRatio="none"><path d="M0,110 L0,95 L360,70 L720,100 L1080,72 L1200,90 L1200,110 Z"/></svg></div>
  </div>
</div>

<div class="outro">
  <p>The summit was never the point — only the layers you crossed to find it.</p>
</div>
CSS
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{background:#10131C;font-family:'Space Grotesk',sans-serif;overflow-x:hidden}

/* sticky scene */
.scene{
  position:sticky;top:0;height:100vh;overflow:hidden;
  background:linear-gradient(180deg,#1B2238 0%,#33324A 45%,#7A5C66 78%,#D89A6E 100%);
}
.scroll-space{height:340vh;position:relative}

/* sun */
.sun{
  position:absolute;left:50%;top:60%;
  width:160px;height:160px;border-radius:50%;
  margin-left:-80px;
  background:radial-gradient(circle,#FFE7B8,#FBBF6E 55%,rgba(251,191,110,0) 72%);
  will-change:transform;
}

/* mountain layers */
.layer{
  position:absolute;left:0;right:0;bottom:0;
  will-change:transform;
}
.layer svg{display:block;width:100%}

.l1 path{fill:#2A3252}
.l2 path{fill:#3D3F5C}
.l3 path{fill:#5A5063}
.l4 path{fill:#7E5F62}
.l5 path{fill:#A06E5C}

/* drifting cloud band */
.cloud{
  position:absolute;width:280px;height:60px;border-radius:60px;
  background:rgba(255,255,255,0.08);filter:blur(8px);
  will-change:transform;
}
.c1{top:18%;left:10%}
.c2{top:30%;left:55%;width:200px}

/* foreground title */
.title{
  position:absolute;left:0;right:0;top:18%;
  text-align:center;will-change:transform,opacity;
}
.title .sub{
  font-family:'Space Grotesk',sans-serif;font-size:12px;
  letter-spacing:0.32em;text-transform:uppercase;
  color:rgba(255,255,255,0.5);margin-bottom:0.9rem;
}
.title h1{
  font-family:'Cormorant Garamond',serif;font-weight:600;
  font-size:clamp(46px,9vw,128px);line-height:0.95;
  color:#F4ECE0;letter-spacing:-0.01em;
}

/* after-scene content */
.outro{
  background:#D89A6E;padding:7rem 6vw 10rem;text-align:center;
}
.outro p{
  font-family:'Cormorant Garamond',serif;font-style:italic;
  font-size:clamp(22px,3.4vw,40px);line-height:1.4;
  color:#3A2418;max-width:20ch;margin:0 auto;
}
JS
const sun=document.getElementById('sun');
const cl1=document.getElementById('cl1');
const cl2=document.getElementById('cl2');
const title=document.getElementById('title');
const L=[1,2,3,4,5].map(i=>document.getElementById('L'+i));
const space=document.querySelector('.scroll-space');

function frame(){
  const rect=space.getBoundingClientRect();
  const total=space.offsetHeight-window.innerHeight;
  const p=Math.min(Math.max(-rect.top/total,0),1); // 0..1

  // sun rises
  sun.style.transform=`translateY(${ -p*420 }px)`;
  // clouds drift sideways
  cl1.style.transform=`translateX(${ p*260 }px)`;
  cl2.style.transform=`translateX(${ -p*320 }px)`;
  // title floats up + fades
  title.style.transform=`translateY(${ -p*200 }px)`;
  title.style.opacity=String(Math.max(1-p*1.6,0));
  // layers rise at different speeds (far slower, near faster)
  const speeds=[40,90,150,220,300];
  L.forEach((el,i)=>{
    el.style.transform=`translateY(${ -p*speeds[i] }px)`;
  });
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);