UNPKG

19.3 kBJavaScriptView Raw
1/*! medium-zoom 1.0.6 | MIT License | https://github.com/francoischalifour/medium-zoom */
2(function(global, factory) {
3 typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = global || self,
4 global.mediumZoom = factory());
5})(this, function() {
6 "use strict";
7 var _extends = Object.assign || function(target) {
8 for (var i = 1; i < arguments.length; i++) {
9 var source = arguments[i];
10 for (var key in source) {
11 if (Object.prototype.hasOwnProperty.call(source, key)) {
12 target[key] = source[key];
13 }
14 }
15 }
16 return target;
17 };
18 var isSupported = function isSupported(node) {
19 return node.tagName === "IMG";
20 };
21 var isNodeList = function isNodeList(selector) {
22 return NodeList.prototype.isPrototypeOf(selector);
23 };
24 var isNode = function isNode(selector) {
25 return selector && selector.nodeType === 1;
26 };
27 var isSvg = function isSvg(image) {
28 var source = image.currentSrc || image.src;
29 return source.substr(-4).toLowerCase() === ".svg";
30 };
31 var getImagesFromSelector = function getImagesFromSelector(selector) {
32 try {
33 if (Array.isArray(selector)) {
34 return selector.filter(isSupported);
35 }
36 if (isNodeList(selector)) {
37 return [].slice.call(selector).filter(isSupported);
38 }
39 if (isNode(selector)) {
40 return [ selector ].filter(isSupported);
41 }
42 if (typeof selector === "string") {
43 return [].slice.call(document.querySelectorAll(selector)).filter(isSupported);
44 }
45 return [];
46 } catch (err) {
47 throw new TypeError("The provided selector is invalid.\n" + "Expects a CSS selector, a Node element, a NodeList or an array.\n" + "See: https://github.com/francoischalifour/medium-zoom");
48 }
49 };
50 var createOverlay = function createOverlay(background) {
51 var overlay = document.createElement("div");
52 overlay.classList.add("medium-zoom-overlay");
53 overlay.style.background = background;
54 return overlay;
55 };
56 var cloneTarget = function cloneTarget(template) {
57 var _template$getBounding = template.getBoundingClientRect(), top = _template$getBounding.top, left = _template$getBounding.left, width = _template$getBounding.width, height = _template$getBounding.height;
58 var clone = template.cloneNode();
59 var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
60 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
61 clone.removeAttribute("id");
62 clone.style.position = "absolute";
63 clone.style.top = top + scrollTop + "px";
64 clone.style.left = left + scrollLeft + "px";
65 clone.style.width = width + "px";
66 clone.style.height = height + "px";
67 clone.style.transform = "";
68 return clone;
69 };
70 var createCustomEvent = function createCustomEvent(type, params) {
71 var eventParams = _extends({
72 bubbles: false,
73 cancelable: false,
74 detail: undefined
75 }, params);
76 if (typeof window.CustomEvent === "function") {
77 return new CustomEvent(type, eventParams);
78 }
79 var customEvent = document.createEvent("CustomEvent");
80 customEvent.initCustomEvent(type, eventParams.bubbles, eventParams.cancelable, eventParams.detail);
81 return customEvent;
82 };
83 var mediumZoom = function mediumZoom(selector) {
84 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
85 var Promise = window.Promise || function Promise(fn) {
86 function noop() {}
87 fn(noop, noop);
88 };
89 var _handleClick = function _handleClick(event) {
90 var target = event.target;
91 if (target === overlay) {
92 close();
93 return;
94 }
95 if (images.indexOf(target) === -1) {
96 return;
97 }
98 toggle({
99 target: target
100 });
101 };
102 var _handleScroll = function _handleScroll() {
103 if (isAnimating || !active.original) {
104 return;
105 }
106 var currentScroll = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
107 if (Math.abs(scrollTop - currentScroll) > zoomOptions.scrollOffset) {
108 setTimeout(close, 150);
109 }
110 };
111 var _handleKeyUp = function _handleKeyUp(event) {
112 var key = event.key || event.keyCode;
113 if (key === "Escape" || key === "Esc" || key === 27) {
114 close();
115 }
116 };
117 var update = function update() {
118 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
119 var newOptions = options;
120 if (options.background) {
121 overlay.style.background = options.background;
122 }
123 if (options.container && options.container instanceof Object) {
124 newOptions.container = _extends({}, zoomOptions.container, options.container);
125 }
126 if (options.template) {
127 var template = isNode(options.template) ? options.template : document.querySelector(options.template);
128 newOptions.template = template;
129 }
130 zoomOptions = _extends({}, zoomOptions, newOptions);
131 images.forEach(function(image) {
132 image.dispatchEvent(createCustomEvent("medium-zoom:update", {
133 detail: {
134 zoom: zoom
135 }
136 }));
137 });
138 return zoom;
139 };
140 var clone = function clone() {
141 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
142 return mediumZoom(_extends({}, zoomOptions, options));
143 };
144 var attach = function attach() {
145 for (var _len = arguments.length, selectors = Array(_len), _key = 0; _key < _len; _key++) {
146 selectors[_key] = arguments[_key];
147 }
148 var newImages = selectors.reduce(function(imagesAccumulator, currentSelector) {
149 return [].concat(imagesAccumulator, getImagesFromSelector(currentSelector));
150 }, []);
151 newImages.filter(function(newImage) {
152 return images.indexOf(newImage) === -1;
153 }).forEach(function(newImage) {
154 images.push(newImage);
155 newImage.classList.add("medium-zoom-image");
156 });
157 eventListeners.forEach(function(_ref) {
158 var type = _ref.type, listener = _ref.listener, options = _ref.options;
159 newImages.forEach(function(image) {
160 image.addEventListener(type, listener, options);
161 });
162 });
163 return zoom;
164 };
165 var detach = function detach() {
166 for (var _len2 = arguments.length, selectors = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
167 selectors[_key2] = arguments[_key2];
168 }
169 if (active.zoomed) {
170 close();
171 }
172 var imagesToDetach = selectors.length > 0 ? selectors.reduce(function(imagesAccumulator, currentSelector) {
173 return [].concat(imagesAccumulator, getImagesFromSelector(currentSelector));
174 }, []) : images;
175 imagesToDetach.forEach(function(image) {
176 image.classList.remove("medium-zoom-image");
177 image.dispatchEvent(createCustomEvent("medium-zoom:detach", {
178 detail: {
179 zoom: zoom
180 }
181 }));
182 });
183 images = images.filter(function(image) {
184 return imagesToDetach.indexOf(image) === -1;
185 });
186 return zoom;
187 };
188 var on = function on(type, listener) {
189 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
190 images.forEach(function(image) {
191 image.addEventListener("medium-zoom:" + type, listener, options);
192 });
193 eventListeners.push({
194 type: "medium-zoom:" + type,
195 listener: listener,
196 options: options
197 });
198 return zoom;
199 };
200 var off = function off(type, listener) {
201 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
202 images.forEach(function(image) {
203 image.removeEventListener("medium-zoom:" + type, listener, options);
204 });
205 eventListeners = eventListeners.filter(function(eventListener) {
206 return !(eventListener.type === "medium-zoom:" + type && eventListener.listener.toString() === listener.toString());
207 });
208 return zoom;
209 };
210 var open = function open() {
211 var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, target = _ref2.target;
212 var _animate = function _animate() {
213 var container = {
214 width: document.documentElement.clientWidth,
215 height: document.documentElement.clientHeight,
216 left: 0,
217 top: 0,
218 right: 0,
219 bottom: 0
220 };
221 var viewportWidth = void 0;
222 var viewportHeight = void 0;
223 if (zoomOptions.container) {
224 if (zoomOptions.container instanceof Object) {
225 container = _extends({}, container, zoomOptions.container);
226 viewportWidth = container.width - container.left - container.right - zoomOptions.margin * 2;
227 viewportHeight = container.height - container.top - container.bottom - zoomOptions.margin * 2;
228 } else {
229 var zoomContainer = isNode(zoomOptions.container) ? zoomOptions.container : document.querySelector(zoomOptions.container);
230 var _zoomContainer$getBou = zoomContainer.getBoundingClientRect(), _width = _zoomContainer$getBou.width, _height = _zoomContainer$getBou.height, _left = _zoomContainer$getBou.left, _top = _zoomContainer$getBou.top;
231 container = _extends({}, container, {
232 width: _width,
233 height: _height,
234 left: _left,
235 top: _top
236 });
237 }
238 }
239 viewportWidth = viewportWidth || container.width - zoomOptions.margin * 2;
240 viewportHeight = viewportHeight || container.height - zoomOptions.margin * 2;
241 var zoomTarget = active.zoomedHd || active.original;
242 var naturalWidth = isSvg(zoomTarget) ? viewportWidth : zoomTarget.naturalWidth || viewportWidth;
243 var naturalHeight = isSvg(zoomTarget) ? viewportHeight : zoomTarget.naturalHeight || viewportHeight;
244 var _zoomTarget$getBoundi = zoomTarget.getBoundingClientRect(), top = _zoomTarget$getBoundi.top, left = _zoomTarget$getBoundi.left, width = _zoomTarget$getBoundi.width, height = _zoomTarget$getBoundi.height;
245 var scaleX = Math.min(naturalWidth, viewportWidth) / width;
246 var scaleY = Math.min(naturalHeight, viewportHeight) / height;
247 var scale = Math.min(scaleX, scaleY);
248 var translateX = (-left + (viewportWidth - width) / 2 + zoomOptions.margin + container.left) / scale;
249 var translateY = (-top + (viewportHeight - height) / 2 + zoomOptions.margin + container.top) / scale;
250 var transform = "scale(" + scale + ") translate3d(" + translateX + "px, " + translateY + "px, 0)";
251 active.zoomed.style.transform = transform;
252 if (active.zoomedHd) {
253 active.zoomedHd.style.transform = transform;
254 }
255 };
256 return new Promise(function(resolve) {
257 if (target && images.indexOf(target) === -1) {
258 resolve(zoom);
259 return;
260 }
261 var _handleOpenEnd = function _handleOpenEnd() {
262 isAnimating = false;
263 active.zoomed.removeEventListener("transitionend", _handleOpenEnd);
264 active.original.dispatchEvent(createCustomEvent("medium-zoom:opened", {
265 detail: {
266 zoom: zoom
267 }
268 }));
269 resolve(zoom);
270 };
271 if (active.zoomed) {
272 resolve(zoom);
273 return;
274 }
275 if (target) {
276 active.original = target;
277 } else if (images.length > 0) {
278 var _images = images;
279 active.original = _images[0];
280 } else {
281 resolve(zoom);
282 return;
283 }
284 active.original.dispatchEvent(createCustomEvent("medium-zoom:open", {
285 detail: {
286 zoom: zoom
287 }
288 }));
289 scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
290 isAnimating = true;
291 active.zoomed = cloneTarget(active.original);
292 document.body.appendChild(overlay);
293 if (zoomOptions.template) {
294 var template = isNode(zoomOptions.template) ? zoomOptions.template : document.querySelector(zoomOptions.template);
295 active.template = document.createElement("div");
296 active.template.appendChild(template.content.cloneNode(true));
297 document.body.appendChild(active.template);
298 }
299 document.body.appendChild(active.zoomed);
300 window.requestAnimationFrame(function() {
301 document.body.classList.add("medium-zoom--opened");
302 });
303 active.original.classList.add("medium-zoom-image--hidden");
304 active.zoomed.classList.add("medium-zoom-image--opened");
305 active.zoomed.addEventListener("click", close);
306 active.zoomed.addEventListener("transitionend", _handleOpenEnd);
307 if (active.original.getAttribute("data-zoom-src")) {
308 active.zoomedHd = active.zoomed.cloneNode();
309 active.zoomedHd.removeAttribute("srcset");
310 active.zoomedHd.removeAttribute("sizes");
311 active.zoomedHd.src = active.zoomed.getAttribute("data-zoom-src");
312 active.zoomedHd.onerror = function() {
313 clearInterval(getZoomTargetSize);
314 console.warn("Unable to reach the zoom image target " + active.zoomedHd.src);
315 active.zoomedHd = null;
316 _animate();
317 };
318 var getZoomTargetSize = setInterval(function() {
319 if (active.zoomedHd.complete) {
320 clearInterval(getZoomTargetSize);
321 active.zoomedHd.classList.add("medium-zoom-image--opened");
322 active.zoomedHd.addEventListener("click", close);
323 document.body.appendChild(active.zoomedHd);
324 _animate();
325 }
326 }, 10);
327 } else if (active.original.hasAttribute("srcset")) {
328 active.zoomedHd = active.zoomed.cloneNode();
329 active.zoomedHd.removeAttribute("sizes");
330 active.zoomedHd.removeAttribute("loading");
331 var loadEventListener = active.zoomedHd.addEventListener("load", function() {
332 active.zoomedHd.removeEventListener("load", loadEventListener);
333 active.zoomedHd.classList.add("medium-zoom-image--opened");
334 active.zoomedHd.addEventListener("click", close);
335 document.body.appendChild(active.zoomedHd);
336 _animate();
337 });
338 } else {
339 _animate();
340 }
341 });
342 };
343 var close = function close() {
344 return new Promise(function(resolve) {
345 if (isAnimating || !active.original) {
346 resolve(zoom);
347 return;
348 }
349 var _handleCloseEnd = function _handleCloseEnd() {
350 active.original.classList.remove("medium-zoom-image--hidden");
351 document.body.removeChild(active.zoomed);
352 if (active.zoomedHd) {
353 document.body.removeChild(active.zoomedHd);
354 }
355 document.body.removeChild(overlay);
356 active.zoomed.classList.remove("medium-zoom-image--opened");
357 if (active.template) {
358 document.body.removeChild(active.template);
359 }
360 isAnimating = false;
361 active.zoomed.removeEventListener("transitionend", _handleCloseEnd);
362 active.original.dispatchEvent(createCustomEvent("medium-zoom:closed", {
363 detail: {
364 zoom: zoom
365 }
366 }));
367 active.original = null;
368 active.zoomed = null;
369 active.zoomedHd = null;
370 active.template = null;
371 resolve(zoom);
372 };
373 isAnimating = true;
374 document.body.classList.remove("medium-zoom--opened");
375 active.zoomed.style.transform = "";
376 if (active.zoomedHd) {
377 active.zoomedHd.style.transform = "";
378 }
379 if (active.template) {
380 active.template.style.transition = "opacity 150ms";
381 active.template.style.opacity = 0;
382 }
383 active.original.dispatchEvent(createCustomEvent("medium-zoom:close", {
384 detail: {
385 zoom: zoom
386 }
387 }));
388 active.zoomed.addEventListener("transitionend", _handleCloseEnd);
389 });
390 };
391 var toggle = function toggle() {
392 var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, target = _ref3.target;
393 if (active.original) {
394 return close();
395 }
396 return open({
397 target: target
398 });
399 };
400 var getOptions = function getOptions() {
401 return zoomOptions;
402 };
403 var getImages = function getImages() {
404 return images;
405 };
406 var getZoomedImage = function getZoomedImage() {
407 return active.original;
408 };
409 var images = [];
410 var eventListeners = [];
411 var isAnimating = false;
412 var scrollTop = 0;
413 var zoomOptions = options;
414 var active = {
415 original: null,
416 zoomed: null,
417 zoomedHd: null,
418 template: null
419 };
420 if (Object.prototype.toString.call(selector) === "[object Object]") {
421 zoomOptions = selector;
422 } else if (selector || typeof selector === "string") {
423 attach(selector);
424 }
425 zoomOptions = _extends({
426 margin: 0,
427 background: "#fff",
428 scrollOffset: 40,
429 container: null,
430 template: null
431 }, zoomOptions);
432 var overlay = createOverlay(zoomOptions.background);
433 document.addEventListener("click", _handleClick);
434 document.addEventListener("keyup", _handleKeyUp);
435 document.addEventListener("scroll", _handleScroll);
436 window.addEventListener("resize", close);
437 var zoom = {
438 open: open,
439 close: close,
440 toggle: toggle,
441 update: update,
442 clone: clone,
443 attach: attach,
444 detach: detach,
445 on: on,
446 off: off,
447 getOptions: getOptions,
448 getImages: getImages,
449 getZoomedImage: getZoomedImage
450 };
451 return zoom;
452 };
453 function styleInject(css, ref) {
454 if (ref === void 0) ref = {};
455 var insertAt = ref.insertAt;
456 if (!css || typeof document === "undefined") {
457 return;
458 }
459 var head = document.head || document.getElementsByTagName("head")[0];
460 var style = document.createElement("style");
461 style.type = "text/css";
462 if (insertAt === "top") {
463 if (head.firstChild) {
464 head.insertBefore(style, head.firstChild);
465 } else {
466 head.appendChild(style);
467 }
468 } else {
469 head.appendChild(style);
470 }
471 if (style.styleSheet) {
472 style.styleSheet.cssText = css;
473 } else {
474 style.appendChild(document.createTextNode(css));
475 }
476 }
477 var css = ".medium-zoom-overlay{position:fixed;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s;will-change:opacity}.medium-zoom--opened .medium-zoom-overlay{cursor:pointer;cursor:zoom-out;opacity:1}.medium-zoom-image{cursor:pointer;cursor:zoom-in;transition:transform .3s cubic-bezier(.2,0,.2,1)!important}.medium-zoom-image--hidden{visibility:hidden}.medium-zoom-image--opened{position:relative;cursor:pointer;cursor:zoom-out;will-change:transform}";
478 styleInject(css);
479 return mediumZoom;
480});