react-component / tween-one

Animate One React Element
https://tween-one.vercel.app/
MIT License
380 stars 30 forks source link

试着用Ant Motion的Sortlist调用tween-one在动画收尾的时候会err #62

Closed Right10Arthur closed 4 years ago

Right10Arthur commented 4 years ago

TweenOne.js:317 Uncaught TypeError: Cannot read property 'totalTime' of null at TweenOne.frame (TweenOne.js:317) at Object.raf [as func] (TweenOne.js:367) at ticker.js:77 at Array.forEach () at ./node_modules/rc-tween-one/es/ticker.js.p.tick (ticker.js:76) frame @ TweenOne.js:317 raf @ TweenOne.js:367 (anonymous) @ ticker.js:77 ./node_modules/rc-tween-one/es/ticker.js.p.tick @ ticker.js:76 requestAnimationFrame (async) ./node_modules/raf/index.js.module.exports @ index.js:64 ./node_modules/rc-tween-one/es/ticker.js.p.tick @ ticker.js:84 requestAnimationFrame (async) ./node_modules/raf/index.js.module.exports @ index.js:64 ./node_modules/rc-tween-one/es/ticker.js.p.tick @ ticker.js:84 requestAnimationFrame (async) ./node_modules/raf/index.js.module.exports @ index.js:64 ./node_modules/rc-tween-one/es/ticker.js.p.tick @ ticker.js:84

Right10Arthur commented 4 years ago

主要原因应该不是TweenOne的错,希望不管父组件用的是不是有问题,子组件足够鲁棒强壮。 原listsort在mouseup时会在动画结束时通过state去重新调整组件中子节点的顺序,原代码如下,我用TS整理过listSort

 public onMouseUp = (e: any) => {
    const draging = this.draging
    if (!draging) {
      return;
    }

    document.body.style.overflow = 'null';
    this.props.onEventChange(e, 'up');

    if (this.props.dragClassName) {
      /**在拖拽完成的dom的css中,去除props.dragClassName*/
      draging.dragingDom.className = `${draging.dragingDom.className.replace(this.props.dragClassName, '').trim()}`;
    }

    const childAnimation = this.state.childDragingTweenOneAnimation.map((anim: AnimInfo, index: number) => {
      if (draging.oldIdx !== index)
        return anim;
      else {/**结束时只对拖动的结点进行收场动画 */
        ...省略
        return {
          ...lastAnim,
          onComplete: () => {
            /**动画完成时,修改children的排序 */
              const children = this.changeChildIndex(this.state.children, draging.oldIdx, draging.newIdx);
              const callbackBool = draging.oldIdx !== draging.newIdx;
              this.setState(
                { listStyle: {}, childDragingStyle: [], children, childDragingTweenOneAnimation: [], }
                , () => {
                  /**通知组件使用者数据做了修改 */
                  callbackBool && this.props.onChange(children);
                  delete this.draging;
                })
            );
          },
        };
      }
    });

    this.setState({ childDragingTweenOneAnimation: childAnimation });
  };

改为用process.nextTick包一下就好了

  onComplete: () => {
    /**动画完成时,修改children的排序 */
    process.nextTick(() => {
      console.log( `${draging.oldIdx} ==> ${draging.newIdx}`)
      const children = this.changeChildIndex(this.state.children, draging.oldIdx, draging.newIdx);
      const callbackBool = draging.oldIdx !== draging.newIdx;
      this.setState(
        { listStyle: {}, childDragingStyle: [], children, childDragingTweenOneAnimation: [], }
        , () => {
          /**通知组件使用者数据做了修改 */
          callbackBool && this.props.onChange(children);
          delete this.draging;
        })
    }
    );
  },

引发这个bug的原因是,会有机率释放的时候,被动移动的兄弟节点在react按children更新后尚有未完成动画,这样会从未完成的动画跳变成最后的结果,则这时如果不用nextTick包一下,则会引发TweenOne报上方我提的err,也就是onComplete完成后,TweenOne后续还在计算动画? 这个bug可以如下重现,不断将第一个节点移动到第二个节点,视觉上都是对的情况下,会有很大机率出现0==>0也就是收尾的时候算出没变?什么原因我会继续跟踪一下。 6ListSort.tsx:250 0 ==> 1 2ListSort.tsx:250 0 ==> 0 另,非常感谢阿里AntD的前端兄弟的开源代码,觉的从可读性还是可以再有所提高,也希望能为您们助一臂之力,再次感谢! 觉的可读性提高的最明显的是onMouseMove的代码,我改写的如下

   public onMouseMove = (e: any) => {
    const draging = this.draging
    if (!draging) {
      return;
    }
    draging.mouseXY.x = e.touches === undefined ? e.clientX : e.touches[0].clientX;
    draging.mouseXY.y = e.touches === undefined ? e.clientY : e.touches[0].clientY;
    const newStyleS = this.state.childDragingStyle;
    let childAnimation = this.state.childDragingTweenOneAnimation;

    const { oldStyleS, oldIdx } = draging;

    if (this.props.animType === 'x') {
      // 懒得写现在没用。。。做成组件后加
      newStyleS[oldIdx].left = draging.mouseXY.x! - draging.mouseXY.startX + draging.mouseXY.left;
    }
    else {
      /*1.跟据鼠标位置 实时 修改拖动的节点的位置,
      修改拖动节点的style.top实时改变位置*/
      const newTop = newStyleS[oldIdx].top = draging.mouseXY.y! - draging.mouseXY.startY + draging.mouseXY.top;
      /*2.计算拖动节点 要占的新的索引
      和拖动前的位置比对,鼠标落在哪个节点中就要占哪个位置*/
      let newIdx = 0;
      for (const oldStyle of oldStyleS) {
        if (newTop < (oldStyle.top + oldStyle.height)) break;
        ++newIdx;
      }
      newIdx = draging.newIdx = Math.min(newIdx, oldStyleS.length - 1)
      /*3.修改列表中的其它节点的位置 
      修改Anim.top会有动效*/
      childAnimation = childAnimation.map((anim: AnimInfo, idx: any) => {
        if (idx === oldIdx) {//当前拖动的节点,不参与通过动效去修改节点位置
          return anim
        }
        if (newIdx === oldIdx) {//拖动的节点回到拖动前的索引,其它节点都返回到拖动前的top
          return { top: oldStyleS[idx].top };
        }
        const height = oldStyleS[oldIdx].height + oldStyleS[oldIdx].marginHeight;
        if (newIdx > oldIdx) {/**当新的位置在旧的位置下方时 */
          if (idx > oldIdx && idx <= newIdx)
            return { top: oldStyleS[idx].top - height };
        }
        else if (oldIdx > newIdx) {/**当新的位置在旧的位置上方时 */
          if (idx < oldIdx && idx >= newIdx)
            return { top: oldStyleS[idx].top + height };
        }
        return { top: oldStyleS[idx].top };
      });
    }
    this.setState({ childDragingStyle: newStyleS, childDragingTweenOneAnimation: childAnimation });
  };
Right10Arthur commented 4 years ago

sortlist出错的原因找到啦,因为onMouseMove通过this.draging是不是为空来判断当前拖拽是否结束,所以不要在onmouseup->动画结束->setState结束时->delete this.draging;,而在onmouseup函数最后就delete this.draging; 原代码和我有一样的问题,他是用的onmouseup->动画结束->setState结束时->this.isDrage = false;