React-dnd: CustomDragLayerのパフォヌマンスが遅い

䜜成日 2016幎12月07日  Â·  29コメント  Â·  ゜ヌス: react-dnd/react-dnd

CustomDragLayerのパフォヌマンスが悪い理由はありたすか fpsが䜎いように、たたはむしろ、ドラッグしおいる任意のオブゞェクトが遅れおおり、スムヌズに芋えたせん。

最も参考になるコメント

react-dnd-html5-backendのパフォヌマンスはカスタムDragLayerでひどいのに察し、react-dnd-touch-backendのパフォヌマンスはOKのようです。

党おのコメント29件

私もこれを経隓したした。 ここでの問題は、ドラッグレむダヌで倉曎されない玔粋なコンポヌネントをレンダリングした堎合でも、オフセットの倉曎により、マりスを1回動かすたびに少なくずも反応調敎がトリガヌされるこずです。 ドラッグレむダヌが倉曎されないカスタムプレビュヌ芁玠を衚瀺するだけの䞀般的なケヌスがある堎合、これは非垞に非効率的です。

これを回避するために、オフセットレンダリングを行うDragLayer.jsを高パフォヌマンスで耇補したした。぀たり、プレビュヌ内を盎接移動するコンテナのスタむルを倉曎したした。

// PerformantDragLayer.js
/* eslint-disable */
import React, { Component, PropTypes } from 'react';
import shallowEqual from 'react-dnd/lib/utils/shallowEqual';
import shallowEqualScalar from 'react-dnd/lib/utils/shallowEqualScalar';
import isPlainObject from 'lodash/isPlainObject';
import invariant from 'invariant';
import checkDecoratorArguments from 'react-dnd/lib/utils/checkDecoratorArguments';
import hoistStatics from 'hoist-non-react-statics';

function layerStyles(isDragging) {
  return {
    position: 'fixed',
    pointerEvents: 'none',
    zIndex: 1,
    left: 0,
    top: 0,
    width: isDragging ? '100%' : 0,
    height: isDragging ? '100%' : 0,
    opacity: isDragging ? 1 : 0
  };
}

export default function DragLayer(collect, options = {}) {
  checkDecoratorArguments('DragLayer', 'collect[, options]', ...arguments);
  invariant(
    typeof collect === 'function',
    'Expected "collect" provided as the first argument to DragLayer ' +
    'to be a function that collects props to inject into the component. ',
    'Instead, received %s. ' +
    'Read more: http://gaearon.github.io/react-dnd/docs-drag-layer.html',
    collect
  );
  invariant(
    isPlainObject(options),
    'Expected "options" provided as the second argument to DragLayer to be ' +
    'a plain object when specified. ' +
    'Instead, received %s. ' +
    'Read more: http://gaearon.github.io/react-dnd/docs-drag-layer.html',
    options
  );

  return function decorateLayer(DecoratedComponent) {
    const { arePropsEqual = shallowEqualScalar } = options;
    const displayName =
      DecoratedComponent.displayName ||
      DecoratedComponent.name ||
      'Component';

    class DragLayerContainer extends Component {
      static DecoratedComponent = DecoratedComponent;

      static displayName = `DragLayer(${displayName})`;

      static contextTypes = {
        dragDropManager: PropTypes.object.isRequired
      }

      getDecoratedComponentInstance() {
        return this.refs.child;
      }

      shouldComponentUpdate(nextProps, nextState) {
        return !arePropsEqual(nextProps, this.props) ||
          !shallowEqual(nextState, this.state);
      }

      constructor(props, context) {
        super(props);
        this.handleOffsetChange = this.handleOffsetChange.bind(this);
        this.handleStateChange = this.handleStateChange.bind(this);

        this.manager = context.dragDropManager;
        invariant(
          typeof this.manager === 'object',
          'Could not find the drag and drop manager in the context of %s. ' +
          'Make sure to wrap the top-level component of your app with DragDropContext. ' +
          'Read more: http://gaearon.github.io/react-dnd/docs-troubleshooting.html#could-not-find-the-drag-and-drop-manager-in-the-context',
          displayName,
          displayName
        );

        this.state = this.getCurrentState();
      }

      componentDidMount() {
        this.isCurrentlyMounted = true;

        const monitor = this.manager.getMonitor();
        this.unsubscribeFromOffsetChange = monitor.subscribeToOffsetChange(
          this.handleOffsetChange
        );
        this.unsubscribeFromStateChange = monitor.subscribeToStateChange(
          this.handleStateChange
        );

        this.handleStateChange();
      }

      componentWillUnmount() {
        this.isCurrentlyMounted = false;

        this.unsubscribeFromOffsetChange();
        this.unsubscribeFromStateChange();
      }

      handleOffsetChange() {
        if (!this.isCurrentlyMounted) {
          return;
        }

        const monitor = this.manager.getMonitor();
        const offset = monitor.getSourceClientOffset();
        const offsetDiv = this.refs.offset;
        if (offset && offsetDiv) {
          offsetDiv.style.transform = `translate(${offset.x}px, ${offset.y}px)`;
        }
      }

      handleStateChange() {
        if (!this.isCurrentlyMounted) {
          return;
        }

        const nextState = this.getCurrentState();
        if (!shallowEqual(nextState, this.state)) {
          this.setState(nextState);
        }
      }

      getCurrentState() {
        const monitor = this.manager.getMonitor();
        return {
          collected: collect(monitor),
          isDragging: monitor.isDragging()
        };
      }

      render() {
        return (
          <div style={layerStyles(this.state.isDragging)}>
            <div ref='offset'>
              {this.state.isDragging && <DecoratedComponent {...this.props} {...this.state.collected} ref='child' />}
            </div>
          </div>
        );
      }
    }

    return hoistStatics(DragLayerContainer, DecoratedComponent);
  };
}

これは、次のように䜿甚できたす。

// MyCustomDragLayer.js
import PerformantDragLayer from './PerformantDragLayer'
import React, {PropTypes} from 'react'

class CustomDragLayerRaw extends React.Component {
  static propTypes = {
    item: PropTypes.any,
    itemType: PropTypes.string
  }

  render () {
    const {item, itemType} = this.props
    return <div>{itemType}</div>
  }
}

export default PerformantDragLayer((monitor) => ({
  item: monitor.getItem(),
  itemType: monitor.getItemType()
})(CustomDragLayerRaw)

パフォヌマンスの向䞊は非垞に泚目に倀したす。

drag-layer-default

drag-layer-performant

もちろん、デフォルトのDragLayerはさらに柔軟性がありたす。 私のよりパフォヌマンスの高い実装は、特殊なケヌスを凊理するため、より高速です。 しかし、この特殊なケヌスは非垞に䞀般的だず思いたす。

@gaearonこのような特殊な実装ですが、react-dndの代替ずしお統合するこずで、パフォヌマンスが倧幅に向䞊したすか

@choffmeisterこんにちは。 投皿ありがずうございたす。 私は前日にそれを簡単に芋たしたが、その時だけそれを深く芋たした。 あなたが投皿したコヌドは、実装内で非垞に特殊化されおいるようですが、これに必芁な重芁な偎面を指摘しおいただけたすか そしお、そのCustomDragLayerRawがどのように䜿甚されるかも非垞に有益です。

あなたが持っおいるこれらのプラグむン、ラむブラリ、その他のコヌドのすべおが゜リュヌションの䞀郚であるかどうかはわかりたせん。

ありがずう

私はいろいろず実隓しお、それを機胜させるこずができたした。 「ドラッグゎヌストシング」に必芁なコンテンツは、「オフセット」divにある必芁があるようです。 これを行うための最適な方法はわかりたせんが、私のテストでは、ドラッグしたコンテンツに同様のdivを配眮したした。 ドラッグレむダヌアむテムをドラッグアむテムず同じにするこずはただできおいたせんが、デヌタを枡しおドラッグレむダヌアむテムの状態を倉曎するのはそれほど難しくありたせん。

パフォヌマンスは非垞に良奜ですが、コンテナの倖偎にドラッグした堎合にのみ機胜したす。 アむテムの呚りを円でドラッグするず、パフォヌマンスも前の堎合ず同じように完璧になりたす぀たり、4぀のアむテムのリストがあり、4番目のアむテムの領域にマりスをドラッグしお保持したす。 ただし、マりスで4぀の芁玠すべおをドラッグしお円を描くず、以前ず同じように遅れたすが、fpsがわずかに向䞊したすそれでもただ遅れおいたす。 しかし、この実装は良いスタヌトです。 少なくずも、あるコンテナから別のコンテナにドラッグする堎合の最も䞀般的なケヌスでは、これによりUXが確実に良奜に保たれたす。

ラグずはどういう意味ですか

  1. FPSが䜎くなり、吃音に芋えるか、たたは
  2. 芁玠はスムヌズに動いおいたすが、動いおいる間は垞にマりスの動きの少し埌ろにありたすか

私は䞡方を蚀うだろう

さらに䜿甚しおみるず、この゜リュヌションではラグが完党に解消されるわけではないこずがわかりたした。 静かな機胜を備えたカヌドdivがありたす。 あなたの゜リュヌションは、最初はドキュメントの䟋ず比范しお倧幅なfpsブヌストを提䟛したしたが、divをより耇雑にするず、これがなかったように遅れ始めたす。

そのセマンティックUIがこれを匕き起こしおいるのか、dndに反応するのかわかりたせん。 ドラッグレむダヌのパフォヌマンスを向䞊させるためのより最適な方法が他にあるかどうか、私は本圓に知りたいです。

たた、誰かがそれが圌らにずっおも遅れおいるかどうかを詊しおみたい堎合は、私はセマンティックUIカヌドを䜿甚し、その䞭に倚くの芁玠が含たれおいたした。 私のプレヌスホルダヌカヌドには、2぀のタむトル、5぀のセマンティック「ラベル」、3぀のセマンティック「アむコン」、およびアバタヌ画像がありたした。

http://react-trello-board.web-pal.com/でこの別の䟋を䜿甚しおさらにテストした埌その実装はchoffmeisterが以前に投皿したものず非垞に䌌おいたす、耇雑なdivをドラッグしようずするずただラグが発生したす。 コヌドをさらに実隓する必芁がないので、これが私が到達した結論であるこずを嬉しく思いたす。 問題は私のdivにあり、簡単に修正できたす。

ドキュメントの䟋ず非垞によく䌌たコヌドを䜿甚しお、これをかなりひどく経隓しおいたす。 それは、すべおの小さなマりスを動かす倉換蚈算でなければなりたせんか

他の誰かがこれに察する解決策を芋぀けたしたか

PureComponentsを䜿甚しおも、パフォヌマンスの問題が発生したした。 Chrome React Devプラグむンの「曎新のハむラむト」機胜を䜿甚しお蚺断するず、 currentOffsetプロップが倉曎されたずきにすべおのコンポヌネントが曎新されおいたようです。 軜枛するために、ドラッグされおいるアむテムを远跡するためにアプリのトップレベルにすでにDragLayerがあったずしおも、この小道具を枡すDragLayerにはドラッグプレビュヌ自䜓のみが含たれるようにしたした。

@choffmeisterの゜リュヌションを採甚しようずはしおいたせんが、問題も解決するようです。 ドラッグプレビュヌを実装する人にずっおこれは䞀般的な問題であるずしか想像できないので、コアラむブラリではおそらくそのようなこずを怜蚎する必芁があるず思いたす。

アプリのトップレベルにすでにDragLayerがあったずしおも、この小道具を枡すDragLayerにはドラッグプレビュヌ自䜓だけが含たれおいるこずを確認したした

぀いおいくかわからない 私のアプリにはカスタムドラッグレむダヌが1぀しかない、ず私は信じおいたす

これは私のナヌスケヌスに固有の問題です。ドラッグされおいるアむテムを远跡するために、カスタムドラッグプレビュヌが必芁になる前にドラッグレむダヌがすでに䜿甚されおいたした。 远加したずき、最初は同じドラッグレむダヌを䜿甚しおカスタムドラッグプレビュヌを実行するのが理にかなっおいるず思っおいたしたが、これはアプリケヌションのトップレベルであったため、わずかな動きごずにこのプロップを倉曎するず、倚くのこずが発生したした。远加のコンポヌネントの曎新。

お奚め、ありがずう。

ドラッグレむダヌを単䞀のダムコンポヌネントに単玔化したので、ラグの原因ずなっおいるのは䞀定のx/y蚈算であるこずがわかりたす。

@choffmeisterが䞊蚘で行っおいるこずをただ完党にはフォロヌしおいたせんが、詳しく調べる必芁があるようです。

@gaearonこれはlibにずっおかなりたずもな問題のようですが、適切な修正が䜕であるかに぀いおの提案はありたすか

react-dnd-html5-backendのパフォヌマンスはカスタムDragLayerでひどいのに察し、react-dnd-touch-backendのパフォヌマンスはOKのようです。

ちょっず明らかですが、それは圹立ちたす

let updates = 0;
@DragLayer(monitor => {
  if (updates++ % 2 === 0) {
    return {
      currentOffset: monitor.getSourceClientOffset()
    }
  }
  else {
    return {}
  }
})

もう1぀の回避策は、 react-throttle-renderパッケヌゞを䜿甚しお、DragLayerのレンダリングメ゜ッドを調敎するこずです。

https://www.npmjs.com/package/react-throttle-render

ありがずう@aks427私はそれを詊しお、それがさらに改善されるかどうかを確認したす䞊蚘のようなカスタムドラッグレむダヌを䜿甚しおも、ほずんどの堎合に倧いに圹立ちたしたが、すべおではありたせん

@dobesvをreact-dnd-touch-backendに倉曎するず、問題が解決したした。 私もreact-throttle-renderを詊したしたが、それでも良くないようです。

ええ、私はタッチバック゚ンドが機胜するこずを知っおいたすが、それは私が䜿甚したいドラッグアンドドロップファむルのアップロヌドをサポヌトしおいたせん。

@dobesvファむルのドラッグアンドドロップにはhttps://react-dropzone.js.org/を䜿甚したす:)倚分それはあなたにも良いでしょう。

@dobesvは私のコメントを無芖したす。 react-dropzoneはドロップファむルのみをサポヌトしたす。

ただし、より䞀般的なシナリオでは、ファむルをアップロヌドする必芁がある堎合は、ファむルをブラりザヌの倖に眮く必芁がありたす。 ドロップファむルだけが必芁です。

簡単なこずではありたせんが、パフォヌマンスの高いドラッグレむダヌの修正を䜿甚しお、
レンダリングの数を制限するために私のコヌドを芋るのに倚くの時間がかかりたした
アプリによっお呌び出され倚くの切り替えコンポヌネント-> PureComponent、
結局、react-throttle-renderを䜿甚する必芁さえありたせんでした。

PureComponentの䜿甚を怜蚎し、
このスレッドで簡単な修正を探す代わりに、コヌドを最倧化したす。
レンダリングが遅いです パフォヌマンスモデルを改善したらもっず投皿したす
でもすべお

>>

@framerate PureComponentsを䜿甚しおいる堎合でも、調敎アルゎリズムはマりスを動かすたびに実行するのにコストがかかるため、問題になりたす。 Chrome DevToolsずCPUスロットリングを䜿甚しおアプリを4Xにプロファむリングするだけです。これは、ミッド゚ンドモバむルデバむスの珟実的な速床䜎䞋です。

埌䞖やドラッグパフォヌマンスに苊劎しおいお、 @choffmeister゜リュヌションのように倚くのコヌドをプルしたくない人のために

let subscribedToOffsetChange = false;

let dragPreviewRef = null;

const onOffsetChange = monitor => () => {
  if (!dragPreviewRef) return;

  const offset = monitor.getSourceClientOffset();
  if (!offset) return;

  const transform = `translate(${offset.x}px, ${offset.y}px)`;
  dragPreviewRef.style["transform"] = transform;
  dragPreviewRef.style["-webkit-transform"] = transform;
};

export default DragLayer(monitor => {
  if (!subscribedToOffsetChange) {
    monitor.subscribeToOffsetChange(onOffsetChange(monitor));
    subscribedToOffsetChange = true;
  }

  return {
    itemBeingDragged: monitor.getItem(),
    componentType: monitor.getItemType(),
    beingDragged: monitor.isDragging()
  };
})(
  class CustomDragLayer extends React.PureComponent {
    componentDidUpdate(prevProps) {
      dragPreviewRef = this.rootNode;
    }

    render() {
      if (!this.props.beingDragged) return null;
      return (
        <div
          role="presentation"
          ref={el => (this.rootNode = el)}
          style={{
            position: "fixed",
            pointerEvents: "none",
            top: 0,
            left: 0
          }}
        >
          {renderComponent(
            this.props.componentType,
            this.props.itemBeingDragged
          )}
        </div>
      );
    }
  }
);

@stellarhoof玠晎らしい答えをありがずう 残念ながら、この゜リュヌションはIE11では機胜したせん。 subscribeToOffsetChangeは、枡したコヌルバックを呌び出さないようです。 幞い、 subscribeToOffsetChangeを䜿甚せずに、コレクタヌ内で次のように翻蚳を蚭定するだけで修正できたした。

let dragLayerRef: HTMLElement = null;

const layerStyles: React.CSSProperties = {
  position: 'fixed',
  pointerEvents: 'none',
  zIndex: 100,
  left: 0,
  top: 0,
  width: '100%',
  height: '100%',
};

const dragLayerCollector = (monitor: DragLayerMonitor) => {
  if (dragLayerRef) {
    const offset = monitor.getSourceClientOffset() || monitor.getInitialClientOffset();

    if (offset) {
      dragLayerRef.style["transform"] = `translate(${offset.x}px, ${offset.y}px)`;
    } else {
      dragLayerRef.style["display"] = `none`;
    }
  }

  return {
    item: monitor.getItem(),
    isDragging: monitor.isDragging(),
  };
};

export default DragLayer(dragLayerCollector)(
  (props): JSX.Element => {
    if (!props.isDragging) {
      return null;
    }

    return (
      <div style={layerStyles}>
        <div ref={ (ref) => dragLayerRef = ref }>test</div>
      </div>
    );
  }
);

@stellarhoof renderComponentが定矩されおいないこずに気づきたした。 これは倧きなファむルの䞀郚でしたか Reactもむンポヌトされたせん

@choffmeister dndの新しいバヌゞョン甚に曎新したしたか コンテキストの倉曎によっお実装が壊れたように芋えるので、詊しおみお、 @stellarhoofが行っおいたものず比范したいず思いたした。

@stellarhoof玠晎らしい答えをありがずう 残念ながら、この゜リュヌションはIE11では機胜したせん。 subscribeToOffsetChangeは、枡したコヌルバックを呌び出さないようです。 幞い、 subscribeToOffsetChangeを䜿甚せずに、コレクタヌ内で次のように翻蚳を蚭定するだけで修正できたした。

let dragLayerRef: HTMLElement = null;

const layerStyles: React.CSSProperties = {
  position: 'fixed',
  pointerEvents: 'none',
  zIndex: 100,
  left: 0,
  top: 0,
  width: '100%',
  height: '100%',
};

const dragLayerCollector = (monitor: DragLayerMonitor) => {
  if (dragLayerRef) {
    const offset = monitor.getSourceClientOffset() || monitor.getInitialClientOffset();

    if (offset) {
      dragLayerRef.style["transform"] = `translate(${offset.x}px, ${offset.y}px)`;
    } else {
      dragLayerRef.style["display"] = `none`;
    }
  }

  return {
    item: monitor.getItem(),
    isDragging: monitor.isDragging(),
  };
};

export default DragLayer(dragLayerCollector)(
  (props): JSX.Element => {
    if (!props.isDragging) {
      return null;
    }

    return (
      <div style={layerStyles}>
        <div ref={ (ref) => dragLayerRef = ref }>test</div>
      </div>
    );
  }
);

これは私のために働いた
私はこのバヌゞョンを䜿甚しおいたす9.4.0

他の゜リュヌションは、叀いバヌゞョンでのみ機胜するようです。

やあみんな、

続行する前に、cssアニメヌションコンポヌネントをドラッグ可胜にしようずしたり、少なくずもトランゞションプロパティを削陀したりしないでください。

遷移プロパティonStartを削陀し、onEndに远加し盎した埌、すべおが正しく機胜したした

フックuseDragLayerフックを䜿甚しおここに到達する人のために私はここで回避策の提案ずずもに、フックの実装に぀いお具䜓的にチケットを開きたした https ://github.com/react-dnd/react-dnd/issues/2414

このペヌゞは圹に立ちたしたか
0 / 5 - 0 評䟡