user12246115
user12246115

Reputation:

How can you instantiate a .gif file in a React app?

I have a React app that does 3 things; displays the elements in an array, adds an element on button press and removes an element on button press.

Inside of my image, I am displaying my local .gif file. My .gif file is a single animation .gif, I turned off infinite looping because I want the .gif to spawn in and then remain static.

My error comes when I add a second .gif element to my array. Only the first element displays its animation and the rest display the final slide in the .gif.

I believe that I may be able to solve my issue if I manage to instantiate the element but I am not sure how I would go about doing that.

Here is an excerpt from my code::

function App(){
    const [numbValue, numbUpdate] = useState(0);
    const [starsValue, starsUpdate] = useState(null);
    
    function DisplayGIF(numbToDisp){
      let value=[];
      for(let i=0;i<numbToDisp;i++){
        value.push(<img className="icon" src={process.env.PUBLIC_URL + "/once-star-filled.gif"} alt="animated star"/>)
      }
      starsUpdate(<>{value.map(element=>{return element;})}</>);
    }

    function Add(){
      numbUpdate(numbValue+1);
      DisplayGIF(numbValue+1);
    }

    function Sub(){
      numbUpdate(numbValue-1);
      DisplayGIF(numbValue-1);
    }

    return(<>
      <p onClick={Add}>+</p>
      {starsValue}
      <p onClick={Sub}>-</p>
    </>);
  }

Output:: First add :: displays 1 image that is animated until the end Consecutive adds :: displays x images that display the final frame in the animation

Upvotes: 1

Views: 1242

Answers (1)

marvinav
marvinav

Reputation: 717

Please, try this one.

function App() {
  const [stars, setStars] = useState(0);

  return (
    <React.Fragment>
      <p onClick={() => setStars((s) => s + 1)}>+</p>
      {new Array(stars).fill().map((s, ind) => {
        return <Star key={ind}></Star>;
      })}
      <p onClick={() => setStars((s) => (s === 0 ? 0 : s - 1))}>-</p>
    </React.Fragment>
  );
}

export function Star(props) {
  const [id] = useState(Math.random()); // Generate unique id for this item
  return (
    <img
      className="icon"
      src={`${process.env.PUBLIC_URL}/once-star-filled.gif?id=${id}`}
      alt="animated star"
    />
  );
}

I fix some notation errors, like function name should be started from small letter. Moreover, you could use only one state to store and render stars gif. Also, it is better to create another React function element <Star />. In this way you can reuse this gif later, and add some props, for instance different alter text, src attribute and et al.

[Update 1]

I disagree with @Karl comment, there is a significant flaw in his solution: when the src is formed using the ind, elements will be animated only once (ex: remove element with ind=2 and add again element with ind=2 give us a static image).

So I decided to supplement my answer with one more option, which I would not use in production, but it is interesting and solves the OP's problem.

What is the new solution? We fetch the image through fetch, convert it to dataUrl, delete the first part represented metadata, and pass it to the Star elements for rendering as src props. Each element adds meta-information with its own Id, which does not affect the file itself, but the browser perceives the picture as new. Therefore, all start animates every time their appear on page.

import React, { useState, useRef } from "react";

import "./App.css";

function App() {
  const [stars, setStars] = useState(0);
  const [data, setData] = useState(null);

  const ref = useRef(null);

  React.useEffect(() => {
    fetch("./ezgif-2-110436c6c12b.gif")
      .then((res) => res.blob())
      .then(async (text) => {
        const reader = new FileReader();
        reader.onload = (ev) => {
          setData(
            ev.currentTarget.result.replace("data:image/gif;base64", "")
          );
        };
        reader.readAsDataURL(text);
      });
  }, []);

  return (
    <React.Fragment>
      <p onClick={() => setStars((s) => s + 1)}>+</p>
      {data &&
        new Array(stars).fill().map((s, ind) => {
          return <Star src={data} key={ind}></Star>;
        })}
      <p onClick={() => setStars((s) => (s === 0 ? 0 : s - 1))}>-</p>
    </React.Fragment>
  );
}

/**
 *
 * @param {{src: string}} props
 * @returns
 */
export function Star(props) {
  const [id] = useState(Math.random());

  return (
    <img
      className="icon"
      src={`data:image/gif;base64;${id}` + props.src}
      alt="animated star"
    />
  );
}

export default App;

Upvotes: 1

Related Questions