11import * as React from "react"
22import { createRef , useEffect , useMemo , useRef , useState } from "react"
33
4- import { applyStyles , clamp , isBrowser , isFunction } from "./utils"
4+ import { applyStyles , clamp , isFunction } from "./utils"
55
66interface Size {
77 width : number
88 height : number
99}
1010
11+ interface Position {
12+ x : number
13+ y : number
14+ }
15+
1116enum Interaction {
1217 None = "NONE" ,
1318 Resize = "RESIZE" ,
@@ -50,6 +55,7 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
5055 style,
5156} ) => {
5257 const [ size , setSize ] = useState < Size > ( { width : 0 , height : 0 } )
58+ const [ scrollPosition , setScrollPosition ] = useState < Position > ( { x : 0 , y : 0 } )
5359 const [ interaction , setInteraction ] = useState < Interaction > ( Interaction . None )
5460 const [ loaded , setLoaded ] = useState < number > ( 0 )
5561 const [ , setError ] = useState < number > ( 0 )
@@ -125,101 +131,88 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
125131 pageY ?: number ,
126132 preventDefault = false ,
127133 ) => {
128- if ( isBrowser ) {
129- const { width , height } = _interaction === Interaction . Resize ? getDimensions ( ) : size
134+ const { width , height } = _interaction === Interaction . Resize ? getDimensions ( ) : size
135+ const { x : scrollLeft , y : scrollTop } = scrollPosition
130136
131- const bodyScrollTop =
132- document . body . scrollTop ||
133- document . documentElement . scrollTop ||
134- document . scrollingElement . scrollTop ||
135- window . scrollY ||
136- window . pageYOffset
137- const bodyScrollLeft =
138- document . body . scrollLeft ||
139- document . documentElement . scrollLeft ||
140- document . scrollingElement . scrollLeft ||
141- window . scrollX ||
142- window . pageXOffset
143- const containerRect = elementsRef . current . container . current . getBoundingClientRect ( )
137+ const containerRect = elementsRef . current . container . current . getBoundingClientRect ( )
144138
145- const offsetX = ( pageX - containerRect . left - bodyScrollLeft ) / width
146- const offsetY = ( pageY - containerRect . top - bodyScrollTop ) / height
147- const containerCenterX = pageX - containerRect . left - bodyScrollLeft - width / 2
148- const containerCenterY = pageY - containerRect . top - bodyScrollTop - height / 2
139+ const offsetX = ( pageX - containerRect . left - scrollLeft ) / width
140+ const offsetY = ( pageY - containerRect . top - scrollTop ) / height
141+ const containerCenterX = pageX - containerRect . left - scrollLeft - width / 2
142+ const containerCenterY = pageY - containerRect . top - scrollTop - height / 2
149143
150- const containerRotationX = ( ( offsetY - containerCenterY ) / ( height / 2 ) ) * 8
151- const containerRotationY = ( ( containerCenterX - offsetX ) / ( width / 2 ) ) * 8
152- const layerTranslationX = ( offsetX - containerCenterX ) * 0.01
153- const layerTranslationY = ( offsetY - containerCenterY ) * 0.01
154- const lightAngle = ( Math . atan2 ( containerCenterY , containerCenterX ) * 180 ) / Math . PI - 90
144+ const containerRotationX = ( ( offsetY - containerCenterY ) / ( height / 2 ) ) * 8
145+ const containerRotationY = ( ( containerCenterX - offsetX ) / ( width / 2 ) ) * 8
146+ const layerTranslationX = ( offsetX - containerCenterX ) * 0.01
147+ const layerTranslationY = ( offsetY - containerCenterY ) * 0.01
148+ const lightAngle = ( Math . atan2 ( containerCenterY , containerCenterX ) * 180 ) / Math . PI - 90
155149
156- const computedStyles : ILayeredImageStyles = {
157- [ Interaction . None ] : defaultStyles ,
158- [ Interaction . Resize ] : {
159- root : {
160- height : `${ height } px` ,
161- transform : `perspective(${ width * 3 } px)` ,
162- } ,
150+ const computedStyles : ILayeredImageStyles = {
151+ [ Interaction . None ] : defaultStyles ,
152+ [ Interaction . Resize ] : {
153+ root : {
154+ height : `${ height } px` ,
155+ transform : `perspective(${ width * 3 } px)` ,
163156 } ,
164- [ Interaction . Hover ] : {
165- container : {
166- transform : `rotateX(${ - clamp ( containerRotationX , - 8 , 8 ) } deg)
157+ } ,
158+ [ Interaction . Hover ] : {
159+ container : {
160+ transform : `rotateX(${ - clamp ( containerRotationX , - 8 , 8 ) } deg)
167161 rotateY(${ - clamp ( containerRotationY , - 8 , 8 ) } deg)
168162 translateX(${ - layerTranslationX * 5 } px)
169163 translateY(${ - layerTranslationY * 5 } px)
170164 scale(1.1)` ,
171- } ,
172- layer : ( index : number ) => ( {
173- transform : `translateX(${ clamp ( layerTranslationX , - 2 , 2 ) * 1.4 * index } px)
165+ } ,
166+ layer : ( index : number ) => ( {
167+ transform : `translateX(${ clamp ( layerTranslationX , - 2 , 2 ) * 1.4 * index } px)
174168 translateY(${ clamp ( layerTranslationY , - 2 , 2 ) * 1.4 * index } px)
175169 scale(1.04)` ,
176- } ) ,
177- light : {
178- backgroundImage : `linear-gradient(${ lightAngle } deg, ${ lightColor } 0%, transparent 80%)` ,
179- } ,
180- shadow : {
181- boxShadow : `0 40px 100px ${ shadowColor } , 0 10px 20px ${ shadowColor } ` ,
182- } ,
170+ } ) ,
171+ light : {
172+ backgroundImage : `linear-gradient(${ lightAngle } deg, ${ lightColor } 0%, transparent 80%)` ,
183173 } ,
184- [ Interaction . Active ] : {
185- container : {
186- transitionDuration : "0.075s" ,
187- transform : `rotateX(${ containerRotationX / 1.4 } deg)
174+ shadow : {
175+ boxShadow : `0 40px 100px ${ shadowColor } , 0 10px 20px ${ shadowColor } ` ,
176+ } ,
177+ } ,
178+ [ Interaction . Active ] : {
179+ container : {
180+ transitionDuration : "0.075s" ,
181+ transform : `rotateX(${ containerRotationX / 1.4 } deg)
188182 rotateY(${ containerRotationY / 1.4 } deg)
189183 scale(1)` ,
190- } ,
191- layer : ( index : number ) => ( {
192- transform : `translateX(${ - layerTranslationX * index } px)
184+ } ,
185+ layer : ( index : number ) => ( {
186+ transform : `translateX(${ - layerTranslationX * index } px)
193187 translateY(${ - layerTranslationY * index } px)
194188 scale(1.02)` ,
195- } ) ,
196- light : {
197- backgroundImage : `linear-gradient(${ lightAngle } deg, ${ lightColor } 0%, transparent 80%)` ,
198- } ,
199- shadow : {
200- ...defaultStyles . shadow ,
201- transitionDuration : "0.075s" ,
202- } ,
189+ } ) ,
190+ light : {
191+ backgroundImage : `linear-gradient(${ lightAngle } deg, ${ lightColor } 0%, transparent 80%)` ,
203192 } ,
204- } [ _interaction ]
193+ shadow : {
194+ ...defaultStyles . shadow ,
195+ transitionDuration : "0.075s" ,
196+ } ,
197+ } ,
198+ } [ _interaction ]
205199
206- if ( preventDefault ) {
207- event . preventDefault ( )
208- }
200+ if ( preventDefault ) {
201+ event . preventDefault ( )
202+ }
209203
210- for ( const [ element , styles ] of Object . entries ( computedStyles ) ) {
211- if ( element === "layer" ) {
212- layers . forEach ( ( _ , index ) =>
213- applyStyles ( elementsRef . current . layers [ index ] . current , isFunction ( styles ) ? styles ( index ) : styles ) ,
214- )
215- } else {
216- applyStyles ( elementsRef . current [ element ] . current , styles )
217- }
204+ for ( const [ element , styles ] of Object . entries ( computedStyles ) ) {
205+ if ( element === "layer" ) {
206+ layers . forEach ( ( _ , index ) =>
207+ applyStyles ( elementsRef . current . layers [ index ] . current , isFunction ( styles ) ? styles ( index ) : styles ) ,
208+ )
209+ } else {
210+ applyStyles ( elementsRef . current [ element ] . current , styles )
218211 }
219-
220- setSize ( { width, height } )
221- setInteraction ( _interaction )
222212 }
213+
214+ setSize ( { width, height } )
215+ setInteraction ( _interaction )
223216 }
224217
225218 // prettier-ignore
@@ -238,6 +231,20 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
238231
239232 useEffect ( ( ) => {
240233 const handleWindowResize = ( ) => computeStyles ( Interaction . Resize )
234+ const handleWindowScroll = ( ) => {
235+ setScrollPosition ( {
236+ x :
237+ document . documentElement . scrollLeft ||
238+ document . scrollingElement . scrollLeft ||
239+ window . scrollX ||
240+ window . pageXOffset ,
241+ y :
242+ document . documentElement . scrollTop ||
243+ document . scrollingElement . scrollTop ||
244+ window . scrollY ||
245+ window . pageYOffset ,
246+ } )
247+ }
241248
242249 layers . forEach ( ( layer ) => {
243250 const image = new Image ( )
@@ -248,9 +255,11 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
248255 } )
249256
250257 window . addEventListener ( "resize" , handleWindowResize )
258+ window . addEventListener ( "scroll" , handleWindowScroll )
251259
252260 return ( ) => {
253261 window . removeEventListener ( "resize" , handleWindowResize )
262+ window . removeEventListener ( "scroll" , handleWindowScroll )
254263 }
255264 } , [ ] )
256265
0 commit comments