HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> 3D Car Animation Model | Nothing4us </title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@300;500;800&display=swap" rel="stylesheet">
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- partial:index.partial.html -->
<div id="page" class="h-full bg-gradient-to-r from-transparent to-slate-200/30 bg-slate-200 text-slate-900 transition-colors duration-[2000ms] antialiased relative overflow-hidden select-none">
<div id="loading" class="absolute inset-0 z-50 bg-slate-200/80 flex items-center justify-center">
<svg class="animate-spin h-10 w-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="2">
<circle class="opacity-20" cx="12" cy="12" r="11" />
<path d="m23 12c0 6.1-4.9 11-11 11" />
</g>
</svg>
</div>
<model-viewer class="absolute left-0 top-0 w-full h-full z-30 pointer-events-none" src="https://cdn.glitch.global/ccaf12bb-59b0-46bb-bd74-b9467ca22e3d/scene.gltf?v=1674149863924" exposure="1" shadow-intensity="3" camera-orbit="0deg 10deg 20%" camera-target="-9.5m -8m 5m"></model-viewer>
<div class="absolute left-0 top-1/2 -translate-y-1/2 z-0 pointer-events-none">
<div class="title opacity-0 text-[clamp(200px,40vh,500px)] font-bold text-slate-900/5 opacity-0 pointer-events-none flex whitespace-nowrap will-change-transform">
<span class="mx-20">240K GT</span>
<span class="mx-20">240K GT</span>
<span class="mx-20">240K GT</span>
<span class="mx-20">240K GT</span>
<span class="mx-20">240K GT</span>
<span class="mx-20">240K GT</span>
</div>
</div>
<div class="swiper w-full h-full absolute left-0 top-0 z-40 opacity-0">
<div class="swiper-wrapper">
<section class="swiper-slide w-full h-full flex flex-col justify-end relative p-[5vw]">
<div id="inner-1" class="flex flex-col items-start gap-4 lg:gap-8 w-full sm:w-2/3 lg:w-1/3 3xl:w-1/4 md:text-lg">
<h1 class="text-[clamp(26px,4vw,60px)] leading-none font-light uppercase">
Datsun
<br class="hidden lg:block" />
<span class="font-bold">240K GT</span>
</h1>
<p>
Introducing a Masterpiece, the combination of all Datsun’s great automotive innovations.
</p>
<strong class="hidden lg:block">
From 30,500 €
</strong>
<div class="flex flex-wrap gap-2">
<a href="#section-2" data-slide-to="1" class="uppercase px-6 py-3 md:px-12 md:py-4 rounded-full text-xs md:text-base tracking-wider text-slate-50 bg-blue-700 hover:bg-blue-600 active:bg-blue-900 transition-colors grow text-center">
Learn more
</a>
<a href="#" target="_blank" rel="noopener" class="uppercase px-6 py-3 md:px-12 md:py-4 rounded-full text-xs md:text-base tracking-wider bg-transparent hover:bg-slate-50/50 active:bg-white transition-colors grow text-center">Contact us</a>
</div>
</div>
<p> © Codingwithbharath</p>
</section>
<section class="swiper-slide w-full h-full flex flex-col items-end justify-end lg:justify-center relative z-50 px-[5vw] py-[5vh]">
<div id="inner-2" class="flex flex-col items-start gap-5 lg:gap-8 w-full sm:w-2/3 lg:w-1/3 2xl:w-1/4 p-[2.5vw] mb-8 lg:mb-0 sm:box-content rounded-xl sm:rounded-[2vw] md:text-lg text-slate-50 bg-zinc-900/20 backdrop-blur-lg">
<h2 class="text-[clamp(22px,3vw,36px)] leading-[110%]">
A compact car with an exceptional power-to-weight ratio
</h2>
<p class="mb-10 lg:mb-7">
In addition to all the usual Datsun free extra, the 240K gives you ultimate refinements like electric rear window defroster; adjustable tilt steering wheel.
<p> © Codingwithbharath</p>
</p>
<a href="#section-3" data-slide-to="2" class="uppercase px-12 py-4 rounded-full text-xs md:text-base tracking-wider text-slate-50 bg-blue-700 hover:bg-blue-600 active:bg-blue-900 transition-colors absolute bottom-0 translate-y-1/2">
Personalise
</a>
</div>
</section>
<section class="swiper-slide w-full h-full flex flex-col items-center relative px-[5vw] py-[5vh]">
<div id="inner-3" class="relative z-50 flex flex-col items-center gap-4 text-white drop-shadow-[0_2px_3px_rgba(0,0,0,0.9)]">
<h4 class="text-[clamp(22px,3vw,36px)]">
Paint
</h4>
<div class="flex gap-8">
<button aria-label="White" data-color="#CBD5E1" class="active relative rounded-full w-[40px] h-[30px] bg-gradient-to-r from-slate-50 to-slate-500 bg-slate-200 outline-0 hover:scale-110 focus:scale-110 transition-all"></button>
<button aria-label="Blue" data-color="#355F99" class="relative rounded-full w-[40px] h-[30px] bg-gradient-to-r from-blue-500 to-blue-900 bg-blue-700 outline-0 hover:scale-110 focus:scale-110 transition-all"></button>
<button aria-label="Red" data-color="#923939" class="relative rounded-full w-[40px] h-[30px] bg-gradient-to-r from-red-400 to-red-800 bg-red-600 outline-0 hover:scale-110 focus:scale-110 transition-all"></button>
</div>
</div>
</section>
</div>
<div class="swiper-pagination"></div>
</div>
<picture class="absolute left-0 top-1/2 w-full h-full z-0 opacity-0">
<source srcSet="https://cdn.glitch.global/ccaf12bb-59b0-46bb-bd74-b9467ca22e3d/bg-road-3000.jpg?v=1674210862901" media="(min-width: 2000px)" />
<source srcSet="https://cdn.glitch.global/ccaf12bb-59b0-46bb-bd74-b9467ca22e3d/bg-road-2000.jpg?v=1674210859490, https://cdn.glitch.global/ccaf12bb-59b0-46bb-bd74-b9467ca22e3d/bg-road-3000.jpg?v=1674210862901 2x" media="(min-width: 1000px)" />
<source srcSet="https://cdn.glitch.global/ccaf12bb-59b0-46bb-bd74-b9467ca22e3d/bg-road-1000.jpg?v=1674210857284, https://cdn.glitch.global/ccaf12bb-59b0-46bb-bd74-b9467ca22e3d/bg-road-2000.jpg?v=1674210859490 2x" media="(min-width: 500px)" />
<img class="w-full h-full object-cover" srcSet="https://cdn.glitch.global/ccaf12bb-59b0-46bb-bd74-b9467ca22e3d/bg-road-1000.jpg?v=1674210857284 2x" src="https://cdn.glitch.global/ccaf12bb-59b0-46bb-bd74-b9467ca22e3d/bg-road-500.jpg?v=1674211189312" alt="Landscape" />
</picture>
</div>
<!-- partial -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/Swiper/8.4.6/swiper-bundle.min.js'></script>
<script src='https://unpkg.co/gsap@3/dist/gsap.min.js'></script>
<script src='https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js'></script>
<script src="./script.js"></script>
</body>
</html>
CSS:
@import url('https://fonts.googleapis.com/css2?family=Bree+Serif&family=Caveat:wght@400;700&family=Lobster&family=Monoton&family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&family=Playfair+Display+SC:ital,wght@0,400;0,700;1,700&family=Playfair+Display:ital,wght@0,400;0,700;1,700&family=Roboto:ital,wght@0,400;0,700;1,400;1,700&family=Source+Sans+Pro:ital,wght@0,400;0,700;1,700&family=Work+Sans:ital,wght@0,400;0,700;1,700&display=swap');
html,
body {
width: 100%;
height: 100%;
}
body {
font-family: 'Manrope', sans-serif;
}
model-viewer {
--poster-color: #e2e8f0;
--progress-bar-color: rgba(30, 41, 59, .5);
--progress-bar-height: 10px;
}
[data-color]::before {
content: "";
display: block;
position: absolute;
inset: -5px;
border: 2px solid transparent;
border-radius: 999px;
}
[data-color].active::before,
[data-color]:focus::before {
border-color: white;
}
JS:
/* VARIABLES */
const page = document.getElementById('page'),
loading = document.getElementById('loading'),
slider = document.querySelector('.swiper'),
inner1 = document.getElementById('inner-1'),
inner2 = document.getElementById('inner-2'),
inner3 = document.getElementById('inner-3'),
car = document.querySelector('model-viewer'),
slideToButtons = document.querySelectorAll('[data-slide-to]'),
colorButtons = document.querySelectorAll('[data-color]'),
title = document.querySelectorAll('.title'),
bgImage = document.querySelector('picture');
const innerAnimationActive = {
duration: 1,
delay: 0.5,
ease: Power4.easeOut,
autoAlpha: 1,
yPercent: 0,
};
const innerAnimationHidden = {
duration: 1,
ease: Power4.easeOut,
autoAlpha: 0,
yPercent: -20,
};
/* VERTICAL SLIDER */
const swiper = new Swiper(slider, {
direction: 'vertical',
speed: 1500,
grabCursor: true,
touchRatio: 2,
threshold: 1,
preventInteractionOnTransition: true,
mousewheel: {
forceToAxis: true,
},
keyboard: {
enabled: true,
},
on: {
init: () => {
/* SLIDER & TITLE FADE IN */
gsap.to(slider, {
duration: 1,
ease: Power4.easeOut,
autoAlpha: 1,
});
gsap.to(title, innerAnimationActive);
/* TITLE INFINITE LOOP */
title.forEach(function(e, i) {
let row_width = e.getBoundingClientRect().width;
let row_item_width = e.children[0].getBoundingClientRect().width;
let offset = ((2 * row_item_width) / row_width) * 100 * -1;
let duration = 30 * (i + 1);
gsap.set(e, {
xPercent: 0
});
gsap.to(e, {
duration: duration,
ease: "none",
xPercent: offset,
repeat: -1
});
});
}
},
});
/* ON LOAD */
car.addEventListener('load', (event) => {
/* FADE OUT LOADING SCREEN */
gsap.to(loading, {
duration: 1,
ease: Power4.easeOut,
autoAlpha: 0,
});
/* 3D CHARACTERISTICS */
const materials = car.model.materials,
paint = materials[10];
/* CHANGE CAR PAINT */
paint.pbrMetallicRoughness.setBaseColorFactor('#CBD5E1');
/* CAR POSITION */
const exposure1 = '1',
orbit1 = '0deg 50deg 50%',
exposure2 = '0.4',
orbit2 = '-60deg 60deg 50%',
exposure3 = '1',
orbit3 = '44deg 83deg 50%';
let target1,
target2,
target3;
const setCarPosition = () => {
if (window.innerWidth <= 900) {
target1 = '-9.5m -11.9m 4.2m';
target2 = '-8.8m -12.7m 4.8m';
target3 = '-9.8m -10m 3.8m';
} else {
target1 = '-9.5m -12.9m 2.2m';
target2 = '-5.8m -12.5m 3.8m';
target3 = '-12m -10.7m 1.7m';
}
}
setCarPosition();
const carPosition = (exposure, orbit, target) => {
return ({
duration: 1.5,
ease: Power4.easeOut,
attr: {
['exposure']: exposure,
['camera-orbit']: orbit,
['camera-target']: target,
}
});
};
/* ANIMATION ON LOAD */
gsap.to(car, carPosition(exposure1, orbit1, target1));
/* SLIDE CHANGE */
swiper.on('slideChange', function() {
if (swiper.activeIndex === 0) {
gsap.to(car, carPosition(exposure1, orbit1, target1));
page.classList.remove('bg-zinc-900');
page.classList.add('bg-slate-200');
} else if (swiper.activeIndex === 1) {
gsap.to(car, carPosition(exposure2, orbit2, target2));
page.classList.remove('bg-slate-200');
page.classList.add('bg-zinc-900');
} else if (swiper.activeIndex === 2) {
gsap.to(car, carPosition(exposure3, orbit3, target3));
page.classList.remove('bg-zinc-900');
page.classList.add('bg-slate-200');
}
if (swiper.activeIndex === 0) {
gsap.to(inner1, innerAnimationActive);
gsap.to(title, innerAnimationActive);
} else {
gsap.to(inner1, innerAnimationHidden);
gsap.to(title, innerAnimationHidden);
}
if (swiper.activeIndex === 1) {
gsap.to(inner2, innerAnimationActive);
} else {
gsap.to(inner2, innerAnimationHidden);
}
if (swiper.activeIndex === 2) {
gsap.to(inner3, innerAnimationActive);
gsap.to(bgImage, {
duration: 1,
delay: 1,
ease: Power4.easeOut,
autoAlpha: 1,
yPercent: -50,
});
} else {
gsap.to(inner3, innerAnimationHidden);
gsap.to(bgImage, {
duration: 0.5,
ease: Power4.easeOut,
autoAlpha: 0,
yPercent: 0,
});
}
});
/* WINDOW RESIZE CAR POSITION */
swiper.on('resize', function() {
setCarPosition();
if (swiper.activeIndex === 0) {
gsap.to(car, carPosition(exposure1, orbit1, target1));
} else if (swiper.activeIndex === 1) {
gsap.to(car, carPosition(exposure2, orbit2, target2));
} else if (swiper.activeIndex === 2) {
gsap.to(car, carPosition(exposure3, orbit3, target3));
}
});
/* SLIDE TO */
slideToButtons.forEach((button) => {
button.addEventListener('click', (e) => {
const index = e.target.dataset.slideTo;
if (index !== undefined) {
swiper.slideTo(index);
}
e.preventDefault();
});
});
/* PAINT */
colorButtons.forEach((button) => {
button.addEventListener('click', (e) => {
/* CHANGE COLOR */
const color = e.target.dataset.color;
if (color !== undefined) {
paint.pbrMetallicRoughness.setBaseColorFactor(color);
}
/* BUTTON ACTIVE */
colorButtons.forEach((otherButton) => {
otherButton.classList.remove('active');
});
e.target.classList.add('active');
e.preventDefault();
});
});
});