useElementPageContext Organization
The useElementPageContext hook provides a function to add custom elements to a PDF page.
It enables you to create custom elements such as a bounding box, a shape or an image for a specified page.
This is useful for labelling certain parts of a PDF page or displaying certain content dynamically.
Return Type
Section titled “Return Type”| Name | Type | Description |
|---|---|---|
| clearElements | (pageNumber: number) => void | Remove all custom elements from the specific page |
| elementList | Record<number, Array<HTMLElement | JSX.Element>> | Access the current scale level of the current PDF page |
| removeElement | (pageNumber: number, index: number) => void | Remove a custom element from the specific page |
| scrollToElement | (page: number, index: number, config?: { behavior: ‘smooth’ | ‘auto’ | ‘instant’ }) => void | Scroll to a specific element on the given page, centering it in the viewer. The default scroll behavior is ‘smooth’ |
| updateElement | (pageNumber: number, element: (dimension: {width: number, height: number}, rotate: number, zoom: number) => HTMLElement | JSX.Element) => void | Add a custom element to the specific page |
To ensure the element page context can be accessed, you will need to use useElementPageContext inside a component which is a child of RPProvider.
This example shows how to create a CustomElementPage component which uses useElementPageContext to handle the element page functionality.
import { useCallback, useState, useEffect } from 'react' import { RPConfig, RPProvider, RPDefaultLayout, RPPages, useElementPageContext } from '@pdf-viewer/react'
const createHTMLElement = ( id, pageDimension, position = { x: 0, y: 0 } ) => { const div = document.createElement('div') // set id to element div.id = id // setting style div.style.backgroundColor = 'green' // set dimension as percentage of page dimension div.style.width = `${(100 / pageDimension.width) * 100}%` div.style.height = `${(100 / pageDimension.height) * 100}%` div.style.color = 'white' div.style.padding = '2px 4px' div.style.borderRadius = '4px' div.style.position = 'absolute' // set position as percentage of page dimension div.style.top = `${(position.y / pageDimension.height) * 100}%` div.style.left = `${(position.x / pageDimension.width) * 100}%` return div }
// function to create react jsx element const createJSXElement = ( id, pageDimension, position = { x: 0, y: 0 }, removeElement ) => { return ( <div id={id} style={{ backgroundColor: 'red', // set dimension as percentage of page dimension width: `${(100 / pageDimension.width) * 100}%`, height: `${(100 / pageDimension.height) * 100}%`, color: 'white', padding: '2px 4px', borderRadius: '4px', position: 'absolute', // set position as percentage of page dimension top: `${(position.y / pageDimension.height) * 100}%`, left: `${(position.x / pageDimension.width) * 100}%`, zIndex: 10 }} > <button onClick={removeElement}>remove</button> </div> ) }
const CustomElement = () => { const { updateElement, clearElements, scrollToElement } = useElementPageContext() const [page, setPage] = useState(1) const [x, setX] = useState(0) const [y, setY] = useState(0) const [list, setList] = useState({ [1]: [ { x: 0, y: 0 }, { x: 50, y: 50, removable: true } ] })
useEffect(() => { // assume page dimension at scale 100% is 612 x 792 const pageDimension = { width: 612, height: 792 }
const pages = Object.keys(list) // list element Object.entries(list).forEach(([page, items]) => { items.forEach((item, idx) => { updateElement(Number(page), (_prev = [], _dimension, _rotate, _scale) => { const id = `${page}-${idx}` const newElement = item.removable ? createJSXElement( id, pageDimension, { x: item.x, y: item.y }, item.removable ? () => removeItem(Number(page), idx) : undefined ) : createHTMLElement(id, pageDimension, { x: item.x, y: item.y }) return [..._prev, newElement] }) }) })
return () => { pages.forEach((page) => { clearElements(Number(page)) }) clearElements(1) } }, [updateElement, list, clearElements])
// remove item from list const removeItem = useCallback((page, index) => { setList((prev) => { const newList = { ...prev } newList[page] = newList[page].filter((_, i) => i !== index) return newList }) }, [])
const handleClear = useCallback(() => { setList((prev) => { const newList = { ...prev } delete newList[page] return newList }) }, [page])
const handleAdd = useCallback(() => { setList((prev) => ({ ...prev, [page]: [ ...(prev[page] || []), { x, y } ] })) }, [page, x, y])
const handleGoToElement = useCallback(() => { scrollToElement(1, 0) }, [scrollToElement])
return ( <> <div> page: <input type="number" value={page} onChange={(e) => setPage(Number(e.target.value))} /> </div> <div> x: <input type="number" value={x} onChange={(e) => setX(Number(e.target.value))} /> </div> <div> y: <input type="number" value={y} onChange={(e) => setY(Number(e.target.value))} /> </div> <button onClick={handleClear}>clear</button> <button onClick={handleAdd}>add</button> <button onClick={handleGoToElement}>go to first element on page 1</button> </> ) }
export const AppPdfViewer = () => { return ( <RPConfig licenseKey="YOUR_LICENSE_KEY"> <RPProvider src="https://cdn.codewithmosh.com/image/upload/v1721763853/guides/web-roadmap.pdf"> <CustomElement /> <RPDefaultLayout> <RPPages /> </RPDefaultLayout> </RPProvider> </RPConfig> ) } import { useCallback, useState, useEffect, FC } from 'react' import { RPConfig, RPProvider, RPDefaultLayout, RPPages, useElementPageContext } from '@pdf-viewer/react'
const createHTMLElement = ( id: string, pageDimension: { width: number; height: number }, position = { x: 0, y: 0 } ) => { const div = document.createElement('div') // set id to element div.id = id // setting style div.style.backgroundColor = 'green' // set dimension as percentage of page dimension div.style.width = `${(100 / pageDimension.width) * 100}%` div.style.height = `${(100 / pageDimension.height) * 100}%` div.style.color = 'white' div.style.padding = '2px 4px' div.style.borderRadius = '4px' div.style.position = 'absolute' // set position as percentage of page dimension div.style.top = `${(position.y / pageDimension.height) * 100}%` div.style.left = `${(position.x / pageDimension.width) * 100}%` return div }
// function to create react jsx element const createJSXElement = ( id: string, pageDimension: { width: number; height: number }, position = { x: 0, y: 0 }, removeElement?: () => void ) => { return ( <div id={id} style={{ backgroundColor: 'red', // set dimension as percentage of page dimension width: `${(100 / pageDimension.width) * 100}%`, height: `${(100 / pageDimension.height) * 100}%`, color: 'white', padding: '2px 4px', borderRadius: '4px', position: 'absolute', // set position as percentage of page dimension top: `${(position.y / pageDimension.height) * 100}%`, left: `${(position.x / pageDimension.width) * 100}%`, zIndex: 10 }} > <button onClick={removeElement}>remove</button> </div> ) }
const CustomElement = () => { const { updateElement, clearElements, scrollToElement } = useElementPageContext() const [page, setPage] = useState(1) const [x, setX] = useState(0) const [y, setY] = useState(0) const [list, setList] = useState< Record<number, Array<{ x: number; y: number; removable?: boolean }>> >({ [1]: [ { x: 0, y: 0 }, { x: 50, y: 50, removable: true } ] })
useEffect(() => { // assume page dimension at scale 100% is 612 x 792 const pageDimension = { width: 612, height: 792 }
const pages = Object.keys(list) // list element Object.entries(list).forEach(([page, items]) => { items.forEach((item, idx) => { updateElement(Number(page), (_prev = [], _dimension, _rotate, _scale) => { const id = `${page}-${idx}` const newElement = item.removable ? createJSXElement( id, pageDimension, { x: item.x, y: item.y }, item.removable ? () => removeItem(Number(page), idx) : undefined ) : createHTMLElement(id, pageDimension, { x: item.x, y: item.y }) return [..._prev, newElement] }) }) })
return () => { pages.forEach((page) => { clearElements(Number(page)) }) clearElements(1) } }, [updateElement, list, clearElements])
// remove item from list const removeItem = useCallback((page: number, index: number) => { setList((prev) => { const newList = { ...prev } newList[page] = newList[page].filter((_, i) => i !== index) return newList }) }, [])
const handleClear = useCallback(() => { setList((prev) => { const newList = { ...prev } delete newList[page] return newList }) }, [page])
const handleAdd = useCallback(() => { setList((prev) => ({ ...prev, [page]: [ ...(prev[page] || []), { x, y } ] })) }, [page, x, y])
const handleGoToElement = useCallback(() => { scrollToElement(1, 0) }, [scrollToElement])
return ( <> <div> page: <input type="number" value={page} onChange={(e) => setPage(Number(e.target.value))} /> </div> <div> x: <input type="number" value={x} onChange={(e) => setX(Number(e.target.value))} /> </div> <div> y: <input type="number" value={y} onChange={(e) => setY(Number(e.target.value))} /> </div> <button onClick={handleClear}>clear</button> <button onClick={handleAdd}>add</button> <button onClick={handleGoToElement}>go to first element on page 1</button> </> ) }
export const AppPdfViewer: FC = () => { return ( <RPConfig licenseKey="YOUR_LICENSE_KEY"> <RPProvider src="https://cdn.codewithmosh.com/image/upload/v1721763853/guides/web-roadmap.pdf"> <CustomElement /> <RPDefaultLayout> <RPPages /> </RPDefaultLayout> </RPProvider> </RPConfig> ) }