import Lenis from '@studio-freight/lenis'; import { gsap } from 'gsap'; import { CustomEase } from 'gsap/CustomEase'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; import * as dat from 'lil-gui'; import MouseFollower from 'mouse-follower'; import SplitType from 'split-type'; import { initAbout, initMarqueeParlons, initParallax, initSliderTem, initSliderWork, initUtils } from 'src/utils/utils'; import * as THREE from 'three'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; gsap.registerPlugin(ScrollTrigger); gsap.registerPlugin(CustomEase); // GSAP MATCH MEDIA // create const mm = ScrollTrigger.matchMedia(); const breakPoint = 992; const isDesktop = `(min-width: ${breakPoint}px)`; const isMobile = `(max-width: ${breakPoint - 1}px)`; // WAIT FOR PAGE LOADING FUNCTION window.addEventListener('DOMContentLoaded', (event) => { //-- SPLIT TEXT--// const typeSplit = new SplitType('[text-split]', { types: 'words, chars', tagName: 'span' }); // Avoid flash of unstyled content gsap.set('[chars-slide-up] .char', { yPercent: 100 }); gsap.set('[chars-slide-up]', { autoAlpha: 1 }); gsap.set('[fade-up]', { yPercent: 50, autoAlpha: 0 }); // INIT SCROLL AND LOADING //--------------// // LENIS SCROLL // //--------------// const pageWrapper = document.querySelector('.page_wrap'); const lenis = new Lenis({ lerp: 0.1, wheelMultiplier: 0.7, infinite: false, gestureOrientation: 'vertical', smoothWheel: true, normalizeWheel: false, smoothTouch: false, wheelEventsTarget: pageWrapper }); function raf(time) { //parallax.animate(); lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); function connectToScrollTrigger() { lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add((time) => { lenis.raf(time * 1000); }); console.log('lenis connect to scrolltrigger'); } // Uncomment this if using GSAP ScrollTrigger connectToScrollTrigger(); // LENIS SCROLL TOP AND STOP function topAndStop() { // Function to scroll to the top of the window // SCROLL TOP $(document).ready(function () { $(this).scrollTop(0); }); // Wait for the scrolling animation to finish and then call scrollStop() setTimeout(lenis.stop(), 10); } // LENIS SCROLL START function scrollStart() { lenis.start(); console.log('Start of header animation and scroll start !'); } // LENIS SCROLL TO // Grab all elements that have a "data-target" attribute const scrollButtons = document.querySelectorAll('[data-target]'); // For each element, listen to a "click" event scrollButtons.forEach((button) => { button.addEventListener('click', (e) => { e.preventDefault(); // get the DOM element by the ID (data-target value) const { target } = button.dataset, immediate = 'immediate' in button.dataset !== -1 ? true : false, $el = document.getElementById(target.replace('#', '')); // Use lenis.scrollTo() to scroll the page to the right element lenis.scrollTo($el, { offset: 0, immediate: immediate, duration: 2, easing: (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2) // https://easings.net }); }); }); //-------------------// // LOADING ANIMATION // //-------------------// const customEase = 'M0,0,C0,0,0.13,0.34,0.238,0.442,0.305,0.506,0.322,0.514,0.396,0.54,0.478,0.568,0.468,0.56,0.522,0.584,0.572,0.606,0.61,0.719,0.714,0.826,0.798,0.912,1,1,1,1'; let counter = { value: 0 }; let loaderDuration = 3; // If not a first time visit in this tab if (sessionStorage.getItem('visited') !== null) { loaderDuration = 1; counter = { value: 270 }; } sessionStorage.setItem('visited', 'true'); function updateLoaderText() { const progress = Math.round(counter.value); const loaderNumber = document.querySelector('.loader_number'); if (loaderNumber) { loaderNumber.textContent = progress; } } function endLoaderAnimation() { const tl = gsap.timeline({ onStart: homeHeader }); tl.to( '.loader', { '--clip': '0%', duration: 2, ease: 'expo.out' }, '0' ).to( '.loader_content', { y: '-100', opacity: '0', duration: 0.5, ease: 'expo.inOut' }, '<' ); } const tl = gsap.timeline({ onStart: topAndStop, onComplete: endLoaderAnimation }); tl.to(counter, { value: 360, onUpdate: updateLoaderText, duration: loaderDuration, ease: CustomEase.create('custom', customEase) }); //-- HEADING ANIMATION --// function homeHeader() { const titleFx = document.querySelector('[title-fx'); const titleFxSplite = new SplitType(titleFx); const tl = gsap.timeline({ onStart: scrollStart }); tl.from( titleFxSplite.lines, { y: '100%', duration: 1, delay: 0.2, stagger: 0.1, ease: 'expo.out' }, '0' ) .from( titleFx, { y: 150, duration: 2, delay: 0.2, ease: 'expo.out' }, '0' ) .from( '[top-item-fx]', { y: 50, autoAlpha: 0, duration: 2, stagger: { each: 0.1 }, ease: 'expo.out' }, '0+=25%' ) .from( '[baseline-fx]', { y: 150, opacity: 0, duration: 3, ease: 'expo.out' }, '<+=10%' ); // Header scrub animation on Desktop //const hero = document.querySelector(".section-home-hero--pv0"); // add a media query. When it matches, the associated function will run mm.add('(min-width: 991px)', () => { const heroTxt = document.querySelector('.section_hero_txt_wrap'); const heroImg = document.querySelector('.section_hero_img--fullh'); gsap.to(heroImg, { opacity: 0, scale: 1.3, scrollTrigger: { trigger: heroImg, start: 'bottom bottom', end: 'bottom top', scrub: true //markers: true } }); gsap.to(heroTxt, { y: 200, scrollTrigger: { trigger: heroImg, start: 'bottom bottom', end: 'bottom top', scrub: true //markers: true } }); return () => { // optional // custom cleanup code here (runs when it STOPS matching) }; }); } //-- CURSOR --// function setupCursor() { mm.add(isDesktop, () => { MouseFollower.registerGSAP(gsap); const cursor = new MouseFollower({ speed: 0.6, ease: 'expo.out', stickDelta: 0, skewing: 0.1, hiddenState: '-hidden', textState: '-text', iconState: '-icon', activeState: '-active', mediaState: '-media' }); console.log('cursor init'); return () => { // optional // custom cleanup code here (runs when it STOPS matching) cursor.destroy(); console.log('cursor is destroy'); }; }); } //-- OVERLAY MENU --// const overlayMenu = () => { const overlay = document.getElementById('overlay'); // Set the GSAP animation if (overlay) { let isOpen = false; gsap.set(overlay, { y: '-101vh', display: 'flex' }); const overlayAnim = gsap.timeline({ paused: true }); mm.add('(min-width: 991px)', () => { overlayAnim .to( overlay, { duration: 1, ease: 'power4.out', y: 0, autoAlpha: 1 }, 0 ) .to( '.overlay [chars-slide-up] .char', { yPercent: 0, duration: 0.5, ease: 'power4.out', stagger: 0.01 }, '<+=20%' ) .to( '.overlay [fade-up]', { autoAlpha: 1, yPercent: 0, duration: 0.5, ease: 'power4.out', stagger: 0.1 }, '<+=30%' ); }); // on mobile mm.add('(max-width: 990px)', () => { gsap.set('.overlay [chars-slide-up] .char', { yPercent: 0 }); gsap.set('.overlay [chars-slide-up]', { autoAlpha: 1 }); gsap.set('.overlay [fade-up]', { yPercent: 0, autoAlpha: 1 }); overlayAnim.to( overlay, { duration: 1, ease: 'power4.out', y: 0, autoAlpha: 1 }, 0 ); }); const openMenu = document.querySelector('[open-menu]'); const closeMenu = document.querySelector('[close-menu]'); const hamburgers = [openMenu, closeMenu]; if (hamburgers.length > 0) { hamburgers.forEach((hamburger, i) => { //-- Menu animation --// hamburger.addEventListener('click', () => { isOpen = !isOpen; //toggle if (isOpen) { overlayAnim.timeScale(1).play(); openMenu.setAttribute('aria-expanded', 'true'); closeMenu.setAttribute('aria-expanded', 'true'); overlay.setAttribute('aria-hidden', 'false'); lenis.stop(); } else { overlayAnim.timeScale(2).reverse(); openMenu.setAttribute('aria-expanded', 'false'); closeMenu.setAttribute('aria-expanded', 'false'); overlay.setAttribute('aria-hidden', 'true'); lenis.start(); } }); //-- Reverse menu animation on hash link click event --// const overlayNavLinks = document.querySelectorAll(".overlay a[href^='#']"); overlayNavLinks.forEach((link) => { link.addEventListener('click', () => { if (isOpen) { isOpen = !isOpen; // Toggle overlayAnim.timeScale(2).reverse(); openMenu.setAttribute('aria-expanded', 'false'); closeMenu.setAttribute('aria-expanded', 'false'); overlay.setAttribute('aria-hidden', 'true'); lenis.start(); } }); }); }); } } }; //-- DROPDOWN WORKS MENU --// const dropdownMenu = () => { const dropdown = document.getElementById('dropdown-works'); const dropdownBtn = document.querySelectorAll('[open-dropdown]'); let isOpen = false; if (dropdown) { if (dropdownBtn.length > 0) { gsap.set(dropdown, { y: '-101vh', display: 'flex', autoAlpha: 0 }); mm.add('(min-width: 991px)', () => { dropdownBtn.forEach((item) => { item.open = () => { if (!isOpen) { isOpen = true; const tl = gsap.timeline({ defaults: { overwrite: true }, onStart: () => { item.setAttribute('aria-expanded', 'true'); dropdown.setAttribute('aria-hidden', 'false'); } }); tl.to( dropdown, { duration: 1, ease: 'power4.out', y: 0, autoAlpha: 1 }, 0 ); } }; item.close = () => { if (isOpen) { isOpen = false; const tl = gsap.timeline({ defaults: { overwrite: true }, onStart: () => { item.setAttribute('aria-expanded', 'false'); dropdown.setAttribute('aria-hidden', 'true'); } }); tl.to( dropdown, { duration: 1, ease: 'power4.in', y: '-101vh', autoAlpha: 0 }, 0 ); } }; item.addEventListener('mouseenter', () => { item.open(); }); item.addEventListener('mouseleave', () => { item.close(); }); }); }); } } }; document.body.classList.remove('loading'); initUtils(); setupCursor(); overlayMenu(); dropdownMenu(); initParallax(); initMarqueeParlons(); initAbout(); initSliderWork(); initSliderTem(); }); mm.add(isDesktop, () => { /** * THREE JS ESCALIER 3D + GSAP Animation */ THREE.ColorManagement.enabled = false; /** * PATHS */ const dracoPath = 'https://cdn.jsdelivr.net/npm/@bw-inedev/metal-360@latest/dist/draco/'; const texturePath = 'https://cdn.jsdelivr.net/npm/@bw-inedev/metal-360@latest/dist/textures/matcaps/12.png'; const modelPath = 'https://cdn.jsdelivr.net/npm/@bw-inedev/metal-360@latest/dist/models/roue-compress-v2.glb'; /** * Add CANVAS to main page */ const main = document.querySelector('.page_main'); const creatCanvas = document.createElement('canvas'); creatCanvas.classList.add('webgl'); main.appendChild(creatCanvas); /** * Loaders */ const loadingManager = new THREE.LoadingManager(() => { gsap.to(overlayMaterial.uniforms.uAlpha, { duration: 3, value: 0 }); }); const dracoLoader = new DRACOLoader(loadingManager); dracoLoader.setDecoderPath(dracoPath); const gltfLoader = new GLTFLoader(loadingManager); gltfLoader.setDRACOLoader(dracoLoader); /** * Debug */ // const gui = new dat.GUI(); /** * Base */ // Canvas const canvas = document.querySelector('.webgl'); // Scene const scene = new THREE.Scene(); /** * Objects */ // Texture const textureLoader = new THREE.TextureLoader(); const matcapTexture = textureLoader.load(texturePath); /** * Overlay */ const overlayGeometry = new THREE.PlaneGeometry(2, 2, 1, 1); const overlayMaterial = new THREE.ShaderMaterial({ transparent: true, uniforms: { uAlpha: { value: 1 } }, vertexShader: ` void main() { gl_Position = vec4(position, 1.0); } `, fragmentShader: ` uniform float uAlpha; void main() { gl_FragColor = vec4(0.0, 0.0, 0.0, uAlpha); } ` }); const overlay = new THREE.Mesh(overlayGeometry, overlayMaterial); scene.add(overlay); /** * Models */ let wheels; let bigGroup; let mediumGroup; let smallGroup; let wheelsGroups; gltfLoader.load(modelPath, (gltf) => { wheels = gltf.scene; wheels.traverse((child) => { child.material = material; }); wheelsGroups = new THREE.Group(); bigGroup = new THREE.Group(); mediumGroup = new THREE.Group(); smallGroup = new THREE.Group(); wheelsGroups.name = 'all_wheels'; smallGroup.name = 'group_small_wheel'; mediumGroup.name = 'group_medium_wheel'; bigGroup.name = 'group_big_wheel'; // scene.add(wheelsGroups); scene.add(wheelsGroups); scene.add(bigGroup); scene.add(mediumGroup); scene.add(smallGroup); bigGroup.add(wheels.children[0]); mediumGroup.add(wheels.children[0]); smallGroup.add(wheels.children[0]); wheelsGroups.add(bigGroup, mediumGroup, smallGroup); tick(); // In orther tout animate global object add mesh to a group // Axis helper // const meshGroupAxes = new THREE.AxesHelper(5); // const bigGroupAxes = new THREE.AxesHelper(5); // const bigWheelAxes = new THREE.AxesHelper(2); // wheelsGroups.add(meshGroupAxes); // bigGroup.add(bigGroupAxes); // bigGroup.children[0].add(bigWheelAxes); const allWheels = { init: { p: [0, 0, 0], r: [0, 0, 0], s: 1 }, start: { p: [-16, 0, 0], r: [0, 0, 0], s: 1 }, tl1: { p: [-4, -2, 0], r: [0, 0.5, 0], s: 1 }, tl3: { p: [-15, -2, 0], r: [0, 0, 0], s: 1.7 } }; const bigWheel = { mesh: wheelsGroups.children[0], init: { p: [0, 0, 0], r: [0, 0, 0], s: 1 }, tl1: { p: [0, 0, 0], r: [0, 0, 0], s: 1 }, tl3: { p: [0, 0, 0], r: [0, 0, 0], s: 1 } }; const mediumWheel = { mesh: wheelsGroups.children[1], init: { p: [0, 0, 0], r: [0, 0, 0], s: 1 }, tl1: { p: [0, 0, 0], r: [0, 0, 0], s: 1 }, tl3: { p: [0, 0, 0], r: [0, 0, 0], s: 1 } }; const smallWheel = { mesh: wheelsGroups.children[2], init: { p: [4, -1.24, 1], r: [1, 0.42, 1], s: 1 }, tl1: { p: [0, 0, 0], r: [0, 0, 0], s: 1 }, tl3: { p: [0, 0, 0], r: [0, 0, 0], s: 1 } }; // init wheels positions wheelsGroups.position.x = allWheels.start.p[0]; wheelsGroups.position.y = allWheels.start.p[1]; wheelsGroups.position.z = allWheels.start.p[2]; wheelsGroups.rotation.x = allWheels.start.r[0]; wheelsGroups.rotation.y = allWheels.start.r[1]; wheelsGroups.rotation.z = allWheels.start.r[2]; wheelsGroups.scale.x = allWheels.start.s; wheelsGroups.scale.y = allWheels.start.s; wheelsGroups.scale.y = allWheels.start.s; const scrubValue = 1; const scrollStart = 'top 100%'; const scrollEnd = 'top 0'; // const scrollStart = 'top-=200px 100%'; // const scrollEnd = 'top-=200px 0%'; const tl1 = gsap.timeline({ defaults: { ease: 'none' }, scrollTrigger: { trigger: '[anime-one]', scrub: scrubValue, // preventOverlaps: true, start: scrollStart, end: scrollEnd } }); tl1 .to( wheelsGroups.position, { x: allWheels.tl1.p[0], y: allWheels.tl1.p[1], z: allWheels.tl1.p[2] }, 0 ) .to( wheelsGroups.rotation, { x: allWheels.tl1.r[0], y: allWheels.tl1.r[1], z: allWheels.tl1.r[2] }, 0 ) .to( wheelsGroups.scale, { x: allWheels.tl1.s, y: allWheels.tl1.s, z: allWheels.tl1.s }, 0 ) .to( bigWheel.mesh.position, { x: bigWheel.tl1.p[0], y: bigWheel.tl1.p[1], z: bigWheel.tl1.p[2] }, 0 ) .to( bigWheel.mesh.rotation, { x: bigWheel.tl1.r[0], y: bigWheel.tl1.r[1], z: bigWheel.tl1.r[2] }, 0 ) .to( bigWheel.mesh.scale, { x: bigWheel.tl1.s, y: bigWheel.tl1.s, z: bigWheel.tl1.s }, 0 ) .to( mediumWheel.mesh.position, { x: mediumWheel.tl1.p[0], y: mediumWheel.tl1.p[1], z: mediumWheel.tl1.p[2] }, 0 ) .to( mediumWheel.mesh.rotation, { x: mediumWheel.tl1.r[0], y: mediumWheel.tl1.r[1], z: mediumWheel.tl1.r[2] }, 0 ) .to( mediumWheel.mesh.scale, { x: mediumWheel.tl1.s, y: mediumWheel.tl1.s, z: mediumWheel.tl1.s }, 0 ) .to( smallWheel.mesh.position, { x: smallWheel.tl1.p[0], y: smallWheel.tl1.p[1], z: smallWheel.tl1.p[2] }, 0 ) .to( smallWheel.mesh.rotation, { x: smallWheel.tl1.r[0], y: smallWheel.tl1.r[1], z: smallWheel.tl1.r[2] }, 0 ) .to( smallWheel.mesh.scale, { x: smallWheel.tl1.s, y: smallWheel.tl1.s, z: smallWheel.tl1.s }, 0 ); const tl3 = gsap.timeline({ defaults: { ease: 'none' }, scrollTrigger: { trigger: '[anime-two]', scrub: scrubValue, // preventOverlaps: true, // markers: true, start: scrollStart, end: scrollEnd } }); tl3 .to( wheelsGroups.position, { x: allWheels.tl3.p[0], y: allWheels.tl3.p[1], z: allWheels.tl3.p[2] }, 0 ) .to( wheelsGroups.rotation, { x: allWheels.tl3.r[0], y: allWheels.tl3.r[1], z: allWheels.tl3.r[2] }, 0 ) .to( wheelsGroups.scale, { x: allWheels.tl3.s, y: allWheels.tl3.s, z: allWheels.tl3.s }, 0 ) .to( bigWheel.mesh.position, { x: bigWheel.tl3.p[0], y: bigWheel.tl3.p[1], z: bigWheel.tl3.p[2] }, 0 ) .to( bigWheel.mesh.rotation, { x: bigWheel.tl3.r[0], y: bigWheel.tl3.r[1], z: bigWheel.tl3.r[2] }, 0 ) .to( bigWheel.mesh.scale, { x: bigWheel.tl3.s, y: bigWheel.tl3.s, z: bigWheel.tl3.s }, 0 ) .to( mediumWheel.mesh.position, { x: mediumWheel.tl3.p[0], y: mediumWheel.tl3.p[1], z: mediumWheel.tl3.p[2] }, 0 ) .to( mediumWheel.mesh.rotation, { x: mediumWheel.tl3.r[0], y: mediumWheel.tl3.r[1], z: mediumWheel.tl3.r[2] }, 0 ) .to( mediumWheel.mesh.scale, { x: mediumWheel.tl3.s, y: mediumWheel.tl3.s, z: mediumWheel.tl3.s }, 0 ) .to( smallWheel.mesh.position, { x: smallWheel.tl3.p[0], y: smallWheel.tl3.p[1], z: smallWheel.tl3.p[2] }, 0 ) .to( smallWheel.mesh.rotation, { x: smallWheel.tl3.r[0], y: smallWheel.tl3.r[1], z: smallWheel.tl3.r[2] }, 0 ) .to( smallWheel.mesh.scale, { x: smallWheel.tl3.s, y: smallWheel.tl3.s, z: smallWheel.tl3.s }, 0 ); /////////////////////// // Debug // gui.add(wheelsGroups.position, 'x', -8, 8, 0.01).name('TranslateX'); // gui.add(wheelsGroups.position, 'y', -8, 8, 0.01).name('ElevationY'); // gui.add(wheelsGroups.position, 'z', -8, 8, 0.01).name('TranslationZ'); // gui.add(wheelsGroups.rotation, 'x', -8, 8, 0.01).name('RotationX'); // gui.add(wheelsGroups.rotation, 'y', -8, 8, 0.01).name('RotationY'); // gui.add(wheelsGroups.rotation, 'z', -8, 8, 0.01).name('RotationZ'); // gui.add(bigWheel.mesh.position, 'x', -8, 8, 0.01).name('Big-TranslateX'); // gui.add(bigWheel.mesh.position, 'y', -8, 8, 0.01).name('Big-ElevationY'); // gui.add(bigWheel.mesh.position, 'z', -8, 8, 0.01).name('Big-TranslationZ'); // gui.add(bigWheel.mesh.rotation, 'x', -8, 8, 0.01).name('Big-RotationX'); // gui.add(bigWheel.mesh.rotation, 'y', -8, 8, 0.01).name('Big-RotationY'); // gui.add(bigWheel.mesh.rotation, 'z', -8, 8, 0.01).name('Big-RotationZ'); // gui.add(bigWheel.mesh.scale, 'x', 0, 2, 0.5).name('Big-ScaleX'); // gui.add(bigWheel.mesh.scale, 'y', 0, 2, 0.5).name('Big-ScaleY'); // gui.add(bigWheel.mesh.scale, 'z', 0, 2, 0.5).name('Big-ScaleZ'); // gui.add(mediumWheel.mesh.position, 'x', -8, 8, 0.01).name('Med-TranslateX'); // gui.add(mediumWheel.mesh.position, 'y', -8, 8, 0.01).name('Med-ElevationY'); // gui.add(mediumWheel.mesh.position, 'z', -8, 8, 0.01).name('Med-TranslationZ'); // gui.add(mediumWheel.mesh.rotation, 'x', -8, 8, 0.01).name('Med-RotationX'); // gui.add(mediumWheel.mesh.rotation, 'y', -8, 8, 0.01).name('Med-RotationY'); // gui.add(mediumWheel.mesh.rotation, 'z', -8, 8, 0.01).name('Med-RotationZ'); // gui.add(mediumWheel.mesh.scale, 'x', 0, 2, 0.5).name('medium-ScaleX'); // gui.add(mediumWheel.mesh.scale, 'y', 0, 2, 0.5).name('medium-ScaleY'); // gui.add(mediumWheel.mesh.scale, 'z', 0, 2, 0.5).name('medium-ScaleZ'); // gui.add(smallWheel.mesh.position, 'x', -8, 8, 0.01).name('Small-TranslateX'); // gui.add(smallWheel.mesh.position, 'y', -8, 8, 0.01).name('Small-ElevationY'); // gui.add(smallWheel.mesh.position, 'z', -8, 8, 0.01).name('Small-TranslationZ'); // gui.add(smallWheel.mesh.rotation, 'x', -8, 8, 0.01).name('Small-RotationX'); // gui.add(smallWheel.mesh.rotation, 'y', -8, 8, 0.01).name('Small-RotationY'); // gui.add(smallWheel.mesh.rotation, 'z', -8, 8, 0.01).name('Small-RotationZ'); // gui.add(smallWheel.mesh.scale, 'x', 0, 2, 0.5).name('small-ScaleX'); // gui.add(smallWheel.mesh.scale, 'y', 0, 2, 0.5).name('small-ScaleY'); // gui.add(smallWheel.mesh.scale, 'z', 0, 2, 0.5).name('small-ScaleZ'); }); // Light // const ambientLight = new THREE.AmbientLight(0xecddb0, 50); // scene.add(ambientLight); // const pointLight = new THREE.PointLight(0xecddb0, 150); // pointLight.position.x = 0; // pointLight.position.y = 0; // pointLight.position.z = 0; // scene.add(pointLight); // const directionalLight = new THREE.DirectionalLight(0xecddb0, 50); // scene.add(directionalLight); const hemisphereLight = new THREE.HemisphereLight(0xecddb0, 0x0000000, 50); scene.add(hemisphereLight); const material = new THREE.MeshStandardMaterial(); material.color = new THREE.Color(0x090b10); material.roughness = 0.5; material.metalness = 0.2; material.wireframe = true; // In orther tout animate global object add mesh to a group // Axis helper // const lightAxes = new THREE.AxesHelper(5); // pointLight.add(lightAxes); // const ambientLightAxes = new THREE.AxesHelper(5); // ambientLight.add(ambientLightAxes); // Debug // gui.add(ambientLight.position, 'x', -3, 3, 0.01).name('TranslateX'); // gui.add(ambientLight.position, 'y', -3, 3, 0.01).name('ElevationY'); // gui.add(ambientLight.position, 'z', -3, 3, 0.01).name('TranslationZ'); // gui.add(pointLight.position, 'x', -3, 3, 0.01).name('TranslateX'); // gui.add(pointLight.position, 'y', -3, 3, 0.01).name('ElevationY'); // gui.add(pointLight.position, 'z', -3, 3, 0.01).name('TranslationZ'); // Material // const material = new THREE.MeshMatcapMaterial(); // material.matcap = matcapTexture; // material.wireframe = true; /** * Sizes */ const sizes = { width: window.innerWidth, height: window.innerHeight }; window.addEventListener('resize', () => { // Update sizes sizes.width = window.innerWidth; sizes.height = window.innerHeight; // Update camera camera.aspect = sizes.width / sizes.height; camera.updateProjectionMatrix(); // Update renderer renderer.setSize(sizes.width, sizes.height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); }); /** * Camera */ // Group const cameraGroup = new THREE.Group(); scene.add(cameraGroup); // Base camera const camera = new THREE.PerspectiveCamera(35, sizes.width / sizes.height, 0.1, 100); // camera.position.z = 25; camera.position.z = 11; cameraGroup.add(camera); // Debug // gui.add(camera.position, "z", 8, 20, 1).name("Camera Zoom"); /** * Renderer */ const renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true }); renderer.outputColorSpace = THREE.LinearSRGBColorSpace; renderer.setSize(sizes.width, sizes.height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); /** * Cursor */ const cursor = {}; cursor.x = 0; cursor.y = 0; window.addEventListener('mousemove', (event) => { cursor.x = event.clientX / sizes.width - 0.5; cursor.y = event.clientY / sizes.height - 0.5; }); /** * Animate */ const clock = new THREE.Clock(); let previousTime = 0; const tick = () => { if (wheelsGroups) { const elapsedTime = clock.getElapsedTime(); const deltaTime = elapsedTime - previousTime; previousTime = elapsedTime; // Animate camera //camera.position.y = -scrollY / sizes.height; const parallaxX = cursor.x * 0.1; const parallaxY = -cursor.y * 0.1; cameraGroup.position.x += (parallaxX - cameraGroup.position.x) * 5 * deltaTime; cameraGroup.position.y += (parallaxY - cameraGroup.position.y) * 5 * deltaTime; // Animate meshes rotation bigGroup.children[0].rotation.y += deltaTime * 0.1; mediumGroup.children[0].rotation.y += deltaTime * -0.05; smallGroup.children[0].rotation.y += deltaTime * 0.1; } // Render renderer.render(scene, camera); // Call tick again on the next frame window.requestAnimationFrame(tick); }; });