Skip to content
Jump to the Next Bbox Highlight Area

When the PDF document is loaded, the system programmatically highlights specific regions using bounding box coordinates and allows users to jump to the next highlight area:

  1. Highlight multiple regions on page 1 (first page) with exact coordinates
  2. Highlight additional regions on page 2 (second page) with exact coordinates
  3. Each highlighted region uses a different color to distinguish different areas of interest
  4. Automatically scales highlights when users zoom in/out
  5. Allow users to jump to the next highlight area by clicking the button in the left sidebar

The bounding box coordinates define precise rectangular areas on the PDF pages, making it ideal for:

  • Highlighting table cells, form fields, or specific document sections
  • Marking regions detected by OCR or document analysis tools
  • Displaying annotations from external data sources with precise positioning
  • Visualizing search results or text/table extraction

Note: For more explanation on bounding box coordinates, refer to Understanding Bounding Box Coordinates.

The useElementPageContext hook provides the updateElement and scrollToElement functions to programmatically add overlay elements to PDF pages and scroll to the next highlight area. This function accepts a page number and a callback that returns React elements positioned using bounding box coordinates. The callback receives the current scale factor, allowing highlights to automatically adjust when users zoom in or out.

Here are the key concepts we’re using for this example

NameObjective
updateElementAdd custom overlay elements to specific pages based on bbox coordinates
scrollToElementScroll to a specific element on the given page, centering it in the viewer.
Bounding Box (bbox)Define rectangular regions with x, y, width, and height in PDF points
Scale FactorAutomatically adjust highlight positioning for different zoom levels
import { useEffect } from "react";
import {
RPProvider,
RPPages,
useElementPageContext,
RPLayout,
RPHorizontalBar,
} from "@pdf-viewer/react";
// Define highlight data structure
const highlightDataList = [
{
id: "highlight-1",
pageIndex: 0, // Zero-based page index
index: 0, // Zero-based index of the highlight on the page
bbox: { x: 70, y: 70, width: 470, height: 50 },
highlightColor: "rgba(255, 179, 0, 0.5)", // Orange
},
{
id: "highlight-2",
pageIndex: 0,
index: 1,
bbox: { x: 45, y: 330, width: 255, height: 100 },
highlightColor: "rgba(0, 255, 255, 0.5)", // Light blue
},
{
id: "highlight-3",
pageIndex: 0,
index: 2,
bbox: { x: 310, y: 684, width: 250, height: 45 },
highlightColor: "rgba(0, 0, 255, 0.5)", // Blue
},
{
id: "highlight-4",
pageIndex: 1,
index: 0,
bbox: { x: 310, y: 142, width: 255, height: 30 },
highlightColor: "rgba(255, 0, 255, 0.5)", // Magenta
},
];
// Helper function to group highlights by page
function groupByPageIndex(highlights) {
return highlights.reduce((acc, item) => {
const pageIndex = item.pageIndex;
if (!acc[pageIndex]) {
acc[pageIndex] = [];
}
acc[pageIndex].push(item);
return acc;
}, {});
}
const BboxHighlightLayer = () => {
const { updateElement } = useElementPageContext();
useEffect(() => {
// Group highlights by page for efficient rendering
const highlightsByPage = groupByPageIndex(highlightDataList);
// Iterate through each page that has highlights
for (const pageIndexStr in highlightsByPage) {
const pageIndex = Number(pageIndexStr);
const pageHighlights = highlightsByPage[pageIndex];
// Update the specified page with highlight overlays
// Note: updateElement uses 1-based page numbers
updateElement(
pageIndex + 1,
(_prevElements, _dimension, _rotate, scale) => {
// Calculate scale factor for proper positioning at any zoom level
const scaleFactor = scale / 100;
// Create a highlight overlay for each bbox on this page
return pageHighlights.map((highlight, index) => {
const { bbox, highlightColor, id } = highlight;
return (
<div
key={id || `highlight-${pageIndex}-${index}`}
style={{
position: "absolute",
// Apply scale factor to bbox coordinates
left: bbox.x * scaleFactor,
top: bbox.y * scaleFactor,
width: bbox.width * scaleFactor,
height: bbox.height * scaleFactor,
backgroundColor: highlightColor,
pointerEvents: "none",
}}
/>
);
});
}
);
}
}, [updateElement]);
return null;
};
// Left sidebar displaying BboxHighlightLayer list
const MyLeftSidebar = () => {
const { scrollToElement } = useElementPageContext();
const handleClickListItem = (highlight) => {
scrollToElement(highlight.pageIndex + 1, highlight.index);
};
return (
<>
{/* BboxHighlightLayer is the layer that displays the bboxes of the highlights */}
<BboxHighlightLayer />
{/* Left sidebar displaying BboxHighlightLayer list */}
<div style={{ padding: "8px", overflow: "auto" }}>
<ol style={{ listStyle: "none", padding: 0, margin: 0 }}>
{highlightDataList.map((highlight) => (
<li
key={highlight.id}
style={{ fontSize: "12px", marginTop: "12px" }}
>
<div
style={{
padding: "5px",
cursor: "pointer",
backgroundColor: "rgba(100, 100, 100, 0.2)",
}}
onClick={() => handleClickListItem(highlight)}
>
<div style={{ paddingRight: "5px" }}>
page: {highlight.pageIndex + 1}
</div>
<span>id: {highlight.id}</span>
<div>
bbox: x:{highlight.bbox.x}, y:{highlight.bbox.y}, w:
{highlight.bbox.width}, h:{highlight.bbox.height}
</div>
</div>
</li>
))}
</ol>
</div>
</>
);
};
const AppPdfViewer = () => {
return (
<>
<RPProvider src="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf">
<RPLayout
toolbar={{
topbar: { component: <RPHorizontalBar /> },
leftSidebar: { component: <MyLeftSidebar /> },
}}
>
<RPPages />
</RPLayout>
</RPProvider>
</>
);
};
export default AppPdfViewer;

Notes:

  • The updateElement function uses 1-based page numbers, so add 1 to the zero-based pageIndex
  • The scrollToElement function uses 1-based page numbers, so add 1 to the zero-based pageIndex
  • The index parameter from scrollToElement is the zero-based index of the highlights on the page
  • The scale parameter from updateElement represents zoom percentage (100 = 100%, 150 = 150%)
  • Apply the scale factor to all bbox coordinates for proper positioning at different zoom levels
  • Use pointerEvents: "none" to prevent highlights from interfering with PDF interactions