I’ve always been curious about how some vue/ React UI library button popover animations can be catapulted from the direction of the button. The effect is amazing, but I haven’t looked into it. Then I came across an animated effect called FLIP, and realized that it could perfectly achieve the above impressive effects. I turned to the book. At first, I had no idea what it was. After their lowercase some demo, gradually realize the meaning of which. So write it down.

Project source Git address

The principle of

FLIP is a set of process rules for animation thought and execution. It expresses the meanings: First, Last, Invert, Play.

  • First — The initial state of the element before it begins the transition animation, i.e. position, size information
  • Last – The final state of the element
  • invertCalculating the initial and final statesChange the amount, like width, height, transparency and so on. Then reverse all of these states and use themtransformThe corresponding attribute of the
  • playStart the animation and set the end state of the animation to remove our ininvertSet intransformProperties, and restoreopacityProperties.

Can’t read words? It doesn’t matter, demo is on its way…

Native JS uses FLIP

The following demo implements such a small function as a button popover. Demo Address (using Chrome)

<! --html-->
<div class="dialog-btn"></div>
<div class="dialog-wrapper">
  <div class="dialog -large">
    <h1>hello world!</h1>
  </div>
</div>
Copy the code
// js
const wrapper = document.querySelector('.dialog-wrapper')
const dialog = wrapper.querySelector('.dialog')
const dialogBtn = document.querySelector('.dialog-btn')

dialogBtn.addEventListener('click', () = > {// first -- gets the initial state of the moving element
  const first = dialogBtn.getBoundingClientRect()
  
  // last -- Triggers the motion element to reach its final state
  wrapper.style.display = 'block'
  const last = dialog.getBoundingClientRect()
  
  // invert -- count changes in motion elements
  const dx = first.x - last.x   // Change in X-axis displacement
  const dy = first.y - last.y   // Change in y displacement
  const dw = first.width / last.width   // Width change
  const dh = first.height / last.height // Height change
  
  // Play -- Triggers the motion element to start moving
  dialog.animate(
    [
      {
        transform: `
          translate(${dx}px, ${dy}px)
          scale(${dw}.${dh})
        `.opacity: 0.6
      },
      {
        transform: ` translate(0, 0) scale(1, 1) `.opacity: 1}, {duration: 6000.easing: 'cubic - the bezier (0.2, 0, 0.2, 1)'})},false)
Copy the code

Effect:

To summarize the above code:

  1. Motion Elements – The initial size of the modal box captures the size of the button, not necessarily the original CSS size of the popover. You can seeflipYou can really do whatever you want, whatever the initial state is, the initial state of the animation is.
  2. Get the last state, is directly through the CSS to display the modal box, throughgetBoundingClientRectTo obtain position size information.
  3. In the second step, the element is already in its final state, and the original requirement for animation is to animate the element from its initial state to its final state. Therefore, we expect to restore the element to its initial state in the third step, and then trigger the animation in the first step. withfirstlastYou have to apply the difference to the element to get it back to its original state. For example 🌰, initial stateLeft to 20 px, final stateLeft for 100 px. The change in X-axis displacement is zero20-100=-80pxAt this point we settransform: translateX(-80px)I can return the element to its original state.
  4. Trigger the tween animation and set the final state totransform: translateX(0)Css3 manages animation execution.

Ask questions

  • What are the advantages of an F-L-I-P process for animation?

    A: To put it bluntly, the advantages of CSS3 itself. One is the use of the browser itself, only need to determine the start and end of the animation node location size information, by the rendering engine to automatically complete the tanner animation. Second, the transform and opacity properties themselves can trigger GPU rendering, providing excellent animation performance. If you work with JS + DOM yourself, the amount of animation control can be quite heavy, and you usually need to import third-party libraries.

  • What is the intention of invert inversion?

    A: When we animate flip, we add some transform properties. These are extra properties that affect our original CSS layout. By reversing the transform and setting it to None, the transform-related properties will only exist for the life of the transition animation. The end of the animation no longer affects the CSS layout of the original DOM.

How to inMVVMUse the FLIP on the frame

As you all know, MVVM frameworks are data-driven, and dom manipulation is not as convenient as jQuery. In 2020, MVVM plays an important role. How to integrate MVVM framework is also a big topic. The following will take React as an example to discuss

Determine the F – L – I – P

  • F— How to obtainfirstThe location of the? The location size information before DOM update can generally be found incomponentWillReceiveProps,componentWillUpdateThese life cycle functions
  • L— How to obtainlastThe location of the? This corresponds to the dom updated hook function, i.ecomponentDidMount,componentDidUpdateorSetStat callbackIn the
  • I– byFLTo calculate the difference. When the DOM changes multiple times, last timelastThe state is the next onefirstThe state. So every DOM change needs to be logged and a state manager needs to be maintained.
  • P — Call CSS3 tween animation with parameters derived from step I

Realize the button popover

Let’s try out the online preview of the above example – button popup scene. From a reuse perspective, we want to wrap the animation logic into a separate Component, which we define as a Flipper, where the associated Flip logic will be placed.

According to the Flip rule:

  • First — The position of the buttonfirstRect, passed to the Flipper by the parent element
    class App extends Component {
    
      state = {
        showDialog: false.firstRect: {},
      }
    
      render () {
        return (
          <div>
            <button
              ref={el= > this.btnRef = el}
              onClick={this.onClick}
            >open dialog</button>
    
            {
              this.state.showDialog
                ? (
                  <div className="dialog-wrapper">
                    <Flipper
                      duration={1000}
                      firstRect={firstRect}
                    >
                      <Dialog
                        key="dialog"
                        close={this.close.bind(this)}
                      />
                    </Flipper>
                  </div>
                )
                : null
            }
    
          </div>
        )
      }
      
      componentDidMount () {
        <! Get size of button -->
        this.setState({ firstRect: this.btnRef.getBoundingClientRect() })
      }
    }
    Copy the code
  • Last/Invert/play — in this case, animating an element from nothing to something, in componentDidMount, the modal box is inserted into the Document, which is the last state of the modal box. To get the actual DOM structure in the React Component, you need to borrow some of the React top-level apis.
    class Flipper extends Component {
    
      state = {
        showDialog: false.firstRect: {},
      }
    
      render () {
        return (
          <>
            <! We need to add the ref information to the node to get the modal box DOM service in JS. So use React. CloneElement to process this.props. Children -->
            <! --{ this.props.children }-->{ React.Children.map(this.props.children, node => { return React.cloneElement(node, { ref: node.key }); })}</>
        )
      }
      
      componentDidMount () {
        <! - start f - l - I - p - >
        this.doFlip()
      }
    
      doFlip () {
        <! - first information - >let first = this.props.firstRect if (! first) return;<! --last message, this.props. Children represents the <Dialog /> component, and gets a DOM node from reactdom.finddomNode -->
        const dom = ReactDOM.findDOMNode(this.refs[this.props.children.key])
        const last = dom.getBoundingClientRect()
        <! - inver information - >const diffX = first.x - last.x const diffY = first.y - last.y if (! diffX && ! diffY) return;<! - trigger play -- -- >
        const task = dom.animate(
          [
            {
              opacity: 0,
              transform: `translate(${diffX}px, ${diffY}px)`
            },
            {
              opacity: 1,
              transform: `translate(0, 0)`
            }
          ],
          {
            duration: +this.props.duration,
            easing: 'ease'
          }
        )
    
        task.onfinish = () => {
          <! -- End of tween animation -->}}}Copy the code

Conclusion: THE MVVM framework represents a data-driven idea, but when we made flip animation, we manipulated DOM directly and did not manage CSS properties as state.

Implement list add delete shift

Implementation effect: online preview

I thought it was ok to add the logic of popover, but I found the problem was not as simple as I imagined. The problems sorted out are as follows:

  • There are multiple animated elements in a list. How do you store the state of an unknown number of animated elements and how do you identify them

    Each element that performs animation needs to be bound with a key props as a unique identifier. This ensures that dom cannot be regenerated during animation as long as the key is not cleared. Second, we need to add a ref attribute to each movement element, which is easier to set with the key value. Therefore, key is used to store and index element information. CacheRect can also be placed in componentWillReceiveProps calls, but only in props change triggered when, can choose according to the actual circumstance of coding.

    cacheRect () {
      this.state.cloneChildren.forEach(node= > {
        this.cacheRectData[node.key] = ReactDOM.findDOMNode(this.refs[node.key]).getBoundingClientRect() }) } componentWillUpdate () { <! -- both state and props are triggered when updating -->this.cacheRect()
    }
    
    Copy the code
  • Elements have a long life cycle, not only entering animation, leaving animation, but also transposition animation due to the change of the relative position of elements. How to manage the position size information of an element during the whole life cycle from entering – moving – leaving

    The difficulty with flip is getting first and last:

    • Enter the animation, f is personalized and L is passed after the document is insertedgetBoundingClientRectGet, and then perform the animation;
    • Mobile animation, on behalf of the dom has been in the structure of the document, there may be many times the flip of animation, so every time before the start of the flip to get the latest information on f, f is the dom state before the update, can generally be in componentWillReceiveProps componentWillUpdate, L is the updated state, obtained in componentDidUpdate, and animation parameters are calculated according to the previously recorded FIRST information

      Important note: The above statement refers to elements that are not in the animation process. For the element in motion, since the element ‘key’ remains unchanged, it cannot re-render its final state, while getBoundingClientRect always gets the current state, so it needs to calculate by offsetTop and offsetLeft. The resulting value ignores the influence of transform related values

    • Leaving animation, F also gets the latest data before DOM updates, and L is also personalized.

    Tip: In general, use getBoundingClientRect to get first Last information. Where the tween animation is to be performed, if the state of first is available now, the state of last should be saved before. If you can get the state of last now, save the state of first before you get it. If getBoundingClientRect doesn’t work, think of another way to calculate it.

  • The element gets deleted, the DOM immediately disappears, and I can’t animate the dom that’s gone

    Yes, data-driven, dom updates as soon as the data is updated. So we need to set a child of this.state.children to act as parent of this.props. Children, and put dom deleted from props.

  • In lists, there is a problem when moving too fast. The last state of a moving element changes before the current animation is complete. This can be caused by adding/deleting/transposing other data in the list. Faced with this situation, it is necessary to timely correct the motion trajectory and reset the Flip animation to ensure the continuity of the animation.

    This is basically the crux of implementing Flip in MVVM, as well as getting and setting first and last.

    For example, after the element is deleted, during the animation period, we actually need to keep the element, which will cause the element to occupy its position, and subsequent sibling elements can not eliminate its position, so we need to set the element to be deleted to position: absolute and set reasonable left and top values, so that other elements can reasonably transition. In addition, elements in progress before leaving the animation should not be triggered multiple times to delete the tween animation, need to provide a state to determine the condition.

    Trick: do more motion decomposition, split into x axis, Y axis direction of the analysis will be clear and simple, like learning physics.

Precautions for using FLIP

  • The animation element itself cannot havetransformProperty, because it brings conflict.
  • As the principle of use is still based ontransform, so the boundaries of application scenarios cannot be beyond CSS3. Specifically, that isThe displacement,The zoom,opacity.
  • If you want to modify the animation of an element in the animation again, what can you do? The answer, of course, is to end the current animation and reset the animation. But this isn’t easy to do in all kinds of situations. This is not easy to handle in a situation where the user can trigger the reset animation frequently at will.
  • In fact, my implementation is not perfect, can not be used in any scenario, just provide a way of thinking, there can be more communication.

reference

  • FLIP technology to make animation easier
  • React – Flip – Move open source