<div class="content exploded">
<div class="layer background background-animation">
<img src="img/bg.png" class="bordered" />
</div>
<div class="layer train train-animation">
<img src="img/train.png" class="bordered" />
</div>
<div class="layer foreground foreground-animation">
<img src="img/foreground.png" class="bordered" />
</div>
<div class="frame"></div>
</div>
* {
box-sizing: border-box;
}
body {
background-color: rgb(197, 217, 255);
overflow-x: auto;
height: 100dvh;
margin: 0;
display: grid;
place-items: center;
}
.content {
position: relative;
height: 600px;
width: 200px;
}
.layer {
position: static;
}
.background {
transform: translateX(0);
}
.train {
transform: translateX(-440px);
}
.foreground {
transform: translateX(-210px);
}
.background-animation {
animation: background-shift 4s linear 1 forwards;
}
.train-animation {
animation: train-shift 4s linear 1 forwards;
}
.foreground-animation {
animation: foreground-shift 4s linear 1 forwards;
}
@keyframes background-shift {
to {
transform: translateX(-80px);
}
}
@keyframes train-shift {
to {
transform: translateX(-140px);
}
}
@keyframes foreground-shift {
to {
transform: translateX(-400px);
}
}
/* Just for demostration, not required for animation */
.frame {
position: absolute;
top: 0;
width: 200px;
aspect-ratio: 1;
border: 3px solid rgb(0, 255, 21);
}
.exploded .frame {
top: 2px;
}
.bordered {
border: 2px dotted red;
}
.dg.ac {
top: 2px !important;
right: unset !important;
left: 2px !important;
}
@media screen and (max-width: 600px) {
.content {
margin-block-start: 100px;
}
}
const gui = new dat.GUI({ name: "My GUI" });
const content = document.getElementsByClassName("content")[0];
const layers = document.getElementsByClassName("layer");
const images = document.getElementsByTagName("img");
const config = {
overflow: true,
exploded: true,
replay,
reset,
};
gui
.add(config, "overflow")
.name("Overflow?")
.onChange((newValue) => {
if (newValue === false) {
content.style.overflow = "hidden";
} else {
content.style.overflow = "visible";
}
});
gui
.add(config, "exploded")
.name("Explode?")
.onChange((newValue) => {
if (newValue === true) {
const overFlowCheckbox = document.querySelector(
"div.dg.main li:first-child input"
);
overFlowCheckbox.checked = true;
overFlowCheckbox.disabled = true;
content.style.overflow = "visible";
for (let element of layers) {
element.style.position = "static";
}
} else {
config.overflow = false;
const overFlowCheckbox = document.querySelector(
"div.dg.main li:first-child input"
);
if (overFlowCheckbox.checked === true) {
overFlowCheckbox.click();
}
overFlowCheckbox.disabled = false;
for (let element of layers) {
element.style.position = "absolute";
}
}
document.querySelector(".content").classList.toggle("exploded");
toggleBorders();
});
gui
.add(config, "replay")
.name("Play Animation")
.onChange(() => {
replay();
});
function replay() {
reset();
setTimeout(() => {
layers[0].classList.add("background-animation");
layers[1].classList.add("train-animation");
layers[2].classList.add("foreground-animation");
}, 20);
}
gui
.add(config, "reset")
.name("Reset Animation")
.onChange(() => {
reset();
});
function reset() {
layers[0].classList.remove("background-animation");
layers[1].classList.remove("train-animation");
layers[2].classList.remove("foreground-animation");
}
function toggleBorders() {
for (let img of images) {
img.classList.toggle("bordered");
}
}
function init() {
const overFlowCheckbox = document.querySelector(
"div.dg.main li:first-child input"
);
overFlowCheckbox.disabled = true;
}
init();
This is a visual exposition of a parallax animation. It give you control of splitting out each of the layers of the animation to visualise how the effect is created. The animation is of a train travelling through a country landscape at the golden hour.
This is part of an exploration of the parallax effect. You can read more in my article How to make an awesome horizontal parallax animation. It was inspired by the codepen CSS Parallax Orbs by Jamie Coulter.