【问题标题】:React JS animations based on JSON data基于 JSON 数据的 React JS 动画
【发布时间】:2019-05-01 01:39:37
【问题描述】:

我正在使用 React/Redux 并将动画数据存储在 JSON 中并试图让它显示在 React 页面上。

我正在使用setTimeout(用于暂停)和setInterval(用于动画移动)。但是,我似乎无法理解如何正确实现动画,并认为我正在以完全错误的方式处理事情。

JSON 数据:

"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
            }
        ]
    }
]

每个对象可以有多个与之相关的图像。动画将继续不停地重复。每个对象都应该与其他每个对象同时移动,这样我就可以创建一个由各种动物和对象围绕它移动的场景。

动画代码:

我很确定我在这里叫错了树,但我的代码看起来像这样:

  // 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 和 this.props.data

Redux 将数据作为道具引入。所以,我有一个从我的 componentDidMount 和 componentWillReceiveProps 函数调用的函数,它将原始图像集传递给 loadAnimationFunction。

我的渲染()

在我的render() 函数中,我有这样的东西:

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

在我的代码中,还提到了 x 和 y 因子。这是因为动画出现的背景可能会变小或变大。因此,我还缩放每个动画的开始和结束 x/y 坐标的位置,以及缩放动画图像本身。

时间和暂停时间

时间表示动画应该花费的时间,以毫秒为单位。暂停时间表示在移动到下一个动画之前暂停多长时间。

问题

代码不能流畅地移动动画,它们似乎偶尔会跳来跳去。

此外,当我在页面上的任意位置单击鼠标时,它会导致动画跳转到另一个位置。为什么点击鼠标会影响动画?

我注意到的一件事是,如果我打开控制台进行调试,这确实会减慢动画速度。

我可以对我的代码执行哪些操作以使动画按预期工作?

【问题讨论】:

    标签: javascript reactjs animation redux


    【解决方案1】:

    React 并不完全适用于动画。我不是说你不能为 react 组件设置动画,但这不是 react 试图解决的问题的一部分。它所做的是为您提供一个很好的框架,让多个 UI 部分相互交互。 IE。例如,在创建游戏时,您将使用 react 和 redux 来创建和管理屏幕、按钮等。但是游戏本身将被单独包含而不使用 react。

    啰嗦一句,如果你想用动画react是不行的,最好用greensock的动画库之类的东西:https://greensock.com/ 他们提供了如何将它与 react 结合使用的教程:https://greensock.com/react

    【讨论】:

      【解决方案2】:

      我相信您的根本问题在于 React/Redux 处理状态的方式。 React 可以将多个更新请求批处理在一起以提高渲染效率。如果没有进一步的诊断措施,我的猜测是setState 之后的状态处理会响应过于僵硬。

      解决方案将更新您的动画在状态系统之外,要么使用现成的框架,要么只是自己处理动画;获取对元素的引用并更新它,而不是在每次更新状态时重新渲染元素。

      【讨论】:

        【解决方案3】:

        您正在尝试使用setInterval 为您的元素设置动画,该setState 坐标和absolute 位置。所有这些都无法实现出色的性能。

        首先,setInterval 不应该用于动画,您应该更喜欢 requestAnimationFrame,因为它允许 60fps 的动画,因为动画将在浏览器下一次重绘之前运行。

        其次,执行setState 会重新渲染您的整个组件,这可能会对渲染时间产生影响,因为我假设您的组件不会仅渲染您的图像。您应该尽量避免重新渲染未更改的内容,因此请尝试为动画隔离图像。

        最后,当你定位你的元素具有lefttop属性,但你应该坚持这一点,定位,而不是动画,因为浏览器会逐个像素地做动画,就无法创造出好的表演。相反,您应该使用 CSS translate(),因为它可以进行亚像素计算,并且可以在 GPU 上运行,从而可以实现 60fps 的动画。 Paul Irish 有一个good article


        话虽如此,您可能应该使用react-motion,这将使您获得流畅的动画效果:

        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>
        

        还有 React 转换组,Transition 可以使用translate 动画来移动您的元素,如上所述。你也应该去看看反应动画文档here

        React Pose 也值得一试,它非常易于使用,并且通过干净的 API 也能很好地执行。 Here 是 React 的入门页面。


        这是一个使用您的概念与坐/走/跑步循环的快速演示。请注意 react-motion 是如何处理帧之间过渡的唯一方法,而无需对过渡的持续时间进行硬编码,go against a fluid UI,状态只处理不同的步骤。

        引用 react-motion Readme:

        对于 95% 的动画组件用例,我们不必求助于硬编码的缓动曲线和持续时间。为你的 UI 元素设置一个刚度和阻尼,让物理的魔法来处理其余的事情。这样,您就不必担心动画行为中断等琐碎情况。它还极大地简化了 API。

        如果您对默认弹簧不满意,可以更改damplingstiffness 参数。有一个app 可以帮助您找到最让您满意的。

        Source

        【讨论】:

        • 看起来很酷,但不尊重动画时间(例如你的第三个动画是 2 秒,在此期间应该在开始和结束之间插入位置)
        • 我有...想法是从开始 x 到结束 x 进行动画处理,并且该动画应该花费 2 秒。我并不是说你需要计算步骤或类似的东西
        • 话虽如此,我理解你关于流体用户界面的观点......但是你应该计算一个速度并使用它,因为你的动画是错误的
        【解决方案4】:

        如果不深入了解 JS 中的动画(这里已经有很多有效的答案),您应该考虑如何渲染图像:

        <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>
        

        在编译这个(或者它在文档中?)时,您实际上应该看到一个警告,因为您使用循环索引作为键。随着添加/删除更多图像,这应该导致图像对象在不同的​​ div 中呈现。如果您对 div 有 css-transition 效果,这一点尤其重要。 TLDR:使用一些标识符作为key 而不是变量i(可能在您创建动画时生成一个?) 此外,如果您在 div 上有 css 过渡,则应将其删除,因为与 setInterval 的更改一起,过渡计算将无法跟上更改。

        【讨论】:

          【解决方案5】:

          让 css 做过渡。使用 transform: translate 而不是 top and left。

          您的示例中的动画很容易用 css transitiontransition-delaytransform 表达。 我会努力将 JSON 转换为 css(使用 cssInJs 解决方案,允许您即时生成类)并将这些类应用于图像。

          类似的东西(您的 JSON 示例的工作示例): https://stackblitz.com/edit/react-animate-json

          const App = () =>
            <div>
              {objects.map(object =>
                <Item item={object} />)
              }
            </div>
          

          Item.js:

          class Item extends React.Component {
            state = { selected: 0, classNames: {} }
            componentDidMount() {
              this.nextImage();
              this.generateClassNames();
            }
          
            generateClassNames = () => {
              const stylesArray = this.props.item.image_set.flatMap((image, index) => {
                const { startx, starty, endx = startx, endy = starty, time } = image;
                return [{
                  [`image${index}_start`]: {
                    transform: `translate(${startx}px,${starty}px)`,
                    transition: `all ${time || 0}ms linear`
                  }
                }, {
                  [`image${index}_end`]: { transform: `translate(${endx}px,${endy}px)` }
                }]
              });
          
              const styles = stylesArray.reduce((res, style) => ({ ...res, ...style }), {})
          
              const { classes: classNames } = jss.createStyleSheet(styles).attach();
              this.setState({ classNames });
            }
          
            nextImage = async () => {
              const { image_set } = this.props.item;
              let currentImage = image_set[this.state.selected];
              await wait(currentImage.pause);
              await wait(currentImage.time);
          
              this.setState(({ selected }) =>
                ({ selected: (selected + 1) % image_set.length }), this.nextImage)
            }
          
            render() {
              const { selected, classNames } = this.state;
              const startClassName = classNames[`image${selected}_start`];
              const endClassName = classNames[`image${selected}_end`];
              return <img
                className={`${startClassName} ${endClassName}`}
                src={this.props.item.image_set[selected].image}
              />
            }
          }
          
          const wait = (ms) => new Promise(res => setTimeout(res, ms));
          

          【讨论】:

            猜你喜欢
            • 2017-08-24
            • 2016-10-19
            • 1970-01-01
            • 2017-11-20
            • 1970-01-01
            • 1970-01-01
            • 2020-03-24
            • 1970-01-01
            • 2021-10-06
            相关资源
            最近更新 更多