medium-zoom.esm.js 19 KB

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