How to switch image on scroll in React (like on Apple website)?

Viewed 3075

I try to switch from an image to another one when scrolling with React but I don't know how to do this... maybe with React hook. I know it's possible with jQuery but I don't want to use it.

Example here: https://www.apple.com/iphone-12/, look at the "Five fresh finishes".

Apple

import React, {useState} from 'react';
import './BackgroundDiscover.css';
import logoOrdiW from "../../images/mathis_computer_white.jpg";
import logoOrdiB from "../../images/mathis_computer_black.jpg";


const BackgroundDiscover = () => {
    const [imageSrc, SetSrc] = useState(logoOrdiW);
    const switchBrackground = () => {
        SetSrc(logoOrdiB);
    }
    return (
        <>
            <div className="container-discover">
                <div className='container-logo-white'>
                    <img src={imageSrc} alt="logo ordinateur mathis blanc" onScroll={switchBrackground}/>
                </div>
            </div>
        </>
    );
};



export default BackgroundDiscover;
.container-discover {
    display: flex;
    flex-direction: column;
}

.container-logo-white img {
    width: 100%;
}

.container-logo-black img {
    width: 100%;
}
3 Answers

There are plenty of solutions for this but basically you can set in this way. Wheel event listener turns either positive or negative value so you should create an algorithm if you want to impelement different designs.

import React, { useState } from "react";
export default function App() {
  const [bgImage, handleImage] = useState(
    "https://media.wired.com/photos/5e9f56f143e5800008514457/1:1/w_1277,h_1277,c_limit/Gear-Feature-Apple_new-iphone-se-white_04152020.jpg"
  );
  window.addEventListener("wheel", (e) => {
    if (e.deltaY > 0) {
      handleImage(
        "https://media.wired.com/photos/5bcea2642eea7906bba84c67/master/w_2560%2Cc_limit/iphonexr.jpg"
      );
    } else {
      handleImage(
        "https://media.wired.com/photos/5e9f56f143e5800008514457/1:1/w_1277,h_1277,c_limit/Gear-Feature-Apple_new-iphone-se-white_04152020.jpg"
      );
    }
  });

  return (
    <div className="App">
      <img
        style={{ width: "400px", height: "300px" }}
        id="myimage"
        src={bgImage}
        alt=""
      />
    </div>
  );
}
   

And as a result you can use bgImage state in your image src.

This is a codesandbox example https://codesandbox.io/s/pensive-vaughan-cfc63?fontsize=14&hidenavigation=1&theme=dark

You don't need jQuery or something like this. You could subscribe to scroll event in useEffect() hook with addEventListener and check scrollTop of a scrolling container.

If you want to do the same effect as the Apple website has, you could add some magic with position: sticky, height in vh units and checking window.innerHeight.

Note that this code is simplified, not optimized, and only needed to understand the idea:

CodeSandbox

index.js:

import { useLayoutEffect, useState } from "react";
import { render } from "react-dom";
import classnames from "classnames";
import "./index.css";

const images = [0, 1, 2, 3, 4];

const App = () => {
  const [visibleImagesMap, setVisibleImagesMap] = useState(
    images.reduce((map, image) => {
      map[image] = false;
      return map;
    }, {})
  );

  useLayoutEffect(() => {
    const handleScroll = () => {
      const scrollTop = document.documentElement.scrollTop;
      const viewportHeight = window.innerHeight;

      const newVisibleImagesMap = images.reduce((map, image) => {
        map[image] = scrollTop >= image * viewportHeight;
        return map;
      }, {});

      setVisibleImagesMap(newVisibleImagesMap);
    };

    window.addEventListener("scroll", handleScroll);
    handleScroll();

    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return (
    <div className="app">
      <div className="sticky">
        <div className="frame">
          {images.map((image) => (
            <div
              className={classnames("image", `image_${image}`, {
                image_visible: visibleImagesMap[image]
              })}
              key={image}
            />
          ))}
        </div>
      </div>
    </div>
  );
};

render(<App />, document.getElementById("root"));

index.css:

body {
  margin: 0;
}

.app {
  height: 500vh;
}

.sticky {
  position: sticky;
  top: 0;
  height: 100vh;
}

.frame {
  z-index: 1;
  position: relative;
  height: 100%;
  width: 100%;
}

.image {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  background-repeat: no-repeat;
  background-size: cover;
}

.image_0 {
  z-index: 0;
  background-image: url("./images/0.jpeg");
}

.image_1 {
  z-index: 1;
  background-image: url("./images/1.jpeg");
}

.image_2 {
  z-index: 2;
  background-image: url("./images/2.jpeg");
}

.image_3 {
  z-index: 3;
  background-image: url("./images/3.jpeg");
}

.image_4 {
  z-index: 4;
  background-image: url("./images/4.jpeg");
}

.image_visible {
  opacity: 1;
}

You can try useEffect and useRef.

useEffect is sideload functions, and useRef is for real dom referances.

import React, { useRef, useState, useEffect } from 'react';

import logoOrdiW from "../../images/mathis_computer_white.jpg";
import logoOrdiB from "../../images/mathis_computer_black.jpg";

import './BackgroundDiscover.css';

const BackgroundDiscover = () => {
  const img = useRef();
  const [src, setSrc] = useState(logoOrdiW);
  const switchBrackground = () => {
    setSrc(logoOrdiB);
  }
  useEffect(() => {
    img.current.onscroll = switchBrackground;
  }, []);

  return (
    <div className="container-discover">
      <div className='container-logo-white'>
        <img src={src} alt="logo ordinateur mathis blanc" ref={img} />
      </div>
    </div>
  );
};

export default BackgroundDiscover;
Related