Three.js - Scroll Based Animation: Unterschied zwischen den Versionen
Aus Wikizone
| (3 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
| + | == Links == | ||
| + | [[ThreeJS]] | ||
== Snippets == | == Snippets == | ||
=== Check Scroll Position === | === Check Scroll Position === | ||
| Zeile 154: | Zeile 156: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | === Scroll Trigger === | ||
| + | Wir nutzen GSAP aber nur für die Animation, nicht für den Trigger: | ||
| + | <syntaxhighlight lang="javascript"> | ||
| + | /** | ||
| + | * Sizes | ||
| + | */ | ||
| + | const sizes = { | ||
| + | width: window.innerWidth, | ||
| + | height: window.innerHeight | ||
| + | } | ||
| + | |||
| + | window.addEventListener('resize', () => | ||
| + | { | ||
| + | // Update sizes | ||
| + | sizes.width = window.innerWidth | ||
| + | sizes.height = window.innerHeight | ||
| + | //... | ||
| + | }) | ||
| + | // ... | ||
| − | + | /** | |
| + | * Scroll | ||
| + | */ | ||
| + | let scrollY = window.scrollY | ||
| + | let currentSection = 0 | ||
| + | |||
| + | window.addEventListener('scroll', () => | ||
| + | { | ||
| + | scrollY = window.scrollY | ||
| + | const newSection = Math.round(scrollY / sizes.height) //liefert 0,1,2... | ||
| + | if(newSection != currentSection) | ||
| + | { | ||
| + | console.log('changed', currentSection) | ||
| + | currentSection = newSection | ||
| + | // Section Trigger | ||
| + | gsap.to( | ||
| + | sectionMeshes[currentSection].rotation, | ||
| + | { | ||
| + | duration: 1.5, | ||
| + | ease: 'power2.inOut', | ||
| + | x: '+=6', | ||
| + | y: '+=3', | ||
| + | z: '+=1.5' | ||
| + | } | ||
| + | ) | ||
| + | } | ||
| + | }) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | |||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | <syntaxhighlight lang=" | + | |
| + | == Complete Example (three.js journey) == | ||
| + | '''HTML''' | ||
| + | <syntaxhighlight lang="html5"> | ||
| + | <!DOCTYPE html> | ||
| + | <html lang="en"> | ||
| + | <head> | ||
| + | <meta charset="UTF-8"> | ||
| + | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| + | <title>21 - Scroll base animation</title> | ||
| + | </head> | ||
| + | <body> | ||
| + | <canvas class="webgl"></canvas> | ||
| + | |||
| + | <section class="section"> | ||
| + | <h1>My Portfolio</h1> | ||
| + | </section> | ||
| + | <section class="section"> | ||
| + | <h2>My projects</h2> | ||
| + | </section> | ||
| + | <section class="section"> | ||
| + | <h2>Contact me</h2> | ||
| + | </section> | ||
| + | </body> | ||
| + | </html> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | <syntaxhighlight lang=" | + | |
| + | '''CSS''' | ||
| + | <syntaxhighlight lang="css"> | ||
| + | * | ||
| + | { | ||
| + | margin: 0; | ||
| + | padding: 0; | ||
| + | } | ||
| + | |||
| + | html, | ||
| + | body | ||
| + | { | ||
| + | background: #1e1a20; | ||
| + | } | ||
| + | |||
| + | .webgl | ||
| + | { | ||
| + | position: fixed; | ||
| + | top: 0; | ||
| + | left: 0; | ||
| + | outline: none; | ||
| + | } | ||
| + | |||
| + | |||
| + | .section | ||
| + | { | ||
| + | display: flex; | ||
| + | align-items: center; | ||
| + | height: 100vh; | ||
| + | position: relative; | ||
| + | font-family: 'Cabin', sans-serif; | ||
| + | color: #ffeded; | ||
| + | text-transform: uppercase; | ||
| + | font-size: 7vmin; | ||
| + | padding-left: 10%; | ||
| + | padding-right: 10%; | ||
| + | } | ||
| + | |||
| + | section:nth-child(odd) | ||
| + | { | ||
| + | justify-content: flex-end; | ||
| + | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | + | '''JavaScript''' | |
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
| + | import './style.css' | ||
| + | import * as THREE from 'three' | ||
| + | import * as dat from 'lil-gui' | ||
| + | import gsap from 'gsap' | ||
| + | |||
| + | /** | ||
| + | * Debug | ||
| + | */ | ||
| + | const gui = new dat.GUI() | ||
| + | |||
| + | const parameters = { | ||
| + | materialColor: '#ffeded' | ||
| + | } | ||
| + | |||
| + | gui | ||
| + | .addColor(parameters, 'materialColor') | ||
| + | .onChange(() => | ||
| + | { | ||
| + | material.color.set(parameters.materialColor) | ||
| + | particlesMaterial.color.set(parameters.materialColor) | ||
| + | }) | ||
| + | |||
| + | /** | ||
| + | * Base | ||
| + | */ | ||
| + | // Canvas | ||
| + | const canvas = document.querySelector('canvas.webgl') | ||
| + | |||
| + | // Scene | ||
| + | const scene = new THREE.Scene() | ||
| + | /** | ||
| + | * Objects | ||
| + | */ | ||
| + | |||
| + | // Texture | ||
| + | const textureLoader = new THREE.TextureLoader() | ||
| + | const gradientTexture = textureLoader.load('textures/gradients/3.jpg') | ||
| + | gradientTexture.magFilter = THREE.NearestFilter // prevent interpolation of colors | ||
| + | |||
| + | // Material | ||
| + | const material = new THREE.MeshToonMaterial({ | ||
| + | color: parameters.materialColor, | ||
| + | gradientMap: gradientTexture | ||
| + | }) | ||
| + | |||
| + | // Meshes | ||
| + | const mesh1 = new THREE.Mesh( | ||
| + | new THREE.TorusGeometry(1, 0.4, 16, 60), | ||
| + | material | ||
| + | ) | ||
| + | const mesh2 = new THREE.Mesh( | ||
| + | new THREE.ConeGeometry(1, 2, 32), | ||
| + | material | ||
| + | ) | ||
| + | const mesh3 = new THREE.Mesh( | ||
| + | new THREE.TorusKnotGeometry(0.8, 0.35, 100, 16), | ||
| + | material | ||
| + | ) | ||
| + | |||
| + | scene.add(mesh1, mesh2, mesh3) | ||
| + | |||
| + | // position objects | ||
| + | const objectsDistance = 4 | ||
| + | mesh1.position.y = - objectsDistance * 0 | ||
| + | mesh2.position.y = - objectsDistance * 1 | ||
| + | mesh3.position.y = - objectsDistance * 2 | ||
| + | mesh1.position.x = 2 | ||
| + | mesh2.position.x = - 2 | ||
| + | mesh3.position.x = 2 | ||
| + | |||
| + | const sectionMeshes = [ mesh1, mesh2, mesh3 ] | ||
| + | |||
| + | /** | ||
| + | * Particles | ||
| + | */ | ||
| + | const particlesCount = 200 | ||
| + | |||
| + | const positions = new Float32Array(particlesCount * 3) | ||
| + | for(let i = 0; i < particlesCount; i++) | ||
| + | { | ||
| + | positions[i * 3 + 0] = (Math.random() - 0.5) * 10 | ||
| + | positions[i * 3 + 1] = objectsDistance * 0.5 - Math.random() * objectsDistance * sectionMeshes.length | ||
| + | positions[i * 3 + 2] = (Math.random() - 0.5) * 10 | ||
| + | } | ||
| + | |||
| + | // Geometry | ||
| + | const particlesGeometry = new THREE.BufferGeometry() | ||
| + | particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)) | ||
| + | |||
| + | // Material | ||
| + | const particlesMaterial = new THREE.PointsMaterial({ | ||
| + | color: parameters.materialColor, | ||
| + | sizeAttenuation: true, | ||
| + | size: 0.03 | ||
| + | }) | ||
| + | |||
| + | // Points | ||
| + | const particles = new THREE.Points(particlesGeometry, particlesMaterial) | ||
| + | scene.add(particles) | ||
| + | |||
| + | /** | ||
| + | * Lights | ||
| + | */ | ||
| + | const directionalLight = new THREE.DirectionalLight('#ffffff', 1) | ||
| + | directionalLight.position.set(1, 1, 0) | ||
| + | scene.add(directionalLight) | ||
| + | |||
| + | /** | ||
| + | * 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 (used to apply camara parallax without interfering the camera scroll) | ||
| + | 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 = 6 | ||
| + | cameraGroup.add(camera) | ||
| + | |||
| + | |||
| + | /** | ||
| + | * Renderer | ||
| + | */ | ||
| + | const renderer = new THREE.WebGLRenderer({ | ||
| + | canvas: canvas, | ||
| + | alpha: true | ||
| + | }) | ||
| + | renderer.setSize(sizes.width, sizes.height) | ||
| + | renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) | ||
| + | |||
| + | /** | ||
| + | * Scroll | ||
| + | */ | ||
| + | let scrollY = window.scrollY | ||
| + | let currentSection = 0 | ||
| + | |||
| + | window.addEventListener('scroll', () => | ||
| + | { | ||
| + | scrollY = window.scrollY | ||
| + | const newSection = Math.round(scrollY / sizes.height) | ||
| + | if(newSection != currentSection) | ||
| + | { | ||
| + | console.log('changed', currentSection) | ||
| + | currentSection = newSection | ||
| + | // Section Trigger | ||
| + | gsap.to( | ||
| + | sectionMeshes[currentSection].rotation, | ||
| + | { | ||
| + | duration: 1.5, | ||
| + | ease: 'power2.inOut', | ||
| + | x: '+=6', | ||
| + | y: '+=3', | ||
| + | z: '+=1.5' | ||
| + | } | ||
| + | ) | ||
| + | } | ||
| + | }) | ||
| + | |||
| + | /** | ||
| + | * 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 | ||
| + | |||
| + | //console.log(cursor) | ||
| + | }) | ||
| + | |||
| + | |||
| + | /** | ||
| + | * Animate | ||
| + | */ | ||
| + | const clock = new THREE.Clock() | ||
| + | let previousTime = 0 | ||
| + | |||
| + | const tick = () => | ||
| + | { | ||
| + | const elapsedTime = clock.getElapsedTime() | ||
| + | // we need deltaTime to calc correct timbased (not fps based) movement for our easing | ||
| + | const deltaTime = elapsedTime - previousTime | ||
| + | previousTime = elapsedTime | ||
| + | |||
| + | // Animate meshes | ||
| + | for(const mesh of sectionMeshes) | ||
| + | { | ||
| + | // we use deltaTime this time, because gsap rotation would not apply otherwise | ||
| + | // mesh.rotation.x = elapsedTime * 0.1 | ||
| + | // mesh.rotation.y = elapsedTime * 0.12 | ||
| + | mesh.rotation.x += deltaTime * 0.1 | ||
| + | mesh.rotation.y += deltaTime * 0.12 | ||
| + | } | ||
| + | |||
| + | // Animate camera | ||
| + | // scroll objects in sync to html | ||
| + | camera.position.y = - scrollY / sizes.height * objectsDistance | ||
| + | |||
| + | // camera parallax on cursor movement with easing | ||
| + | const parallaxX = cursor.x * 0.5 | ||
| + | const parallaxY = - cursor.y * 0.5 | ||
| + | cameraGroup.position.x += (parallaxX - cameraGroup.position.x) * 5 * deltaTime | ||
| + | cameraGroup.position.y += (parallaxY - cameraGroup.position.y) * 5 * deltaTime | ||
| + | |||
| + | // Render | ||
| + | renderer.render(scene, camera) | ||
| + | |||
| + | // Call tick again on the next frame | ||
| + | window.requestAnimationFrame(tick) | ||
| + | } | ||
| + | |||
| + | tick() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | |||
== Magnetic Sections - Todo == | == Magnetic Sections - Todo == | ||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Aktuelle Version vom 17. Februar 2022, 03:30 Uhr
Links[Bearbeiten]
Snippets[Bearbeiten]
Check Scroll Position[Bearbeiten]
let scrollY = window.scrollY
window.addEventListener('scroll', () =>
{
scrollY = window.scrollY
console.log(scrollY)
})
Scroll objects in sync to html scroll[Bearbeiten]
// position objects
const objectsDistance = 4
mesh1.position.y = - objectsDistance * 0
mesh2.position.y = - objectsDistance * 1
mesh3.position.y = - objectsDistance * 2
//...
// Calc sizes (HINT-no resize event in this example)
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
//...
// Base camera
const camera = new THREE.PerspectiveCamera(35, sizes.width / sizes.height, 0.1, 100)
camera.position.z = 6
scene.add(camera)
// Control Scrolling
let scrollY = window.scrollY
window.addEventListener('scroll', () => { scrollY = window.scrollY })
// ...
// tick function
const tick = () =>
{
// ...
// Animate camera
camera.position.y = - scrollY / sizes.height * objectsDistance // scroll objects in sync to html
// ...
}
tick()
Cursor based Parallax[Bearbeiten]
// ...
/**
* Camera
*/
// Group (used to apply camara parallax without interfering the camera scroll)
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 = 6
cameraGroup.add(camera)
// ...
const tick = () =>
{
// Animate camera
// scroll objects in sync to html
camera.position.y = - scrollY / sizes.height * objectsDistance
// cameraGroup parallax on cursor movement
const parallaxX = cursor.x
const parallaxY = - cursor.y
cameraGroup.position.x = parallaxX
cameraGroup.position.y = parallaxY
}
// ...
Parallax with easing[Bearbeiten]
Variation von oben
// ...
/**
* Animate
*/
const clock = new THREE.Clock()
let previousTime = 0
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// we need deltaTime to calc correct timbased (not fps based) movement for our easing
const deltaTime = elapsedTime - previousTime
previousTime = elapsedTime
//...
// Animate camera
// scroll objects in sync to html
camera.position.y = - scrollY / sizes.height * objectsDistance
// camera parallax on cursor movement
const parallaxX = cursor.x * 0.5
const parallaxY = - cursor.y * 0.5
//cameraGroup.position.x = parallaxX
//cameraGroup.position.y = parallaxY
// add some easing
cameraGroup.position.x += (parallaxX - cameraGroup.position.x) * 5 * deltaTime
cameraGroup.position.y += (parallaxY - cameraGroup.position.y) * 5 * deltaTime
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()
Partikel für zusätzliche Räumlichkeit einsetzen[Bearbeiten]
/**
* Particles
*/
const particlesCount = 200
const positions = new Float32Array(particlesCount * 3)
for(let i = 0; i < particlesCount; i++)
{
positions[i * 3 + 0] = (Math.random() - 0.5) * 10
positions[i * 3 + 1] = objectsDistance * 0.5 - Math.random() * objectsDistance * sectionMeshes.length
positions[i * 3 + 2] = (Math.random() - 0.5) * 10
}
// Geometry
const particlesGeometry = new THREE.BufferGeometry()
particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
// Material
const particlesMaterial = new THREE.PointsMaterial({
color: parameters.materialColor,
sizeAttenuation: true,
size: 0.03
})
// Points
const particles = new THREE.Points(particlesGeometry, particlesMaterial)
scene.add(particles)
Scroll Trigger[Bearbeiten]
Wir nutzen GSAP aber nur für die Animation, nicht für den Trigger:
/**
* Sizes
*/
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
window.addEventListener('resize', () =>
{
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
//...
})
// ...
/**
* Scroll
*/
let scrollY = window.scrollY
let currentSection = 0
window.addEventListener('scroll', () =>
{
scrollY = window.scrollY
const newSection = Math.round(scrollY / sizes.height) //liefert 0,1,2...
if(newSection != currentSection)
{
console.log('changed', currentSection)
currentSection = newSection
// Section Trigger
gsap.to(
sectionMeshes[currentSection].rotation,
{
duration: 1.5,
ease: 'power2.inOut',
x: '+=6',
y: '+=3',
z: '+=1.5'
}
)
}
})
Complete Example (three.js journey)[Bearbeiten]
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>21 - Scroll base animation</title>
</head>
<body>
<canvas class="webgl"></canvas>
<section class="section">
<h1>My Portfolio</h1>
</section>
<section class="section">
<h2>My projects</h2>
</section>
<section class="section">
<h2>Contact me</h2>
</section>
</body>
</html>
CSS
*
{
margin: 0;
padding: 0;
}
html,
body
{
background: #1e1a20;
}
.webgl
{
position: fixed;
top: 0;
left: 0;
outline: none;
}
.section
{
display: flex;
align-items: center;
height: 100vh;
position: relative;
font-family: 'Cabin', sans-serif;
color: #ffeded;
text-transform: uppercase;
font-size: 7vmin;
padding-left: 10%;
padding-right: 10%;
}
section:nth-child(odd)
{
justify-content: flex-end;
}
JavaScript
import './style.css'
import * as THREE from 'three'
import * as dat from 'lil-gui'
import gsap from 'gsap'
/**
* Debug
*/
const gui = new dat.GUI()
const parameters = {
materialColor: '#ffeded'
}
gui
.addColor(parameters, 'materialColor')
.onChange(() =>
{
material.color.set(parameters.materialColor)
particlesMaterial.color.set(parameters.materialColor)
})
/**
* Base
*/
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Scene
const scene = new THREE.Scene()
/**
* Objects
*/
// Texture
const textureLoader = new THREE.TextureLoader()
const gradientTexture = textureLoader.load('textures/gradients/3.jpg')
gradientTexture.magFilter = THREE.NearestFilter // prevent interpolation of colors
// Material
const material = new THREE.MeshToonMaterial({
color: parameters.materialColor,
gradientMap: gradientTexture
})
// Meshes
const mesh1 = new THREE.Mesh(
new THREE.TorusGeometry(1, 0.4, 16, 60),
material
)
const mesh2 = new THREE.Mesh(
new THREE.ConeGeometry(1, 2, 32),
material
)
const mesh3 = new THREE.Mesh(
new THREE.TorusKnotGeometry(0.8, 0.35, 100, 16),
material
)
scene.add(mesh1, mesh2, mesh3)
// position objects
const objectsDistance = 4
mesh1.position.y = - objectsDistance * 0
mesh2.position.y = - objectsDistance * 1
mesh3.position.y = - objectsDistance * 2
mesh1.position.x = 2
mesh2.position.x = - 2
mesh3.position.x = 2
const sectionMeshes = [ mesh1, mesh2, mesh3 ]
/**
* Particles
*/
const particlesCount = 200
const positions = new Float32Array(particlesCount * 3)
for(let i = 0; i < particlesCount; i++)
{
positions[i * 3 + 0] = (Math.random() - 0.5) * 10
positions[i * 3 + 1] = objectsDistance * 0.5 - Math.random() * objectsDistance * sectionMeshes.length
positions[i * 3 + 2] = (Math.random() - 0.5) * 10
}
// Geometry
const particlesGeometry = new THREE.BufferGeometry()
particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
// Material
const particlesMaterial = new THREE.PointsMaterial({
color: parameters.materialColor,
sizeAttenuation: true,
size: 0.03
})
// Points
const particles = new THREE.Points(particlesGeometry, particlesMaterial)
scene.add(particles)
/**
* Lights
*/
const directionalLight = new THREE.DirectionalLight('#ffffff', 1)
directionalLight.position.set(1, 1, 0)
scene.add(directionalLight)
/**
* 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 (used to apply camara parallax without interfering the camera scroll)
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 = 6
cameraGroup.add(camera)
/**
* Renderer
*/
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
alpha: true
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
/**
* Scroll
*/
let scrollY = window.scrollY
let currentSection = 0
window.addEventListener('scroll', () =>
{
scrollY = window.scrollY
const newSection = Math.round(scrollY / sizes.height)
if(newSection != currentSection)
{
console.log('changed', currentSection)
currentSection = newSection
// Section Trigger
gsap.to(
sectionMeshes[currentSection].rotation,
{
duration: 1.5,
ease: 'power2.inOut',
x: '+=6',
y: '+=3',
z: '+=1.5'
}
)
}
})
/**
* 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
//console.log(cursor)
})
/**
* Animate
*/
const clock = new THREE.Clock()
let previousTime = 0
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// we need deltaTime to calc correct timbased (not fps based) movement for our easing
const deltaTime = elapsedTime - previousTime
previousTime = elapsedTime
// Animate meshes
for(const mesh of sectionMeshes)
{
// we use deltaTime this time, because gsap rotation would not apply otherwise
// mesh.rotation.x = elapsedTime * 0.1
// mesh.rotation.y = elapsedTime * 0.12
mesh.rotation.x += deltaTime * 0.1
mesh.rotation.y += deltaTime * 0.12
}
// Animate camera
// scroll objects in sync to html
camera.position.y = - scrollY / sizes.height * objectsDistance
// camera parallax on cursor movement with easing
const parallaxX = cursor.x * 0.5
const parallaxY = - cursor.y * 0.5
cameraGroup.position.x += (parallaxX - cameraGroup.position.x) * 5 * deltaTime
cameraGroup.position.y += (parallaxY - cameraGroup.position.y) * 5 * deltaTime
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()