Skip to content

Latest commit

 

History

64 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GitHub License

Edirom Image Viewer Component

This web component displays IIIF images using the OpenSeadragon library. It is intended to be used in the Edirom Online, but can also be (re-)used in other web applications. No compilation or building is necessary to use the web component.

Note: This repository only contains the bare JavaScript-based component. There is a separate demo suite for web components developed in the Edirom Online Reloaded project, where the component can be seen and tested.

Features

  • IIIF Support: Load IIIF manifests or direct tile sources
  • Image Navigation: Navigate through multi-page image sequences
  • Zoom & Pan: Interactive zoom and pan controls
  • Zoom Limits: Enforced minzoomlevel / maxzoomlevel bounds — programmatic zoom is clamped to the configured range
  • Rotation: Rotate images to any angle
  • Customizable Controls: Show/hide navigator, zoom buttons, home button, fullscreen toggle
  • Attribute-Driven: All interactions through standard HTML attributes
  • Custom Configuration: Pass advanced OpenSeadragon options via JSON
  • Event Communication: Custom events for state changes
  • Region Navigation (zones-data / zone): Define a lookup map of named regions (zones) and jump to any of them with pixel-precise, aspect-preserving viewport fits, including automatic cross-page navigation. All navigable regions — including music measures/bars and MEI <mdiv> movements — are expressed as ordinary zone entries; the host chooses the key convention (e.g. namespaced measure:... / mdiv:... keys).
  • Annotation Overlays (annotations-data / show-annotations): Render clickable annotation badges on top of the current image, with a persistent show/hide toggle, category/priority filtering (visible-categories / visible-priorities), and component-rendered hover tooltips (host-supplied HTML via the tooltip field).
  • Rectangle Fit (fitrect): Fit the viewport to an arbitrary image-pixel rectangle.
  • View Mode (view-mode): Declarative view-mode attribute that is recorded and re-broadcast for host code to react to.

License

The edirom-image-viewer.js comes with the MIT license.

The imported OpenSeadragon library comes with the BSD-3-Clause license.

How to Use This Web Component

1. Include the Component

Add the web component script to your HTML page's <head>:

<script src="https://cdn.jsdelivr.net/npm/openseadragon@4.1.1/build/openseadragon/openseadragon.min.js"></script>
<script src="path/to/edirom-image-viewer.js"></script>

2. Add the Custom Element

Include the custom element in your HTML <body>:

<edirom-image-viewer 
    tilesources='["https://example.com/iiif/manifest.json"]'
    shownavigator="true"
    showzoomcontrol="true"
    sequencemode="true">
</edirom-image-viewer>

3. Interact via Attributes

Control the component by setting attributes programmatically:

const viewer = document.querySelector('edirom-image-viewer');
viewer.setAttribute('zoom', '2.5');
viewer.setAttribute('rotation', '90');
viewer.setAttribute('pagenumber', '5');

4. Listen to Events

The component fires custom events when state changes:

viewer.addEventListener('communicate-zoom-update', (event) => {
    console.log('Zoom changed:', event.detail);
});

Attributes

Note: All attribute values are strings. The data type information indicates the expected format.

Important Note on Page Numbering: Page numbers are 1-based for user-facing interactions. This means:

  • Page 1 = First image
  • Page 2 = Second image
  • And so on...

This applies to pagenumber attribute and all page-related methods. The component automatically converts between 1-based (user) and 0-based (internal) indexing.

Attribute Type Description Default
tilesources string JSON array of IIIF manifest URLs or tile source URLs. Example: '["https://example.com/manifest.json"]' or '["https://example.com/info.json"]' ""
pagenumber number Current page number in a multi-image sequence (1-based, where 1 = first image). 1
zoom number Zoom level of the viewer. Values are clamped to [minzoomlevel, maxzoomlevel]. 1
rotation number Rotation angle in degrees (0-360). 0
preserveviewport boolean Preserve the current viewport (zoom/pan) when changing pages. false
clicktozoom boolean Enable click-to-zoom functionality. true
minzoomlevel number Minimum allowed zoom level. Programmatic zoom is clamped to this lower bound. OSD default
maxzoomlevel number Maximum allowed zoom level. Programmatic zoom is clamped to this upper bound. OSD default
shownavigationcontrol boolean Show/hide all navigation controls. true
sequencemode boolean Enable sequence mode for multi-image navigation. false
shownavigator boolean Show/hide the navigator mini-map. true
showzoomcontrol boolean Show/hide zoom in/out buttons. true
showhomecontrol boolean Show/hide the home/reset view button. true
showfullpagecontrol boolean Show/hide the fullscreen toggle button. true
showsequencecontrol boolean Show/hide previous/next page buttons (requires sequencemode="true"). true
triggerhome boolean Trigger home position reset (set to "true" to reset view to initial state). "false"
triggerfullscreen boolean Trigger fullscreen mode toggle (set to "true" to toggle fullscreen). "false"
openseadragon-options string JSON object with additional OpenSeadragon configuration options. Example: '{"showNavigator": true}' ""
zones-data string JSON object mapping zone keys to zone objects. Each zone: { page: number, ulx: number, uly: number, lrx: number, lry: number } (coordinates optional). Also used for measures and movements via host-chosen namespaced keys. See Region Navigation for details. "{}"
zone string Key of the zone to navigate to. Must exist in zones-data. Setting this attribute triggers navigation to the zone. Append |<nonce> to re-fire navigation to the same zone. ""
annotations-data string JSON array of annotation overlays. Each entry: { idPrefix, id, title, uri, categories, priority, fn, tooltip, plist }, where tooltip is optional host-supplied HTML rendered by the component on hover and plist is an array of image-pixel regions { id, ulx, uly, lrx, lry, type }. Rendered as clickable badges. See Annotation Overlays. "[]"
show-annotations boolean Show/hide the rendered annotation overlays. Toggles visibility without discarding annotations-data; the last state persists across page changes. false
visible-categories string JSON array of category ids that should remain visible. ["undefined"] (no category taxonomy) or absent shows all; [] hides all; otherwise a badge is shown only if one of its categories is listed. See Annotation Overlays. null
visible-priorities string JSON array of priority ids that should remain visible. Same ["undefined"] / [] / list semantics as visible-categories. A badge is shown only when it passes both filters. null
fitrect string Fit the viewport to an image-pixel rectangle "x,y,width,height". An optional trailing ,<nonce> token re-fires the same fit. ""
view-mode string Declarative view mode (e.g. pageBasedView / measureBasedView). Recorded and re-broadcast via the view-mode-changed event for host code to react to. ""

Public Methods

The component provides the following public methods:

Navigation

  • nextPage() - Navigate to the next page
  • previousPage() - Navigate to the previous page
  • goToPage(pageNumber) - Navigate to a specific page
  • getCurrentPage() - Get the current page number
  • getTotalPages() - Get the total number of pages

Zoom

  • zoomIn() - Zoom in by 20%
  • zoomOut() - Zoom out by 20%
  • setZoom(level) - Set zoom to a specific level (clamped to [minzoomlevel, maxzoomlevel])
  • getZoom() - Get the current zoom level

View Control

  • home() - Reset view to initial state
  • setFullScreen(fullScreen) - Set fullscreen mode (true/false)
  • toggleFullScreen() - Toggle fullscreen mode
  • isFullScreen() - Check if in fullscreen mode

Rotation

  • rotate(degrees) - Rotate by specified degrees (relative)
  • setRotation(degrees) - Set rotation to specific angle (absolute)
  • getRotation() - Get current rotation angle

Examples

Basic IIIF Manifest

<edirom-image-viewer 
    tilesources='["https://example.com/iiif/manifest.json"]'>
</edirom-image-viewer>

Multi-Page Sequence with Controls and Trigger Attributes

<edirom-image-viewer 
    id="viewer"
    tilesources='["https://content.staatsbibliothek-berlin.de/dc/69007087X-0001/info.json", "https://content.staatsbibliothek-berlin.de/dc/69007087X-0002/info.json"]'
    pagenumber="1"
    zoom="1"
    rotation="0"
    triggerhome="false"
    triggerfullscreen="false"
    sequencemode="true"
    showsequencecontrol="true"
    shownavigator="true"
    showzoomcontrol="true"
    showhomecontrol="true"
    showfullpagecontrol="true">
</edirom-image-viewer>

Controlling via JavaScript and Triggers

const viewer = document.querySelector('edirom-image-viewer');

// Navigate to page 3
viewer.setAttribute('pagenumber', '3');

// Zoom to level 2
viewer.setAttribute('zoom', '2');

// Reset to home position
viewer.setAttribute('triggerhome', 'true');

// Toggle fullscreen
viewer.setAttribute('triggerfullscreen', 'true');

Custom OpenSeadragon Configuration

IIIF Support

The component supports both IIIF manifests and direct image tile sources:

  • IIIF Manifests: Automatically fetches and parses IIIF Presentation API manifests to extract image URLs
  • Direct Tile Sources: Use IIIF Image API info.json URLs directly

Trigger Attributes

Trigger attributes are used to invoke actions on the viewer. Set these attributes to "true" to trigger the corresponding action:

  • triggerhome: Reset view to initial/home position
  • triggerfullscreen: Toggle fullscreen mode on/off

Example:

const viewer = document.querySelector('edirom-image-viewer');
viewer.setAttribute('triggerhome', 'true');      // Reset to home position
viewer.setAttribute('triggerfullscreen', 'true'); // Toggle fullscreen

Events

The component fires a generic communicate-[property]-update event whenever any observed attribute changes:

  • communicate-zoom-update - Fired when zoom level changes
  • communicate-rotation-update - Fired when rotation changes
  • communicate-pagenumber-update - Fired when page changes
  • communicate-triggerhome-update - Fired when home is triggered
  • communicate-triggerfullscreen-update - Fired when fullscreen is triggered
  • And one for every other observable attribute

The component also fires dedicated semantic events:

Event Detail Fired when
page-changed { pageNumber } (1-based) The viewer navigates to a new page.
zone-changed { zoneKey, zone } Navigation to a zone completes.
view-mode-changed { viewMode } The view-mode attribute changes.
zoom { zoom } The OpenSeadragon viewport zoom changes.
image-ready The image/tiles have finished loading.
annotation-click { id, uri, fn, title, element } An annotation badge is clicked.
annotation-mouseenter / annotation-mouseleave { id, uri, fn, title, element } The pointer enters/leaves an annotation badge.
annotation-filter-changed { visibleCategories, visiblePriorities } visible-categories or visible-priorities changes (incl. externally).
viewer.addEventListener('page-changed', (event) => {
    console.log('Navigated to page:', event.detail.pageNumber);
});

viewer.addEventListener('zone-changed', (event) => {
    console.log('Navigated to zone:', event.detail.zoneKey);
});

Region Navigation

The component supports pixel-precise navigation to named rectangular regions (zones) on any page, independent of OSD's own sequence controls. A single lookup map drives all region navigation. Measures, movements and any other navigable region are expressed as ordinary zone entries — the host picks the key convention (e.g. namespaced measure:... / mdiv:... keys).

Lookup map Trigger attribute Completion event Typical use
zones-data zone zone-changed Any named region: generic zones, measures/bars, MEI <mdiv> movements

Region Object Format

Each entry in the zones-data map must have a 1-based page number and (for precise fits) pixel coordinates (ulx, uly, lrx, lry) defining the upper-left and lower-right corners of the region. An entry may carry only a page (no coordinates) to navigate to that page and show it whole — useful for movement first pages.

{
  "measure:1": { "page": 1, "ulx": 100, "uly": 200, "lrx": 800, "lry": 600 },
  "measure:2": { "page": 1, "ulx": 900, "uly": 200, "lrx": 1600, "lry": 600 },
  "mdiv:mov2": { "page": 2 }
}
  • Same page: the viewer fits the region directly (aspect-preserving).
  • Cross-page: the component navigates to the target page first, waits until its tiles are loaded, then applies the region — ensuring coordinate conversion is always accurate.
  • Updating zones-data: setting a new zones-data value while a zone is active re-applies the current zone against the updated data.

Push model: data map + trigger

The zones-data attribute is a lookup map (set once, performs no navigation on its own). The zone attribute is the navigation trigger and must hold a key that exists in the map. To re-fire navigation to the same key, append a |<nonce> token to the trigger value — it is stripped before lookup:

let nonce = 0;
viewer.setAttribute('zone', 'measure:1|' + (++nonce)); // jump
viewer.setAttribute('zone', 'measure:1|' + (++nonce)); // jump again to the same zone

An empty trigger value (zone="") is ignored, so it is safe as a default in markup.

Example: Zone Navigation

<edirom-image-viewer
    id="viewer"
    sequencemode="true"
    showsequencecontrol="false"
    tilesources='[...]'
    zones-data='{}'
    zone="">
</edirom-image-viewer>
const viewer = document.querySelector('#viewer');

// Populate the lookup map
viewer.setAttribute('zones-data', JSON.stringify({
    'measure:1': { page: 1, ulx: 100, uly: 200, lrx: 800, lry: 600 },
    'mdiv:mov2': { page: 2 }
}));

// Trigger navigation
viewer.setAttribute('zone', 'measure:1');

viewer.addEventListener('zone-changed', (event) => {
    console.log('Navigated to zone:', event.detail.zoneKey);
});

viewer.addEventListener('page-changed', (event) => {
    console.log('Page is now:', event.detail.pageNumber);
});

Rectangle Fit (fitrect)

Fit the viewport to an arbitrary image-pixel rectangle, independent of any lookup map. The value is "x,y,width,height" in image-pixel coordinates, with an optional trailing ,<nonce> token to re-fire the same fit:

viewer.setAttribute('fitrect', '500,300,1200,800');

View Mode (view-mode)

A declarative attribute the host can set to record the active view mode (e.g. pageBasedView / measureBasedView). The component stores it and re-broadcasts it via the view-mode-changed event; the actual layout swap is owned by the surrounding host application:

viewer.setAttribute('view-mode', 'measureBasedView');
viewer.addEventListener('view-mode-changed', (event) => {
    console.log('View mode:', event.detail.viewMode);
});

Annotation Overlays

The component renders clickable annotation badges on top of the current image using a push/persist model: the host pushes the full set of annotations via annotations-data, toggles their visibility via show-annotations, and narrows them down by category/priority via visible-categories / visible-priorities. The component owns all rendering, showing, hiding and filtering — the host never touches the DOM.

annotations-data format

annotations-data is a JSON array of annotation descriptors. Annotations pointing at the same region share one stacked container, and each badge carries the CSS classes annotIcon {categories} {priority} {type} so edition stylesheets can target them:

viewer.setAttribute('annotations-data', JSON.stringify([
    {
        idPrefix: 'viewer1',
        id: 'annot1',
        title: 'Slur added',
        uri: 'xmldb:exist:///db/.../annot1.xml',
        categories: 'wega.annotation.category.bogensetzung',
        priority: 'ediromAnnotPrio1',
        fn: '',                       // host click action (opaque to the component)
        tooltip: '<div class="annotTip">…host-supplied HTML…</div>', // rendered by the component on hover
        plist: [                      // one or more image-pixel regions
            { id: 'm1', ulx: 100, uly: 100, lrx: 160, lry: 160, type: 'measure' }
        ]
    }
]));

viewer.setAttribute('show-annotations', 'true');

Each badge dispatches annotation-click, annotation-mouseenter and annotation-mouseleave CustomEvents (with detail = { id, uri, fn, title, element }). The host uses annotation-click (via the opaque fn) for its click behaviour. The tooltip is rendered by the component itself: if an annotation carries a tooltip HTML string, the component shows it in a positioned, reusable tooltip element on hover and hides it on leave — the host only supplies the HTML (and may still listen to the mouse events for its own highlighting).

Category & priority filtering

visible-categories and visible-priorities are JSON arrays of the category / priority ids that should remain visible. A badge is shown only when it passes both filters (one of its categories is listed and its priority is listed). The sentinel values mirror the host's filter menus:

Value Meaning
absent / null no filter pushed yet — show all
["undefined"] the edition has no such taxonomy — show all
[] every item unchecked — hide all
["catA", "catB"] show only badges whose category/priority is listed
// show only the "bogensetzung" category, any priority
viewer.setAttribute('visible-categories', JSON.stringify(['wega.annotation.category.bogensetzung']));
viewer.setAttribute('visible-priorities', JSON.stringify(['undefined']));

Filtering hides individual badges via display, and a stacked container is hidden once none of its badges pass the filter. Both show-annotations and the filters toggle visibility (not display) at the container level so OpenSeadragon redraws don't override the hide. The chosen show/hide state and filter are remembered and re-applied to every freshly rendered page, so they persist across page navigation until changed.

Whenever visible-categories or visible-priorities changes (including when set externally, e.g. via DevTools), the component dispatches an annotation-filter-changed CustomEvent with detail = { visibleCategories, visiblePriorities } (the current filter arrays, or null for "no filter"). The host can listen for it to keep its own filter UI (e.g. menu checkboxes) in sync with the component's state.

Browser Support

The component uses modern web standards (Custom Elements, Shadow DOM) and requires a modern browser with ES6+ support

About

Edirom Image Viewer

Topics

Resources

Code of conduct

Stars

0 stars

Watchers

4 watching

Forks

Releases

Packages

Contributors

Languages