簡體   English   中英

如何在 React.js 中預加載圖像?

[英]How to preload images in React.js?

如何在 React.js 中預加載圖像? 我有像菜單一樣工作的下拉選擇組件,但我必須為項目預加載圖像圖標,因為有時它們在第一次打開時不可見。

我努力了:

https://github.com/sambernard/react-preload

https://github.com/wizardzloy/react-img-preload

第一個有很好的 API,易於理解和使用,但是垃圾郵件控制台警告說即使它們已經加載,圖像也沒有加載。 第二個有奇怪的 API,但我嘗試了示例,它沒有預加載任何東西。

所以我可能需要自己實現一些東西,但不知道從哪里開始。 或者另一種可能性是使用 webpack 加載它們。

假設你有pictures: string[]; - 在組件的道具中定義的圖片 url 數組。 您應該在組件中定義componentDidMount()方法,然后為要預加載的每張圖片創建新的Image對象:

componentDidMount() {
    this.props.pictures.forEach((picture) => {
        const img = new Image();
        img.src = picture.fileName;
    });
}

它強制瀏覽器加載所有圖像。

重要的事情是將圖像變量保存為 PERSISTANT。

在持久上下文中,在全局變量中或在顯示圖像之前不會被卸載的組件中:

window.preloadedPictures = []

在我的組件中:

var preloadedData = pictures.map((picture) => {
    const img = new Image();
    img.src = picture.fileName;
    return img
});

myContextOrGlobalVar.preloadedPictures = preloadedData ///IMPORTANT 

如果您有一些圖像在緩存后仍在重新加載,請嘗試將它們存儲在window對象中:

componentDidMount() {
    const imagesPreload = [image1, image2, image3];
    imagesPreload.forEach((image) => {
        const newImage = new Image();
        newImage.src = image;
        window[image] = newImage;
    });
}

(您不需要從window對象調用圖像)

這是 React Hooks(使用TypeScript )在組件中預加載圖像的半完整示例。 你也可以把它做成一個鈎子。

在這些示例中,我們在這里使用useEffectuseState ,但實際的預加載工作在我們擁有的preloadImage()函數中。 另請注意,導入圖像您將獲得一個字符串。

直接在組件內添加

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

import Image1 from 'images/image1.png'
import Image2 from 'images/image2.jpg'
import Image3 from 'images/image3.svg'

const preloadSrcList: string[] = [
  Image1,
  Image2,
  Image3,
]

function preloadImage (src: string) {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.onload = function() {
      resolve(img)
    }
    img.onerror = img.onabort = function() {
      reject(src)
    }
    img.src = src
  })
}

export default function Component() {
  const [assetsLoaded, setAssetsLoaded] = useState<boolean>(false)

  useEffect(() => {
    let isCancelled = false

    async function effect() {
      if (isCancelled) {
        return
      }

      const imagesPromiseList: Promise<any>[] = []
      for (const i of preloadSrcList) {
        imagesPromiseList.push(preloadImage(i))
      }
  
      await Promise.all(imagesPromiseList)

      if (isCancelled) {
        return
      }

      setAssetsLoaded(true)
    }

    effect()

    return () => {
      isCancelled = true
    }
  }, [])

  if (!assetsLoaded) {
    return <p>Preloading Assets</p>
  }

  return <p>Assets Finished Preloading</p>
}

作為一個鈎子更好:useImagePreloader()

import { useEffect, useState } from 'react'

function preloadImage (src: string) {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.onload = function() {
      resolve(img)
    }
    img.onerror = img.onabort = function() {
      reject(src)
    }
    img.src = src
  })
}

export default function useImagePreloader(imageList: string[]) {
  const [imagesPreloaded, setImagesPreloaded] = useState<boolean>(false)

  useEffect(() => {
    let isCancelled = false

    async function effect() {
      console.log('PRELOAD')

      if (isCancelled) {
        return
      }

      const imagesPromiseList: Promise<any>[] = []
      for (const i of imageList) {
        imagesPromiseList.push(preloadImage(i))
      }
  
      await Promise.all(imagesPromiseList)

      if (isCancelled) {
        return
      }

      setImagesPreloaded(true)
    }

    effect()

    return () => {
      isCancelled = true
    }
  }, [imageList])

  return { imagesPreloaded }
}

在組件中使用 useImagePreloader()

import React, { useEffect, useState } from 'react'
import useImagePreloader from 'hooks/useImagePreloader'

import Image1 from 'images/image1.png'
import Image2 from 'images/image2.jpg'
import Image3 from 'images/image3.svg'

const preloadSrcList: string[] = [
  Image1,
  Image2,
  Image3,
]

export default function Component() {
  const { imagesPreloaded } = useImagePreloader(preloadSrcList)

  if (!imagesPreloaded) {
    return <p>Preloading Assets</p>
  }

  return <p>Assets Finished Preloading</p>
}

這有效:

import im1 from 'img/im1.png'
import im2 from 'img/im2.png'
import im3 from 'img/im3.png'

componentDidMount() {
    imageList = [im1, im2, im3]
    imageList.forEach((image) => {
        new Image().src = image
    });
}

如果它只是提供幾個小“圖標” - (為什么不使用字體? ) - 如果服務器提供壓縮文件,你可以使用 base64 例如。

否則,如果選擇不是立即可見的,您還可以將 img 標簽(帶有display: none )添加到以前的 HTML。 另一種方法是將 Image 對象附加到 DOM 並在顯示組件之前等待.onload (您提到的庫使用這種方法)。

據我所知,webpack 或 react 在這里無法為您做任何特別的事情。 這是客戶端的東西,這些只是實現您自己的預加載 API 的工具(甚至使用 JS/TS、React、Angular 中的現有 API ......)

反應鈎方式

useEffect(() => {
  //preloading image
  faceArray.forEach((face) => {
    const img = new Image();
    img.src = image;
  });
}, []);

這是一種將此功能放入自定義掛鈎的方法,您可以在任何功能組件中使用該掛鈎。

缺少一些答案的一個重要部分是將對預加載圖像的引用存儲在諸如窗口對象之類的東西中。 如果您不這樣做,您可能會發現瀏覽器會在實際需要圖像時重新請求圖像,盡管它們已經被預加載。

第一次使用這個鈎子時,會在window中添加一個名為usePreloadImagesData的鍵,並將其初始化為{}

每次在組件中使用鈎子時,都會向window.usePreloadImagesData添加一個新鍵。 密鑰的名稱是隨機生成的。 它被初始化為[]

該鈎子采用圖像源字符串數組。 對於每個圖像源,都會創建一個new Image()並將其添加到window.usePreloadImagesData[randomStr]數組中。

當組件被卸載,或者傳遞給自定義鈎子的數組發生變化時, window.usePreloadImagesData[randomStr]將被刪除以避免內存泄漏。

這個例子是用 Typescript 寫的。

import { useEffect } from 'react';

declare global {
  interface Window {
    usePreloadImagesData?: Record<string, unknown[]>;
  }
}

export const usePreloadImages = (imageSrcs: string[]): void => {
  useEffect(() => {
    const randomStr = Math.random().toString(32).slice(2) + Date.now();
    window.usePreloadImagesData = window.usePreloadImagesData ?? {};
    window.usePreloadImagesData[randomStr] = [];
    for (const src of imageSrcs) {
      // preload the image
      const img = new Image();
      img.src = src;
      // keep a reference to the image
      window.usePreloadImagesData[randomStr].push(img); 
    }
    return () => {
      delete window.usePreloadImagesData?.[randomStr];
    };
  }, [ imageSrcs ]);
};

使用此鈎子時,確保傳遞給鈎子的圖像源字符串數組是常量; 否則會導致不必要的重新渲染。 即,要么在組件之外創建它,要么使用類似React.useMemo的東西。

例如

import React from 'react';
import { usePreloadImages } from '../hooks/usePreloadImages';

import img1 from './image-1.jpg';
import img2 from './image-2.jpg';

const preload = [ img1, img2 ]; // create constant array here, outside the component

export const MyComponent: React.FC = () => {

  usePreloadImages(preload); // correct
  // usePreloadImages([ img1, img2 ]); // incorrect

  return (
    // ...
  );
};

好吧,我的朋友,你有一些鏈接 rel preload /prefetch 和 webpack 延遲加載塊的選項。

更多信息 :

https://github.com/GoogleChromeLabs/preload-webpack-plugin

如果您只需要在單個組件中預加載圖像:

const preloadedSpinner = useRef();

useEffect(() => {
    preloadedSpinner.current = new Image();
    preloadedSpinner.current.src = "my/image/source";
}, []);

您可以使用 src 將它們包含在您的組件中,並將初始內聯樣式設置為display: none ,然后根據您的狀態刪除圖像的樣式。 這樣,您的圖像將被預加載,但在您需要顯示之前不會顯示。 這是一個例子:

<SliderItem
    style={slideIndex === 1 ? null : { display: 'none' }}
    src={imgSrc}
    alt={'Kitchen furniture'}
    text={'Caption Text'}
    textStyle={classes.text}
    MySlides={classes.MySlides}
  />
  <SliderItem
    style={slideIndex === 2 ? null : { display: 'none' }}
    src={'/bedroom.jpg'}
    alt={'Bedroom furniture'}
    text={'Caption Text'}
    textStyle={classes.text}
    MySlides={classes.MySlides}
  />
  <SliderItem
    style={slideIndex === 3 ? null : { display: 'none' }}
    src={'/living-room.jpg'}
    alt={'Living-room furniture'}
    text={'Caption Text'}
    textStyle={classes.text}
    MySlides={classes.MySlides}
  />

您可以使用以下自定義掛鈎:

import { useEffect, useState } from 'react';

const preloadImage = (src: string): Promise<HTMLImageElement> =>
  new Promise((resolve, reject) => {
    const img = new Image();

    img.onload = () => resolve(img);
    img.onerror = img.onabort = () => reject();
    img.src = src;
  });

const useImagePreloader = (imageList: string[]) => {
  const [imagesPreloaded, setImagesPreloaded] = useState(false);

  useEffect(() => {
    let isCancelled = false;

    const preloadImages = async () => {
      const imagesPromiseList: Promise<HTMLImageElement>[] =  imageList.map((img) =>
        preloadImage(img),
      );

      try {
        await Promise.all(imagesPromiseList);
      } catch (error: unknown) {
        console.error(error);
      }

      if (isCancelled) {
        return;
      }

      setImagesPreloaded(true);
    };

    preloadImages();

    return () => {
      isCancelled = true;
    };
  }, [imageList]);

  return { imagesPreloaded };
};

export default useImagePreloader;

你可以使用 React suspense 來預加載圖片!

讓我們預加載一些圖像

  • 就我而言,我從圖像的初始 src 屬性開始,並希望在瀏覽器加載圖像后延遲更改它們。
import { useEffect, useState } from 'react';

export const useImageLoader = (initialSrc: string, currentSrc: string) => {
  const [imageSrc, _setImageSrc] = useState(initialSrc);

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

  return [imageSrc];
};

這是一個非常晚的答案,我在渲染 React Fluent UI 組件的下拉圖像時遇到了完全相同的問題。 解決方案是將文件的擴展名從 .png 更改為 .jpg ,並且顯然也更新了常量中的擴展名。 而已!

我認為最好的方法是為圖像使用數據URI

componentWillMount() {
   this.setState({image: "data:image/gif;base64,R0lGODlhEAAQAMQA..."})
}

render() {
   <img src={this.state.image} />
}

假設您有一個包含每個圖像數據的images數組,可以在單行中完成

componentDidMount() {
  this.props.images.forEach(image => (new Image().src = image.src));
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM