1- import * as THREE from 'three'
21import { onCleanup , onMount } from 'solid-js'
3- import vertexShader from '../../shaders/vertex.glsl?raw'
4- import fragmentShader from '../../shaders/fragment.glsl?raw'
5-
6- /** Clamp value between min and max (GSAP.utils.clamp equivalent). */
7- function clamp ( min : number , max : number , value : number ) : number {
8- return Math . min ( max , Math . max ( min , value ) )
9- }
10-
11- /** Linear interpolation (GSAP.utils.interpolate equivalent). */
12- function lerp ( current : number , target : number , ease : number ) : number {
13- return current + ( target - current ) * ease
14- }
2+ import {
3+ applyScrollDomTransforms ,
4+ attachCanvasToBody ,
5+ getScrollStageElements ,
6+ initializeLineElement ,
7+ removeCanvasFromBody ,
8+ } from './scroll-stage-dom'
9+ import { createScrollState , type Viewport } from './scroll-stage-scroll'
10+ import { createUniformAnimator } from './scroll-stage-uniforms'
11+ import { createWebglStage } from './scroll-stage-webgl'
12+ import { EASE , ROTATION_SPEED } from './scroll-stage-settings'
1513
1614export interface ScrollStageProps {
1715 /** Ref to the .content element that contains .scroll__content and .layout__line */
1816 contentRef : ( ) => HTMLElement | undefined
1917}
2018
21- const EASE = 0.05
22- const SOFT_THRESHOLD = 0.01
23- const CAMERA_FOV = 75
24- const CAMERA_NEAR = 0.1
25- const CAMERA_FAR = 10
26- const CAMERA_Z = 2.5
27- const ICOSAHEDRON_DETAIL = 64
28- const ROTATION_SPEED = 0.05
29- const MOBILE_SCALE = 0.75
30- const PIXEL_RATIO_MAX = 1.5
31- const EASE_MULTIPLIER = 2
32-
33- const SETTINGS = {
34- uAmplitude : { end : 4 , start : 4 } ,
35- uDeepPurple : { end : 0 , start : 1 } ,
36- uDensity : { end : 1 , start : 1 } ,
37- uFrequency : { end : 4 , start : 0 } ,
38- uOpacity : { end : 0.66 , start : 0.1 } ,
39- uStrength : { end : 1.1 , start : 0 } ,
40- } as const
41-
4219export function ScrollStage ( props : ScrollStageProps ) {
4320 onMount ( ( ) => {
4421 const contentElement = props . contentRef ( )
@@ -47,140 +24,55 @@ export function ScrollStage(props: ScrollStageProps) {
4724 return
4825 }
4926
50- const scrollContent = contentElement . querySelector < HTMLElement > ( '.scroll__content' )
51- const lineElement_ = contentElement . querySelector < HTMLElement > ( '.layout__line' )
27+ const elements = getScrollStageElements ( contentElement )
5228
53- if ( ! scrollContent || ! lineElement_ ) {
29+ if ( ! elements ) {
5430 return
5531 }
56- const scrollContentElement = scrollContent
57- const lineElement = lineElement_
58-
59- let viewport = { height : window . innerHeight , width : window . innerWidth }
60-
61- const scroll = {
62- hard : 0 ,
63- height : 0 ,
64- limit : 0 ,
65- normalized : 0 ,
66- running : false ,
67- soft : 0 ,
68- }
69-
70- const currentUniforms : Record < string , number > = {
71- uAmplitude : SETTINGS . uAmplitude . start ,
72- uDeepPurple : SETTINGS . uDeepPurple . start ,
73- uDensity : SETTINGS . uDensity . start ,
74- uFrequency : SETTINGS . uFrequency . start ,
75- uOpacity : SETTINGS . uOpacity . start ,
76- uStrength : SETTINGS . uStrength . start ,
77- }
7832
79- function setSizes ( ) {
80- scroll . height = scrollContentElement . getBoundingClientRect ( ) . height
81- scroll . limit = scrollContentElement . clientHeight - viewport . height
82- document . body . style . height = `${ scroll . height } px`
83- }
84-
85- const scene = new THREE . Scene ( )
86-
87- scene . background = new THREE . Color ( 0x00_00_00 )
88- const renderer = new THREE . WebGLRenderer ( { alpha : true , antialias : true } )
89- const canvas = renderer . domElement
90-
91- canvas . classList . add ( 'webgl' )
92- canvas . style . cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;z-index:0;pointer-events:none'
93- document . body . insertBefore ( canvas , document . body . firstChild )
94-
95- const camera = new THREE . PerspectiveCamera ( CAMERA_FOV , viewport . width / viewport . height , CAMERA_NEAR , CAMERA_FAR )
96-
97- camera . position . set ( 0 , 0 , CAMERA_Z )
98- scene . add ( camera )
99-
100- const geometry = new THREE . IcosahedronGeometry ( 1 , ICOSAHEDRON_DETAIL )
101-
102- const material = new THREE . ShaderMaterial ( {
103- blending : THREE . AdditiveBlending ,
104- fragmentShader,
105- transparent : true ,
106- uniforms : {
107- uAmplitude : { value : SETTINGS . uAmplitude . start } ,
108- uDeepPurple : { value : SETTINGS . uDeepPurple . start } ,
109- uDensity : { value : SETTINGS . uDensity . start } ,
110- uFrequency : { value : SETTINGS . uFrequency . start } ,
111- uOpacity : { value : SETTINGS . uOpacity . start } ,
112- uStrength : { value : SETTINGS . uStrength . start } ,
113- } ,
114- vertexShader,
115- wireframe : true ,
116- } )
117- const mesh = new THREE . Mesh ( geometry , material )
118-
119- scene . add ( mesh )
33+ initializeLineElement ( elements . lineElement )
12034
121- const clock = new THREE . Clock ( )
122- let rafId : number
35+ let viewport : Viewport = { height : window . innerHeight , width : window . innerWidth }
36+ const scrollState = createScrollState ( elements . scrollContentElement , { ease : EASE } )
12337
124- function updateScrollValues ( ) {
125- scroll . hard = clamp ( 0 , scroll . limit , window . scrollY )
126- scroll . soft = lerp ( scroll . soft , scroll . hard , EASE )
38+ scrollState . setViewport ( viewport )
12739
128- if ( scroll . soft < SOFT_THRESHOLD ) {
129- scroll . soft = 0
130- }
40+ const stage = createWebglStage ( viewport )
13141
132- scroll . normalized = scroll . limit > 0 ? scroll . soft / scroll . limit : 0
133- scrollContentElement . style . transform = `translateY(${ - scroll . soft } px)`
134- mesh . rotation . x = scroll . normalized * Math . PI
135- lineElement . style . transform = `scaleX(${ scroll . normalized } )`
136- lineElement . style . transformOrigin = 'left'
42+ attachCanvasToBody ( stage . canvas )
13743
138- for ( const key of Object . keys ( SETTINGS ) as ( keyof typeof SETTINGS ) [ ] ) {
139- const setting = SETTINGS [ key ]
44+ const uniformAnimator = createUniformAnimator ( stage . material , EASE )
45+ let rafId : number
14046
141- const target = setting . start + scroll . normalized * ( setting . end - setting . start )
47+ const updateScrollValues = ( ) => {
48+ scrollState . updatePosition ( window . scrollY )
49+ const { metrics} = scrollState . state
14250
143- currentUniforms [ key ] = lerp ( currentUniforms [ key ] , target , EASE * EASE_MULTIPLIER )
144- ; ( material . uniforms [ key ] as THREE . IUniform < number > ) . value = currentUniforms [ key ]
145- }
51+ applyScrollDomTransforms ( elements , metrics . soft , metrics . normalized )
52+ stage . mesh . rotation . x = metrics . normalized * Math . PI
53+ uniformAnimator . update ( metrics . normalized )
14654 }
14755
148- function update ( ) {
149- const elapsed = clock . getElapsedTime ( )
56+ const update = ( ) => {
57+ const elapsed = stage . clock . getElapsedTime ( )
15058
151- mesh . rotation . y = elapsed * ROTATION_SPEED
59+ stage . mesh . rotation . y = elapsed * ROTATION_SPEED
15260 updateScrollValues ( )
153- renderer . render ( scene , camera )
61+ stage . render ( )
15462 rafId = requestAnimationFrame ( update )
15563 }
15664
157- function onResize ( ) {
65+ const onResize = ( ) => {
15866 viewport = { height : window . innerHeight , width : window . innerWidth }
159- setSizes ( )
160-
161- if ( viewport . width < viewport . height ) {
162- mesh . scale . set ( MOBILE_SCALE , MOBILE_SCALE , MOBILE_SCALE )
163- } else {
164- mesh . scale . set ( 1 , 1 , 1 )
165- }
166-
167- camera . aspect = viewport . width / viewport . height
168- camera . updateProjectionMatrix ( )
169- renderer . setSize ( viewport . width , viewport . height )
170- renderer . setPixelRatio ( Math . min ( window . devicePixelRatio , PIXEL_RATIO_MAX ) )
67+ scrollState . setViewport ( viewport )
68+ scrollState . updateSizes ( )
69+ stage . updateScale ( viewport )
70+ stage . resize ( viewport )
17171 }
17272
173- function onScroll ( ) {
174- if ( ! scroll . running ) {
175- scroll . running = true
176-
177- requestAnimationFrame ( ( ) => {
178- scroll . running = false
179- } )
180- }
181- }
73+ const { onScroll} = scrollState
18274
183- setSizes ( )
75+ scrollState . updateSizes ( )
18476 onResize ( )
18577 window . addEventListener ( 'scroll' , onScroll )
18678 window . addEventListener ( 'resize' , onResize )
@@ -190,14 +82,8 @@ export function ScrollStage(props: ScrollStageProps) {
19082 cancelAnimationFrame ( rafId )
19183 window . removeEventListener ( 'scroll' , onScroll )
19284 window . removeEventListener ( 'resize' , onResize )
193- geometry . dispose ( )
194- material . dispose ( )
195- renderer . forceContextLoss ( )
196- renderer . dispose ( )
197-
198- if ( canvas . parentNode ) {
199- canvas . remove ( )
200- }
85+ stage . dispose ( )
86+ removeCanvasFromBody ( stage . canvas )
20187 } )
20288 } )
20389
0 commit comments