Accelerating Globe on Scroll
A CSS wireframe globe whose rotation speed scales with scroll velocity. Eight mirrored ring segments animate in a looping GSAP timeline; when the user scrolls, the timeline's timeScale spikes based on scroll speed and eases back to 1x after scrolling stops.
Setup — External Scripts
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>Code
<div data-accelerating-globe="" class="globe">
<div class="globe__before"></div>
<div class="globe__back">
<div class="globe__back-circle"></div>
<div class="globe__back-circle is--1"></div>
<div class="globe__back-circle is--2"></div>
<div class="globe__back-circle is--3"></div>
<div class="globe__back-circle is--4"></div>
<div class="globe__back-circle is--5"></div>
</div>
<div class="globe__front">
<div class="globe__mirror">
<div data-accelerating-globe-circle="" class="globe__circle">
<div class="globe__circle-inner"></div>
</div>
<div data-accelerating-globe-circle="" class="globe__circle">
<div class="globe__circle-inner"></div>
</div>
<div data-accelerating-globe-circle="" class="globe__circle">
<div class="globe__circle-inner"></div>
</div>
<div data-accelerating-globe-circle="" class="globe__circle">
<div class="globe__circle-inner"></div>
</div>
</div>
<div class="globe__mirror is--duplicate">
<div data-accelerating-globe-circle="" class="globe__circle">
<div class="globe__circle-inner"></div>
</div>
<div data-accelerating-globe-circle="" class="globe__circle">
<div class="globe__circle-inner"></div>
</div>
<div data-accelerating-globe-circle="" class="globe__circle">
<div class="globe__circle-inner"></div>
</div>
<div data-accelerating-globe-circle="" class="globe__circle">
<div class="globe__circle-inner"></div>
</div>
</div>
</div>
</div>.globe {
color: #d500ff;
justify-content: center;
align-items: center;
width: 37.5vw;
display: flex;
position: relative;
}
.globe__before {
padding-top: 100%;
}
.globe__back {
border-radius: 50%;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
display: flex;
position: absolute;
overflow: hidden;
}
.globe__back-circle {
border: 1px solid;
border-radius: 50%;
width: 100%;
height: 100%;
position: absolute;
}
.globe__back-circle.is--1 {
width: 50%;
height: 16%;
top: 0%;
}
.globe__back-circle.is--2 {
width: 87.5%;
height: 24%;
top: 14%;
}
.globe__back-circle.is--3 {
border-radius: 50%;
width: 100%;
height: 28%;
top: 36%;
}
.globe__back-circle.is--4 {
width: 87.5%;
height: 24%;
top: 62%;
}
.globe__back-circle.is--5 {
width: 50%;
height: 16%;
top: 84%;
}
.globe__front,
.globe__mirror {
width: 100%;
height: 100%;
position: absolute;
}
.globe__mirror.is--duplicate {
transform: scaleX(-1);
}
.globe__circle {
width: 50%;
height: 100%;
position: absolute;
left: auto;
right: 50%;
overflow: hidden;
}
.globe__circle-inner {
border: 1px solid;
border-radius: 50%;
width: 200%;
height: 100%;
position: absolute;
}function initAcceleratingGlobe() {
document.querySelectorAll('[data-accelerating-globe]').forEach(function(globe) {
const circles = globe.querySelectorAll('[data-accelerating-globe-circle]');
if (circles.length < 8) return;
const tl = gsap.timeline({
repeat: -1,
defaults: { duration: 1, ease: "none" },
});
const widths = [
["50%", "37.5%" ],
["37.5%", "25%" ],
["25%", "12.5%" ],
["calc(12.5% + 1px)","calc(0% + 1px)" ],
["calc(0% + 1px)", "calc(12.5% + 1px)"],
["12.5%", "25%" ],
["25%", "37.5%" ],
["37.5%", "50%" ],
];
circles.forEach((el, i) => {
const [fromW, toW] = widths[i];
tl.fromTo(el, { width: fromW }, { width: toW }, i === 0 ? 0 : "<");
});
let lastY = window.scrollY;
let lastT = performance.now();
let stopTimeout;
function onScroll() {
const now = performance.now();
const dy = window.scrollY - lastY;
const dt = now - lastT;
lastY = window.scrollY;
lastT = now;
const velocity = dt > 0 ? (dy / dt) * 1000 : 0; // px/s
const boost = Math.abs(velocity * 0.005);
const targetScale = boost + 1;
tl.timeScale(targetScale);
clearTimeout(stopTimeout);
stopTimeout = setTimeout(() => {
gsap.to(tl, {
timeScale: 1,
duration: 0.6,
ease: "power2.out",
overwrite: true,
});
}, 100);
}
window.addEventListener("scroll", onScroll, { passive: true });
});
}
document.addEventListener('DOMContentLoaded', function() {
initAcceleratingGlobe();
});Attributes
| Name | Type | Default | Description |
|---|---|---|---|
| data-accelerating-globe | attribute | — | Root element of each globe instance. The script queries all elements with this attribute, so multiple independent globes on the same page are supported. |
| data-accelerating-globe-circle | attribute | — | Each animated ring segment. Requires exactly 8 (4 per .globe__mirror). The script skips the globe if fewer than 8 are found. |
Notes
- •Requires GSAP loaded via CDN before the script runs.
- •At least 8 [data-accelerating-globe-circle] elements are required — the script skips any globe with fewer.
- •Multiple globes on the same page are supported; each gets its own timeline and scroll listener.
- •The scroll listener uses { passive: true } for performance.
- •Adjust the 0.005 multiplier in the velocity → timeScale formula to make the speed boost more or less sensitive to scroll.
Guide
Ring animation
The 8 [data-accelerating-globe-circle] elements animate their width in sequence within a single repeating timeline. All 8 tweens are placed at position 0 (they run in parallel), cycling through widths that simulate a meridian sweeping across the sphere surface.
Scroll velocity to timeScale
On each scroll event, the script calculates velocity in px/s from the delta scroll and delta time. The timeScale is set to 1 + |velocity| × 0.005. After 100ms of no scrolling, it tweens back to 1x with a power2.out ease.
const velocity = dt > 0 ? (dy / dt) * 1000 : 0; // px/s
const boost = Math.abs(velocity * 0.005);
tl.timeScale(boost + 1);Mirrored structure
The .globe__front contains two .globe__mirror wrappers. The second (.is--duplicate) is flipped horizontally with scaleX(-1). Together they create the illusion of a full sphere from 4 half-circle rings per side.
Static back layer
The .globe__back contains 6 ellipses positioned at fixed latitude lines (the full equator plus 5 latitude bands). These are purely CSS and do not animate — they provide the grid reference that makes the rotating front rings read as a 3D sphere.