1. Write at the front

Vue official website transition and animation chapter has a very cool shuffle animation, here is also the FLIP idea to do transition animation,Vue transition and animation documentation.

2. What is FLIP?

  • F: represents FISRT, which represents the initial state of all transition elements
  • L: represents last, indicating the end state of all transition elements
  • IInvert = InvertThis is the heart of FlIP’s idea. It means that the state of transition elements (top, left, width, height, opacity, etc.) is returned to the previous state. How to do the regression? For example, if an element has been shifted 100px to the right from the initial position of 0px to the end, set it to reversetransform: translateX(0-100px)Make it look as if the element is still in its original position.
  • P: stands for play. Remove Invert and set it to CSStansition: all 1sTransitions, in which the element animates its path from start to finish.

3. Realize shuffle animation based on Vue

Obtaining the final state of the element in Vue must be done in $nextTick to ensure the final state of the element. NextTick: MutationObserver is just floating around, microTask is the core!

  • html
<div id="root">

  <button @click="handleShuffle">shuffle</button>

  <div class="container">

    <div class="item" v-for="item in arr" :key="item" ref="box">

      {{item}}

    </div>

  </div>

</div>

Copy the code
  • css
.container {

  margin100px auto 0;

  width450px;

  height450px;

  display: flex;

  flex-wrap: wrap;

}

.item {

  width50px;

  height50px;

  border1px solid # 333;

  display: flex;

  justify-content: center;

  align-items: center;

  box-sizing: border-box;

}

Copy the code
  • js
new Vue({

  el'#root'.

  data() {

    return {

      arr: [...Array(81).keys()]

    }

  },

  methods: {

    handleShuffle() {

      // First

      [...this.$refs.box].forEach(item= > {

        const { left, top } = item.getBoundingClientRect();

        item.dataset.left = left;

        item.dataset.top = top;

        item.style.transition = 'unset';

      });

      

      // Array is out of order

      this.arr.sort((a, b) = > Math.random() - 0.5);

      

      // Get the final location of the element in nextTick

      this.$nextTick((a)= > {

        [...this.$refs.box].forEach(item= > {

          // Step 2: Last

          const { left: currentLeft, top: currentTop } = item.getBoundingClientRect();

          const { left: oldLeft, top: oldTop } = item.dataset;

          // step 3: Invert

          item.style.transform = `translate3d(${oldLeft - currentLeft}px, ${oldTop - currentTop}px, 0)`;

        })

      });



      // Step 4: Play

      requestAnimationFrame((a)= > {

        [...this.$refs.box].forEach(item= > {

          item.style.transform = 'translate3d(0, 0, 0)';

          item.style.transition = 'all 1s';

        })

      });

    }

  }

})

Copy the code

4. Why play in requestAnimationFrame?

Take a look at this code:

for (var i = 0; i < 100; i++) {

  document.body.style.background = 'red';

  document.body.style.background = 'blue';

}

Copy the code
  • There’s an important thing about browser rendering,The browser does not render every time the code executes. In the Performance panel of the browser, see:
  • Instead of rendering according to our conventional wisdom, the browser performed a final render of the state bits.The browser will push the JS operation into a render queue, and after the JS execution, it will pull out the render queue task and render it uniformly.
  • RequestAnimationFrame is called before the page is rerendered$nextTick; Invert; Invert; Invert; Invert; Invert; Invert; Invert; Invert; When rendering, change the position of the element back, i.etransform: translate3d(0, 0, 0), and added to the elementtansition: all 1sTransitions, so that elements are animated as they change position, are highly recommendedWhat you don’t know about EventLoop and browser rendering, frame animation, idle callbacksThis article, it goes into detailrequestAnimationFrame andrequestIdleCallback.
  • In addition, you can also use web Animation for animation. Make the following changes:
this.$nextTick((a)= > {

  [...this.$refs.box].forEach(el= > {

    const { left: currentLeft, top: currentTop } = el.getBoundingClientRect();

    const { left: oldLeft, top: oldTop } = el.dataset;

    var player = el.animate([

      { transform`translate3d(${oldLeft - currentLeft}px, ${oldTop - currentTop}px, 0)` },

      { transform'`translate3d(0, 0, 0)' }

] and {

      duration500.

      easing'cubic - the bezier (,0,0.32 0, 1)'.

    });

  })

});

Copy the code

For Web Animation compatibility:We can use the official onespolyfill.

5. Implement shuffle animations using React Hooks

The useEffect callback function in useLayoutEffect is executed immediately after the DOM update, but before the browser does any drawing. Here we get the final state of the element, similar to $nextTick in Vue.

import { useState, useRef, useLayoutEffect, useEffect } from "react";

import './App.css';



// Shuffle the array order

// Take one element at a time from the unprocessed array and place it at the end of the array

// That is, the end of the array is the already processed element, and so on

// https://github.com/ccforward/cc/issues/44

function shuffleArr(arr{

  let len = arr.length;

  while (len) {

    const random = Math.floor(Math.random() * len);

    len--;

    // const lastNum = arr[len];

    // const randomNum = arr[random];



    // arr[len] = randomNum;

    // arr[random] = lastNum;



    // es6 variable swap

    [arr[len], arr[random]] = [arr[random], arr[len]];

  }

  return arr;

}



function App({

  const [arr, setArr] = useState([...Array(81).keys()]);

  const containerRef = useRef(null);



  const handleClick = (a)= > {

    const itemList = containerRef.current.querySelectorAll(".item");

    // First

    [...itemList].forEach((item) = > {

      const { left, top } = item.getBoundingClientRect();

      item.dataset.left = left;

      item.dataset.top = top;

      item.style.transition = "unset";

    });



    // Disorder the array

    const newArr = shuffleArr(arr);

    setArr([...newArr]);

  }



  useLayoutEffect((a)= > {

    const itemList = containerRef.current.querySelectorAll(".item");



    [...itemList].forEach((item) = > {

      // Step 2: Last

      const { left: currentLeft, top: currentTop } = item.getBoundingClientRect();

      const { left: oldLeft, top: oldTop } = item.dataset;

      // step 3: Invert

      item.style.transform = `translate3d(${oldLeft - currentLeft}px, ${oldTop - currentTop}px, 0)`;

    });

  }, [arr]);



  useEffect((a)= > {

    const itemList = containerRef.current.querySelectorAll(".item");



    // Step 4: Play

    requestAnimationFrame((a)= > {

      [...itemList].forEach((item) = > {

        item.style.transform = `translate3d(0, 0, 0)`;

        item.style.transition = "all 1s";

      });

    });

  }, [arr]);



  return (

    <div>

      <button onClick={handleClick}>btn</button>

      <div className="container" ref={containerRef}>

        {arr.map((item) => (

          <div className="item" key={item}>

            {item}

          </div>

        ))}

      </div>

    </div>


  );

}



export default App;

Copy the code

6. Reference materials

  1. What you don’t know about EventLoop and browser rendering, frame animation, idle callbacks
  2. React and Vue are both using the FLIP idea to achieve smooth movement
  3. FLIP Your Animations
  4. Web_Animations_API
  5. Vue official website Shuffle animation parsing
  6. NextTick: MutationObserver is just floating around, microTask is the core!