React-dnd: Bad screenshot on regular drag, choppy motion on custom drag preview

Created on 8 May 2016  ·  22Comments  ·  Source: react-dnd/react-dnd

Like many, I have a list of sortable cards. In my case, each card is a content block of a blog post and some of these are code snippets rendered with react-codemirror. The drag preview for the snippets included a large white background that was equal to the width and the height of the text within codemirror's code editor. This was undesirable so I tried fixing it with CSS but had no luck, and as I read more documentation I'm thinking it's not possible because dnd is screenshotting the dom node before any new classes or styles can be applied to it (please correct me if that's wrong of course)

Because I couldn't get css to work, I tried using a custom drag layer to show something different for the preview. This works great except the dragging becomes really choppy where it was actually quite smooth before.

Here's a video demonstrating the issue I just described:

https://drive.google.com/file/d/0Bzbw-6Q_sVTySUNiNmJGcFVIQ00/view?usp=sharing

And I suppose you'll also want to see some code. Here is my custom drag layer

import React, { Component, PropTypes } from 'react'
import { DragLayer } from 'react-dnd'

function collect(monitor) {
  return {
    item: monitor.getItem(),
    currentOffset: monitor.getSourceClientOffset(),
    isDragging: monitor.isDragging()
  }
}

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

function getItemStyles(props) {
  var currentOffset = props.currentOffset
  if (!currentOffset) {
    return {
      display: 'none'
    }
  }

  var x = currentOffset.x
  var y = currentOffset.y
  var transform = 'translate(' + x + 'px, ' + y + 'px)'
  return {
    transform: transform,
    WebkitTransform: transform
  }
}

class CardDragLayer extends Component {
  static propTypes = {
    item: PropTypes.object
  }

  render() {
    const { item, isDragging } = this.props

    if (!isDragging)
      return false

    return (
      <div style={layerStyles}>
        <div className='drag-preview' style={getItemStyles(this.props)}>
          <i className='fa fa-hand-scissors-o fa-6'></i>
        </div>
      </div>
    )
  }
}

export default DragLayer(collect)(CardDragLayer)

Here are the config options for the drag source & drop target

export const cardSource = {
  beginDrag(props) {
    return {
      index: props.index
    }
  }
}

export const cardTarget = {
  hover(props, monitor, component) {
    const dragIndex = monitor.getItem().index
    const hoverIndex = props.index

    if (dragIndex === hoverIndex)
      return

    props.swap(dragIndex, hoverIndex)
    monitor.getItem().index = hoverIndex;
  }
}

export function collectSource(connect, monitor) {
  return {
    connectDragSource: connect.dragSource(),
    connectDragPreview: connect.dragPreview(),
    isDragging: monitor.isDragging()
  }
}

export function collectTarget(connect) {
  return {
    connectDropTarget: connect.dropTarget()
  }
}

And here is the Card component that's being dragged

import React, { PropTypes, Component } from 'react'
import { Snippet, Markdown } from './Show'
import { DragSource, DropTarget } from 'react-dnd'
import { getEmptyImage } from 'react-dnd-html5-backend'
import { cardSource, cardTarget, collectSource, collectTarget } from './dragDropConfig'
import classnames from 'classnames'

class SnippetCard extends Component {
  options() {
    return {
      readOnly: true,
      scrollbarStyle: 'null',
      viewportMargin: Infinity
    }
  }

  render() {
    const { language, text } = this.props
    return(
      <Snippet
        options={this.options()}
        language={language.value}
        text={text.value} />
    )
  }
}

class MarkdownCard extends Component {
  render() {
    const { text } = this.props
    return (
      <div className='markdown-card'>
        <div className='card-content'>
          <Markdown text={text.value} />
        </div>
      </div>
    )
  }
}

const components = {
  snippet: SnippetCard,
  markdown: MarkdownCard
}

class Card extends Component {
  static propTypes = {
    connectDragSource: PropTypes.func.isRequired,
    connectDropTarget: PropTypes.func.isRequired,
    index: PropTypes.number.isRequired,
    block: PropTypes.object.isRequired,
    swap: PropTypes.func.isRequired
  }

  componentDidMount() {
    const { connectDragPreview } = this.props
    connectDragPreview(getEmptyImage(), {
      captureDraggingState: true
    })
  }

  render() {
    const { isDragging, block, connectDragSource, connectDropTarget } = this.props
    const Component = components[block.format.value]
    const classes = classnames('card', {
      dragging: isDragging
    })

    return connectDragSource(connectDropTarget((
      <div className={classes}>
        <div className='card-header'>
          <div>
            <label>
              {block.format.value}
            </label>
          </div>
          <Component {...block} />
        </div>
      </div>
    )))
  }
}

const Source = DragSource('card', cardSource, collectSource)(Card)
export default DropTarget('card', cardTarget, collectTarget)(Source)

Let me know if you need to see any more code than that, I'm thinking this should be sufficient.

Suggestions for either a) how I might use css to hide the white background or b) make the custom drag preview drag more smoothly would be very much appreciated!

Note that this work is part of a screencast course on building apps with react/redux and that I am planning to do probably 2-3 short videos on react-dnd to show how the sortable content block cards are implemented. Hoping to release those as soon as possible!

needs info wontfix

Most helpful comment

Yeah, we started by removing every component to find the one that was causing the issue, and once we found it, drilled down to the part that was causing the slowdown, and then on the outer-most element that can accept a style param we wrote: style={{transform: translate3d(0, 0, 0)}}

All 22 comments

I have noticed choppiness too with the custom dragLayer (just running the website example) : http://gaearon.github.io/react-dnd/examples-drag-around-custom-drag-layer.html

It is all fine in Chrome but the animation is choppy in Firefox. (on linux mint, firefox 46.0.1)

Awesome library by the way !

@sslotsky Did you ever figure this out?

If not, I can dive into this and see if there's either anything clever in CSS we can do, or alternatively anything we can do to reduce the choppiness. Other people have reported that custom drag layer performance leaves a lot to be desired.

Nope, still haven't figured it out. A solution would be great for when I get around to recording the next video!

We are also seeing this too, subscribing to this issue for any updates

@kesne Just commenting to keep this alive. If you have any updates worth sharing I'm sure we'd love to hear about it :)

Yeah, I'd love a solution to this as well. I'm looking to implement more than simply a screenshot of the draggable object, but essentially a super simple react component.

I'll throw in on this as well. We are also seeing the same problem.

I am running into this too.. Simple drag and drops working fine. But I have an implementation that is too complex, and this is happening.

Have the same issue. Did anybody come up with some solution or workaround?

@alexey-belous We scrapped this library and went with https://github.com/bevacqua/react-dragula

Same issue here, would be happy about any further comments.

I've been investigating this issue and figured out that screenshot of dragging dom node is transparent only on Windows independently of browser.

I was able to greatly improve quality by just removing a console.log() from my getItemStyles(props) method. This doesn't help the OP, but maybe somebody else stumbling here.

So I cloned this and ran the dev server and the custom drag preview example is also choppy. However, when I build the site (by running npm run build-site) the site is converted to regular markup (without React component references) and now the drag preview example is running smoothly.

So my hypothesis is that ReactDOM.renderToString() improves drag and drop performance for custom drag preview.

This is still a major issue... :(

592 Seems to have had some luck, but I haven't been able to fork and edit yet. Maybe someone else can get to it before I do.

@gaearon we love you man! Don't leave us hanging! Point us in the right direction and I'm sure we'll fix it :)

I was having a problem with choppy animation on a custom drag layer and discovered it was because of an extremely complex DOM tree.

I work on an app that has a table component that can display up to thousands of rows and columns of tabular data, which creates something like 4 DOM elements per cell or so. When the table was empty, the drag layer was working fine, but as the amount of data displayed in the table increased, the performance of the custom drag layer slowed down. (In this case, the table is not involved at all with the operation of the drag sources or drop targets).

In this case, performance in Firefox didn't change, while performance in Chrome got dramatically worse the more the complexity of the DOM tree increased.

I was able to solve the issue by pushing the table into its own compositing layer in Chrome, by styling it with an empty 3D transform property: transform: translate3d(0, 0, 0)

After that, performance was comparable to the demo page. I'm not sure what the implications for this library are, but by pushing complex parts of your application's DOM tree into separate compositing layers, you may be able to fix your performance problems.

@mgerring Do you have more info on how you did that? I'd like to try it on my app

Yeah, we started by removing every component to find the one that was causing the issue, and once we found it, drilled down to the part that was causing the slowdown, and then on the outer-most element that can accept a style param we wrote: style={{transform: translate3d(0, 0, 0)}}

I meant to leave this comment on #592 but I think it applies here as well

@mgerring solution worked for me.

@alexey-belous We scrapped this library and went with https://github.com/bevacqua/react-dragula
+1

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Was this page helpful?
0 / 5 - 0 ratings