← Back toDrag and drop in React

Customize the appearance of a ghost element

Written byPhuoc Nguyen
Created
26 Nov, 2023
Tags
ghost element, HTML 5 drag and drop
During an HTML 5 drag and drop operation, a ghost element appears as a visual representation of the item being dragged. It's essentially a copy of the original element that follows the cursor as it moves across the screen. This feature provides important feedback during the drag and drop process, allowing users to see what they're dragging and where they're dropping it. You can customize the appearance of the ghost element using CSS to match the style of your application, making it more intuitive and user-friendly.
There are several reasons why you might want to customize the ghost element during an HTML 5 drag and drop operation.
Firstly, customizing the ghost element can help to reinforce your brand identity. By matching the style of the ghost element to your application's color scheme, typography, and overall design aesthetic, you can create a more cohesive user experience.
Secondly, in some cases, the default appearance of the ghost element may not be clear enough for users to easily understand what they're dragging and where they're dropping it. By customizing the ghost element, you can make it more visually distinct and easier to recognize.
Lastly, customizing the ghost element can make a significant difference in the accessibility of your application for users with visual impairments or other disabilities that affect their ability to perceive visual information. By using high-contrast colors or other accessibility-friendly design elements, you can ensure that all users have equal access to your drag and drop functionality.
By taking advantage of customization options for HTML 5 drag and drop operations, you can create a more intuitive and user-friendly experience for your application's users. In this post, we'll learn how to customize the appearance of the ghost element with React.

Updating the CSS classes of a ghost element

To give a custom look and feel to a ghost element in HTML 5 drag and drop, we can use JavaScript to handle the `dragstart` and `dragend` events.
When the `dragstart` event is triggered, we can use the `classList.add()` method to add a CSS class to the target element. This enables us to apply custom styles to the original element being dragged, as well as any ghost elements associated with it.
Similarly, when the `dragend` event is triggered, we can use the `classList.remove()` method to remove a CSS class from the target element. This will restore it to its original state before it was dragged.
Here's an example of how you might handle these events in React:
tsx
const handleDragStart = (e) => {
e.target.classList.add('dragging');
};

const handleDragEnd = (e) => {
e.target.classList.remove('dragging');
};

return (
<div
draggable="true"
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
Drag me
</div>
);
In this example, we've created two event handlers: `handleDragStart` and `handleDragEnd`. The first handler adds a CSS class called `dragging` to the target element when the drag operation begins (`onDragStart`). The second handler removes the `dragging` class when the drag operation ends (`onDragEnd`).
To make the ghost element stand out from other elements on the page, we've added a dashed border of 2 pixels to the `dragging` class. You can customize the `.dragging` class further by adding additional CSS properties such as background color or opacity to make it more visually appealing.
css
.dragging {
border: 2px dashed rgb(203 213 225);
}
By using event handlers in combination with CSS customization options for HTML 5 drag and drop operations, you can create a more intuitive and user-friendly experience for your application's users.
The biggest advantage of this approach is that it not only changes the appearance of the ghost element, but also the original element. However, if you want to avoid this, there are other solutions described in the next sections. Let's move on to see how they can address this issue.

Using the setDragImage() API

In addition to customizing the ghost element using CSS, you can also update it during the drag and drop operation using the `setDragImage()` API. This method allows you to specify a new image for the ghost element at any point during the drag and drop process.
To use `setDragImage()`, you'll need to create a new element that will be used as the updated ghost element. Then, you can pass this element into `setDragImage()` along with an x and y offset that determines where the ghost element should be positioned relative to the cursor.
This technique can be particularly useful when dragging complex or dynamic content, such as images or video. By updating the ghost element on-the-fly, you can provide users with more accurate feedback about what they are dragging and where they are dropping it.
Here's how to use this API inside the `dragstart` event handler:
ts
const handleDragStart = (e) => {
const dragEle = e.target;

const ghostEle = dragEle.cloneNode(true);
ghostEle.classList.add('dragging');
document.body.appendChild(ghostEle);

const nodeRect = dragEle.getBoundingClientRect();
e.dataTransfer.setDragImage(
ghostEle,
e.clientX - nodeRect.left,
e.clientY - nodeRect.top
);
};
In the `handleDragStart` function, the first thing we do is retrieve a reference to the element being dragged using `e.target`. Then, we create a clone of this element using the `cloneNode()` method, which becomes the ghost element.
To make the ghost element look different from the original element, we add a CSS class called `dragging` using the `classList.add()` method. In this class, we can included custom styles such as a dashed border or background color, just like what we did in the previous section.
The ghost element is then appended to the document body using `document.body.appendChild()`, which ensures that it appears on top of all other elements during the drag and drop operation.
To create an accurate visual representation of what's being dragged, we use the `setDragImage()` method on the data transfer object (`e.dataTransfer`). This method takes the cloned ghost element as the image and the x and y offsets from the cursor position. We calculate the offsets by subtracting the target element's `left` and `top` coordinates from the `clientX` and `clientY` coordinates of the mouse event.
By updating both elements simultaneously with custom styles and creating an accurate visual representation during the drag and drop operation, users can feel more confident and in control of their interactions with your application.
Check out the demo below to see it in action.
When you drag the target element, two issues occur. First, the ghost element is duplicated as soon as the target element is dragged. And if we do this many times without removing the ghost element, more and more duplicates appear on the page.
To solve the first issue, we can hide the ghost element. We achieve this by setting its `left` property to a large negative value and giving it a `fixed` position. This moves the ghost element off-screen, making it invisible to the user.
css
.dragging {
position: fixed;
left: -9999px;
}
To solve the problem of having multiple ghost elements created and added to the page during drag and drop operations, we can use a React ref to store a reference to the ghost element. By doing this, we can remove it from the DOM once the drag operation is complete.
First, we create a new ref using the `useRef()` hook.
ts
const ghostRef = React.useRef();
Next, within the `handleDragStart` event handler, we assign the cloned ghost element to this reference.
ts
const handleDragStart = (e) => {
// Store reference to ghost element in ref
ghostRef.current = ghostEle;
};
Lastly, in the `handleDragEnd` event handler, we verify whether there is a reference to the ghost element saved in our reference variable. If it is, we remove it from the DOM using the `remove` function.
ts
const handleDragEnd = (e) => {
// Remove the ghost element from DOM
const ghostEle = ghostRef.current;
if (ghostEle) {
ghostEle.remove();
}
};
By using a React `ref` to store a reference to the cloned ghost element and removing it from the DOM when no longer needed, we can ensure that only one instance of the ghost element exists at any given time. This helps to keep our application performant and avoid cluttering up the DOM with unnecessary elements.
Take a look at the demo below to see how both issues have been resolved:
To get rid of the ghost element during dragging without using another React `ref`, we can add an event listener to the original element. This listener will remove the ghost element from the DOM when the `dragend` event occurs.
Here's an updated version of our `handleDragStart` function that includes this event listener:
ts
const handleDragStart = (e) => {
const dragEle = e.target;
const ghostEle = dragEle.cloneNode(true);

const handleDragEnd = () => {
ghostEle.remove();
dragEle.removeEventListener('dragend', handleDragEnd);
};

dragEle.addEventListener('dragend', handleDragEnd);
};
We've created a new function called `handleDragEnd` in this version. It removes the ghost element from the DOM by using its `remove()` method. We add this function as an event listener to the original element being dragged with `addEventListener()`.
To make sure it only runs once and doesn't interfere with any subsequent drag and drop operations, we remove the event listener itself inside this event listener using `removeEventListener()`.
By adding this simple event listener, we can remove the ghost element from the DOM without needing to use an additional React `ref`. It's a quick and easy solution!

Conclusion

In conclusion, customizing HTML 5 drag and drop operations can greatly enhance your application's user experience. By utilizing event handlers in combination with CSS customization options or the `setDragImage()` API, you can create unique ghost elements that accurately represent the item being dragged and its destination.
To prevent clutter in the DOM, it's important to remove these ghost elements once the drag and drop operation is complete. You can accomplish this by using a React `ref` to store a reference to the cloned ghost element or by adding an event listener to remove it from the DOM.
If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn