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