In this post, we will animate an airplane using scroll Trigger.
Scroll Trigger is a very interesting tool that You can animate anything with while scrolling effect.
we created a project about an airplane guiding template and used a scroll trigger tool to animate the plane while scrolling.
HTML Code:
<div class="content">
<div class="loading">Loading</div>
<div class="trigger"></div>
<div class="section">
<h1>Airplanes.</h1>
<h3>The beginners guide.</h3>
<p>You've probably forgotten what these are.</p>
<!-- <div class="phonetic">/ ˈɛərˌpleɪn /</div> -->
<div class="scroll-cta">Scroll</div>
</div>
<div class="section right">
<h2>They're kinda like buses...</h2>
</div>
<div class="ground-container">
<div class="parallax ground"></div>
<div class="section right">
<h2>..except they leave the ground.</h2>
<p>Saaay what!?.</p>
</div>
<div class="section">
<h2>They fly through the sky.</h2>
<p>For realsies!</p>
</div>
<div class="section right">
<h2>Defying all known physical laws.</h2>
<p>It's actual magic!</p>
</div>
<div class="parallax clouds"></div>
</div>
<div class="blueprint">
<svg width="100%" height="100%" viewbox="0 0 100 100">
<line id="line-length" x1="10" y1="80" x2="90" y2="80" stroke-width="0.5"></line>
<path id="line-wingspan" d="M10 50, L40 35, M60 35 L90 50" stroke-width="0.5"></path>
<circle id="circle-phalange" cx="60" cy="60" r="15" fill="transparent" stroke-width="0.5"></circle>
</svg>
<div class="section dark ">
<h2>The facts and figures.</h2>
<p>Lets get into the nitty gritty...</p>
</div>
<div class="section dark length">
<h2>Length.</h2>
<p>Long.</p>
</div>
<div class="section dark wingspan">
<h2>Wing Span.</h2>
<p>I dunno, longer than a cat probably.</p>
</div>
<div class="section dark phalange">
<h2>Left Phalange</h2>
<p>Missing</p>
</div>
<div class="section dark">
<h2>Engines</h2>
<p>Turbine funtime</p>
</div>
<!-- <div class="section"></div> -->
</div>
<div class="sunset">
<div class="section"></div>
<div class="section end">
<h2>Fin.</h2>
<ul class="credits">
<li>Plane model by <a href="https://poly.google.com/view/8ciDd9k8wha" target="_blank">Google</a></li>
<li>Animated using <a href="https://greensock.com/scrolltrigger/" target="_blank">GSAP ScrollTrigger</a></li>
</ul>
</div>
</div>
</div>
CSS Code:
@import url("https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&display=swap");
svg {
z-index: 100;
}
:root {
--padding: 10vmin;
--color-background: #d0cbc7;
--font-size-large: 8vw;
--font-size-medium: 4vw;
--font-size-normal: 2vw;
}
@media only screen and (min-width: 800px) {
:root {
--font-size-large: 64px;
--font-size-medium: 32px;
--font-size-normal: 16px;
}
}
@media only screen and (max-width: 500px) {
:root {
--font-size-large: 40px;
--font-size-medium: 20px;
--font-size-normal: 14px;
}
}
a {
color: white;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
li {
margin-top: 10px;
}
html,
body {
margin: 0;
min-height: 100%;
min-width: 100%;
font-family: "Libre Baskerville", serif;
background-color: var(--color-background);
font-weight: 400;
font-size: var(--font-size-normal);
overflow-x: hidden;
}
canvas {
position: fixed;
z-index: 10;
top: 0;
left: 0;
z-index: 2;
pointer-events: none;
visibility: hidden;
opacity: 0;
}
.solid {
-webkit-clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}
.wireframe {
-webkit-clip-path: polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%);
clip-path: polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%);
}
.content {
position: relative;
z-index: 1;
}
.content .trigger {
position: absolute;
top: 0;
height: 100%;
}
.content .section {
position: relative;
padding: var(--padding);
--pad2: calc(var(--padding) * 2);
width: calc(100vw - var(--pad2));
height: calc(100vh - var(--pad2));
margin: 0 auto;
z-index: 2;
}
.content .section.dark {
color: white;
background-color: black;
}
.content .section.right {
text-align: right;
}
.content .blueprint {
position: relative;
background-color: #131c2a;
background-image: linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px), linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
background-size: 100px 100px, 100px 100px, 20px 20px, 20px 20px;
background-position: -2px -2px, -2px -2px, -1px -1px, -1px -1px;
background-attachment: fixed;
}
.content .blueprint svg {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
stroke: white;
pointer-events: none;
visibility: hidden;
}
.content .blueprint .dark {
background-color: transparent;
}
.content .ground-container {
position: relative;
overflow: hidden;
}
.content .ground-container .parallax {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: -100px;
background-repeat: no-repeat;
background-position: top center;
background-size: cover;
transform-origin: top center;
}
.content .ground-container .ground {
z-index: -1;
background-image: url("https://assets.codepen.io/557388/background-reduced.jpg");
}
.content .ground-container .clouds {
z-index: 2;
background-image: url("https://assets.codepen.io/557388/clouds.png");
}
.content .scroll-cta,
.content .credits {
position: absolute;
bottom: var(--padding);
}
.content .scroll-cta {
font-size: var(--font-size-medium);
opacity: 0;
}
.content .sunset {
background: url("https://assets.codepen.io/557388/sunset-reduced.jpg") no-repeat top center;
background-size: cover;
transform-origin: top center;
}
.content h1,
.content h2 {
font-size: var(--font-size-large);
margin: 0vmin 0 2vmin 0;
font-weight: 700;
display: inline;
}
.content h3 {
font-size: var(--font-size-medium);
font-weight: 400;
margin: 0;
}
.content .end h2 {
margin-bottom: 50vh;
}
.content .loading {
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-medium);
}
JavaScript Code:
function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;} // clearing the console (just a CodePen thing)
console.clear();
// there are 3 parts to this
//
// Scene: Setups and manages threejs rendering
// loadModel: Loads the 3d obj file
// setupAnimation: Creates all the GSAP
// animtions and scroll triggers
//
// first we call loadModel, once complete we call
// setupAnimation which creates a new Scene
class Scene {
constructor(model) {_defineProperty(this, "render",
() => {
for (var ii = 0; ii < this.views.length; ++ii) {if (window.CP.shouldStopExecution(3)) break;if (window.CP.shouldStopExecution(0)) break;
var view = this.views[ii];
var camera = view.camera;
var bottom = Math.floor(this.h * view.bottom);
var height = Math.floor(this.h * view.height);
this.renderer.setViewport(0, 0, this.w, this.h);
this.renderer.setScissor(0, bottom, this.w, height);
this.renderer.setScissorTest(true);
camera.aspect = this.w / this.h;
this.renderer.render(this.scene, camera);
}window.CP.exitedLoop(3);window.CP.exitedLoop(0);
});_defineProperty(this, "onResize",
() => {
this.w = window.innerWidth;
this.h = window.innerHeight;
for (var ii = 0; ii < this.views.length; ++ii) {if (window.CP.shouldStopExecution(4)) break;if (window.CP.shouldStopExecution(1)) break;
var view = this.views[ii];
var camera = view.camera;
camera.aspect = this.w / this.h;
let camZ = (screen.width - this.w * 1) / 3;
camera.position.z = camZ < 180 ? 180 : camZ;
camera.updateProjectionMatrix();
}window.CP.exitedLoop(4);window.CP.exitedLoop(1);
this.renderer.setSize(this.w, this.h);
this.render();
});this.views = [{ bottom: 0, height: 1 }, { bottom: 0, height: 0 }];this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });this.renderer.setSize(window.innerWidth, window.innerHeight);this.renderer.shadowMap.enabled = true;this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;this.renderer.setPixelRatio(window.devicePixelRatio);document.body.appendChild(this.renderer.domElement); // scene
this.scene = new THREE.Scene();for (var _ii = 0; _ii < this.views.length; ++_ii) {if (window.CP.shouldStopExecution(2)) break;var _view = this.views[_ii];var _camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);_camera.position.fromArray([0, 0, 180]);_camera.layers.disableAll();_camera.layers.enable(_ii);_view.camera = _camera;_camera.lookAt(new THREE.Vector3(0, 5, 0));} //light
window.CP.exitedLoop(2);this.light = new THREE.PointLight(0xffffff, 0.75);this.light.position.z = 150;this.light.position.x = 70;this.light.position.y = -20;this.scene.add(this.light);this.softLight = new THREE.AmbientLight(0xffffff, 1.5);this.scene.add(this.softLight); // group
this.onResize();window.addEventListener("resize", this.onResize, false);var edges = new THREE.EdgesGeometry(model.children[0].geometry);let line = new THREE.LineSegments(edges);line.material.depthTest = false;line.material.opacity = 0.5;line.material.transparent = true;line.position.x = 0.5;line.position.z = -1;line.position.y = 0.2;this.modelGroup = new THREE.Group();model.layers.set(0);line.layers.set(1);this.modelGroup.add(model);this.modelGroup.add(line);this.scene.add(this.modelGroup);}}function loadModel() {
gsap.registerPlugin(ScrollTrigger);
gsap.registerPlugin(DrawSVGPlugin);
gsap.set("#line-length", { drawSVG: 0 });
gsap.set("#line-wingspan", { drawSVG: 0 });
gsap.set("#circle-phalange", { drawSVG: 0 });
var object;
function onModelLoaded() {
object.traverse(function (child) {
let mat = new THREE.MeshPhongMaterial({
color: 0x171511,
specular: 0xd0cbc7,
shininess: 5,
flatShading: true });
child.material = mat;
});
setupAnimation(object);
}
var manager = new THREE.LoadingManager(onModelLoaded);
manager.onProgress = (item, loaded, total) => console.log(item, loaded, total);
var loader = new THREE.OBJLoader(manager);
loader.load(
"https://assets.codepen.io/557388/1405+Plane_1.obj",
function (obj) {
object = obj;
});
}
function setupAnimation(model) {
let scene = new Scene(model);
let plane = scene.modelGroup;
gsap.fromTo(
"canvas",
{ x: "50%", autoAlpha: 0 },
{ duration: 1, x: "0%", autoAlpha: 1 });
gsap.to(".loading", { autoAlpha: 0 });
gsap.to(".scroll-cta", { opacity: 1 });
gsap.set("svg", { autoAlpha: 1 });
let tau = Math.PI * 2;
gsap.set(plane.rotation, { y: tau * -0.25 });
gsap.set(plane.position, { x: 80, y: -32, z: -60 });
scene.render();
var sectionDuration = 1;
gsap.fromTo(
scene.views[1],
{ height: 1, bottom: 0 },
{
height: 0,
bottom: 1,
ease: "none",
scrollTrigger: {
trigger: ".blueprint",
scrub: true,
start: "bottom bottom",
end: "bottom top" } });
gsap.fromTo(
scene.views[1],
{ height: 0, bottom: 0 },
{
height: 1,
bottom: 0,
ease: "none",
scrollTrigger: {
trigger: ".blueprint",
scrub: true,
start: "top bottom",
end: "top top" } });
gsap.to(".ground", {
y: "30%",
scrollTrigger: {
trigger: ".ground-container",
scrub: true,
start: "top bottom",
end: "bottom top" } });
gsap.from(".clouds", {
y: "25%",
scrollTrigger: {
trigger: ".ground-container",
scrub: true,
start: "top bottom",
end: "bottom top" } });
gsap.to("#line-length", {
drawSVG: 100,
scrollTrigger: {
trigger: ".length",
scrub: true,
start: "top bottom",
end: "top top" } });
gsap.to("#line-wingspan", {
drawSVG: 100,
scrollTrigger: {
trigger: ".wingspan",
scrub: true,
start: "top 25%",
end: "bottom 50%" } });
gsap.to("#circle-phalange", {
drawSVG: 100,
scrollTrigger: {
trigger: ".phalange",
scrub: true,
start: "top 50%",
end: "bottom 100%" } });
gsap.to("#line-length", {
opacity: 0,
drawSVG: 0,
scrollTrigger: {
trigger: ".length",
scrub: true,
start: "top top",
end: "bottom top" } });
gsap.to("#line-wingspan", {
opacity: 0,
drawSVG: 0,
scrollTrigger: {
trigger: ".wingspan",
scrub: true,
start: "top top",
end: "bottom top" } });
gsap.to("#circle-phalange", {
opacity: 0,
drawSVG: 0,
scrollTrigger: {
trigger: ".phalange",
scrub: true,
start: "top top",
end: "bottom top" } });
let tl = new gsap.timeline({
onUpdate: scene.render,
scrollTrigger: {
trigger: ".content",
scrub: true,
start: "top top",
end: "bottom bottom" },
defaults: { duration: sectionDuration, ease: "power2.inOut" } });
let delay = 0;
tl.to(".scroll-cta", { duration: 0.25, opacity: 0 }, delay);
tl.to(plane.position, { x: -10, ease: "power1.in" }, delay);
delay += sectionDuration;
tl.to(
plane.rotation,
{ x: tau * 0.25, y: 0, z: -tau * 0.05, ease: "power1.inOut" },
delay);
tl.to(plane.position, { x: -40, y: 0, z: -60, ease: "power1.inOut" }, delay);
delay += sectionDuration;
tl.to(
plane.rotation,
{ x: tau * 0.25, y: 0, z: tau * 0.05, ease: "power3.inOut" },
delay);
tl.to(plane.position, { x: 40, y: 0, z: -60, ease: "power2.inOut" }, delay);
delay += sectionDuration;
tl.to(
plane.rotation,
{ x: tau * 0.2, y: 0, z: -tau * 0.1, ease: "power3.inOut" },
delay);
tl.to(plane.position, { x: -40, y: 0, z: -30, ease: "power2.inOut" }, delay);
delay += sectionDuration;
tl.to(plane.rotation, { x: 0, z: 0, y: tau * 0.25 }, delay);
tl.to(plane.position, { x: 0, y: -10, z: 50 }, delay);
delay += sectionDuration;
delay += sectionDuration;
tl.to(
plane.rotation,
{ x: tau * 0.25, y: tau * 0.5, z: 0, ease: "power4.inOut" },
delay);
tl.to(plane.position, { z: 30, ease: "power4.inOut" }, delay);
delay += sectionDuration;
tl.to(
plane.rotation,
{ x: tau * 0.25, y: tau * 0.5, z: 0, ease: "power4.inOut" },
delay);
tl.to(plane.position, { z: 60, x: 30, ease: "power4.inOut" }, delay);
delay += sectionDuration;
tl.to(
plane.rotation,
{ x: tau * 0.35, y: tau * 0.75, z: tau * 0.6, ease: "power4.inOut" },
delay);
tl.to(plane.position, { z: 100, x: 20, y: 0, ease: "power4.inOut" }, delay);
delay += sectionDuration;
tl.to(
plane.rotation,
{ x: tau * 0.15, y: tau * 0.85, z: -tau * 0, ease: "power1.in" },
delay);
tl.to(plane.position, { z: -150, x: 0, y: 0, ease: "power1.inOut" }, delay);
delay += sectionDuration;
tl.to(
plane.rotation,
{
duration: sectionDuration,
x: -tau * 0.05,
y: tau,
z: -tau * 0.1,
ease: "none" },
delay);
tl.to(
plane.position,
{ duration: sectionDuration, x: 0, y: 30, z: 320, ease: "power1.in" },
delay);
tl.to(
scene.light.position,
{ duration: sectionDuration, x: 0, y: 0, z: 0 },
delay);
}
loadModel();
Thank You!