Как анимировать изменение значения состояния в React?

#javascript #html #reactjs

#javascript #HTML #reactjs

Вопрос:

У меня есть диалоговое окно PDFViewer в моем веб-приложении, созданном в React, в нем отображаются PDF-файлы с использованием этого пакета: https://github.com/wojtekmaj/react-pdf , он имеет свойство scale для определения масштаба компонента просмотра PDF как такового:

 <Page pageNumber={pageNumber} scale={scale} height={props.height} />
 

Свойство scale сохраняется в состоянии и изменяется элементами управления на странице. Он работает правильно, единственная проблема заключается в том, что он не анимирует переход при изменении масштаба. Каков правильный способ плавной настройки состояния шкалы?

Ответ №1:

Я не нашел такой опции в их github, так что вам нужно будет реализовать ее самостоятельно.

Вам понадобится:

  • Два значения состояния — одно для целевого масштаба и одно для текущего масштаба.
  • Знание setTimeout
  • Немного арифметики

Простой и очень медленный пример:

   // initially we set both states to same value
  let [target_scale, set_target_scale] = React.useState(1);
  let [current_scale, set_current_scale] = React.useState(1);
  let [step, set_step] = React.useState(0);

  // we need to get our step. We calculate it when target changes
  React.useEffect(
    function () {
      // step is amount that we want to animate on each frame
      // 1000 is animation duration (in this case it is one second), and 16 is amount of ms to get 60fps
      // 1000 / 16 will give us amount of steps to perform animation. In this case it would be 62.5 frames. Math floor wil make it 62 frames
      // Note that if current_scale is larger then target scale, we will get negative value
      set_step((target_scale - current_scale) / Math.ceil(1000 / 16));
    },
    [target_scale]
  );

  // Now we need and effect that will look for state changes and run our animation
  React.useEffect(
    function () {
      // we don't need it to run if both scales are the same
      // because all numbers in JS are doubles, we want to round it up, to not miss this point
      if (current_scale.toFixed(2) === target_scale.toFixed(2)) return;
      let timeout_id = window.setTimeout(
        () => set_current_scale(current_scale   step),
        16
      );
      return () => window.clearTimeout(timeout_id);
    },
    [target_scale, current_scale, step]
  );
 

Вот рабочий пример:

https://jsfiddle.net/136wjope/1/

Удивительная часть здесь в том, что React значительно оптимизирует этот материал. Плохая часть в том, что это грязно и, вероятно, глючит. Также здесь нет приятного смягчения. Но, по крайней мере, вы знаете, как это сделать, что вам нужно. Итак, чтобы не тратить рабочее время впустую, я предлагаю вам использовать библиотеку react-spring. Это основано на скорости, и это делает ваши анимации приятными. Также он может анимировать произвольные значения, что тоже очень здорово.

Здесь все вышеизложенное переделано для react-spring: https://codesandbox.io/s/clever-grothendieck-bxo6g?file=/src/App.js

Отказ от ответственности: у меня практически нулевой опыт работы с react-spring, поэтому вам обязательно следует прочитать документацию и посмотреть примеры.

Комментарии:

1. Спасибо, я заработал, но он действительно отстает, все еще работаю над обходными путями, есть идеи, что я мог бы сделать, чтобы сделать его плавным?

2. Я сделал это с помощью react-spring, и он воспроизводит анимацию примерно со скоростью 2 кадра в секунду…

3. Обнаружена эта проблема . По-видимому, вся страница принудительно перезаписывается, поэтому она мигает при каждом изменении масштаба.

4. Хм, да, это было то, с чем я столкнулся. Мне удалось решить проблему, используя renderTextLayer=false prop на странице, это позволяет отображать страницу в формате svg вместо серии пролетов, и это очень помогло с производительностью.