Blog Post

Implement drag and drop for Nutrient in a React application

Illustration: Implement drag and drop for Nutrient in a React application

In this tutorial, you’ll implement a drag-and-drop example using React and our Web PDF SDK. This will allow you to drag an image onto a PDF page and add the image as an image annotation.

Getting started

You’ll use Next.js to get started. If you aren’t familiar with Next.js, you can learn about it by reading the official documentation.

First, you’ll scaffold the Next.js application and install the necessary dependencies:

npx create-next-app@latest
npm install --save pspdfkit

Setting up the public folder

Create a public folder in the root directory of your Next.js project and add an example.pdf file inside the folder. This is where Nutrient will load the PDF file from:

mkdir public
cp example.pdf ./public/

Integrating Nutrient

To integrate Nutrient, first move the static files needed by Nutrient to the public folder:

cp -R ./node_modules/pspdfkit/dist/ ./public/

Once this is done, use dynamic imports with "use client" to import Nutrient lazily after the component has mounted on the client site. You’ll do this for two reasons:

  • To avoid loading Nutrient on the server side. As Nutrient only works on the client side, bundling it with the application isn’t a good idea.

  • To load it only when required to make sure that time to first paint isn’t affected by it.

Add the following code in src/app/page.js:

// src/app/page.js
'use client';

import { useEffect, useRef } from 'react';

export default function App() {
	const containerRef = useRef(null);
	const PSPDFKit = useRef(null);

	useEffect(() => {
		const container = containerRef.current;

		(async function () {
			PSPDFKit.current = await import('pspdfkit');
			instance.current = await PSPDFKit.current.load({
				container,
				document: '/example.pdf',
				baseUrl: `${window.location.protocol}//${window.location.host}/`,
				theme: PSPDFKit.current.Theme.DARK,
			});
		})();

		return () => PSPDFKit.current?.unload(container);
	}, []);

	return <div ref={containerRef} style={{ height: '100vh' }} />;
}

If you open http://localhost:3000, you’ll see example.pdf loaded on the screen. Next, add an image that needs to be dragged to the PDF document. You can add more images based on the same logic.

Implementing drag and drop

You’ll use the native drag-and-drop APIs to implement the functionality. The draggable images should adhere to the following constraints:

  • The img tag should have a draggable prop set to true.

  • The img tag should handle the onDragStart event.

const handleDragStart = useCallback(
	(ev) => ev.dataTransfer.setData('text/plain', ev.target.src),
	[],
);

return (
	//...
	<img
		src="image.png"
		draggable="true"
		onDragStart={handleDragStart}
	/>
	//...
);

In the code above, you handled the drag start event. Now you have to handle the onDrop event. The onDrop callback gets an event containing the coordinates of the drop point and the data that was set in the onDragStart callback. You can access that data using evebet.dataTransfer.getData("text/plain"):

const handleDrop = (event) => {
	ev.preventDefault();

	const imgSrc = ev.dataTransfer.getData('text/plain');

	// The coordinates of the drop point relative to the entire window.
	console.log(event.clientX, event.clientY);
};

return (
	//...
	<div onDrop={handleDrop} onDragOver={(ev) => ev.preventDefault()}>
		<div ref={containerRef} style={{ height: '100vh' }} />
	</div>
	//...
);

Once you get the drop coordinates, you’ll have to convert the coordinates in the page space to get the position of the drop point inside the PDF and create an image annotation. You can use Instance#transformClientToPageSpace to do that.

After you have the transformed coordinates, you can use the current page index and the image src to add an image annotation to the PDF:

const handleDrop = useCallback(
	(ev) => {
		(async function () {
			ev.preventDefault();
			// Get the ID of the target and add the moved element to the target's DOM.
			const img = ev.dataTransfer.getData('text/plain');

			const pointInPage = await instance.current.transformClientToPageSpace(
				new PSPDFKit.current.Geometry.Point({
					x: ev.clientX,
					y: ev.clientY,
				}),
				instance.current.viewState.currentPageIndex,
			);

			// Generate a blob from the image URL.
			const image = await fetch(img);
			const blob = await image.blob();

			const imageAttachmentId = await instance.current.createAttachment(
				blob,
			);

			// Create an image annotation from all the above details.
			const annotation = new PSPDFKit.current.Annotations.ImageAnnotation(
				{
					pageIndex: instance.current.viewState.currentPageIndex,
					contentType: 'image/jpeg',
					imageAttachmentId,
					description: 'Example Image Annotation',
					boundingBox: new PSPDFKit.current.Geometry.Rect({
						left: pointInPage.x,
						top: pointInPage.y,
						width: 200,
						height: 135,
					}),
				},
			);

			await instance.current.create(annotation);
		})();
	},
	[instance.current, PSPDFKit.current],
);

The above drop callback will create an image annotation at the same place where you dragged the image on the PDF. If you want to change the image’s alignment, you can adjust the left and top positions that you passed to the image bounding box.

Conclusion

This blog explains the essential code blocks that will help you implement drag and drop in your React application. Please check out this GitHub repository to see the complete working code. You can interact with the live drag-and-drop demo for images with Nutrient below or in the Catalog example.

Nutrient offers a commercial, feature-rich, and completely customizable React PDF library that’s easy to integrate and comes with well-documented APIs to handle advanced use cases. Try it for free, or visit our demo to see it in action.

FAQ

Here are a few frequently asked questions about using drag and drop.

What is the purpose of using drag and drop in a PDF application?

Drag and drop functionality allows users to easily add images as annotations to a PDF document, enhancing the interactivity of the application.

This post recommends using Next.js, a React framework, to efficiently integrate Nutrient and manage application routing.

How can I handle dropped images in my application?

You can handle dropped images by using the onDrop event to access the image source and transform the drop coordinates to place the annotation correctly.

What do I need to install before integrating Nutrient into my project?

You need to install the Nutrient package, along with any dependencies required for your React application, such as Next.js.

Can I customize the appearance of the images added to the PDF?

Yes, you can customize the bounding box of the image annotation by adjusting the left and top properties when creating the annotation.

Author
Ritesh Kumar Web Engineer

Ritesh loves to write code, play keyboard, and paint. He likes working on projects that involve developer tooling, design systems, and music. He wants to make art a part of everyone’s life by using technology.

Explore related topics

Related products
Share post
Free trial Ready to get started?
Free trial