UNPKG

medium-zoom

Version:

A JavaScript library for zooming images like Medium. Works with React and any web framework.

482 lines (480 loc) • 20.7 kB
/*! medium-zoom 1.1.2 | MIT License | https://github.com/francoischalifour/medium-zoom */ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define([], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.mediumZoom = factory()); })(this, function () { "use strict"; // src/utils.js var isSupported = function isSupported(node) { return node.tagName === "IMG"; }; var isNodeList = function isNodeList(selector) { return NodeList.prototype.isPrototypeOf(selector); }; var isNode = function isNode(selector) { return selector && selector.nodeType === 1; }; var isSvg = function isSvg(image) { return (image.currentSrc || image.src).substr(-4).toLowerCase() === ".svg"; }; var getImagesFromSelector = function getImagesFromSelector(selector) { try { if (Array.isArray(selector)) return selector.filter(isSupported); if (isNodeList(selector)) return [].slice.call(selector).filter(isSupported); if (isNode(selector)) return [selector].filter(isSupported); if (typeof selector === "string") return [].slice.call(document.querySelectorAll(selector)).filter(isSupported); return []; } catch (err) { throw new TypeError("The provided selector is invalid.\nExpects a CSS selector, a Node element, a NodeList or an array.\nSee: https://github.com/francoischalifour/medium-zoom"); } }; var createOverlay = function createOverlay(background) { var overlay = document.createElement("div"); overlay.classList.add("medium-zoom-overlay"); overlay.style.background = background; return overlay; }; var cloneTarget = function cloneTarget(template) { var _template$getBounding = template.getBoundingClientRect(), top = _template$getBounding.top, left = _template$getBounding.left, width = _template$getBounding.width, height = _template$getBounding.height; var clone = template.cloneNode(); var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; clone.removeAttribute("id"); clone.style.position = "absolute"; clone.style.top = top + scrollTop + "px"; clone.style.left = left + scrollLeft + "px"; clone.style.width = width + "px"; clone.style.height = height + "px"; clone.style.transform = ""; return clone; }; var createCustomEvent = function createCustomEvent(type, detail) { if (typeof window.CustomEvent === "function") return new CustomEvent(type, { detail: detail }); var customEvent = document.createEvent("CustomEvent"); customEvent.initCustomEvent(type, false, false, detail); return customEvent; }; // src/medium-zoom.js var _mediumZoom = function mediumZoom(selector, options) { if (options === void 0) { options = {}; } /** * Ensure the compatibility with IE11 if no Promise polyfill are used. */ var Promise = window.Promise || function Promise(fn) { function noop() {} fn(noop, noop); }; var _dispatchEvent = function _dispatchEvent(image, type) { return image.dispatchEvent(createCustomEvent("medium-zoom:" + type, { zoom: zoom })); }; var _handleClick = function _handleClick(event) { var target = event.target; if (target === overlay) { _close(); return; } if (images.indexOf(target) === -1) return; toggle({ target: target }); }; var _handleScroll = function _handleScroll() { if (!active.__mz_original) return; var currentScroll = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; if (active.__mz_isPinching || window.visualViewport && window.visualViewport.scale > active.__mz_viewportScale) { scrollTop = currentScroll; clearTimeout(active.__mz_scrollTimer); active.__mz_scrollTimer = null; return; } if (isAnimating) return; if (Math.abs(scrollTop - currentScroll) > zoomOptions.scrollOffset && !active.__mz_scrollTimer) active.__mz_scrollTimer = setTimeout(_close, 150); }; var _handleTouch = function _handleTouch(event) { if (event.touches.length >= 2) active.__mz_isPinching = true; if (active.__mz_isPinching) _handleScroll(); if (event.touches.length === 0) active.__mz_isPinching = false; }; var _handleKeyUp = function _handleKeyUp(event) { var key = event.key || event.keyCode; if (key === "Escape" || key === "Esc" || key === 27) _close(); }; var update = function update(options) { if (options === void 0) { options = {}; } var newOptions = options; if (options.background) overlay.style.background = options.background; if (options.container && options.container instanceof Object) newOptions.container = _extends({}, zoomOptions.container, options.container); if (options.template) newOptions.template = isNode(options.template) ? options.template : document.querySelector(options.template); zoomOptions = _extends({}, zoomOptions, newOptions); images.forEach(function (image) { _dispatchEvent(image, "update"); }); return zoom; }; var clone = function clone(options) { return _mediumZoom(_extends({}, zoomOptions, options)); }; var clickListening = false; var _updateClickListener = function _updateClickListener() { var enabled = images.length > 0; if (clickListening === enabled) return; document[enabled ? "addEventListener" : "removeEventListener"]("click", _handleClick); clickListening = enabled; }; var attach = function attach() { for (var _len = arguments.length, selectors = new Array(_len), _key = 0; _key < _len; _key++) { selectors[_key] = arguments[_key]; } var newImages = selectors.reduce(function (imagesAccumulator, currentSelector) { return [].concat(imagesAccumulator, getImagesFromSelector(currentSelector)); }, []); newImages.filter(function (newImage) { return images.indexOf(newImage) === -1; }).forEach(function (newImage) { images.push(newImage); newImage.classList.add("medium-zoom-image"); }); eventListeners.forEach(function (_ref) { var type = _ref.type, listener = _ref.listener, options = _ref.options; newImages.forEach(function (image) { image.addEventListener(type, listener, options); }); }); _updateClickListener(); return zoom; }; var detach = function detach() { if (active.__mz_zoomed) _close(); for (var _len2 = arguments.length, selectors = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { selectors[_key2] = arguments[_key2]; } var imagesToDetach = selectors.length > 0 ? selectors.reduce(function (imagesAccumulator, currentSelector) { return [].concat(imagesAccumulator, getImagesFromSelector(currentSelector)); }, []) : images; imagesToDetach.forEach(function (image) { image.classList.remove("medium-zoom-image"); _dispatchEvent(image, "detach"); }); images = images.filter(function (image) { return imagesToDetach.indexOf(image) === -1; }); _updateClickListener(); return zoom; }; var on = function on(type, listener, options) { if (options === void 0) { options = {}; } var eventType = "medium-zoom:" + type; images.forEach(function (image) { image.addEventListener(eventType, listener, options); }); eventListeners.push({ type: eventType, listener: listener, options: options }); return zoom; }; var off = function off(type, listener, options) { if (options === void 0) { options = {}; } var eventType = "medium-zoom:" + type; images.forEach(function (image) { image.removeEventListener(eventType, listener, options); }); eventListeners = eventListeners.filter(function (eventListener) { return !(eventListener.type === eventType && eventListener.listener.toString() === listener.toString()); }); return zoom; }; var globalListening = false; var _updateGlobalEvents = function _updateGlobalEvents(enabled) { if (globalListening === enabled) return; var method = enabled ? "addEventListener" : "removeEventListener"; document[method]("keyup", _handleKeyUp); document[method]("scroll", _handleScroll); var touchOptions = { passive: true }; ["touchstart", "touchend", "touchcancel"].forEach(function (type) { return document[method](type, _handleTouch, touchOptions); }); window[method]("resize", _close); globalListening = enabled; }; var _waitForTransition = function _waitForTransition(image, done) { var timer; var frame; var finished = false; var query = window.matchMedia("(prefers-reduced-motion)"); var getTime = function getTime(value) { return Math.max.apply(Math, value.split(",").map(parseFloat)); }; var finish = function finish(event) { if (finished || event && event.propertyName !== "transform") return; cleanup(); done(); }; var motionChange = function motionChange() { if (query.matches) finish(); }; var cleanup = function cleanup() { finished = true; clearTimeout(timer); window.cancelAnimationFrame(frame); image.removeEventListener("transitionend", finish); query.removeListener(motionChange); }; image.addEventListener("transitionend", finish); query.addListener(motionChange); var style = window.getComputedStyle(image); var duration = (getTime(style.transitionDuration) + getTime(style.transitionDelay)) * 1e3; timer = setTimeout(finish, Math.max(0, duration) + 50); if (duration <= 0) frame = window.requestAnimationFrame(function () { return finish(); }); return cleanup; }; var open = function open(_temp) { var _ref2 = _temp === void 0 ? {} : _temp, target = _ref2.target; var current = {}; var _animate = function _animate() { var container = { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, left: 0, top: 0, right: 0, bottom: 0 }; var viewportWidth; var viewportHeight; if (zoomOptions.container) { if (zoomOptions.container instanceof Object) { container = _extends({}, container, zoomOptions.container); viewportWidth = container.width - container.left - container.right - zoomOptions.margin * 2; viewportHeight = container.height - container.top - container.bottom - zoomOptions.margin * 2; } else { var _getBoundingClientRec = (isNode(zoomOptions.container) ? zoomOptions.container : document.querySelector(zoomOptions.container)).getBoundingClientRect(), _width = _getBoundingClientRec.width, _height = _getBoundingClientRec.height, _left = _getBoundingClientRec.left, _top = _getBoundingClientRec.top; container = _extends({}, container, { width: _width, height: _height, left: _left, top: _top }); } } viewportWidth = viewportWidth || container.width - zoomOptions.margin * 2; viewportHeight = viewportHeight || container.height - zoomOptions.margin * 2; var zoomTarget = current.__mz_zoomedHd || current.__mz_original; var naturalWidth = isSvg(zoomTarget) ? viewportWidth : zoomTarget.naturalWidth || viewportWidth; var naturalHeight = isSvg(zoomTarget) ? viewportHeight : zoomTarget.naturalHeight || viewportHeight; var _zoomTarget$getBoundi = zoomTarget.getBoundingClientRect(), top = _zoomTarget$getBoundi.top, left = _zoomTarget$getBoundi.left, width = _zoomTarget$getBoundi.width, height = _zoomTarget$getBoundi.height; if (!width || !height) return; var scaleX = Math.min(Math.max(width, naturalWidth), viewportWidth) / width; var scaleY = Math.min(Math.max(height, naturalHeight), viewportHeight) / height; var scale = Math.min(scaleX, scaleY); var transform = "scale(" + scale + ") translate3d(" + (-left + (viewportWidth - width) / 2 + zoomOptions.margin + container.left) / scale + "px, " + (-top + (viewportHeight - height) / 2 + zoomOptions.margin + container.top) / scale + "px, 0)"; current.__mz_zoomed.style.transform = transform; if (current.__mz_zoomedHd) current.__mz_zoomedHd.style.transform = transform; }; return new Promise(function (resolve) { if (active.__mz_original || target && images.indexOf(target) === -1) { resolve(zoom); return; } active = current; var cancelTransition = function cancelTransition() {}; var cancelImage = function cancelImage() {}; var frame; var finishOpening = function finishOpening(opened) { if (!current.__mz_cancelOpen) return; cancelTransition(); cancelImage(); window.cancelAnimationFrame(frame); current.__mz_cancelOpen = null; isAnimating = false; resolve(zoom); if (opened) { document.body.classList.add("medium-zoom--opened"); _dispatchEvent(current.__mz_original, "opened"); } }; current.__mz_cancelOpen = finishOpening; current.__mz_original = target || images[0]; if (!current.__mz_original) { resolve(zoom); return; } current.__mz_viewportScale = window.visualViewport ? window.visualViewport.scale : 1; _updateGlobalEvents(true); isAnimating = true; _dispatchEvent(current.__mz_original, "open"); if (current.__mz_closing) return; scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; current.__mz_zoomed = cloneTarget(current.__mz_original); document.body.appendChild(overlay); if (zoomOptions.template) { var template = isNode(zoomOptions.template) ? zoomOptions.template : document.querySelector(zoomOptions.template); current.__mz_template = document.createElement("div"); current.__mz_template.appendChild(template.content.cloneNode(true)); document.body.appendChild(current.__mz_template); } if (current.__mz_original.parentElement && current.__mz_original.parentElement.tagName === "PICTURE" && current.__mz_original.currentSrc) current.__mz_zoomed.src = current.__mz_original.currentSrc; document.body.appendChild(current.__mz_zoomed); frame = window.requestAnimationFrame(function () { document.body.classList.add("medium-zoom--opened"); }); current.__mz_original.classList.add("medium-zoom-image--hidden"); current.__mz_zoomed.classList.add("medium-zoom-image--opened"); current.__mz_zoomed.addEventListener("click", _close); var animate = function animate() { _animate(); cancelTransition = _waitForTransition(current.__mz_zoomed, function () { return finishOpening(true); }); }; var zoomSrc = current.__mz_original.getAttribute("data-zoom-src"); if (zoomSrc || current.__mz_original.hasAttribute("srcset")) { var hd = document.createElement("img"); [].slice.call(current.__mz_zoomed.attributes).forEach(function (_ref3) { var name = _ref3.name, value = _ref3.value; if (!/^(src|srcset|sizes|loading)$/.test(name)) hd.setAttribute(name, value); }); current.__mz_zoomedHd = hd; var finishImage = function finishImage() { cancelImage(); if (current.__mz_closing) return; if (hd.naturalWidth) { hd.classList.add("medium-zoom-image--opened"); hd.addEventListener("click", _close); document.body.appendChild(hd); } else { console.warn("Unable to reach the zoom image target " + (hd.currentSrc || hd.src)); current.__mz_zoomedHd = null; } animate(); }; cancelImage = function cancelImage() { hd.removeEventListener("load", finishImage); hd.removeEventListener("error", finishImage); }; hd.addEventListener("load", finishImage); hd.addEventListener("error", finishImage); if (!zoomSrc) hd.srcset = current.__mz_zoomed.srcset; hd.src = zoomSrc || current.__mz_zoomed.src; } else animate(); }); }; var _close = function close() { return new Promise(function (resolve) { var current = active; if (!current.__mz_original || current.__mz_closing) { resolve(zoom); return; } current.__mz_closing = true; if (current.__mz_cancelOpen) current.__mz_cancelOpen(); clearTimeout(current.__mz_scrollTimer); isAnimating = true; var finishClosing = function finishClosing() { current.__mz_original.classList.remove("medium-zoom-image--hidden"); [current.__mz_zoomed, current.__mz_zoomedHd, current.__mz_template, overlay].forEach(function (node) { if (!node) return; node.removeEventListener("click", _close); if (node.parentNode) node.parentNode.removeChild(node); }); active = {}; isAnimating = false; _updateGlobalEvents(false); resolve(zoom); _dispatchEvent(current.__mz_original, "closed"); }; if (current.__mz_zoomed) _waitForTransition(current.__mz_zoomed, finishClosing); document.body.classList.remove("medium-zoom--opened"); if (current.__mz_zoomed) current.__mz_zoomed.style.transform = ""; if (current.__mz_zoomedHd) current.__mz_zoomedHd.style.transform = ""; if (current.__mz_template) { current.__mz_template.style.transition = parseFloat(window.getComputedStyle(current.__mz_zoomed).transitionDuration) ? "opacity 150ms" : "none"; current.__mz_template.style.opacity = 0; } _dispatchEvent(current.__mz_original, "close"); if (!current.__mz_zoomed) finishClosing(); }); }; var toggle = function toggle(_temp2) { var _ref4 = _temp2 === void 0 ? {} : _temp2, target = _ref4.target; if (active.__mz_original) return _close(); return open({ target: target }); }; var getOptions = function getOptions() { return zoomOptions; }; var getImages = function getImages() { return images; }; var getZoomedImage = function getZoomedImage() { return active.__mz_original || null; }; var images = []; var eventListeners = []; var isAnimating = false; var scrollTop = 0; var zoomOptions = options; var active = {}; if (Object.prototype.toString.call(selector) === "[object Object]") zoomOptions = selector;else if (selector || typeof selector === "string") attach(selector); zoomOptions = _extends({ margin: 0, background: "#fff", scrollOffset: 40, container: null, template: null }, zoomOptions); var overlay = createOverlay(zoomOptions.background); var zoom = { open: open, close: _close, toggle: toggle, update: update, clone: clone, attach: attach, detach: detach, on: on, off: off, getOptions: getOptions, getImages: getImages, getZoomedImage: getZoomedImage }; return zoom; }; return _mediumZoom; });