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

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!

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 2
  • Comments: 22 (1 by maintainers)

Most upvoted comments

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 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.

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(<EntireApp />) improves drag and drop performance for custom drag preview.