Parallax Background in React

#This Website#Projects#JavaScript#React#TypeScript

Creating a Parallax Background in React

Libraries for Parallax

For the first version of this website, I wanted to create a space-themed parallax background. After a quick google search, I found some libraries I would be able to use in react:

While these are great libraries for creating parallax effects, I found that none of them really suited my purposes:
  • I wanted to create a parallax to use as a background, with different layers that move at different speeds to simulate 3D-space. But it seemed like spring-parallax was better suited for creating parallax effects in banners or for moving objects.
  • To make the 3d-effect of my background more realistic, I wanted the front layers to have bigger objects than the ones in the back. Because I also wanted to reuse the same image for all layers, it should be possible to specify the size of the image. The image would also need to be repeated over the whole page so you can scroll way down to the bottom of the page and still see a background. Since these things are annoying to to with <img />-tags, using divs and setting the background-image is better. As far as my efforts went, that was not possible with the react-scroll-parallax-library.
  • Therefore, the react-parallax library was best suited for my use case. When I tried to use it, it worked well in itself but caused problems with my existing website (i could not get multiple parallax-layers to keep the height of a long page, using the table of contents on my blogpost pages stopped working, glitches etc.) At this point, I was so annoyed I decided to quickly write it myself since it is not difficult at all, actually.

Parallax Code

The first well-working version looked like this:import React, { useEffect, useRef, useState } from 'react'; export function WithParallax({ children }) { const scroll = useScroll(); const content = useRef(); const h = useElementHeight(content) const height = Math.max(h ? h : 0, window.innerHeight) const w = useElementWidth(content) const width = w && w > 0 ? w : 80 return <div> <Layer speed={0.5} backgroundSize='50%' backgroundColor='#292d3e' url={require('./sterne.png')} /> <Layer speed={0.4} backgroundSize='70%' url={require('./sterne.png')} /> <Layer speed={0.3} backgroundSize='100%' url={require('./sterne.png')}/> <div id="parallaxcontent" ref={content}> {children} </div> </div> function Layer({ speed, backgroundSize = "100%", backgroundColor = "transparent", url=require('./sterne.png') }) { return <div style={{ backgroundColor: backgroundColor, position: "absolute", top: 0, left: 0, overflow: "hidden", zIndex: -20, height: height, width: width }}> <div style={{ height: "2000vh", backgroundImage: 'url(' + url + ')', backgroundSize: backgroundSize, filter: "brightness(100%)", translate: "0px " + (scroll * speed) + "px", }} /> </div>; } } // react hook for getting scroll position on page function useScroll() { const [scrollPosition, setScrollPosition] = useState(0); const handleScroll = () => { const position = window.pageYOffset; setScrollPosition(position); }; useEffect(() => { window.addEventListener('scroll', handleScroll, { passive: true }); return () => { window.removeEventListener('scroll', handleScroll); }; }, []); return scrollPosition; } // Helper functions to get the height and width of the element the parallax is supposed to wrap ( =the whole page) function useElementHeight(ref) { return useElementProp(ref, (el) => el.scrollHeight) } function useElementWidth(ref) { return useElementProp(ref, (el) => el.scrollWidth) } function useElementProp(ref, fn) { const [height, setHeight] = useState(undefined); useEffect(() => { const resizeObserver = new ResizeObserver(entries => { setHeight(fn(ref.current)); }); var toObserve = ref.current resizeObserver.observe(toObserve); return () => { resizeObserver.disconnect(); }; }, []); return height; }

Comments

Feel free to leave your opinion or questions in the comment section below.