Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React JS animations based on JSON data

I am using React/Redux and am storing animation data in JSON and trying to get it to display on a React page.

I am using setTimeout (for pauses) and setInterval (for animation movement). However, I seem to be having trouble understanding how to implement the animations correctly and think I'm going about things totally the wrong way.

JSON data:

"objects": [

    {
        "title": "puppy",
        "image_set": [
            {
                "image": "images/puppy_sitting.png",
                "startx": 520,
                "starty": 28,
                "pause": 1000

            },
            {
                "image": "images/puppy_walking.png",
                "startx": 520,
                "starty": 28,
                "endx": 1,
                "endy": 1,
                "time": 1000
            },
            {
                "image": "images/puppy_crouching.png",
                "startx": 1,
                "starty": 1,
                "endx": 500,
                "endy": 400,
                "time": 2000
            }

        ]
    },
    {
        "title": "scorpion",
        "image_set": [
            {
                "image": "images/scorping_sleeping.png",
                "startx": 100,
                "starty": 400,
                "pause": 5000

            },
            {
                "image": "images/scorpion_walking.png",
                "startx": 100,
                "starty": 400,
                "endx": 500,
                "endy": 400,
                "time": 7000
            },
            {
                "image": "images/scorpion_walking.png",
                "startx": 500,
                "starty": 400,
                "endx": 100,
                "endy": 400,
                "time": 2000
            },
            {
                "image": "images/scorpion_walking.png",
                "startx": 100,
                "starty": 400,
                "endx": 200,
                "endy": 400,
                "time": 7000
            },
            {
                "image": "images/scorpion_walking.png",
                "startx": 200,
                "starty": 400,
                "endx": 100,
                "endy": 400,
                "time": 1000
            }
        ]
    }
]

Each object can have several images related to them. The animations will continue to repeat non-stop. Each object should move concurrently with each of the other objects so that I can create a scene of various animals and objects moving around it.

Animation code:

I'm pretty sure I'm barking up the wrong tree here, but my code looks something like this:

  // image_set is the list of images for a specific object
  // object_num is the array index corresponding to the JSON objects array
  // selected is the array index corresponding to which image in the image_set will be displayed
  runAnimation(image_set, object_num, selected){

        // Uses prevState so that we keep state immutable
        this.setState(prevState => {
            let images = [...prevState.images];

            if (!images[object_num]){
                images.push({image: null, x: 0, y: 0})

            }

            images[object_num].image = image_set[selected].image;
            images[object_num].x = this.getFactoredX(image_set[selected].startx);
            images[object_num].y = this.getFactoredY(image_set[selected].starty);
            return {images: images};
        })

        if (image_set[selected].endx && image_set[selected].endy && image_set[selected].time){
                let x = this.getFactoredX(image_set[selected].startx)
                let y = this.getFactoredY(image_set[selected].starty)
                let startx = x
                let starty = y
                let endx = this.getFactoredX(image_set[selected].endx)
                let endy = this.getFactoredY(image_set[selected].endy)
                let time = image_set[selected].time

                let x_increment = (endx - x) / (time / 50)
                let y_increment = (endy - y) / (time / 50)



                let int = setInterval(function(){

                        x += x_increment
                        y += y_increment


                        if (x > endx || y > endy){
                                clearInterval(int)
                        }

                        this.setState(prevState => {
                                let images = [...prevState.images]

                                if (images[object_num]){
                                        images[object_num].x = x
                                        images[object_num].y = y
                                }


                                return {images: images};


                        })


                }.bind(this),
                 50
                )

        }

        if (image_set[selected].pause && image_set[selected].pause > 0){
                selected++

                if (selected == image_set.length){
                        selected = 0
                }

                setTimeout(function() {
                        this.runAnimation(image_set, object_num, selected)

                }.bind(this),
                  image_set[selected].pause
                )

        }
        else {
                selected++

                if (selected == image_set.length){
                        selected = 0
                }
                setTimeout(function() {
                        this.runAnimation(image_set, object_num, selected)

                }.bind(this),
                        50
                )
        }


  }

Redux and this.props.data

Redux brings in the data as props. So, I have a function called from my componentDidMount and componentWillReceiveProps functions that passes the original image set into the loadAnimationFunction.

My render()

In my render() function I have something like this:

if (this.state.images.length > 1){
    animated = this.state.images.map((image, i) => {
            let x_coord = image.x
            let y_coord = image.y
            return (
                     <div key={i} style={{transform: "scale(" + this.state.x_factor + ")", transformOrigin: "top left", position: "absolute", left: x_coord, top: y_coord}}>
                            <img src={`/api/get_image.php?image=${image.image}`} />
                    </div>
            )

    })
}

x_factor / y_factor

Throughout my code there is also reference to x and y factor. This is because the background that the animations appear in may be scaled smaller or larger. Therefore I also scale the position of the starting and ending x/y coordinates for each animation as well as scale the animated images themselves.

time and pause time

Time indicates the time in ms that the animation should take. Pause time indicates how long in ms to pause before moving to the next animation.

The problem

The code does not move the animations smoothly and they seem to jump around sporadically.

Also, when I click the mouse anywhere on the page it causes the animations to jump to another position. Why would clicking the mouse affect the animation?

One thing I've noticed is that if I have the console open for debugging purposes, this really slows down the animations.

What can I do to my code so that the animations work as expected?

like image 920
kojow7 Avatar asked Nov 29 '18 03:11

kojow7


People also ask

How add JSON animation in React JS?

react-lottie: Working with JSON animations and ReactIn your project's /src directory, create a folder called animations . Inside it, add your JSON file. Note that you can always download a JSON animation by signing up for Lottie. Let's create a component that handles our animation.

How do you implement Lottie animation in React?

Lottie library will be used to add lottie-animation's JSON file to your React site. import Lottie from "react-lottie"; // importing lottie animation JSON // note that you can use any variable in place of "animation" import animation from './myloader.

How use JSON data in React?

json file saved in your /src directory. In Create React App, you can directly import the JSON file and it will work as if it were a JS object, no need to parse it again. To load JSON from file, import it into your component. Now access the properties as you do with regular objects.


2 Answers

You are trying to animate your element using a setInterval doing a setState of the coordinates and with an absolute position. All of these cannot achieve great performance.

First, setInterval should never be used for animations, and you should prefer requestAnimationFrame as it will allow 60fps animations since the animation will be run before the next repaint of the browser.

Second, doing a setState would re-render your whole component which could potentially have an impact on the rendering timing as I assume your component doesn't render only your images. You should try to avoid at maximum to re-render things that haven't changed, so try to isolate your images for the animations.

Lastly, when you position your element with left and top properties, but you should stick to that, positioning, and not animating as the browser would do the animation pixel by pixel and would not be able to create good performances. Instead, you should use CSS translate(), as it can do sub-pixel calculation and will work on the GPU instead, allowing you to achieve 60fps animations. There is a good article on that by Paul Irish.


That being said, you should probably use react-motion which would allow you a smooth animation:

import { Motion, spring } from 'react-motion'

<Motion defaultStyle={{ x: 0 }} style={{ x: spring(image.x), y: spring(image.y) }}>
  {({ x, y }) => (
    <div style={{
       transform: `translate(${x}px, ${y}px)`
    }}>
      <img src={`/api/get_image.php?image=${image.image}`} />
    </div>
  )}
</Motion>

There is also the React Transition Group, Transition could move your elements using a translate animation like explained above. You should also go take a look at the react animations docs here.

Worth a try too, is React Pose, which is pretty easy to use and also performs quite well with a clean API. Here is the get started page for React.


Here is a quick demo using your concept with a sit/walking/running cycle. Notice how react-motion is the only one to handle the transition in between the frames without hardcoding the duration of the transition, which would go against a fluid UI, the state only handles going through the different steps.

Quoting the react-motion Readme:

For 95% of use-cases of animating components, we don't have to resort to using hard-coded easing curves and duration. Set up a stiffness and damping for your UI element, and let the magic of physics take care of the rest. This way, you don't have to worry about petty situations such as interrupted animation behavior. It also greatly simplifies the API.

If you are not satisfied with the default spring, you can change the dampling and stiffness parameters. There's an app which could help you get the one which satisfy you the most.

demo

Source

like image 162
Preview Avatar answered Oct 05 '22 02:10

Preview


React is not exactly meant to be used for animations. I'm not saying you can't animate react components, but it's not part of the problem domain react tries to solve. What it does do is provide you with a nice framework to have the several UI pieces interact with each other. I.e. when creating a game for instance, you'll use react and redux to create and manage the screens, buttons etc. however the game itself, would be separately contained and not use react.

Just a long-winded way to say that if you want to use animations react will not suffice, it's better to use something like greensock's animation library: https://greensock.com/ They provide a tutorial on how to use it in conjunction with react: https://greensock.com/react

like image 36
Creynders Avatar answered Oct 05 '22 00:10

Creynders