Skip to content

Commit

Permalink
fix: compute scroll position in effect
Browse files Browse the repository at this point in the history
  • Loading branch information
llorca committed Sep 13, 2020
1 parent 2b2a376 commit 17f822c
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 80 deletions.
159 changes: 84 additions & 75 deletions lib/LayeredImage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import * as React from "react"
import { createRef, useEffect, useMemo, useRef, useState } from "react"

import { applyStyles, clamp, isBrowser, isFunction } from "./utils"
import { applyStyles, clamp, isFunction } from "./utils"

interface Size {
width: number
height: number
}

interface Position {
x: number
y: number
}

enum Interaction {
None = "NONE",
Resize = "RESIZE",
Expand Down Expand Up @@ -50,6 +55,7 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
style,
}) => {
const [size, setSize] = useState<Size>({ width: 0, height: 0 })
const [scrollPosition, setScrollPosition] = useState<Position>({ x: 0, y: 0 })
const [interaction, setInteraction] = useState<Interaction>(Interaction.None)
const [loaded, setLoaded] = useState<number>(0)
const [, setError] = useState<number>(0)
Expand Down Expand Up @@ -125,101 +131,88 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
pageY?: number,
preventDefault = false,
) => {
if (isBrowser) {
const { width, height } = _interaction === Interaction.Resize ? getDimensions() : size
const { width, height } = _interaction === Interaction.Resize ? getDimensions() : size
const { x: scrollLeft, y: scrollTop } = scrollPosition

const bodyScrollTop =
document.body.scrollTop ||
document.documentElement.scrollTop ||
document.scrollingElement.scrollTop ||
window.scrollY ||
window.pageYOffset
const bodyScrollLeft =
document.body.scrollLeft ||
document.documentElement.scrollLeft ||
document.scrollingElement.scrollLeft ||
window.scrollX ||
window.pageXOffset
const containerRect = elementsRef.current.container.current.getBoundingClientRect()
const containerRect = elementsRef.current.container.current.getBoundingClientRect()

const offsetX = (pageX - containerRect.left - bodyScrollLeft) / width
const offsetY = (pageY - containerRect.top - bodyScrollTop) / height
const containerCenterX = pageX - containerRect.left - bodyScrollLeft - width / 2
const containerCenterY = pageY - containerRect.top - bodyScrollTop - height / 2
const offsetX = (pageX - containerRect.left - scrollLeft) / width
const offsetY = (pageY - containerRect.top - scrollTop) / height
const containerCenterX = pageX - containerRect.left - scrollLeft - width / 2
const containerCenterY = pageY - containerRect.top - scrollTop - height / 2

const containerRotationX = ((offsetY - containerCenterY) / (height / 2)) * 8
const containerRotationY = ((containerCenterX - offsetX) / (width / 2)) * 8
const layerTranslationX = (offsetX - containerCenterX) * 0.01
const layerTranslationY = (offsetY - containerCenterY) * 0.01
const lightAngle = (Math.atan2(containerCenterY, containerCenterX) * 180) / Math.PI - 90
const containerRotationX = ((offsetY - containerCenterY) / (height / 2)) * 8
const containerRotationY = ((containerCenterX - offsetX) / (width / 2)) * 8
const layerTranslationX = (offsetX - containerCenterX) * 0.01
const layerTranslationY = (offsetY - containerCenterY) * 0.01
const lightAngle = (Math.atan2(containerCenterY, containerCenterX) * 180) / Math.PI - 90

const computedStyles: ILayeredImageStyles = {
[Interaction.None]: defaultStyles,
[Interaction.Resize]: {
root: {
height: `${height}px`,
transform: `perspective(${width * 3}px)`,
},
const computedStyles: ILayeredImageStyles = {
[Interaction.None]: defaultStyles,
[Interaction.Resize]: {
root: {
height: `${height}px`,
transform: `perspective(${width * 3}px)`,
},
[Interaction.Hover]: {
container: {
transform: `rotateX(${-clamp(containerRotationX, -8, 8)}deg)
},
[Interaction.Hover]: {
container: {
transform: `rotateX(${-clamp(containerRotationX, -8, 8)}deg)
rotateY(${-clamp(containerRotationY, -8, 8)}deg)
translateX(${-layerTranslationX * 5}px)
translateY(${-layerTranslationY * 5}px)
scale(1.1)`,
},
layer: (index: number) => ({
transform: `translateX(${clamp(layerTranslationX, -2, 2) * 1.4 * index}px)
},
layer: (index: number) => ({
transform: `translateX(${clamp(layerTranslationX, -2, 2) * 1.4 * index}px)
translateY(${clamp(layerTranslationY, -2, 2) * 1.4 * index}px)
scale(1.04)`,
}),
light: {
backgroundImage: `linear-gradient(${lightAngle}deg, ${lightColor} 0%, transparent 80%)`,
},
shadow: {
boxShadow: `0 40px 100px ${shadowColor}, 0 10px 20px ${shadowColor}`,
},
}),
light: {
backgroundImage: `linear-gradient(${lightAngle}deg, ${lightColor} 0%, transparent 80%)`,
},
[Interaction.Active]: {
container: {
transitionDuration: "0.075s",
transform: `rotateX(${containerRotationX / 1.4}deg)
shadow: {
boxShadow: `0 40px 100px ${shadowColor}, 0 10px 20px ${shadowColor}`,
},
},
[Interaction.Active]: {
container: {
transitionDuration: "0.075s",
transform: `rotateX(${containerRotationX / 1.4}deg)
rotateY(${containerRotationY / 1.4}deg)
scale(1)`,
},
layer: (index: number) => ({
transform: `translateX(${-layerTranslationX * index}px)
},
layer: (index: number) => ({
transform: `translateX(${-layerTranslationX * index}px)
translateY(${-layerTranslationY * index}px)
scale(1.02)`,
}),
light: {
backgroundImage: `linear-gradient(${lightAngle}deg, ${lightColor} 0%, transparent 80%)`,
},
shadow: {
...defaultStyles.shadow,
transitionDuration: "0.075s",
},
}),
light: {
backgroundImage: `linear-gradient(${lightAngle}deg, ${lightColor} 0%, transparent 80%)`,
},
}[_interaction]
shadow: {
...defaultStyles.shadow,
transitionDuration: "0.075s",
},
},
}[_interaction]

if (preventDefault) {
event.preventDefault()
}
if (preventDefault) {
event.preventDefault()
}

for (const [element, styles] of Object.entries(computedStyles)) {
if (element === "layer") {
layers.forEach((_, index) =>
applyStyles(elementsRef.current.layers[index].current, isFunction(styles) ? styles(index) : styles),
)
} else {
applyStyles(elementsRef.current[element].current, styles)
}
for (const [element, styles] of Object.entries(computedStyles)) {
if (element === "layer") {
layers.forEach((_, index) =>
applyStyles(elementsRef.current.layers[index].current, isFunction(styles) ? styles(index) : styles),
)
} else {
applyStyles(elementsRef.current[element].current, styles)
}

setSize({ width, height })
setInteraction(_interaction)
}

setSize({ width, height })
setInteraction(_interaction)
}

// prettier-ignore
Expand All @@ -238,6 +231,20 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({

useEffect(() => {
const handleWindowResize = () => computeStyles(Interaction.Resize)
const handleWindowScroll = () => {
setScrollPosition({
x:
document.documentElement.scrollLeft ||
document.scrollingElement.scrollLeft ||
window.scrollX ||
window.pageXOffset,
y:
document.documentElement.scrollTop ||
document.scrollingElement.scrollTop ||
window.scrollY ||
window.pageYOffset,
})
}

layers.forEach((layer) => {
const image = new Image()
Expand All @@ -248,9 +255,11 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
})

window.addEventListener("resize", handleWindowResize)
window.addEventListener("scroll", handleWindowScroll)

return () => {
window.removeEventListener("resize", handleWindowResize)
window.removeEventListener("scroll", handleWindowScroll)
}
}, [])

Expand Down
5 changes: 0 additions & 5 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ export const clamp = (value: number, min: number, max: number) => {
return value != null ? Math.min(Math.max(value, min), maximum) : value
}

/**
* Check whether the code is running in the browser.
*/
export const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"

/**
* Return `true` if the value is a `function`.
*/
Expand Down

0 comments on commit 17f822c

Please sign in to comment.