import React , { useCallback , useState , useEffect } from "react" ; import styled from "styled-components" ; import throttle from "lodash.throttle" ; type Position = "beforeSection" | "inSection" | "afterSection" ; interface BackgroundLayerProps { name : string ; bg : string ; first : boolean ; } interface ContainerProps { curSection : Position ; offset : number ; height : number ; curOpacity : number ; bg : string ; first : boolean ; } interface DynamicStyle { style : { position : string ; top : number | string ; bottom : number | string ; zIndex : number ; opacity : number ; } ; } const enablePassive = { passive : true } ; /**
 * calculates the position, opacity, and order of background
 * elements based on where the user's window is
 */ const dynamicStyleCalculator = ( { curSection , curOpacity , offset , first } ) : DynamicStyle => { if ( curSection === "beforeSection" && ! first ) { return { style : { position : "fixed" , top : 0 , bottom : "auto" , zIndex : - 3 , opacity : curOpacity } } ; } else if ( curSection === "inSection" || ( curSection === "beforeSection" && first ) ) { return { style : { position : "absolute" , top : `${ offset }px` , bottom : "auto" , zIndex : - 2 , opacity : curOpacity } } ; } else { return { style : { position : "fixed" , top : "auto" , bottom : 0 , zIndex : - 1 , opacity : curOpacity } } ; } } ; const Container = styled . div . attrs < { style : string ; } > ( dynamicStyleCalculator ) < ContainerProps > `
  width: 100%;
  height: ${ props => props . height }px;
  background: ${ props => props . bg };

  transition: opacity 250ms linear;
` ; const BackgroundLayer = ( { name , bg , first } ) => { const section = document . getElementById ( name ) ; const [ [ offset , height ] , updateDims ] = useState ( section ? [ section . offsetTop , section . offsetHeight ] : [ 0 , 0 ] ) ; const [ curPos , updatePos ] = useState ( "beforeSection" as Position ) ; const [ curOpacity , updateOpacity ] = useState ( 1 ) ; const updateLayerDims = useCallback ( ( ) => updateDims ( section ? [ section . offsetTop , section . offsetHeight ] : [ 0 , 0 ] ) , [ section ] ) ; const updateLayerPos = useCallback ( ( ) => { const layerBottom = offset + height ; const windowTop = window . scrollY ; const windowBottom = windowTop + window . innerHeight ; let newPos = curPos ; if ( windowBottom >= layerBottom ) { newPos = "afterSection" ; } else if ( offset <= windowTop ) { newPos = "inSection" ; } else if ( windowTop < offset ) { newPos = "beforeSection" ; } if ( newPos !== curPos ) updatePos ( newPos ) ; } , [ curPos , height , offset ] ) ; const updateLayerOpacity = useCallback ( throttle ( ( ) => { const layerBottom = offset + height ; const windowBottom = window . scrollY + window . innerHeight ; const eachOpUnit = 1 / window . innerHeight ; const opPercent = windowBottom - layerBottom ; const newOp = Math . min ( 1 , Math . max ( 0 , 1 - opPercent * eachOpUnit ) ) ; if ( newOp !== curOpacity ) updateOpacity ( newOp ) ; } , 100 ) , [ curOpacity , height , offset ] ) ; useEffect ( updateLayerDims , [ updateLayerDims ] ) ; useEffect ( ( ) => { updateLayerPos ( ) ; updateLayerOpacity ( ) ; window . addEventListener ( "scroll" , updateLayerOpacity , enablePassive ) ; window . addEventListener ( "scroll" , updateLayerPos , enablePassive ) ; window . addEventListener ( "resize" , updateLayerOpacity , enablePassive ) ; window . addEventListener ( "resize" , updateLayerPos , enablePassive ) ; window . addEventListener ( "resize" , updateLayerDims , enablePassive ) ; return ( ) => { window . removeEventListener ( "scroll" , updateLayerPos ) ; window . removeEventListener ( "scroll" , updateLayerOpacity ) ; window . removeEventListener ( "resize" , updateLayerPos ) ; window . removeEventListener ( "resize" , updateLayerOpacity ) ; window . removeEventListener ( "resize" , updateLayerDims ) ; } ; } , [ updateLayerPos , updateLayerOpacity , updateLayerDims ] ) ; return ( < Container curSection = { curPos } offset = { offset } height = { height } curOpacity = { curOpacity } bg = { bg } first = { first } / > ) ; } ; export default BackgroundLayer ;