<bmx-deep-zoom>
A viewer for very large pictures - microscope slides, scans, maps, panels of artwork, satellite and drone mosaics - opened in the page with nothing uploaded, at any size, smoothly: only the tiles the view needs are read, at the detail the screen needs.
17 properties · 8 events · 16 methods · 5 parts
Example
Try it with your own files in confidence: they are opened by your browser, on your device. Nothing is uploaded, no copy reaches us or anyone else, and nothing is kept once you close this tab.
The sample is the Mandelbrot set at 1,048,576 × 786,432 pixels - more than 800 gigapixels - drawn tile by tile on background threads as you zoom, so no file is downloaded. A few places are marked; choose one on the Annotations tab and Go to it, or press N for the next. Draw rectangles, ellipses, polygons, freehand outlines, points and measurements; turn the view with R; compare it with a second colouring side by side or with the swipe divider. Open your own TIFF (also BigTIFF and slide scans such as Aperio SVS and OME-TIFF), JPEG or PNG, or give the address of a Deep Zoom, IIIF or Zoomify picture.
Show markup
<bmx-deep-zoom id="ex-dz" style="--bmx-deep-zoom-height: 38rem">
<span class="ex-dz-tag" data-x="223226" data-y="393216">A small copy of the whole set</span>
</bmx-deep-zoom>
<p class="note">The sample is the Mandelbrot set at 1,048,576 × 786,432 pixels - more than 800 gigapixels - drawn tile by tile on background threads as you zoom, so no file is downloaded. A few places are marked; choose one on the Annotations tab and Go to it, or press N for the next. Draw rectangles, ellipses, polygons, freehand outlines, points and measurements; turn the view with R; compare it with a second colouring side by side or with the swipe divider. Open your own TIFF (also BigTIFF and slide scans such as Aperio SVS and OME-TIFF), JPEG or PNG, or give the address of a Deep Zoom, IIIF or Zoomify picture.</p>
<div class="row" style="gap: 0.75rem; flex-wrap: wrap; align-items: center; margin-block: 0.5rem 1.5rem">
<label>Open your own picture <input type="file" id="ex-dz-file" accept=".tif,.tiff,.svs,.btf,.jpg,.jpeg,.png,.webp" /></label>
<button type="button" id="ex-dz-sample">Show the sample again</button>
<button type="button" id="ex-dz-compare">Compare two colourings</button>
<form id="ex-dz-url-form" style="display: inline-flex; gap: 0.5rem; align-items: center">
<label>Picture address <input type="url" id="ex-dz-url" placeholder="https://…/info.json or …/photo.dzi" size="32" /></label>
<button type="submit">Open</button>
</form>
</div>
<p class="note" id="ex-dz-out" role="status"></p>
<style>
.ex-dz-tag {
padding: 0.125rem 0.5rem;
border-radius: 999px;
background: #111827;
color: #fff;
font: 600 0.75rem system-ui, sans-serif;
white-space: nowrap;
}
</style>
<script type="module">
await customElements.whenDefined('bmx-deep-zoom');
const viewer = document.getElementById('ex-dz');
const out = document.getElementById('ex-dz-out');
viewer.addEventListener('bmxLoad', e => {
const { name, width, height, levels, compare } = e.detail;
out.textContent = `${compare ? 'Compared with ' : ''}${name}: ${width.toLocaleString()} × ${height.toLocaleString()} pixels, ${levels} levels.`;
});
viewer.addEventListener('bmxSelect', e => (out.textContent = e.detail ? `Chose ${e.detail.label || e.detail.shape}.` : ''));
viewer.addEventListener('bmxMeasure', e => (out.textContent = `Measured ${e.detail.text}.`));
viewer.addEventListener('bmxAnnotationChange', e => (out.textContent = `${e.detail.annotations.length} annotations (${e.detail.change}).`));
viewer.addEventListener('bmxSave', e => (out.textContent = `Saved ${e.detail.name} (${Math.round(e.detail.data.size / 1024)} KB).`));
viewer.addEventListener('bmxError', e => (out.textContent = e.detail.message));
document.getElementById('ex-dz-file').addEventListener('change', e => {
if (e.target.files.length) viewer.open(e.target.files[0]);
});
document.getElementById('ex-dz-sample').addEventListener('click', () => viewer.loadSample());
document.getElementById('ex-dz-compare').addEventListener('click', async () => {
await viewer.openCompare('sample:ember');
viewer.compare = 'side';
});
document.getElementById('ex-dz-url-form').addEventListener('submit', e => {
e.preventDefault();
const url = document.getElementById('ex-dz-url').value.trim();
if (url) viewer.open(url);
});
await viewer.loadSample();
</script>
WHAT IT DOES
- Opens pyramids on servers (Deep Zoom, IIIF Image API 2 and 3, Zoomify) and local or remote TIFF and BigTIFF files of any size, tiled or in strips, with their own smaller levels (slide scans such as Aperio SVS, OME-TIFF, VIPS pyramids) or without; ordinary JPEG, PNG and WebP too. Pyramids a picture lacks are built in the background.
- Moves and zooms with the pointer, the wheel, pinches and the keyboard; turns in steps of 90 degrees or freely, and mirrors; an overview map with the view on it to click or drag.
- Compares two pictures, side by side or with a swipe divider, moving together (pictures of different sizes by their proportions) or apart.
- Annotations: rectangles, ellipses, polygons, freehand outlines, points and measurements, labelled, coloured, moved and reshaped; fixed regions from the page; saved and opened as JSON or GeoJSON (QuPath's form).
- Calibrated measuring: the pixel size from the file (Aperio, OME-XML, resolution tags) or by drawing a line of known length; lengths and areas in the unit that reads best; a scale bar.
- Overlays: the page's own elements, placed in the element with
data-xanddata-y(anddata-width,data-height), stay on their spot of the picture as it moves. - Saves the view, or a region at full detail, as PNG.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
annotations |
annotations |
BmxZoomAnnotation[] | string |
[] |
Annotations (a list, or its JSON, or GeoJSON). |
compactBelow |
compact-below |
number |
760 |
Below this width (pixels) the panel goes under the picture. |
compare |
compare |
BmxDeepZoomCompare |
'off' |
How two pictures are compared: one picture only, side by side, or a swipe divider. |
compareSrc |
compare-src |
string |
— | A second picture, to compare with the first. |
flip |
flip |
boolean |
false |
Mirrored left to right. |
label |
label |
string |
— | The accessible name of the viewer. |
linked |
linked |
boolean |
true |
Two pictures move together. |
maxZoom |
max-zoom |
number |
8 |
The closest zoom, in screen pixels for each picture pixel. |
minimap |
minimap |
boolean |
true |
Show the overview map. |
noFiles |
no-files |
boolean |
false |
No opening of files (pictures come from src or open). |
pixelSize |
pixel-size |
number |
— | The size of a pixel in micrometres; taken from the picture when it says, and set by calibrating. |
readOnly |
read-only |
boolean |
false |
Look only: no drawing, changing or calibrating. |
rotation |
rotation |
number |
0 |
The turn, in degrees clockwise. |
showAnnotations |
show-annotations |
boolean |
true |
Show the annotations. |
src |
src |
string |
— | The picture to open: a TIFF, JPEG or PNG address, a Deep Zoom, IIIF or Zoomify description's, or sample. |
strings |
strings |
Partial<BmxDeepZoomStrings> | string |
— | Replacements for any of the wording (an object, or its JSON). |
tool |
tool |
BmxDeepZoomTool |
'pan' |
The active tool. |
Events
| Event | Detail | Description |
|---|---|---|
bmxAnnotationChange |
{ annotations: BmxZoomAnnotation[]; change: "change" | "import" | "remove" | "add"; id?: string | undefined; } |
Fired when annotations are added, changed or removed. |
bmxCalibrate |
{ pixelSize: number | null; } |
Fired when the pixel size is set by calibrating (or cleared). |
bmxError |
{ message: string; name?: string | undefined; } |
Fired when a picture or file cannot be opened. |
bmxLoad |
BmxDeepZoomInfo & { compare: boolean; } |
Fired when a picture has opened. |
bmxMeasure |
{ pixels: number; micrometres: number | null; text: string; annotation: BmxZoomAnnotation; } |
Fired when a measurement is drawn. |
bmxSave |
BmxDeepZoomSaveDetail |
Fired before a file is downloaded. Cancelable: cancel to keep the file yourself. |
bmxSelect |
BmxZoomAnnotation | null |
Fired when an annotation is chosen, or the choice cleared (null). |
bmxViewChange |
BmxDeepZoomViewState |
Fired as the view moves, zooms or turns (at most once a frame). |
Methods
| Method | Signature | Description |
|---|---|---|
closeCompare |
closeCompare() => Promise<void> |
Close the second picture. |
download |
download(kind?: "view" | "region" | "selection" | "annotations" | "geojson") => Promise<void> |
Download the view, the whole picture (up to 8,192 pixels), the chosen shape's area, or the annotations (unless bmxSave is cancelled). |
exportAnnotations |
exportAnnotations(format?: "json" | "geojson") => Promise<string> |
The annotations as a file's text: the viewer's JSON or GeoJSON. |
fit |
fit() => Promise<void> |
Fit the whole picture. |
getAnnotations |
getAnnotations() => Promise<BmxZoomAnnotation[]> |
The annotations. |
getInfo |
getInfo() => Promise<BmxDeepZoomInfo | null> |
What is open. |
getView |
getView() => Promise<BmxDeepZoomViewState | null> |
The view: the picture point in the middle, the zoom, the turn, what shows. |
loadSample |
loadSample() => Promise<BmxDeepZoomInfo | null> |
Open the built-in sample (the Mandelbrot set, a million pixels across, with a few named places marked). |
open |
open(picture: Blob | string) => Promise<BmxDeepZoomInfo | null> |
Open a picture: a File or Blob (TIFF, JPEG, PNG, WebP…), or an address (also of a Deep Zoom, IIIF or Zoomify description), or sample. |
openCompare |
openCompare(picture: Blob | string) => Promise<BmxDeepZoomInfo | null> |
Open a second picture to compare (side by side unless another way is set). |
rotate |
rotate(degrees: number) => Promise<void> |
Turn the view by some degrees (clockwise). |
select |
select(id: string | null) => Promise<void> |
Choose an annotation by id (null clears). |
setAnnotations |
setAnnotations(value: unknown) => Promise<number> |
Replace the annotations (a list, its JSON, a saved file's JSON, or GeoJSON). |
setView |
setView(view: { x?: number; y?: number; zoom?: number; rotation?: number; flip?: boolean; }, animate?: boolean) => Promise<void> |
Move the view: any of the middle point (picture pixels), zoom, turn and mirror. |
snapshot |
snapshot(options?: BmxDeepZoomSnapshotOptions) => Promise<Blob> |
A picture of the view, or of a box of the picture at full detail. |
zoomTo |
zoomTo(target: readonly [number, number, number, number] | { annotation: string; }) => Promise<boolean> |
Zoom to a box of the picture (pixels), or to an annotation by id. |
Slots
| Slot | Description |
|---|---|
(default) |
Overlay elements, placed on the picture by data-x and data-y (picture pixels), sized by data-width and data-height when given. |
CSS shadow parts
| Part | Description |
|---|---|
minimap |
The overview map. |
panel |
The side panel. |
stage |
The picture. |
status |
The status line under the picture. |
toolbar |
The toolbar. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-deep-zoom-height |
The viewer's height. |
--bmx-deep-zoom-panel-width |
The side panel's width. |
--bmx-deep-zoom-radius |
The frame's corner radius. |
--bmx-deep-zoom-stage |
Behind the picture. |