Jump to the Next Bbox Highlight Area
Scenario
Section titled “Scenario”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:
- Highlight multiple regions on page 1 (first page) with exact coordinates
- Highlight additional regions on page 2 (second page) with exact coordinates
- Each highlighted region uses a different color to distinguish different areas of interest
- Automatically scales highlights when users zoom in/out
- 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.
What to Use
Section titled “What to Use”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
| Name | Objective |
|---|---|
updateElement | Add custom overlay elements to specific pages based on bbox coordinates |
scrollToElement | Scroll 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 Factor | Automatically adjust highlight positioning for different zoom levels |
import { useEffect } from "react";import { RPProvider, RPPages, useElementPageContext, RPLayout, RPHorizontalBar,} from "@pdf-viewer/react";
// Define highlight data structureconst 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 pagefunction 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 listconst 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;import { useEffect, type FC } from "react";import { RPProvider, RPPages, useElementPageContext, RPLayout, RPHorizontalBar,} from "@pdf-viewer/react";
// Define interfaces for type safetyinterface BoundingBox { x: number; y: number; width: number; height: number;}
interface HighlightData { id: string; pageIndex: number; // Zero-based page index index: number; // Zero-based index of the highlight on the page bbox: BoundingBox; highlightColor: string;}
// Define highlight data structureconst highlightDataList: HighlightData[] = [ { 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 pagefunction groupByPageIndex( highlights: HighlightData[]): Record<number, HighlightData[]> { return highlights.reduce((acc, item) => { const pageIndex = item.pageIndex; if (!acc[pageIndex]) { acc[pageIndex] = []; } acc[pageIndex].push(item); return acc; }, {} as Record<number, HighlightData[]>);}
const BboxHighlightLayer: FC = () => { 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, highlightDataList]);
return null;};
// Left sidebar displaying BboxHighlightLayer listconst MyLeftSidebar = () => { const { scrollToElement } = useElementPageContext();
const handleClickListItem = (highlight: HighlightData) => { 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: FC = () => { 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
updateElementfunction uses 1-based page numbers, so add 1 to the zero-basedpageIndex - The
scrollToElementfunction uses 1-based page numbers, so add 1 to the zero-basedpageIndex - The
indexparameter fromscrollToElementis the zero-based index of the highlights on the page - The
scaleparameter fromupdateElementrepresents 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