Parallax Cosmos
A 320vh sticky scene with six depth layers and a canvas starfield drifting at independent speeds, depth labels, and a scroll-progress bar.
Parallax Cosmos the 1st of 12 designs in the 12 CSS Scroll Animations collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="prog" id="prog"></div>
<div id="pw">
<div id="pi">
<canvas id="cv"></canvas>
<div class="ly" id="l-r3"><div class="ring r3"></div></div>
<div class="ly" id="l-r2"><div class="ring r2"></div></div>
<div class="ly" id="l-orb"><div class="orb"></div></div>
<div class="ly" id="l-r1"><div class="ring r1"></div></div>
<div class="ly" id="l-ghost"><div class="ghost">DEPTH</div></div>
<div class="ly" id="l-tag"><div class="tagline">Scroll to feel the layers</div></div>
<div class="dot d1" id="dot1"></div>
<div class="dot d2" id="dot2"></div>
<div class="dot d3" id="dot3"></div>
<div class="depth-label a1" id="lbl1">01 — Atmosphere</div>
<div class="depth-label a2" id="lbl2">02 — Orbit</div>
<div class="depth-label a3" id="lbl3">03 — Core</div>
</div>
</div> *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{background:#04040B;overflow-x:hidden}
#pw{height:320vh;position:relative}
#pi{
position:sticky;top:0;height:100vh;overflow:hidden;
background:#04040B;display:flex;align-items:center;justify-content:center;
}
canvas{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.ly{
position:absolute;inset:0;
display:flex;align-items:center;justify-content:center;
will-change:transform;pointer-events:none;
}
.ghost{
font-family:'Bebas Neue',sans-serif;
font-size:clamp(100px,17vw,250px);
color:transparent;
-webkit-text-stroke:1px rgba(201,169,110,0.11);
letter-spacing:0.05em;user-select:none;white-space:nowrap;
}
.orb{
width:clamp(320px,46vw,580px);height:clamp(320px,46vw,580px);
border-radius:50%;
background:radial-gradient(circle at 38% 34%,rgba(201,169,110,0.16),rgba(140,80,220,0.05) 48%,transparent 72%);
}
.ring{border-radius:50%;border:1px solid rgba(201,169,110,0.07)}
.r1{width:clamp(200px,33vw,430px);height:clamp(200px,33vw,430px)}
.r2{width:clamp(450px,72vw,840px);height:clamp(450px,72vw,840px);border-color:rgba(201,169,110,0.04)}
.r3{width:clamp(520px,88vw,1020px);height:clamp(520px,88vw,1020px);border-color:rgba(201,169,110,0.025)}
.tagline{
font-family:'Fraunces',serif;font-style:italic;
font-size:clamp(16px,2.2vw,28px);
color:rgba(201,169,110,0.72);text-align:center;
white-space:nowrap;
}
.dot{
width:3px;height:3px;border-radius:50%;background:#C9A96E;
box-shadow:0 0 14px 5px rgba(201,169,110,0.22);
}
.d1{position:absolute;top:30%;right:24%}
.d2{position:absolute;bottom:26%;left:20%}
.d3{position:absolute;top:58%;right:17%}
/* scroll progress */
.prog{
position:fixed;bottom:0;left:0;height:1px;
background:linear-gradient(to right,#C9A96E 0%,rgba(201,169,110,0.15) 100%);
width:0%;z-index:100;transition:width 0.08s linear;
}
/* depth labels */
.depth-label{
position:absolute;
font-family:'Fraunces',serif;font-style:italic;
font-size:clamp(11px,1.2vw,15px);
color:rgba(201,169,110,0.0);
letter-spacing:0.1em;
transition:color 0.6s ease;
}
.depth-label.a1{top:18%;left:8%}
.depth-label.a2{bottom:20%;right:9%}
.depth-label.a3{top:50%;left:50%;transform:translate(-50%,-50%)} const cv = document.getElementById('cv');
const ctx = cv.getContext('2d');
function drawStars(){
cv.width = window.innerWidth;
cv.height = window.innerHeight;
ctx.clearRect(0,0,cv.width,cv.height);
for(let i=0;i<280;i++){
const x=Math.random()*cv.width, y=Math.random()*cv.height;
const r=Math.random()*1.5+0.15;
ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);
ctx.fillStyle=`rgba(255,252,215,${Math.random()*0.55+0.07})`;
ctx.fill();
}
for(let i=0;i<12;i++){
const x=Math.random()*cv.width, y=Math.random()*cv.height;
ctx.beginPath();ctx.arc(x,y,1.8,0,Math.PI*2);
ctx.fillStyle='rgba(255,250,200,0.75)';ctx.fill();
}
}
drawStars();
window.addEventListener('resize',drawStars);
const pw = document.getElementById('pw');
const prog = document.getElementById('prog');
const layers = [
{el:document.getElementById('l-r3'), range:60},
{el:document.getElementById('l-r2'), range:110},
{el:document.getElementById('l-orb'), range:180},
{el:document.getElementById('l-r1'), range:270},
{el:document.getElementById('l-ghost'),range:480},
{el:document.getElementById('l-tag'), range:90},
];
const lbl1=document.getElementById('lbl1');
const lbl2=document.getElementById('lbl2');
const lbl3=document.getElementById('lbl3');
window.addEventListener('scroll',()=>{
const rect = pw.getBoundingClientRect();
const scrolled = -rect.top;
const maxScroll = pw.offsetHeight - window.innerHeight;
const p = Math.max(0,Math.min(1, scrolled/maxScroll));
prog.style.width = (p*100)+'%';
layers.forEach(l=>{
const offset = (p - 0.5) * l.range;
l.el.style.transform = `translateY(${offset}px)`;
});
lbl1.style.color = p > 0.15 ? `rgba(201,169,110,${Math.min((p-0.15)*4,0.45)})` : 'rgba(201,169,110,0)';
lbl2.style.color = p > 0.45 ? `rgba(201,169,110,${Math.min((p-0.45)*4,0.45)})` : 'rgba(201,169,110,0)';
lbl3.style.color = p > 0.72 ? `rgba(201,169,110,${Math.min((p-0.72)*4,0.45)})` : 'rgba(201,169,110,0)';
document.getElementById('l-ghost').firstElementChild.style.webkitTextStroke =
`1px rgba(201,169,110,${0.06 + p * 0.22})`;
},{passive:true});