Реагировать на анимацию JS на основе данных JSON - PullRequest
0 голосов
/ 29 ноября 2018

Я использую 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
            }
        ]
    }
]

Каждый объект может иметь несколько изображений, связанных с ними.Анимации будут повторяться без перерыва.Каждый объект должен двигаться одновременно с каждым из других объектов, чтобы я мог создать сцену из различных животных и движущихся вокруг него объектов.

Код анимации:

I 'Я почти уверен, что я лаю здесь не то дерево, но мой код выглядит примерно так:

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

My render ()

В моем 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 factor.Это связано с тем, что фон, на котором появляются анимации, может масштабироваться меньше или больше.Поэтому я также масштабирую положение начальной и конечной координат x / y для каждой анимации, а также масштабирую сами анимированные изображения.

время и время паузы

Времяуказывает время в мс, которое должна занять анимация.Время паузы указывает длительность паузы в мс перед переходом к следующей анимации.

Проблема

Код не перемещает анимации плавно, и они, кажется, прыгаютспорадически.

Кроме того, когда я щелкаю мышью в любом месте страницы, анимация переходит в другую позицию.Почему нажатие мыши влияет на анимацию?

Я заметил одну вещь: если у меня открыта консоль для целей отладки, это действительно замедляет анимацию.

Что я могу сделатьв мой код, чтобы анимации работали как положено?

Ответы [ 5 ]

0 голосов
/ 05 декабря 2018

Пусть css сделает переходы.Используйте transform: translate вместо top и left.

Анимации, которые есть в вашем образце, очень легко выразить с помощью css transition, transition-delay и transform.Я приложил бы свои усилия для преобразования 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));
0 голосов
/ 01 декабря 2018

не вдаваясь в подробности об анимации в 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 при добавлении / удалении большего количества изображений.Это особенно важно, если у вас есть эффект css-перехода на div.TLDR: используйте некоторый идентификатор в качестве key вместо переменной i (может быть сгенерировать один при создании анимации?) Также, если у вас есть переход css на div, вы должны удалить его, потому что вместе с изменениямииз setInterval расчет перехода не сможет идти в ногу с изменениями.

0 голосов
/ 01 декабря 2018

Я считаю, что ваша фундаментальная проблема заключается в том, как React / Redux обрабатывает состояние.React может объединять несколько запросов на обновление, чтобы сделать рендеринг более эффективным.Без дальнейших диагностических мер, я предполагаю, что обработка состояния после setState будет просто реагировать слишком жестко.

Решение было бы обновить вашу анимацию вне системы состояний, либо используя готовый-сделанные рамки или просто позаботившись о анимации самостоятельно;получите ссылку на элемент и обновите его вместо повторного рендеринга элемента при каждом обновлении состояния.

0 голосов
/ 01 декабря 2018

Вы пытаетесь анимировать свой элемент, используя setInterval, используя setState координат и позицию absolute.Все это не может обеспечить высокую производительность.

Во-первых, setInterval никогда не следует использовать для анимации, и вы должны предпочесть requestAnimationFrame , так как это разрешит анимацию 60 кадров в секунду, так как анимация будет выполняться раньшеследующая перекраска браузера.

Во-вторых, выполнение setState приведет к повторной визуализации всего вашего компонента, что потенциально может повлиять на время рендеринга, поскольку я предполагаю, что ваш компонент не отображает только ваши изображения.Вы должны стараться избегать повторного рендеринга вещей, которые не изменились, поэтому старайтесь изолировать ваши изображения для анимации.

Наконец, когда вы позиционируете свой элемент с left и top свойств, но вы должны придерживаться этого, располагая, а не анимируя, так как браузер будет делать анимацию попиксельно и не сможет создавать хорошие характеристики.Вместо этого вы должны использовать CSS translate(), так как он может выполнять вычисления на субпикселях и вместо этого будет работать на графическом процессоре, позволяя достигать анимации 60 кадров в секунду.На эту тему написана хорошая статья Пола Айриша.


При этом вам, вероятно, следует использовать реагировать-движение , которое позволит вам плавно анимацию:

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 Group, Transition может перемещать ваши элементы с помощью анимации translate, как описано выше.Вы также должны взглянуть на документы по анимации реакции здесь .

Также стоит попробовать, это React Pose , который довольно прост в использовании и также выполняетдовольно хорошо с чистым API. Здесь - это страница начала работы с React.


Вот краткая демонстрация с использованием вашей концепции с циклом сидя / ходьбы / бега.Обратите внимание, что response-motion является единственным, который обрабатывает переход между кадрами без жесткого кодирования продолжительности перехода, которая будет идти против изменяющегося пользовательского интерфейса , состояние только обрабатывает прохождение различных этапов.

Цитирование реагирующего движения Readme :

В 95% случаев использования анимационных компонентов нам не нужно прибегать к использованию жестко закодированныхКривые ослабления и продолжительность.Установите жесткость и демпфирование для вашего элемента пользовательского интерфейса, и пусть магия физики позаботится обо всем остальном.Таким образом, вам не нужно беспокоиться о мелких ситуациях, таких как прерывание анимации.Это также значительно упрощает API.

Если вас не устраивает пружина по умолчанию, вы можете изменить параметры dampling и stiffness.Существует приложение , которое может помочь вам получить то, которое удовлетворяет вас больше всего.

demo

Источник

0 голосов
/ 01 декабря 2018

React не предназначен для анимации.Я не говорю, что вы не можете оживить реагирующие компоненты, но это не является частью проблемы, которую пытается решить реакция домена.Что он делает, так это предоставляет вам хорошую среду для взаимодействия нескольких частей пользовательского интерфейса.Т.е. при создании игры, например, вы будете использовать реагирование и избыточность для создания и управления экранами, кнопками и т. Д., Однако сама игра будет содержаться отдельно и не будет использовать реагирование.

Просто многословноМожно сказать, что если вы хотите использовать анимацию, реакции будет недостаточно, лучше использовать что-то вроде библиотеки анимации greensock: https://greensock.com/ Они предоставляют учебное пособие о том, как использовать ее в сочетании с реагировать: https://greensock.com/react

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...