react-dnd With Text Drag Preview
react-dnd is a fantastic library for implementing drag and drop features in a React application. In browsers, you have the ability to specify a drag preview for the thing your dragging. Images are easy. Text takes more work -- because you need to generate an image.
I love the abstractions in react-dnd. They seem very elegant. We're going to need another abstraction here, because there's a short list of things that we need to do in order to get a text-based drag preview showing up in our app. These things are required because drag previews in browsers only support images. Thus we will need to take source text from our app code and convert it into an image in order to hand it off to the drag and drop api.
Drag Text from a Canvas
To convert text to an image, we must:
- Create a canvas element
- Fit the canvas to our text
- Style the canvas
- Create a new image element with the canvas data as src
Create a Canvas Element
We choose a canvas element because we can write text onto it and eventually export its bytes.
var c = document.createElement('canvas')
var ctx = c.getContext('2d')
Fit Canvas to the Text
We want the drag preview to be dynamically sized. This is because the text can change in length. The key to our success is available on the Canvas 2D context as measureText
. This function is available in IE9+. In case you don't have access to this function, you can attempt a crude fallback.
function getTextWidth(ctx, fontSize, text) {
if (typeof ctx.measureText === 'function') {
return ctx.measureText(text).width
} else {
var perCharWidth = fontSize / 1.7
return text.length * perCharWidth
}
}
Style the Canvas
Once you know how big to make your drag preview, draw a rectangle upon which you can then draw your text.
In addition to drawing the main content, there are also other stylistic adjustments that you can make via attributes on CanvasRenderingContext2D
. Attributes like fillStyle
, strokeStyle
, and font
are available.
ctx.rect(0, 0, rectWidth, rectHeight)
ctx.save()
ctx.fillStyle = backgroundColor
ctx.strokeStyle = borderColor
ctx.fill()
ctx.stroke()
ctx.restore()
ctx.fillText(text)
Create a new Image Element
Remember, drag preview requires an image to render when the user is dragging. Set the src
of the image to the data from the canvas. This will transfer everything you just created in the canvas into an image.
var img = new Image()
img.src = c.toDataURL()
This image should be ready to use in react-dnd's connect#dragPreview
.
A Library to Help
When making a React drag and drop app, I usually don't look forward to using the sometimes-arcane API of the canvas
element. So, I wrapped it up so I wouldn't have to look at it as often.
If you think this might help you, take a look at react-dnd-text-dragpreview.
How do you go about doing text-based drag previews? Any other good tips?