280 lines
8.4 KiB
JavaScript
280 lines
8.4 KiB
JavaScript
Math.degToRad = degrees => degrees * (Math.PI / 180);
|
|
|
|
class Scene {
|
|
constructor(urls) {
|
|
this._urls = urls;
|
|
}
|
|
|
|
init() {
|
|
return new Promise(resolve => {
|
|
let models = [];
|
|
const manager = new THREE.LoadingManager(() => {
|
|
this.setupCanvas(models);
|
|
resolve();
|
|
});
|
|
const loader = new THREE.GLTFLoader(manager);
|
|
this._urls.forEach(x => loader.load(x.url, gltf => {
|
|
gltf.scene.visible = x.visible;
|
|
models.push({ id: x.id, scene: gltf.scene });
|
|
}));
|
|
});
|
|
}
|
|
setupCanvas(models) {
|
|
// Camera
|
|
this.camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 1, 30000);
|
|
this.camera.position.set(0, 0, 4000);
|
|
|
|
// Scene
|
|
this.scene = new THREE.Scene();
|
|
|
|
// Renderer
|
|
this.renderer = new THREE.WebGLRenderer();
|
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
this.renderer.outputEncoding = THREE.sRGBEncoding;
|
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
|
|
// Light
|
|
const ambientLight = new THREE.AmbientLight(0x111111);
|
|
this.scene.add(ambientLight);
|
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff);
|
|
directionalLight.position.set(2, 0, 1).normalize();
|
|
this.scene.add(directionalLight);
|
|
|
|
models.forEach(x => {
|
|
this[x.id] = x.scene;
|
|
this.scene.add(this[x.id]);
|
|
});
|
|
|
|
document.body.appendChild(this.renderer.domElement);
|
|
|
|
this.render();
|
|
this.animate();
|
|
|
|
window.addEventListener('resize', () => {
|
|
this.camera.aspect = window.innerWidth / window.innerHeight;
|
|
this.camera.updateProjectionMatrix();
|
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
this.render();
|
|
}, false);
|
|
}
|
|
animate() {
|
|
this.render();
|
|
requestAnimationFrame(() => this.animate());
|
|
}
|
|
render() {
|
|
this.renderer.render(this.scene, this.camera);
|
|
}}
|
|
|
|
|
|
class Page {
|
|
constructor(scene) {
|
|
this._scene = scene;
|
|
}
|
|
|
|
get mars() {
|
|
return this._scene.mars;
|
|
}
|
|
|
|
get deimos() {
|
|
return this._scene.deimos;
|
|
}
|
|
|
|
get camera() {
|
|
return this._scene.camera;
|
|
}
|
|
|
|
get content() {
|
|
return [
|
|
{
|
|
title: 'Olympus Mons',
|
|
coords: {
|
|
Latitude: '18.65275889',
|
|
Longitude: '-133.8025067' },
|
|
|
|
selector: '.olympus' },
|
|
|
|
{
|
|
title: 'Gale Crater',
|
|
coords: {
|
|
Latitude: '136.783441',
|
|
Longitude: '-5.10837943' },
|
|
|
|
selector: '.curiosity' },
|
|
|
|
{
|
|
title: 'Valles Marineris',
|
|
coords: {
|
|
Latitude: '-14.00586857',
|
|
Longitude: '-58.5876741' },
|
|
|
|
selector: '.valles' },
|
|
|
|
{
|
|
title: 'Medusae Fossae',
|
|
coords: {
|
|
Latitude: '-2.166284249',
|
|
Longitude: '-164.1956286' },
|
|
|
|
selector: '.medusae' },
|
|
|
|
{
|
|
title: 'Cydonia',
|
|
coords: {
|
|
Latitude: '39.66724753',
|
|
Longitude: '0' },
|
|
|
|
selector: '.cydonia' },
|
|
|
|
{
|
|
title: 'Tharsis Volcanic Region',
|
|
coords: {
|
|
Latitude: '-2.623623306',
|
|
Longitude: '-116.7076259' },
|
|
|
|
selector: '.tharsis' },
|
|
|
|
{
|
|
title: 'Deimos',
|
|
coords: {
|
|
Distance: '23,460 km',
|
|
Radius: '6.2 km' },
|
|
|
|
selector: '.deimos' }];
|
|
|
|
|
|
}
|
|
async init() {
|
|
window.onbeforeunload = function () {
|
|
window.scrollTo(0, 0);
|
|
};
|
|
await this._scene.init();
|
|
document.querySelector('.loader').style.display = 'none';
|
|
this.setupPlugins();
|
|
this.setupAnimScrollTrigger();
|
|
this.setupContentScrollTrigger();
|
|
}
|
|
setupPlugins() {
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
gsap.registerPlugin(TextPlugin);
|
|
gsap.registerPlugin(DrawSVGPlugin);
|
|
}
|
|
setupAnimScrollTrigger() {
|
|
const opening = new gsap.timeline().
|
|
fromTo(this.camera.position, { z: 2000 }, { z: 4000, ease: 'Circ.easeOut', duration: 1.5 }, 'opening').
|
|
fromTo(this.camera.rotation, { y: -Math.degToRad(30), x: -Math.degToRad(-5) }, { y: 0, x: 0, ease: 'Circ.easeOut', duration: 1.5 }, 'opening').
|
|
set(".scroller, .watermark", { visibility: "visible" });
|
|
|
|
const rotation = new gsap.timeline({
|
|
scrollTrigger: {
|
|
toggleActions: "play pause play pause",
|
|
trigger: ".welcome",
|
|
start: "top bottom",
|
|
end: "top -1" } }).
|
|
|
|
|
|
to(this.mars.rotation, { y: Math.degToRad(360), ease: 'none', repeat: -1, duration: 30 });
|
|
|
|
const animation = new gsap.timeline({
|
|
scrollTrigger: {
|
|
trigger: ".content",
|
|
scrub: true,
|
|
start: "top bottom",
|
|
end: "bottom bottom"
|
|
//markers: {startColor: "green", endColor: "red", fontSize: "12px"}
|
|
} }).
|
|
|
|
set(".nav svg, .circle svg", { visibility: "visible" }).
|
|
to(this.mars.rotation, { y: Math.degToRad(133.8025067 + 90), x: Math.degToRad(18.65275889), ease: 'power2.inOut', duration: 0.75 }, 0).
|
|
to(this.camera.position, { z: 3000, ease: 'linear', duration: 0.75 }, 0).
|
|
set(".scroller", { visibility: "hidden" }, 0.2).
|
|
to(this.mars.rotation, { y: -Math.degToRad(136.78344100200877 - 90), x: Math.degToRad(5.1083794384352), ease: 'power2.inOut', duration: 0.75 }, 1).
|
|
to(this.mars.rotation, { y: Math.degToRad(58.5876741 + 90), x: Math.degToRad(-14.00586857), ease: 'power2.inOut', duration: 0.75 }, 2).
|
|
to(this.mars.rotation, { y: Math.degToRad(164.1956286 + 90), x: Math.degToRad(-2.166284249), ease: 'power2.inOut', duration: 0.75 }, 3).
|
|
to(this.mars.rotation, { y: Math.degToRad(90), x: Math.degToRad(39.66724753), ease: 'power2.inOut', duration: 0.75 }, 4).
|
|
to(this.mars.rotation, { y: Math.degToRad(116.7076259 + 90), x: Math.degToRad(-2.623623306), ease: 'power2.inOut', duration: 0.75 }, 5).
|
|
to(this.camera.position, { z: 22000, ease: 'linear' }, 6).
|
|
to(this.camera.rotation, { z: Math.degToRad(10), y: Math.degToRad(10), ease: 'power2.inOut', duration: 0.75 }, 6).
|
|
set(this.deimos, { visible: true }, 6).
|
|
to(this.deimos.scale, { x: 7, y: 7, z: 7, ease: 'linear', duration: 0 }, 6).
|
|
fromTo(this.deimos.position, { x: -500, y: 0, z: 22000 }, { x: -170, y: 0, z: 21000, ease: 'power2.inOut', duration: 0.75 }, 6);
|
|
}
|
|
setupContentScrollTrigger() {
|
|
const onUpdate = function () {
|
|
const target = this.targets()[0];
|
|
const time = this.time();
|
|
const duration = this.duration();
|
|
|
|
if (time >= duration || time <= 0) {
|
|
target.classList.remove('editing');
|
|
return;
|
|
}
|
|
if (!target.classList.contains('editing')) {
|
|
target.classList.add('editing');
|
|
}
|
|
};
|
|
|
|
const getLines = obj => {
|
|
const lines = [];
|
|
Object.keys(obj).forEach(x => {
|
|
lines.push(`${x}: ${obj[x]}`);
|
|
});
|
|
return lines;
|
|
};
|
|
|
|
ScrollTrigger.addEventListener("scrollEnd", function (e) {
|
|
const target = document.querySelectorAll('.editing')[0];
|
|
if (target) {
|
|
target.classList.add('blink');
|
|
}
|
|
});
|
|
|
|
ScrollTrigger.addEventListener("scrollStart", function (e) {
|
|
const target = document.querySelectorAll('.editing')[0];
|
|
if (target) {
|
|
target.classList.remove('blink');
|
|
}
|
|
});
|
|
|
|
this.content.forEach((item, i, arr) => {
|
|
const timeline = new gsap.timeline({
|
|
scrollTrigger: {
|
|
trigger: item.selector,
|
|
scrub: true,
|
|
start: 'top 75%',
|
|
end: `bottom ${i < arr.length - 1 ? '75%' : 'bottom'}`
|
|
//markers: {startColor: "green", endColor: "red", fontSize: "12px"}
|
|
} }).
|
|
|
|
to(`${item.selector} .title`, { text: `${item.title}`, ease: 'linear', duration: 0.25, onUpdate }, 0).
|
|
to(`${item.selector} .lat`, { text: `${getLines(item.coords)[0]}`, ease: 'linear', duration: 0.125, onUpdate }, 0.25).
|
|
to(`${item.selector} .lon`, { text: `${getLines(item.coords)[1]}`, ease: 'linear', duration: 0.125, onUpdate }, 0.375).
|
|
fromTo(`${item.selector} polyline`, { drawSVG: 0 }, { drawSVG: '100%', duration: 0.125 }, 0.5).
|
|
fromTo('#circle', { drawSVG: 0 }, { drawSVG: '100%', duration: 0.125 }, 0.625).
|
|
set(`${item.selector} .image-container`, { visibility: 'visible' }).
|
|
fromTo(`${item.selector} .image-container`, { width: '0%' }, { width: '25vw', duration: 0.125 }, 0.75).
|
|
fromTo(`${item.selector} .image-container`, { height: '0%' }, { height: 'auto', duration: 0.125 }, 0.875);
|
|
|
|
if (i < arr.length - 1) {
|
|
timeline.yoyo(true).
|
|
repeat(1).
|
|
repeatDelay(0.5);
|
|
}
|
|
});
|
|
}}
|
|
|
|
|
|
const urls = [
|
|
{
|
|
id: 'mars',
|
|
url: 'https://assets.codepen.io/1443237/Mars_1_6792.glb',
|
|
visible: true },
|
|
|
|
{
|
|
id: 'deimos',
|
|
url: 'https://assets.codepen.io/1443237/Deimos_1_1000.glb',
|
|
visible: false }];
|
|
|
|
|
|
|
|
new Page(new Scene(urls)).init(); |