UNPKG

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