A Pen by James Drew on CodePen.
Created
October 13, 2023 16:04
-
-
Save jdrew1303/0d4d90acd70ed95005555af0d87c0908 to your computer and use it in GitHub Desktop.
Airplanes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
{ | |
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 ) { | |
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 | |
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); | |
} | |
render = () => | |
{ | |
for ( var ii = 0; ii < this.views.length; ++ ii ) { | |
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 ); | |
} | |
} | |
onResize = () => | |
{ | |
this.w = window.innerWidth; | |
this.h = window.innerHeight; | |
for ( var ii = 0; ii < this.views.length; ++ ii ) { | |
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(); | |
} | |
this.renderer.setSize( this.w, this.h ); | |
this.render(); | |
} | |
} | |
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 * -.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 * .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 * .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 * .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 * .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 *.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 *.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 *.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 *.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(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://codepen.io/steveg3003/pen/zBVakw.js"></script> | |
<script src="https://unpkg.com/[email protected]/build/three.min.js"></script> | |
<script src="https://unpkg.com/[email protected]/examples/js/loaders/OBJLoader.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.1/gsap.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.1/ScrollTrigger.min.js"></script> | |
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/DrawSVGPlugin3.min.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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) { | |
--font-size-large: 64px; | |
--font-size-medium: 32px; | |
--font-size-normal: 16px; | |
} | |
@media only screen and (max-width: 500px) { | |
--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; | |
// overflow: hidden; | |
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 | |
{ | |
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%); | |
} | |
.wireframe | |
{ | |
clip-path: polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%); | |
} | |
.content | |
{ | |
position: relative; | |
z-index: 1; | |
.trigger | |
{ | |
position: absolute; | |
top: 0; | |
height: 100%; | |
} | |
.section | |
{ | |
position: relative; | |
padding: var(--padding); | |
--pad2: calc(var(--padding) * 2); | |
width: calc(100vw - var(--pad2)); | |
height: calc(100vh - var(--pad2)); | |
// min-height: 400px; | |
margin: 0 auto; | |
z-index: 2; | |
&.dark | |
{ | |
color: white; | |
background-color: black; | |
} | |
&.right | |
{ | |
text-align: right; | |
} | |
} | |
.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,.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; | |
svg | |
{ | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100vw; | |
height: 100vh; | |
stroke: white; | |
pointer-events: none; | |
visibility: hidden; | |
} | |
.dark | |
{ | |
background-color: transparent; | |
} | |
} | |
.ground-container | |
{ | |
position: relative; | |
overflow: hidden; | |
// perspective: 2px; | |
.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; | |
} | |
.ground | |
{ | |
z-index: -1; | |
background-image: url("https://assets.codepen.io/557388/background-reduced.jpg"); | |
} | |
.clouds | |
{ | |
z-index: 2; | |
background-image: url("https://assets.codepen.io/557388/clouds.png"); | |
} | |
} | |
.scroll-cta, .credits | |
{ | |
position: absolute; | |
bottom: var(--padding); | |
} | |
.scroll-cta | |
{ | |
font-size: var(--font-size-medium); | |
opacity: 0; | |
} | |
.sunset | |
{ | |
background: url("https://assets.codepen.io/557388/sunset-reduced.jpg") no-repeat top center; | |
background-size: cover; | |
transform-origin: top center; | |
} | |
h1, h2 | |
{ | |
font-size: var(--font-size-large); | |
margin: 0vmin 0 2vmin 0 ; | |
font-weight: 700; | |
display: inline; | |
} | |
h3, | |
{ | |
font-size: var(--font-size-medium); | |
font-weight: 400; | |
margin: 0; | |
} | |
.end h2 | |
{ | |
margin-bottom: 50vh; | |
} | |
.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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment