How does react-dnd's connectDragPreview() work?
Its seems you are using react-dnd-html5-backend. React-dnd-html5-backend provides connectDragSource: https://github.com/react-dnd/react-dnd-html5-backend/blob/85fc956c58a5d1a9fde2fca3c7fca9115a7c87df/src/HTML5Backend.js#L100
And react-dnd-html5-backend works only with html5 drag&drop events: https://github.com/react-dnd/react-dnd-html5-backend/blob/85fc956c58a5d1a9fde2fca3c7fca9115a7c87df/src/HTML5Backend.js#L65-L74
So, you can set up only an Image to you drag effect: https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/setDragImage
You can simply change your img.onload callback:
export const tokenCollector: DragSourceCollector = (connect, monitor) => { return { connectDragSource: connect.dragSource(), connectDragPreview: connect.dragPreview(), isDragging: monitor.isDragging() };};class TokenClass extends React.Component<TokenProps> { componentDidMount() { const { connectDragPreview } = this.props; const img = new Image(); img.src = 'base64-image'; img.onload = () => connectDragPreview(img); } render() { const { connectDragSource, isDragging, children } = this.props; return connectDragSource( <div style={{ opacity: isDragging ? 0.5 : 1 }}> {children} </div> ); }}const dragSource = DragSource(DropType.Token, tokenSpec, tokenCollector)(TokenClass);export { dragSource as Token };
In case you want to use custom markup, you can call connectDragPreview with getEmptyImage argument, and then, implement CustomDragLayer.
Your TokenClass:
import React, { Component } from "react";import { DragSource } from "react-dnd";import logo from "./logo.svg";import { getEmptyImage } from "react-dnd-html5-backend";export const tokenCollector = (connect, monitor) => { return { connectDragSource: connect.dragSource(), connectDragPreview: connect.dragPreview(), isDragging: monitor.isDragging() };};class TokenClass extends React.Component { componentDidMount() { const { connectDragPreview } = this.props; // Use empty image as a drag preview so browsers don't draw it // and we can draw whatever we want on the custom drag layer instead. connectDragPreview(getEmptyImage()); } render() { const { connectDragSource, isDragging, children } = this.props; return connectDragSource( <div style={{ opacity: isDragging ? 0.5 : 1, backgroundColor: "green" }}> {children} </div> ); }}const tokenSpec = { beginDrag() { return {}; }};export default DragSource("DropType.Markup", tokenSpec, tokenCollector)( TokenClass);
CustomDragLayer implementation:
import React, { Component } from "react";import { DragLayer } from "react-dnd";function getItemStyles(props) { const { initialOffset, currentOffset } = props; if (!initialOffset || !currentOffset) { return { display: "none" }; } let { x, y } = currentOffset; const transform = `translate(${x}px, ${y}px)`; return { transform, WebkitTransform: transform };}class CustomDragLayer extends Component { render() { const isDragging = this.props.isDragging; if (!isDragging) { return null; } // You can specify acceptable type: if (this.props.itemType !== "DropType.Markup") { return null; } // The component will work only when dragging return ( <div> <div style={getItemStyles(this.props)}>Custom drag layer!</div> </div> ); }}function collect(monitor) { return { item: monitor.getItem(), itemType: monitor.getItemType(), initialOffset: monitor.getInitialSourceClientOffset(), currentOffset: monitor.getSourceClientOffset(), isDragging: monitor.isDragging() };}export default DragLayer(collect)(CustomDragLayer); // eslint-disable-line new-cap
Also, I uploaded to github solution which works with both an image and a markup. You can upload them and run: yarn installyarn start
Solution: https://github.com/xnimorz/stackoverflow-example/tree/dragAndDrop
import React from 'react';import { DragSource } from 'react-dnd';/* ... */function collect(connect, monitor) { return { connectDragSource: connect.dragSource(), connectDragPreview: connect.dragPreview() };}class ComponentWithCopyEffect { render() { const { connectDragSource } = this.props; return connectDragSource( <div> This div shows a plus icon in some browsers. </div>, { dropEffect: 'copy' } ); }});ComponentWithCopyEffect = DragSource(/* ... */)(ComponentWithCopyEffect);class ComponentWithHandle { render() { const { connectDragSource, connectDragPreview } = this.props; return connectDragPreview( <div> This div is draggable by a handle! {connectDragSource( <div>drag me</div> )} </div> ); }}ComponentWithHandle = DragSource(/* ... */)(ComponentWithHandle);class ComponentWithImagePreview { componentDidMount() { const { connectDragPreview } = this.props; const img = new Image(); img.src = '/image.jpg'; img.onload = () => connectDragPreview(img); } render() { const { connectDragSource } = this.props; return connectDragSource( <div> This div shows an image when dragged! </div> ); }}ComponentWithImagePreview = DragSource(/* ... */)(ComponentWithImagePreview);