https://i.sstatic.net/Z5F8K0mS.gif
After stacking the sticky areas, I faced a challenge in adding text in the same style. Additionally, I created refs for each phone scene but the stickiness was lost. I am uncertain about what caused it to stick initially and how to control the duration each part is visible to the user.
The result is multiple phones rendered, however, the sticky aspect has been compromised.
https://codesandbox.io/p/sandbox/r3f-scroll-rig-sticky-box-forked-3zpm73
import React, { useRef, useEffect } from 'react'
import { Canvas, useThree } from '@react-three/fiber'
import { useGLTF, OrbitControls } from '@react-three/drei'
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import * as THREE from 'three'
gsap.registerPlugin(ScrollTrigger)
const IphoneModel = (ref) => {
const group = useRef()
const { nodes, materials } = useGLTF('/Iphone15.glb')
useEffect(() => {
const video = document.createElement('video')
video.src = 'https://cdn.pixabay.com/video/2024/07/14/221180_tiny.mp4'
video.crossOrigin = 'anonymous'
video.loop = true
video.muted = true
video.play()
const videoTexture = new THREE.VideoTexture(video)
videoTexture.minFilter = THREE.LinearFilter
videoTexture.magFilter = THREE.LinearFilter
videoTexture.encoding = THREE.sRGBEncoding
materials.Screen.map = videoTexture
materials.Screen.needsUpdate = true
const tl = gsap.timeline({
scrollTrigger: {
trigger: ref[0],
scrub: 1,
markers: true,
pin: true,
start: 'top top',
end: 'bottom top'
}
})
tl.to(group.current.rotation, { z: -Math.PI / 8, duration: 2 })
}, [materials.Screen])
return (
<group ref={group} dispose={null} scale={0.2} rotation={[Math.PI / 2, 0, Math.PI / 8]}>
<mesh geometry={nodes.M_Cameras.geometry} material={materials.cam} />
<mesh geometry={nodes.M_Glass.geometry} material={materials['glass.001']} />
<mesh geometry={nodes.M_Metal_Rough.geometry} material={materials.metal_rough} />
<mesh geometry={nodes.M_Metal_Shiny.geometry} material={materials.metal_Shiny} />
<mesh geometry={nodes.M_Plastic.geometry} material={materials.metal_rough} />
<mesh geometry={nodes.M_Portal.geometry} material={materials['M_Base.001']} />
<mesh geometry={nodes.M_Screen.geometry} material={materials.Screen} />
<mesh geometry={nodes.M_Speakers.geometry} material={materials.metal_rough} />
<mesh geometry={nodes.M_USB.geometry} material={materials.metal_rough} />
</group>
)
}
const Background = () => {
const { scene } = useThree()
useEffect(() => {
scene.background = new THREE.Color('#555555')
}, [scene])
return null
}
const TextSection = () => {
const textRefs = useRef([])
useEffect(() => {
gsap.fromTo(
textRefs.current,
{ opacity: 0 },
{
opacity: 1,
stagger: 0.1,
scrollTrigger: {
trigger: '#text-trigger',
start: 'top bottom',
end: 'center center',
scrub: 1,
markers: false
}
}
)
}, [])
const texts = ['Ready 5', 'Ready 4', 'Ready 3', 'Ready 2', 'Ready 1']
return (
<div
id="text-trigger"
style={{
height: '100vh',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
top: '500px'
}}>
{texts.map((text, index) => (
<h1 key={index} ref={(el) => (textRefs.current[index] = el)} style={{ opacity: 0 }}>
{text}
</h1>
))}
</div>
)
}
const ThreeScene = () => {
const threeSceneGroup = useRef()
return (
<div id="three-canvas-container" style={{ width: '100vw', height: '500px' }}>
<div>
<h2>header text</h2>
<p>text text text</p>
</div>
<Canvas camera={{ position: [0, 0, 10], fov: 45 }} gl={{ antialias: true, alpha: false }}>
<ambientLight intensity={0.4} />
<directionalLight position={[5, 10, 7.5]} intensity={1} />
<IphoneModel ref={threeSceneGroup} />
<OrbitControls enableZoom={false} />
<Background />
</Canvas>
</div>
)
}
const App = () => (
<div style={{ display: 'flex', flexDirection: 'column', height: '400vh' }}>
<div className="some-content" style={{ height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<h1>ACTION</h1>
</div>
<ThreeScene />
<ThreeScene />
<ThreeScene />
<ThreeScene />
<ThreeScene />
<TextSection />
</div>
)
export default App
This version renders only one phone but maintains working sticky features.
https://codesandbox.io/p/sandbox/r3f-scroll-rig-sticky-box-forked-jns24q
Struggling with fixing the image orientation, this vector2 function came in handy
const imageTexture = new THREE.TextureLoader().load(image)
imageTexture.wrapS = imageTexture.wrapT = THREE.RepeatWrapping
imageTexture.anisotropy = 16
imageTexture.repeat = new THREE.Vector2(1, -1)
https://codesandbox.io/p/sandbox/r3f-scroll-rig-sticky-box-forked-xwt2x6