From 17f822c97e86727185a9cf959514f257a557fc64 Mon Sep 17 00:00:00 2001 From: Antoine Llorca Date: Sat, 12 Sep 2020 18:57:37 -0700 Subject: [PATCH] fix: compute scroll position in effect --- lib/LayeredImage.tsx | 159 +++++++++++++++++++++++-------------------- lib/utils.ts | 5 -- 2 files changed, 84 insertions(+), 80 deletions(-) diff --git a/lib/LayeredImage.tsx b/lib/LayeredImage.tsx index 3f90476..d530397 100644 --- a/lib/LayeredImage.tsx +++ b/lib/LayeredImage.tsx @@ -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", @@ -50,6 +55,7 @@ export const LayeredImage: React.FC = ({ style, }) => { const [size, setSize] = useState({ width: 0, height: 0 }) + const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 }) const [interaction, setInteraction] = useState(Interaction.None) const [loaded, setLoaded] = useState(0) const [, setError] = useState(0) @@ -125,101 +131,88 @@ export const LayeredImage: React.FC = ({ 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 @@ -238,6 +231,20 @@ export const LayeredImage: React.FC = ({ 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() @@ -248,9 +255,11 @@ export const LayeredImage: React.FC = ({ }) window.addEventListener("resize", handleWindowResize) + window.addEventListener("scroll", handleWindowScroll) return () => { window.removeEventListener("resize", handleWindowResize) + window.removeEventListener("scroll", handleWindowScroll) } }, []) diff --git a/lib/utils.ts b/lib/utils.ts index 7646753..0be98d4 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -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`. */