Custom Annotation Rendering with External Interactive Content

This example shows how to replace default PDF annotation rendering with custom HTML overlays (iframe, WebGL, video, etc.). The overlays synchronize with scrolling, zooming, and layout changes, and are not persisted in the PDF.

// PDF Viewer instance let viewer; // Overlay container let overlayLayer = null; // Stores created overlays by annotation id const overlays = new Map(); window.onload = function () { viewer = new DsPdfViewer("#viewer"); viewer.addDefaultPanels(); viewer.addAnnotationEditorPanel(); viewer.addFormEditorPanel(); viewer.onAfterOpen.register(async () => { createOverlayLayer(); updateLayerTransform(); const pages = await viewer.annotations; render3DAnnotations(pages); // Update positions on scroll viewer.scrollView.addEventListener("scroll", updateAllOverlayPositions); // Update positions on zoom viewer.eventBus.on("scalechange", () => { updateLayerTransform(); // Delay slightly so zoom finishes first setTimeout(updateAllOverlayPositions, 0); }); // Update positions on resize window.addEventListener("resize", () => { updateLayerTransform(); updateAllOverlayPositions(); }); }); viewer.open("/document-solutions/javascript-pdf-viewer/demos/product-bundles/assets/pdf/custom-annotation-rendering.pdf"); }; /** * Creates overlay layer once. */ function createOverlayLayer() { if (overlayLayer) return; const documentView = viewer.hostElement.querySelector(".gcv-document-view"); overlayLayer = document.createElement("div"); overlayLayer.className = "overlay-layer"; // Prevent layer itself from blocking mouse events overlayLayer.style.pointerEvents = "none"; documentView.appendChild(overlayLayer); } /** * Align overlay coordinates with window coordinates. */ function updateLayerTransform() { const documentView = viewer.hostElement.querySelector(".gcv-document-view"); if (!documentView || !overlayLayer) return; const rect = documentView.getBoundingClientRect(); overlayLayer.style.transform = `translate(${-rect.left}px, ${-rect.top}px)`; } /** * Creates overlays once. */ function render3DAnnotations(pages) { for (const page of pages) { for (const annotation of page.annotations) { if (annotation.subtype !== "3D") continue; const id = annotation.id || `${page.pageIndex}_${annotation.rect.join("_")}`; // Skip if already created if (overlays.has(id)) continue; const overlay = create3DOverlay(); overlays.set(id, { overlay, pageIndex: page.pageIndex, annotation }); overlayLayer.appendChild(overlay); } } updateAllOverlayPositions(); } /** * Updates positions of all overlays. */ function updateAllOverlayPositions() { for (const item of overlays.values()) { const rect = viewer.convertPdfRectToWindowCoordinates( item.pageIndex, item.annotation.rect ); updateOverlayPosition(item.overlay, rect); } } /** * Creates a single overlay. */ function create3DOverlay() { const overlay = document.createElement("div"); overlay.className = "model3d-placeholder"; overlay.style.position = "absolute"; // Allow interaction with iframe overlay.style.pointerEvents = "auto"; overlay.innerHTML = ` <iframe src="https://viewer.ctech.com/inline_viewer.html?apikey=CEBA68C1CC918347&model=https%3A%2F%2Fviewer.ctech.com%2Fmodels%2Fcoastal-facility.ctws" width="100%" height="100%" frameborder="0" allowfullscreen ></iframe> `; return overlay; } /** * Updates overlay position and size. */ function updateOverlayPosition(overlay, rect) { const [x1, y1, x2, y2] = rect; overlay.style.left = `${x1}px`; overlay.style.top = `${y1}px`; overlay.style.width = `${x2 - x1}px`; overlay.style.height = `${y2 - y1}px`; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Custom Annotation Rendering with External Interactive Content</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="./src/styles.css"> <script src="/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/dspdfviewer.js"></script> <script src="/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/wasmSupportApi.js"></script> <script src="/document-solutions/javascript-pdf-viewer/demos/resource/js/init.js"></script> <script src="./src/app.js"></script> </head> <body> <div id="viewer"></div> </body> </html>
#viewer { height: 100%; } .overlay-layer { position: absolute; inset: 0; pointer-events: none; z-index: 1000; } .model3d-placeholder { position: absolute; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.75); color: white; font-size: 24px; font-weight: bold; border: 2px dashed white; box-sizing: border-box; }