chenglou / react-motion

A spring that solves your animation problems.
MIT License
21.71k stars 1.15k forks source link

A fix(?) addressing staleness in TransitionMotion:clearUnreadPropStyles #521

Open lukasnovak333 opened 6 years ago

lukasnovak333 commented 6 years ago

In TransitionMotion:clearUnreadPropStyles, we acknowledge that, because style objects can carry data, we can't directly compare them to check for staleness.

   // unlike the other 2 components, we can't detect staleness and optionally
   // opt out of setState here. each style object's data might contain new
   // stuff we're not/cannot compare
    this.setState({
      currentStyles,
      currentVelocities,
      mergedPropsStyles,
      lastIdealStyles,
      lastIdealVelocities,
    });

Leaving staleness unchecked in this case leads to issues when a rerender is triggered very quickly, because the styles have not been interpolated even once . This can lead to mounting or unmounting transitions that 'jitter', that is, they restart again and again. I can post an example video to show what I mean.

When I insert the following (basic) check for staleness, these bugs (for me) disappear.

     // A simple check for staleness
      let stale = false;
      let oldStyle, newStyle, curVelocity;

      // In rare cases, the lengths of currentStyles, this.state.currentStyles,
      // and currentVelocities can differ. This check makes sure we only 
      // check for staleness on styles that are represented in all 
      // 3 arrays
      let shortestArray = [currentStyles, 
                           this.state.currentStyles, 
                           currentVelocities].reduce((short, current) => {
                              return (short.length < current.length? short: current)
                           }, currentStyles);

      // For every well-represented style...
      for(let i in shortestArray) {
        // Check every key (except for data)
        for(let key in this.state.currentStyles[i]) {
          if(key != 'data'){
            oldStyle = this.state.currentStyles[i][key];
            newStyle = currentStyles[i][key];
            curVelocity = currentVelocities[i][key];
            // IF VELOCITY IS 0 BUT OLDSTYLE DIFFERS FROM NEWSTYLE
            // THE STYLES ARE PROBABLY STALE
            if((oldStyle != newStyle) && (curVelocity == 0)) {
              stale = true;
              break;
            } 
            // IF NEWSTYLE 'POINTS' IN A DIFFERENT DIRECTION THAN 
            // CURVELOCITY, IT IS PROBABLY STALE
            if(((oldStyle - newStyle)*curVelocity) < 0) {
              stale = true;
              break;
            }
          }
        }
      }

       // If currentStyles passed our check for staleness,
       // then update!
       if(!stale)
          _this.setState({
            currentStyles: currentStyles,
            currentVelocities: currentVelocities,
            mergedPropsStyles: mergedPropsStyles,
            lastIdealStyles: lastIdealStyles,
            lastIdealVelocities: lastIdealVelocities
          });

Is there something wrong with this check for staleness?