Vivian
Vivian

Reputation: 361

How to Lazy load the background image inside the inline style property (React)?

I'm currently working on my first React app, and I'm facing something tough (for me).

<div
    className="container-fluid"
    id="cityDetail"
    style={{
        backgroundImage: `url(${camera.lastPhoto})`,
        backgroundRepeat: "no-repeat",
        backgroundPosition: "center",
        backgroundSize: "cover",
        height: "100vh"
    }}
>

I would like to lazy load the backgroundImage.

I haven't yet found good components/utilities for doing this. Does anyone have any ideas?

Upvotes: 18

Views: 22415

Answers (7)

Tristanisginger
Tristanisginger

Reputation: 2683

Here is an example using GSAP to set the background image inline using a scroll trigger.

   <LazyLoadBgImg src="http://placekeanu.com/540" className="some-css-class">
        <h1>Tristan is the best</h1>
    </LazyLoadBgImg>

The component:

import React,  { useEffect, useRef, useState } from "react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
    
const LazyLoadBgImg = ({src, className, children}) => {

    const lazyLoadRef = useRef();
    const [loadImage, setLoadImage] = useState(false);

    useEffect(() => {
        ScrollTrigger.create({
            trigger: lazyLoadRef.current,
            start: "top bottom",
            onEnter: () => setLoadImage(true), 
        });      
    }, []);

    return (
        <div ref={lazyLoadRef} className={className} style={{backgroundImage: (loadImage ? "url(" + src  + ")" : '')}}>
            {children}
        </div>
    )
}

export default LazyLoadBgImg;

Upvotes: 0

KaraKunT
KaraKunT

Reputation: 1

import { LazyLoadComponent } from 'react-lazy-load-image-component';

...

<LazyLoadComponent id={key_id}>
   <div style={{ backgroundImage: `url(${src})` }} />
</LazyLoadComponent>

Upvotes: 0

Ali Alavizadeh
Ali Alavizadeh

Reputation: 143

For lazy loading images there is Compoent for it but i am not sure will it work for background images or not. anyway react-lazy-load-image-componen which you can install with yarn or npm like below:

npm i --save react-lazy-load-image-component

or

yarn add react-lazy-load-image-component

then you need to import it in your react app:

import React from 'react';
import { LazyLoadImage } from 'react-lazy-load-image-component';
 
const MyImage = ({ image }) => (
  <div>
    <LazyLoadImage
      alt={image.alt}
      height={image.height}
      src={image.src} // use normal <img> attributes as props
      width={image.width} />
    <span>{image.caption}</span>
  </div>
);
 
export default MyImage;

and for props into it you can see this link: https://www.npmjs.com/package/react-lazy-load-image-component

Upvotes: 0

Overclocked Skid
Overclocked Skid

Reputation: 624

In order to defer loading background-image, you will need to create a stylesheet for the CSS properties that load any file with url, since you don't want those images to delay the first contentful paint. For instance:

FirstModal.module.less

This file has the vital CSS properties that will be loaded first...

.styles {
    &-container {
        position: absolute;
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        align-content: center;
        justify-content: center;
        align-items: center;
    }
}

firstModalBackground.module.less

This file will load after the critical CSS...

.styles {
    &-container {
        background: url('../../images/code/code.jpg') no-repeat center center fixed;
        background-size: cover;
    }
}

For demonstration purposes, I will use React.Component here, but, if you want to try to optimize things, you can also use React.PureComponent (I tested it and everything worked fine).

firstModal.jsx

const classNames = require('classnames');
const React = require('react)';
const {stylesContainer} = require('./firstModal.module.less');

class FirstModal extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            classNames: classNames([stylesContainer])
        };
    }

    async componentDidMount() {
        const backgroundImage = await import(
            './firstModalBackground.module.less'
        );
        this.setState({
            classNames: [
                classNames(this.state.classNames, [backgroundImage.stylesContainer]),
            ]
        });
    }

    render() {
        // console.log(this.state.classNames);
        return <div className={this.state.classNames}>It werks!</div>;
    }
}

module.exports = FirstModal;

You could even take a step further, if you have a low resolution image that loads faster, you can have a "three-step background-image loading", for instance, on componentDidMount:

    async componentDidMount() {
        const lowResolutionBackgroundImage = await import(
            '../greeting-page/firstModalLowResBackground.module.less'
        );
        const baseClass = this.state.classNames;
        this.setState({
            classNames: [
                classNames(baseclass,
                    lowResolutionBackgroundImage.stylesContainer),
                ]
            });
        const backgroundImage = await import(
            './firstModalBackground.module.less'
        );
        this.setState({
            classNames: [
                classNames(baseClass,
                    backgroundImage.stylesContainer),
            ]
        });
    }

Upvotes: 0

Reza Mazarlou
Reza Mazarlou

Reputation: 3156

With Functional Component Like This :

function LazyBackground({src}) {
    const [source, setSource] = useState("preload.jpg")

    useEffect(() => {
        const img = new Image()
        img.src = src
        img.onload = () => setSource(src)
    }, [src])

    return(
        <div style={{backgroundImage: `url(${source})`}}></div>
    )
}

Usage :

<LazyBackground src={"https://example.com/your_image.jpg"}/>

Upvotes: 0

Jesper
Jesper

Reputation: 2224

Here's a custom React hook based on Naoise's answer:

const useProgressiveImage = src => {  
  const [sourceLoaded, setSourceLoaded] = useState(null)

  useEffect(() => {
    const img = new Image()
    img.src = src
    img.onload = () => setSourceLoaded(src)
  }, [src])

  return sourceLoaded 
}

Usage:

const Component = (source, placeholder) => {
  const loaded = useProgressiveImage(source)

  return (
    <div style={{ backgroundImage: `url(${loaded || placeholder})` }} />
  )
}

Upvotes: 39

Naoise Golden
Naoise Golden

Reputation: 8913

Here is a simple component to lazy load images:

class LazyBackground extends React.Component {
  state = { src: null };

  componentDidMount() {
    const { src } = this.props;

    const imageLoader = new Image();
    imageLoader.src = src;

    imageLoader.onload = () => {
      this.setState({ src });
    };
  }

  render() {
    return <div {...this.props} style={{ backgroundImage: `url(${this.state.src || this.props.placeholder})` }} />;
  }
}

You would call it with <LazyBackground src='path/to/hd.jpg' placeholder='path/to/placeholder.jpg' />

Upvotes: 28

Related Questions