Skip to content

Commit

Permalink
lightbox: Prevent panning image out of view.
Browse files Browse the repository at this point in the history
This commit adds a method to detect whether the draggable element has
moved out of view and if it has, move it back into view.

The panzoom library does have a `bounds` option that is supposed to
provide the functionality, but at the time of the commit it does not
appear to work correctly. Upstream bug:
anvaka/panzoom#112
  • Loading branch information
Fingel authored and timabbott committed Feb 23, 2022
1 parent 7d3bbe0 commit 26ec71c
Showing 1 changed file with 70 additions and 3 deletions.
73 changes: 70 additions & 3 deletions static/js/lightbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ export class PanZoomControl {
this.container = container;
this.panzoom = panzoom(this.container, {
smoothScroll: false,
bounds: true,
maxZoom: 5,
// Ideally we'd set `bounds` here, but that feature is
// currently broken upstream. See
// https://github.com/anvaka/panzoom/issues/112.
maxZoom: 100,
minZoom: 0.1,
filterKey() {
// Disable the library's built in keybindings
Expand All @@ -41,7 +43,10 @@ export class PanZoomControl {
$("#lightbox_overlay").data("noclose", true);
});

this.panzoom.on("panend", () => {
this.panzoom.on("panend", (e) => {
// Check if the image has been panned out of view.
this.constrainImage(e);

// Don't remove the noclose attribute on this overlay until after paint,
// otherwise it will be removed too early and close the lightbox
// unintentionally.
Expand All @@ -50,6 +55,14 @@ export class PanZoomControl {
}, 0);
});

this.panzoom.on("zoom", (e) => {
// Check if the image has been zoomed out of view.
// We are using the zoom event instead of zoomend because the zoomend
// event does not fire when using the scroll wheel or pinch to zoom.
// https://github.com/anvaka/panzoom/issues/250
this.constrainImage(e);
});

// key bindings
document.addEventListener("keydown", (e) => {
if (!overlays.lightbox_open()) {
Expand All @@ -73,9 +86,63 @@ export class PanZoomControl {
});
}

constrainImage(e) {
// Instead of using panzoom's built in bounds option which was buggy
// at the time of this writing, we act on pan/zoom events and move the
// image back in to view if it is moved beyond the image-preview container.
// See https://github.com/anvaka/panzoom/issues/112 for upstream discussion.

const {scale, x, y} = e.getTransform();
const image_width = $(".zoom-element > img")[0].clientWidth * scale;
const image_height = $(".zoom-element > img")[0].clientHeight * scale;
const zoom_element_width = $(".zoom-element")[0].clientWidth * scale;
const zoom_element_height = $(".zoom-element")[0].clientHeight * scale;
const max_translate_x = $(".image-preview")[0].clientWidth;
const max_translate_y = $(".image-preview")[0].clientHeight;

// When the image is dragged out of the image-preview container
// (max_translate) it will be "snapped" back so that the number
// of pixels set below will remain visible in the dimension it was dragged.
const return_buffer = 50 * scale;
// Move the image if it gets within this many pixels of the edge.
const border = 20;

const zoom_border_width = (zoom_element_width - image_width) / 2 + image_width;
const zoom_border_height = (zoom_element_height - image_height) / 2 + image_height;
const modified_x = x + zoom_border_width;
const modified_y = y + zoom_border_height;

if (modified_x < 0 + border) {
// Image has been dragged beyond the LEFT of the view.
const move_by = modified_x * -1;
e.moveBy(move_by + return_buffer, 0, false);
} else if (modified_x - image_width > max_translate_x - border) {
// Image has been dragged beyond the RIGHT of the view.
const move_by = modified_x - max_translate_x - image_width;
e.moveBy(-move_by - return_buffer, 0, false);
}

if (modified_y < 0 + border) {
// Image has been dragged beyond the TOP of the view.
const move_by = modified_y * -1;
e.moveBy(0, move_by + return_buffer, false);
} else if (modified_y - image_height > max_translate_y - border) {
// Image has been dragged beyond the BOTTOM of the view.
const move_by = modified_y - max_translate_y - image_height;
e.moveBy(0, -move_by - return_buffer, false);
}
}

reset() {
// To reset the panzoom state, we want to:
// Reset zoom to the initial state.
this.panzoom.zoomAbs(0, 0, 1);
// Re-center the image.
this.panzoom.moveTo(0, 0);
// Always ensure that the overlay is available for click to close.
// This way we don't rely on the above events firing panend,
// of which there is some anecdotal evidence that suggests they
// might be prone to race conditions.
$("#lightbox_overlay").data("noclose", false);
}

Expand Down

0 comments on commit 26ec71c

Please sign in to comment.