惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Jina AI
Jina AI
大猫的无限游戏
大猫的无限游戏
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
Hugging Face - Blog
Hugging Face - Blog
阮一峰的网络日志
阮一峰的网络日志
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
V
V2EX
博客园 - 叶小钗
雷峰网
雷峰网
小众软件
小众软件
量子位
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The GitHub Blog
The GitHub Blog
Martin Fowler
Martin Fowler
G
Google Developers Blog
博客园_首页
博客园 - Franky
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享

Inside Nutrient

A guide to the invisible work behind documents Introducing Nutrient Documents for Salesforce: Native document generation and signing Document AI vs. traditional OCR: Choosing between OCR, AI, and hybrid pipelines PDF SDK compliance and security evaluation checklist for enterprise teams (2026) Invariant Corp replaces paper processes with Nutrient Workflow and scales without limits What is process mapping? A complete guide Nutrient vs. Conga Composer for Salesforce document generation (2026) Document routing: How to automate document distribution The CTO’s AI playbook: Why accountability architecture beats orchestration Compliance workflow automation: Why built-in compliance is table stakes Workflow diagrams: Examples, symbols, and how to build one that actually runs Digital forms: Replace paper forms with automated workflows Approval workflow software: How to automate approvals Why document-centric automation is different The CEO’s AI playbook: Why decision architecture beats model selection Nutrient SDK product updates for Q1 2026 PDF redaction verification: How to prove sensitive data is permanently removed What is a VPAT? The complete guide to accessibility conformance reports What is PDF/UA? The accessible PDF standard explained Salesforce eSignatures: Generate, sign, and track documents in one flow Online document viewer: Options, tradeoffs, and how to embed one Document viewer for web apps: React, Vue, Angular (2026) Best document viewers in 2026: A buyer’s guide How to edit a PDF in Python: Add text, images, and annotations Nutrient advances Workflow platform with agentic AI for enterprise-grade speed and consistency in document-heavy operations How to create a Salesforce quote template from opportunity data The business case for accessibility: Five ways it drives enterprise value Python PDF library comparison (2026): 7 libraries for developers Why your AI agent hallucinates PDF table data PDF.js limitations: When to upgrade to a commercial PDF SDK
PDF.js EventBus guide: Events, hooks, and custom pub/sub ...
Austin Nguyen · 2026-06-08 · via Inside Nutrient

Table of contents

    The EventBus is PDF.js’s internal pub/sub system that powers every viewer interaction. This guide covers built-in events, React hook patterns, and how to extend it for custom annotation features.

    PDF.js EventBus guide: Events, hooks, and custom pub/sub patterns

    TL;DR

    • The EventBus is PDF.js’s internal pub/sub channel — every interaction (page change, zoom, render, search) fires through it.
    • Subscribe with eventBus.on(name, handler) and clean up with eventBus.off(name, handler) in useEffect returns.
    • Dispatch your own events (e.g. annotationcolorchange) to bridge React components and PDF.js’s vanilla JS class instances.

    Prerequisites

    This guide assumes you have a PDFViewer composed from pdfjs-dist/web/pdf_viewer.mjs and an EventBus instance shared between the viewer, link service, and find controller. If you haven’t wired those up yet, start with our blog on how to set up a custom PDF.js viewer in React.

    You’ll also need:

    • pdfjs-dist 4.x or later
    • A React app (the hook patterns below use React 18+ effect semantics)

    Creating an EventBus

    Import the viewer layer and construct a single EventBus instance that every component will share:

    const pdfjs = await import("pdfjs-dist/web/pdf_viewer.mjs");

    const eventBus = new pdfjs.EventBus();

    The EventBus is shared across all PDF.js components — PDFViewer, PDFLinkService, PDFFindController — and your own custom code.

    Subscribing and unsubscribing

    Register a handler with eventBus.on(name, handler) and remove it with eventBus.off(name, handler):

    // Subscribe.

    eventBus.on("pagechanging", (evt) => {

    console.log("Now on page:", evt.pageNumber);

    });

    // Unsubscribe.

    eventBus.off("pagechanging", handler);

    Built-in events reference

    PDF.js fires a fixed set of built-in events. The tables below group the most useful ones — by page lifecycle, scale and rotation, and search — with their payloads and when each fires.

    Page lifecycle

    These events fire as the document loads and as pages scroll into view and finish rendering:

    EventPayloadWhen
    pagesinit{}All pages initialized after document load
    pagechanging{ pageNumber }User scrolls to a different page
    pagerendered{ pageNumber, cssTransform, timestamp }A page canvas finishes rendering
    textlayerrendered{ pageNumber }Text layer overlay is ready on a page

    Scale and rotation

    These events fire whenever the zoom level or page rotation changes:

    EventPayloadWhen
    scalechanging{ scale, presetValue }Zoom level changes
    rotationchanging{ pagesRotation }Page rotation changes

    Search/find

    These events drive find operations and report their results back to your UI:

    EventPayloadWhen
    find{ type, query, caseSensitive, ... }You dispatch this to trigger search
    updatefindmatchescount{ matchesCount: { current, total } }Match count updates
    updatefindcontrolstate{ state, matchesCount }Find state changes (FOUND, NOT_FOUND, WRAPPED, PENDING)

    Pattern: React hook for EventBus events

    Wrap subscription and cleanup in a reusable hook so each listener is removed when the component unmounts:

    function useEventBus(eventBus, eventName, handler) {

    React.useEffect(() => {

    eventBus?.on(eventName, handler);

    return () => {

    eventBus?.off(eventName, handler);

    };

    }, [eventBus, eventName, handler]);

    }

    // Usage.

    useEventBus(eventBus.current, "pagechanging", (evt) => {

    setCurrentPage(evt.pageNumber);

    });

    Pattern: Custom events via EventBus

    The EventBus isn’t limited to built-in events. You can dispatch your own custom events and use it as a cross-component communication channel:

    class PDFAnnotationState {

    #eventBus;

    #color = "red";

    #tool = null;

    #showAnnotations = true;

    constructor({ eventBus }) {

    this.#eventBus = eventBus;

    }

    get color() {

    return this.#color;

    }

    set color(newColor) {

    this.#color = newColor;

    this.#eventBus.dispatch("annotationcolorchange", {

    source: this,

    color: newColor,

    });

    }

    get tool() {

    return this.#tool;

    }

    set tool(newTool) {

    this.#tool = newTool;

    this.#eventBus.dispatch("annotationtoolchange", {

    source: this,

    tool: newTool,

    });

    }

    get showAnnotations() {

    return this.#showAnnotations;

    }

    set showAnnotations(show) {

    this.#showAnnotations = show;

    this.#eventBus.dispatch("annotationshowchange", {

    source: this,

    showAnnotations: show,

    });

    }

    }

    Then listen to these events from any component:

    function useAnnotationState(eventBus, annotationState) {

    const [color, setColor] = React.useState(annotationState?.color);

    const [tool, setTool] = React.useState(annotationState?.tool);

    const [show, setShow] = React.useState(annotationState?.showAnnotations);

    React.useEffect(() => {

    const onColor = (evt) => setColor(evt.color);

    const onTool = (evt) => setTool(evt.tool);

    const onShow = (evt) => setShow(evt.showAnnotations);

    eventBus?.on("annotationcolorchange", onColor);

    eventBus?.on("annotationtoolchange", onTool);

    eventBus?.on("annotationshowchange", onShow);

    return () => {

    eventBus?.off("annotationcolorchange", onColor);

    eventBus?.off("annotationtoolchange", onTool);

    eventBus?.off("annotationshowchange", onShow);

    };

    }, [eventBus, annotationState]);

    return { color, tool, show };

    }

    Why EventBus instead of React state?

    PDF.js components (PDFViewer, PDFFindController, etc.) are not React components — they’re vanilla JS class instances. They can’t subscribe to React state. The EventBus bridges this gap:

    1. Toolbar changes annotation color → sets annotationState.color = "blue"
    2. Setter dispatches annotationcolorchange via EventBus
    3. Annotation component listens via .on("annotationcolorchange") and updates its React state

    This pattern keeps your custom PDF features integrated with PDF.js’s own event system instead of creating a parallel state management layer.

    Key events for common features

    This table maps common viewer features to the events you’ll typically subscribe to:

    FeatureEvents to listen to
    Page navigation toolbarpagechanging
    Zoom controlsscalechanging
    Custom overlay layerstextlayerrendered, pagerendered
    Search results displayupdatefindcontrolstate, updatefindmatchescount
    Annotation renderingtextlayerrendered (to create overlay containers)
    Highlight positioningscalechanging, rotationchanging

    Gotchas

    • Always clean up listeners in useEffect return functions — PDF.js EventBus doesn’t clean up automatically
    • The dispatch method requires a source property in the event object by convention
    • EventBus events are synchronous — heavy handlers will block rendering
    • textlayerrendered fires per page, not once for the whole document

    How Nutrient Web SDK handles this

    Nutrient Web SDK exposes the same kinds of viewer events through standard DOM-style addEventListener/removeEventListener calls. Event names are TypeScript-typed, so your editor can complete them and flag typos.

    instance.addEventListener("viewState.currentPageIndex.change", (pageIndex) => {

    console.log("Now on page:", pageIndex + 1);

    });

    instance.addEventListener("viewState.zoom.change", (zoom) => {

    console.log("Zoom level:", zoom);

    });

    For broader viewer changes, listen to viewState.change and receive the full state diff.

    Learn more about Nutrient Web SDK | Migration guide | Contact Sales

    FAQ

    Use eventBus.on/eventBus.off. The _on/_off forms exist as legacy internal aliases, but PDF.js’s public documentation and viewer source use the non-underscored names. Sticking to the public form makes your code easier to read and less likely to break on a future PDF.js version.

    PDF.js’s own components inspect evt.source to avoid reacting to events they themselves dispatched (preventing feedback loops). When you call eventBus.dispatch("myevent", { source: this, ... }), you’re following the same convention so downstream listeners can filter by origin.

    Yes. Handlers fire in the order they were subscribed, on the same microtask. Long-running work in a listener will block subsequent listeners and delay the rendering pipeline — wrap expensive operations in queueMicrotask, setTimeout, or requestIdleCallback when needed.

    Per page. Each PageView fires its own textlayerrendered event when its text layer overlay finishes rendering. That makes it a useful hook for inserting per-page overlays (highlights, annotations), but a poor signal for “the whole document is ready.”

    Technically yes, but you almost never want to. PDF.js’s viewer components are constructed with a shared EventBus so that PDFViewer, PDFLinkService, PDFFindController, and your code all see the same events. Separate buses defeat the purpose of having one.

    The handler keeps running for the lifetime of the EventBus, even after the React component that registered it has unmounted. That typically manifests as memory leaks and stale-state warnings (handler closes over old state). Always clean up in the useEffect return.

    Explore related topics

    Try for free Ready to get started?

    Related SDK articles

    Explore more