You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3019 lines
85 KiB

  1. /*!
  2. * Viewer.js v1.3.2
  3. * https://fengyuanchen.github.io/viewerjs
  4. *
  5. * Copyright 2015-present Chen Fengyuan
  6. * Released under the MIT license
  7. *
  8. * Date: 2019-01-24T11:01:33.473Z
  9. */
  10. (function (global, factory) {
  11. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  12. typeof define === 'function' && define.amd ? define(factory) :
  13. (global.Viewer = factory());
  14. }(this, (function () { 'use strict';
  15. function _typeof(obj) {
  16. if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
  17. _typeof = function (obj) {
  18. return typeof obj;
  19. };
  20. } else {
  21. _typeof = function (obj) {
  22. return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
  23. };
  24. }
  25. return _typeof(obj);
  26. }
  27. function _classCallCheck(instance, Constructor) {
  28. if (!(instance instanceof Constructor)) {
  29. throw new TypeError("Cannot call a class as a function");
  30. }
  31. }
  32. function _defineProperties(target, props) {
  33. for (var i = 0; i < props.length; i++) {
  34. var descriptor = props[i];
  35. descriptor.enumerable = descriptor.enumerable || false;
  36. descriptor.configurable = true;
  37. if ("value" in descriptor) descriptor.writable = true;
  38. Object.defineProperty(target, descriptor.key, descriptor);
  39. }
  40. }
  41. function _createClass(Constructor, protoProps, staticProps) {
  42. if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  43. if (staticProps) _defineProperties(Constructor, staticProps);
  44. return Constructor;
  45. }
  46. var DEFAULTS = {
  47. /**
  48. * Enable a modal backdrop, specify `static` for a backdrop
  49. * which doesn't close the modal on click.
  50. * @type {boolean}
  51. */
  52. backdrop: true,
  53. /**
  54. * Show the button on the top-right of the viewer.
  55. * @type {boolean}
  56. */
  57. button: true,
  58. /**
  59. * Show the navbar.
  60. * @type {boolean | number}
  61. */
  62. navbar: true,
  63. /**
  64. * Specify the visibility and the content of the title.
  65. * @type {boolean | number | Function | Array}
  66. */
  67. title: true,
  68. /**
  69. * Show the toolbar.
  70. * @type {boolean | number | Object}
  71. */
  72. toolbar: true,
  73. /**
  74. * Custom class name(s) to add to the viewer's root element.
  75. * @type {string}
  76. */
  77. className: '',
  78. /**
  79. * Define where to put the viewer in modal mode.
  80. * @type {string | Element}
  81. */
  82. container: 'body',
  83. /**
  84. * Filter the images for viewing. Return true if the image is viewable.
  85. * @type {Function}
  86. */
  87. filter: null,
  88. /**
  89. * Enable to request fullscreen when play.
  90. * @type {boolean}
  91. */
  92. fullscreen: true,
  93. /**
  94. * Define the initial index of image for viewing.
  95. * @type {number}
  96. */
  97. initialViewIndex: 0,
  98. /**
  99. * Enable inline mode.
  100. * @type {boolean}
  101. */
  102. inline: false,
  103. /**
  104. * The amount of time to delay between automatically cycling an image when playing.
  105. * @type {number}
  106. */
  107. interval: 5000,
  108. /**
  109. * Enable keyboard support.
  110. * @type {boolean}
  111. */
  112. keyboard: true,
  113. /**
  114. * Indicate if show a loading spinner when load image or not.
  115. * @type {boolean}
  116. */
  117. loading: true,
  118. /**
  119. * Indicate if enable loop viewing or not.
  120. * @type {boolean}
  121. */
  122. loop: true,
  123. /**
  124. * Min width of the viewer in inline mode.
  125. * @type {number}
  126. */
  127. minWidth: 200,
  128. /**
  129. * Min height of the viewer in inline mode.
  130. * @type {number}
  131. */
  132. minHeight: 100,
  133. /**
  134. * Enable to move the image.
  135. * @type {boolean}
  136. */
  137. movable: true,
  138. /**
  139. * Enable to zoom the image.
  140. * @type {boolean}
  141. */
  142. zoomable: true,
  143. /**
  144. * Enable to rotate the image.
  145. * @type {boolean}
  146. */
  147. rotatable: true,
  148. /**
  149. * Enable to scale the image.
  150. * @type {boolean}
  151. */
  152. scalable: true,
  153. /**
  154. * Indicate if toggle the image size between its natural size
  155. * and initial size when double click on the image or not.
  156. * @type {boolean}
  157. */
  158. toggleOnDblclick: true,
  159. /**
  160. * Show the tooltip with image ratio (percentage) when zoom in or zoom out.
  161. * @type {boolean}
  162. */
  163. tooltip: true,
  164. /**
  165. * Enable CSS3 Transition for some special elements.
  166. * @type {boolean}
  167. */
  168. transition: true,
  169. /**
  170. * Define the CSS `z-index` value of viewer in modal mode.
  171. * @type {number}
  172. */
  173. zIndex: 2015,
  174. /**
  175. * Define the CSS `z-index` value of viewer in inline mode.
  176. * @type {number}
  177. */
  178. zIndexInline: 0,
  179. /**
  180. * Define the ratio when zoom the image by wheeling mouse.
  181. * @type {number}
  182. */
  183. zoomRatio: 0.1,
  184. /**
  185. * Define the min ratio of the image when zoom out.
  186. * @type {number}
  187. */
  188. minZoomRatio: 0.01,
  189. /**
  190. * Define the max ratio of the image when zoom in.
  191. * @type {number}
  192. */
  193. maxZoomRatio: 100,
  194. /**
  195. * Define where to get the original image URL for viewing.
  196. * @type {string | Function}
  197. */
  198. url: 'src',
  199. /**
  200. * Event shortcuts.
  201. * @type {Function}
  202. */
  203. ready: null,
  204. show: null,
  205. shown: null,
  206. hide: null,
  207. hidden: null,
  208. view: null,
  209. viewed: null,
  210. zoom: null,
  211. zoomed: null
  212. };
  213. var TEMPLATE = '<div class="viewer-container" touch-action="none">' + '<div class="viewer-canvas"></div>' + '<div class="viewer-footer">' + '<div class="viewer-title"></div>' + '<div class="viewer-toolbar"></div>' + '<div class="viewer-navbar">' + '<ul class="viewer-list"></ul>' + '</div>' + '</div>' + '<div class="viewer-tooltip"></div>' + '<div role="button" class="viewer-button" data-viewer-action="mix"></div>' + '<div class="viewer-player"></div>' + '</div>';
  214. var IS_BROWSER = typeof window !== 'undefined';
  215. var WINDOW = IS_BROWSER ? window : {};
  216. var IS_TOUCH_DEVICE = IS_BROWSER ? 'ontouchstart' in WINDOW.document.documentElement : false;
  217. var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;
  218. var NAMESPACE = 'viewer'; // Actions
  219. var ACTION_MOVE = 'move';
  220. var ACTION_SWITCH = 'switch';
  221. var ACTION_ZOOM = 'zoom'; // Classes
  222. var CLASS_ACTIVE = "".concat(NAMESPACE, "-active");
  223. var CLASS_CLOSE = "".concat(NAMESPACE, "-close");
  224. var CLASS_FADE = "".concat(NAMESPACE, "-fade");
  225. var CLASS_FIXED = "".concat(NAMESPACE, "-fixed");
  226. var CLASS_FULLSCREEN = "".concat(NAMESPACE, "-fullscreen");
  227. var CLASS_FULLSCREEN_EXIT = "".concat(NAMESPACE, "-fullscreen-exit");
  228. var CLASS_HIDE = "".concat(NAMESPACE, "-hide");
  229. var CLASS_HIDE_MD_DOWN = "".concat(NAMESPACE, "-hide-md-down");
  230. var CLASS_HIDE_SM_DOWN = "".concat(NAMESPACE, "-hide-sm-down");
  231. var CLASS_HIDE_XS_DOWN = "".concat(NAMESPACE, "-hide-xs-down");
  232. var CLASS_IN = "".concat(NAMESPACE, "-in");
  233. var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible");
  234. var CLASS_LOADING = "".concat(NAMESPACE, "-loading");
  235. var CLASS_MOVE = "".concat(NAMESPACE, "-move");
  236. var CLASS_OPEN = "".concat(NAMESPACE, "-open");
  237. var CLASS_SHOW = "".concat(NAMESPACE, "-show");
  238. var CLASS_TRANSITION = "".concat(NAMESPACE, "-transition"); // Events
  239. var EVENT_CLICK = 'click';
  240. var EVENT_DBLCLICK = 'dblclick';
  241. var EVENT_DRAG_START = 'dragstart';
  242. var EVENT_HIDDEN = 'hidden';
  243. var EVENT_HIDE = 'hide';
  244. var EVENT_KEY_DOWN = 'keydown';
  245. var EVENT_LOAD = 'load';
  246. var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';
  247. var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';
  248. var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';
  249. var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;
  250. var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;
  251. var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;
  252. var EVENT_READY = 'ready';
  253. var EVENT_RESIZE = 'resize';
  254. var EVENT_SHOW = 'show';
  255. var EVENT_SHOWN = 'shown';
  256. var EVENT_TRANSITION_END = 'transitionend';
  257. var EVENT_VIEW = 'view';
  258. var EVENT_VIEWED = 'viewed';
  259. var EVENT_WHEEL = 'wheel';
  260. var EVENT_ZOOM = 'zoom';
  261. var EVENT_ZOOMED = 'zoomed'; // Data keys
  262. var DATA_ACTION = "".concat(NAMESPACE, "Action"); // RegExps
  263. var REGEXP_SPACES = /\s\s*/; // Misc
  264. var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical'];
  265. /**
  266. * Check if the given value is a string.
  267. * @param {*} value - The value to check.
  268. * @returns {boolean} Returns `true` if the given value is a string, else `false`.
  269. */
  270. function isString(value) {
  271. return typeof value === 'string';
  272. }
  273. /**
  274. * Check if the given value is not a number.
  275. */
  276. var isNaN = Number.isNaN || WINDOW.isNaN;
  277. /**
  278. * Check if the given value is a number.
  279. * @param {*} value - The value to check.
  280. * @returns {boolean} Returns `true` if the given value is a number, else `false`.
  281. */
  282. function isNumber(value) {
  283. return typeof value === 'number' && !isNaN(value);
  284. }
  285. /**
  286. * Check if the given value is undefined.
  287. * @param {*} value - The value to check.
  288. * @returns {boolean} Returns `true` if the given value is undefined, else `false`.
  289. */
  290. function isUndefined(value) {
  291. return typeof value === 'undefined';
  292. }
  293. /**
  294. * Check if the given value is an object.
  295. * @param {*} value - The value to check.
  296. * @returns {boolean} Returns `true` if the given value is an object, else `false`.
  297. */
  298. function isObject(value) {
  299. return _typeof(value) === 'object' && value !== null;
  300. }
  301. var hasOwnProperty = Object.prototype.hasOwnProperty;
  302. /**
  303. * Check if the given value is a plain object.
  304. * @param {*} value - The value to check.
  305. * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.
  306. */
  307. function isPlainObject(value) {
  308. if (!isObject(value)) {
  309. return false;
  310. }
  311. try {
  312. var _constructor = value.constructor;
  313. var prototype = _constructor.prototype;
  314. return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');
  315. } catch (error) {
  316. return false;
  317. }
  318. }
  319. /**
  320. * Check if the given value is a function.
  321. * @param {*} value - The value to check.
  322. * @returns {boolean} Returns `true` if the given value is a function, else `false`.
  323. */
  324. function isFunction(value) {
  325. return typeof value === 'function';
  326. }
  327. /**
  328. * Iterate the given data.
  329. * @param {*} data - The data to iterate.
  330. * @param {Function} callback - The process function for each element.
  331. * @returns {*} The original data.
  332. */
  333. function forEach(data, callback) {
  334. if (data && isFunction(callback)) {
  335. if (Array.isArray(data) || isNumber(data.length)
  336. /* array-like */
  337. ) {
  338. var length = data.length;
  339. var i;
  340. for (i = 0; i < length; i += 1) {
  341. if (callback.call(data, data[i], i, data) === false) {
  342. break;
  343. }
  344. }
  345. } else if (isObject(data)) {
  346. Object.keys(data).forEach(function (key) {
  347. callback.call(data, data[key], key, data);
  348. });
  349. }
  350. }
  351. return data;
  352. }
  353. /**
  354. * Extend the given object.
  355. * @param {*} obj - The object to be extended.
  356. * @param {*} args - The rest objects which will be merged to the first object.
  357. * @returns {Object} The extended object.
  358. */
  359. var assign = Object.assign || function assign(obj) {
  360. for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  361. args[_key - 1] = arguments[_key];
  362. }
  363. if (isObject(obj) && args.length > 0) {
  364. args.forEach(function (arg) {
  365. if (isObject(arg)) {
  366. Object.keys(arg).forEach(function (key) {
  367. obj[key] = arg[key];
  368. });
  369. }
  370. });
  371. }
  372. return obj;
  373. };
  374. var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;
  375. /**
  376. * Apply styles to the given element.
  377. * @param {Element} element - The target element.
  378. * @param {Object} styles - The styles for applying.
  379. */
  380. function setStyle(element, styles) {
  381. var style = element.style;
  382. forEach(styles, function (value, property) {
  383. if (REGEXP_SUFFIX.test(property) && isNumber(value)) {
  384. value += 'px';
  385. }
  386. style[property] = value;
  387. });
  388. }
  389. /**
  390. * Check if the given element has a special class.
  391. * @param {Element} element - The element to check.
  392. * @param {string} value - The class to search.
  393. * @returns {boolean} Returns `true` if the special class was found.
  394. */
  395. function hasClass(element, value) {
  396. return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1;
  397. }
  398. /**
  399. * Add classes to the given element.
  400. * @param {Element} element - The target element.
  401. * @param {string} value - The classes to be added.
  402. */
  403. function addClass(element, value) {
  404. if (!value) {
  405. return;
  406. }
  407. if (isNumber(element.length)) {
  408. forEach(element, function (elem) {
  409. addClass(elem, value);
  410. });
  411. return;
  412. }
  413. if (element.classList) {
  414. element.classList.add(value);
  415. return;
  416. }
  417. var className = element.className.trim();
  418. if (!className) {
  419. element.className = value;
  420. } else if (className.indexOf(value) < 0) {
  421. element.className = "".concat(className, " ").concat(value);
  422. }
  423. }
  424. /**
  425. * Remove classes from the given element.
  426. * @param {Element} element - The target element.
  427. * @param {string} value - The classes to be removed.
  428. */
  429. function removeClass(element, value) {
  430. if (!value) {
  431. return;
  432. }
  433. if (isNumber(element.length)) {
  434. forEach(element, function (elem) {
  435. removeClass(elem, value);
  436. });
  437. return;
  438. }
  439. if (element.classList) {
  440. element.classList.remove(value);
  441. return;
  442. }
  443. if (element.className.indexOf(value) >= 0) {
  444. element.className = element.className.replace(value, '');
  445. }
  446. }
  447. /**
  448. * Add or remove classes from the given element.
  449. * @param {Element} element - The target element.
  450. * @param {string} value - The classes to be toggled.
  451. * @param {boolean} added - Add only.
  452. */
  453. function toggleClass(element, value, added) {
  454. if (!value) {
  455. return;
  456. }
  457. if (isNumber(element.length)) {
  458. forEach(element, function (elem) {
  459. toggleClass(elem, value, added);
  460. });
  461. return;
  462. } // IE10-11 doesn't support the second parameter of `classList.toggle`
  463. if (added) {
  464. addClass(element, value);
  465. } else {
  466. removeClass(element, value);
  467. }
  468. }
  469. var REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g;
  470. /**
  471. * Transform the given string from camelCase to kebab-case
  472. * @param {string} value - The value to transform.
  473. * @returns {string} The transformed value.
  474. */
  475. function hyphenate(value) {
  476. return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();
  477. }
  478. /**
  479. * Get data from the given element.
  480. * @param {Element} element - The target element.
  481. * @param {string} name - The data key to get.
  482. * @returns {string} The data value.
  483. */
  484. function getData(element, name) {
  485. if (isObject(element[name])) {
  486. return element[name];
  487. }
  488. if (element.dataset) {
  489. return element.dataset[name];
  490. }
  491. return element.getAttribute("data-".concat(hyphenate(name)));
  492. }
  493. /**
  494. * Set data to the given element.
  495. * @param {Element} element - The target element.
  496. * @param {string} name - The data key to set.
  497. * @param {string} data - The data value.
  498. */
  499. function setData(element, name, data) {
  500. if (isObject(data)) {
  501. element[name] = data;
  502. } else if (element.dataset) {
  503. element.dataset[name] = data;
  504. } else {
  505. element.setAttribute("data-".concat(hyphenate(name)), data);
  506. }
  507. }
  508. var onceSupported = function () {
  509. var supported = false;
  510. if (IS_BROWSER) {
  511. var once = false;
  512. var listener = function listener() {};
  513. var options = Object.defineProperty({}, 'once', {
  514. get: function get() {
  515. supported = true;
  516. return once;
  517. },
  518. /**
  519. * This setter can fix a `TypeError` in strict mode
  520. * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only}
  521. * @param {boolean} value - The value to set
  522. */
  523. set: function set(value) {
  524. once = value;
  525. }
  526. });
  527. WINDOW.addEventListener('test', listener, options);
  528. WINDOW.removeEventListener('test', listener, options);
  529. }
  530. return supported;
  531. }();
  532. /**
  533. * Remove event listener from the target element.
  534. * @param {Element} element - The event target.
  535. * @param {string} type - The event type(s).
  536. * @param {Function} listener - The event listener.
  537. * @param {Object} options - The event options.
  538. */
  539. function removeListener(element, type, listener) {
  540. var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  541. var handler = listener;
  542. type.trim().split(REGEXP_SPACES).forEach(function (event) {
  543. if (!onceSupported) {
  544. var listeners = element.listeners;
  545. if (listeners && listeners[event] && listeners[event][listener]) {
  546. handler = listeners[event][listener];
  547. delete listeners[event][listener];
  548. if (Object.keys(listeners[event]).length === 0) {
  549. delete listeners[event];
  550. }
  551. if (Object.keys(listeners).length === 0) {
  552. delete element.listeners;
  553. }
  554. }
  555. }
  556. element.removeEventListener(event, handler, options);
  557. });
  558. }
  559. /**
  560. * Add event listener to the target element.
  561. * @param {Element} element - The event target.
  562. * @param {string} type - The event type(s).
  563. * @param {Function} listener - The event listener.
  564. * @param {Object} options - The event options.
  565. */
  566. function addListener(element, type, listener) {
  567. var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  568. var _handler = listener;
  569. type.trim().split(REGEXP_SPACES).forEach(function (event) {
  570. if (options.once && !onceSupported) {
  571. var _element$listeners = element.listeners,
  572. listeners = _element$listeners === void 0 ? {} : _element$listeners;
  573. _handler = function handler() {
  574. delete listeners[event][listener];
  575. element.removeEventListener(event, _handler, options);
  576. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  577. args[_key2] = arguments[_key2];
  578. }
  579. listener.apply(element, args);
  580. };
  581. if (!listeners[event]) {
  582. listeners[event] = {};
  583. }
  584. if (listeners[event][listener]) {
  585. element.removeEventListener(event, listeners[event][listener], options);
  586. }
  587. listeners[event][listener] = _handler;
  588. element.listeners = listeners;
  589. }
  590. element.addEventListener(event, _handler, options);
  591. });
  592. }
  593. /**
  594. * Dispatch event on the target element.
  595. * @param {Element} element - The event target.
  596. * @param {string} type - The event type(s).
  597. * @param {Object} data - The additional event data.
  598. * @returns {boolean} Indicate if the event is default prevented or not.
  599. */
  600. function dispatchEvent(element, type, data) {
  601. var event; // Event and CustomEvent on IE9-11 are global objects, not constructors
  602. if (isFunction(Event) && isFunction(CustomEvent)) {
  603. event = new CustomEvent(type, {
  604. detail: data,
  605. bubbles: true,
  606. cancelable: true
  607. });
  608. } else {
  609. event = document.createEvent('CustomEvent');
  610. event.initCustomEvent(type, true, true, data);
  611. }
  612. return element.dispatchEvent(event);
  613. }
  614. /**
  615. * Get the offset base on the document.
  616. * @param {Element} element - The target element.
  617. * @returns {Object} The offset data.
  618. */
  619. function getOffset(element) {
  620. var box = element.getBoundingClientRect();
  621. return {
  622. left: box.left + (window.pageXOffset - document.documentElement.clientLeft),
  623. top: box.top + (window.pageYOffset - document.documentElement.clientTop)
  624. };
  625. }
  626. /**
  627. * Get transforms base on the given object.
  628. * @param {Object} obj - The target object.
  629. * @returns {string} A string contains transform values.
  630. */
  631. function getTransforms(_ref) {
  632. var rotate = _ref.rotate,
  633. scaleX = _ref.scaleX,
  634. scaleY = _ref.scaleY,
  635. translateX = _ref.translateX,
  636. translateY = _ref.translateY;
  637. var values = [];
  638. if (isNumber(translateX) && translateX !== 0) {
  639. values.push("translateX(".concat(translateX, "px)"));
  640. }
  641. if (isNumber(translateY) && translateY !== 0) {
  642. values.push("translateY(".concat(translateY, "px)"));
  643. } // Rotate should come first before scale to match orientation transform
  644. if (isNumber(rotate) && rotate !== 0) {
  645. values.push("rotate(".concat(rotate, "deg)"));
  646. }
  647. if (isNumber(scaleX) && scaleX !== 1) {
  648. values.push("scaleX(".concat(scaleX, ")"));
  649. }
  650. if (isNumber(scaleY) && scaleY !== 1) {
  651. values.push("scaleY(".concat(scaleY, ")"));
  652. }
  653. var transform = values.length ? values.join(' ') : 'none';
  654. return {
  655. WebkitTransform: transform,
  656. msTransform: transform,
  657. transform: transform
  658. };
  659. }
  660. /**
  661. * Get an image name from an image url.
  662. * @param {string} url - The target url.
  663. * @example
  664. * // picture.jpg
  665. * getImageNameFromURL('http://domain.com/path/to/picture.jpg?size=1280×960')
  666. * @returns {string} A string contains the image name.
  667. */
  668. function getImageNameFromURL(url) {
  669. return isString(url) ? url.replace(/^.*\//, '').replace(/[?&#].*$/, '') : '';
  670. }
  671. var IS_SAFARI = WINDOW.navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(WINDOW.navigator.userAgent);
  672. /**
  673. * Get an image's natural sizes.
  674. * @param {string} image - The target image.
  675. * @param {Function} callback - The callback function.
  676. * @returns {HTMLImageElement} The new image.
  677. */
  678. function getImageNaturalSizes(image, callback) {
  679. var newImage = document.createElement('img'); // Modern browsers (except Safari)
  680. if (image.naturalWidth && !IS_SAFARI) {
  681. callback(image.naturalWidth, image.naturalHeight);
  682. return newImage;
  683. }
  684. var body = document.body || document.documentElement;
  685. newImage.onload = function () {
  686. callback(newImage.width, newImage.height);
  687. if (!IS_SAFARI) {
  688. body.removeChild(newImage);
  689. }
  690. };
  691. newImage.src = image.src; // iOS Safari will convert the image automatically
  692. // with its orientation once append it into DOM
  693. if (!IS_SAFARI) {
  694. newImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;';
  695. body.appendChild(newImage);
  696. }
  697. return newImage;
  698. }
  699. /**
  700. * Get the related class name of a responsive type number.
  701. * @param {string} type - The responsive type.
  702. * @returns {string} The related class name.
  703. */
  704. function getResponsiveClass(type) {
  705. switch (type) {
  706. case 2:
  707. return CLASS_HIDE_XS_DOWN;
  708. case 3:
  709. return CLASS_HIDE_SM_DOWN;
  710. case 4:
  711. return CLASS_HIDE_MD_DOWN;
  712. default:
  713. return '';
  714. }
  715. }
  716. /**
  717. * Get the max ratio of a group of pointers.
  718. * @param {string} pointers - The target pointers.
  719. * @returns {number} The result ratio.
  720. */
  721. function getMaxZoomRatio(pointers) {
  722. var pointers2 = assign({}, pointers);
  723. var ratios = [];
  724. forEach(pointers, function (pointer, pointerId) {
  725. delete pointers2[pointerId];
  726. forEach(pointers2, function (pointer2) {
  727. var x1 = Math.abs(pointer.startX - pointer2.startX);
  728. var y1 = Math.abs(pointer.startY - pointer2.startY);
  729. var x2 = Math.abs(pointer.endX - pointer2.endX);
  730. var y2 = Math.abs(pointer.endY - pointer2.endY);
  731. var z1 = Math.sqrt(x1 * x1 + y1 * y1);
  732. var z2 = Math.sqrt(x2 * x2 + y2 * y2);
  733. var ratio = (z2 - z1) / z1;
  734. ratios.push(ratio);
  735. });
  736. });
  737. ratios.sort(function (a, b) {
  738. return Math.abs(a) < Math.abs(b);
  739. });
  740. return ratios[0];
  741. }
  742. /**
  743. * Get a pointer from an event object.
  744. * @param {Object} event - The target event object.
  745. * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.
  746. * @returns {Object} The result pointer contains start and/or end point coordinates.
  747. */
  748. function getPointer(_ref2, endOnly) {
  749. var pageX = _ref2.pageX,
  750. pageY = _ref2.pageY;
  751. var end = {
  752. endX: pageX,
  753. endY: pageY
  754. };
  755. return endOnly ? end : assign({
  756. timeStamp: Date.now(),
  757. startX: pageX,
  758. startY: pageY
  759. }, end);
  760. }
  761. /**
  762. * Get the center point coordinate of a group of pointers.
  763. * @param {Object} pointers - The target pointers.
  764. * @returns {Object} The center point coordinate.
  765. */
  766. function getPointersCenter(pointers) {
  767. var pageX = 0;
  768. var pageY = 0;
  769. var count = 0;
  770. forEach(pointers, function (_ref3) {
  771. var startX = _ref3.startX,
  772. startY = _ref3.startY;
  773. pageX += startX;
  774. pageY += startY;
  775. count += 1;
  776. });
  777. pageX /= count;
  778. pageY /= count;
  779. return {
  780. pageX: pageX,
  781. pageY: pageY
  782. };
  783. }
  784. var render = {
  785. render: function render() {
  786. this.initContainer();
  787. this.initViewer();
  788. this.initList();
  789. this.renderViewer();
  790. },
  791. initContainer: function initContainer() {
  792. this.containerData = {
  793. width: window.innerWidth,
  794. height: window.innerHeight
  795. };
  796. },
  797. initViewer: function initViewer() {
  798. var options = this.options,
  799. parent = this.parent;
  800. var viewerData;
  801. if (options.inline) {
  802. viewerData = {
  803. width: Math.max(parent.offsetWidth, options.minWidth),
  804. height: Math.max(parent.offsetHeight, options.minHeight)
  805. };
  806. this.parentData = viewerData;
  807. }
  808. if (this.fulled || !viewerData) {
  809. viewerData = this.containerData;
  810. }
  811. this.viewerData = assign({}, viewerData);
  812. },
  813. renderViewer: function renderViewer() {
  814. if (this.options.inline && !this.fulled) {
  815. setStyle(this.viewer, this.viewerData);
  816. }
  817. },
  818. initList: function initList() {
  819. var _this = this;
  820. var element = this.element,
  821. options = this.options,
  822. list = this.list;
  823. var items = [];
  824. forEach(this.images, function (image, i) {
  825. var src = image.src;
  826. var alt = image.alt || getImageNameFromURL(src);
  827. var url = options.url;
  828. if (isString(url)) {
  829. url = image.getAttribute(url);
  830. } else if (isFunction(url)) {
  831. url = url.call(_this, image);
  832. }
  833. if (src || url) {
  834. items.push('<li>' + '<img' + " src=\"".concat(src || url, "\"") + ' role="button"' + ' data-viewer-action="view"' + " data-index=\"".concat(i, "\"") + " data-original-url=\"".concat(url || src, "\"") + " alt=\"".concat(alt, "\"") + '>' + '</li>');
  835. }
  836. });
  837. list.innerHTML = items.join('');
  838. this.items = list.getElementsByTagName('li');
  839. forEach(this.items, function (item) {
  840. var image = item.firstElementChild;
  841. setData(image, 'filled', true);
  842. if (options.loading) {
  843. addClass(item, CLASS_LOADING);
  844. }
  845. addListener(image, EVENT_LOAD, function (event) {
  846. if (options.loading) {
  847. removeClass(item, CLASS_LOADING);
  848. }
  849. _this.loadImage(event);
  850. }, {
  851. once: true
  852. });
  853. });
  854. if (options.transition) {
  855. addListener(element, EVENT_VIEWED, function () {
  856. addClass(list, CLASS_TRANSITION);
  857. }, {
  858. once: true
  859. });
  860. }
  861. },
  862. renderList: function renderList(index) {
  863. var i = index || this.index;
  864. var width = this.items[i].offsetWidth || 30;
  865. var outerWidth = width + 1; // 1 pixel of `margin-left` width
  866. // Place the active item in the center of the screen
  867. setStyle(this.list, assign({
  868. width: outerWidth * this.length
  869. }, getTransforms({
  870. translateX: (this.viewerData.width - width) / 2 - outerWidth * i
  871. })));
  872. },
  873. resetList: function resetList() {
  874. var list = this.list;
  875. list.innerHTML = '';
  876. removeClass(list, CLASS_TRANSITION);
  877. setStyle(list, getTransforms({
  878. translateX: 0
  879. }));
  880. },
  881. initImage: function initImage(done) {
  882. var _this2 = this;
  883. var options = this.options,
  884. image = this.image,
  885. viewerData = this.viewerData;
  886. var footerHeight = this.footer.offsetHeight;
  887. var viewerWidth = viewerData.width;
  888. var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);
  889. var oldImageData = this.imageData || {};
  890. var sizingImage;
  891. this.imageInitializing = {
  892. abort: function abort() {
  893. sizingImage.onload = null;
  894. }
  895. };
  896. sizingImage = getImageNaturalSizes(image, function (naturalWidth, naturalHeight) {
  897. var aspectRatio = naturalWidth / naturalHeight;
  898. var width = viewerWidth;
  899. var height = viewerHeight;
  900. _this2.imageInitializing = false;
  901. if (viewerHeight * aspectRatio > viewerWidth) {
  902. height = viewerWidth / aspectRatio;
  903. } else {
  904. width = viewerHeight * aspectRatio;
  905. }
  906. width = Math.min(width * 0.9, naturalWidth);
  907. height = Math.min(height * 0.9, naturalHeight);
  908. var imageData = {
  909. naturalWidth: naturalWidth,
  910. naturalHeight: naturalHeight,
  911. aspectRatio: aspectRatio,
  912. ratio: width / naturalWidth,
  913. width: width,
  914. height: height,
  915. left: (viewerWidth - width) / 2,
  916. top: (viewerHeight - height) / 2
  917. };
  918. var initialImageData = assign({}, imageData);
  919. if (options.rotatable) {
  920. imageData.rotate = oldImageData.rotate || 0;
  921. initialImageData.rotate = 0;
  922. }
  923. if (options.scalable) {
  924. imageData.scaleX = oldImageData.scaleX || 1;
  925. imageData.scaleY = oldImageData.scaleY || 1;
  926. initialImageData.scaleX = 1;
  927. initialImageData.scaleY = 1;
  928. }
  929. _this2.imageData = imageData;
  930. _this2.initialImageData = initialImageData;
  931. if (done) {
  932. done();
  933. }
  934. });
  935. },
  936. renderImage: function renderImage(done) {
  937. var _this3 = this;
  938. var image = this.image,
  939. imageData = this.imageData;
  940. setStyle(image, assign({
  941. width: imageData.width,
  942. height: imageData.height,
  943. // XXX: Not to use translateX/Y to avoid image shaking when zooming
  944. marginLeft: imageData.left,
  945. marginTop: imageData.top
  946. }, getTransforms(imageData)));
  947. if (done) {
  948. if ((this.viewing || this.zooming) && this.options.transition) {
  949. var onTransitionEnd = function onTransitionEnd() {
  950. _this3.imageRendering = false;
  951. done();
  952. };
  953. this.imageRendering = {
  954. abort: function abort() {
  955. removeListener(image, EVENT_TRANSITION_END, onTransitionEnd);
  956. }
  957. };
  958. addListener(image, EVENT_TRANSITION_END, onTransitionEnd, {
  959. once: true
  960. });
  961. } else {
  962. done();
  963. }
  964. }
  965. },
  966. resetImage: function resetImage() {
  967. // this.image only defined after viewed
  968. if (this.viewing || this.viewed) {
  969. var image = this.image;
  970. if (this.viewing) {
  971. this.viewing.abort();
  972. }
  973. image.parentNode.removeChild(image);
  974. this.image = null;
  975. }
  976. }
  977. };
  978. var events = {
  979. bind: function bind() {
  980. var options = this.options,
  981. viewer = this.viewer,
  982. canvas = this.canvas;
  983. var document = this.element.ownerDocument;
  984. addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this));
  985. addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), {
  986. passive: false,
  987. capture: true
  988. });
  989. addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this));
  990. addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this));
  991. addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this));
  992. addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this));
  993. addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this));
  994. addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this));
  995. if (options.toggleOnDblclick) {
  996. addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this));
  997. }
  998. },
  999. unbind: function unbind() {
  1000. var options = this.options,
  1001. viewer = this.viewer,
  1002. canvas = this.canvas;
  1003. var document = this.element.ownerDocument;
  1004. removeListener(viewer, EVENT_CLICK, this.onClick);
  1005. removeListener(viewer, EVENT_WHEEL, this.onWheel, {
  1006. passive: false,
  1007. capture: true
  1008. });
  1009. removeListener(viewer, EVENT_DRAG_START, this.onDragStart);
  1010. removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown);
  1011. removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove);
  1012. removeListener(document, EVENT_POINTER_UP, this.onPointerUp);
  1013. removeListener(document, EVENT_KEY_DOWN, this.onKeyDown);
  1014. removeListener(window, EVENT_RESIZE, this.onResize);
  1015. if (options.toggleOnDblclick) {
  1016. removeListener(canvas, EVENT_DBLCLICK, this.onDblclick);
  1017. }
  1018. }
  1019. };
  1020. var handlers = {
  1021. click: function click(event) {
  1022. var target = event.target;
  1023. var options = this.options,
  1024. imageData = this.imageData;
  1025. var action = getData(target, DATA_ACTION); // Cancel the emulated click when the native click event was triggered.
  1026. if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) {
  1027. clearTimeout(this.clickCanvasTimeout);
  1028. }
  1029. switch (action) {
  1030. case 'mix':
  1031. if (this.played) {
  1032. this.stop();
  1033. } else if (options.inline) {
  1034. if (this.fulled) {
  1035. this.exit();
  1036. } else {
  1037. this.full();
  1038. }
  1039. } else {
  1040. this.hide();
  1041. }
  1042. break;
  1043. case 'hide':
  1044. this.hide();
  1045. break;
  1046. case 'view':
  1047. this.view(getData(target, 'index'));
  1048. break;
  1049. case 'zoom-in':
  1050. this.zoom(0.1, true);
  1051. break;
  1052. case 'zoom-out':
  1053. this.zoom(-0.1, true);
  1054. break;
  1055. case 'one-to-one':
  1056. this.toggle();
  1057. break;
  1058. case 'reset':
  1059. this.reset();
  1060. break;
  1061. case 'prev':
  1062. this.prev(options.loop);
  1063. break;
  1064. case 'play':
  1065. this.play(options.fullscreen);
  1066. break;
  1067. case 'next':
  1068. this.next(options.loop);
  1069. break;
  1070. case 'rotate-left':
  1071. this.rotate(-90);
  1072. break;
  1073. case 'rotate-right':
  1074. this.rotate(90);
  1075. break;
  1076. case 'flip-horizontal':
  1077. this.scaleX(-imageData.scaleX || -1);
  1078. break;
  1079. case 'flip-vertical':
  1080. this.scaleY(-imageData.scaleY || -1);
  1081. break;
  1082. default:
  1083. if (this.played) {
  1084. this.stop();
  1085. }
  1086. }
  1087. },
  1088. dblclick: function dblclick(event) {
  1089. event.preventDefault();
  1090. if (this.viewed && event.target === this.image) {
  1091. // Cancel the emulated double click when the native dblclick event was triggered.
  1092. if (IS_TOUCH_DEVICE && event.isTrusted) {
  1093. clearTimeout(this.doubleClickImageTimeout);
  1094. }
  1095. this.toggle();
  1096. }
  1097. },
  1098. load: function load() {
  1099. var _this = this;
  1100. if (this.timeout) {
  1101. clearTimeout(this.timeout);
  1102. this.timeout = false;
  1103. }
  1104. var element = this.element,
  1105. options = this.options,
  1106. image = this.image,
  1107. index = this.index,
  1108. viewerData = this.viewerData;
  1109. removeClass(image, CLASS_INVISIBLE);
  1110. if (options.loading) {
  1111. removeClass(this.canvas, CLASS_LOADING);
  1112. }
  1113. image.style.cssText = 'height:0;' + "margin-left:".concat(viewerData.width / 2, "px;") + "margin-top:".concat(viewerData.height / 2, "px;") + 'max-width:none!important;' + 'position:absolute;' + 'width:0;';
  1114. this.initImage(function () {
  1115. toggleClass(image, CLASS_MOVE, options.movable);
  1116. toggleClass(image, CLASS_TRANSITION, options.transition);
  1117. _this.renderImage(function () {
  1118. _this.viewed = true;
  1119. _this.viewing = false;
  1120. if (isFunction(options.viewed)) {
  1121. addListener(element, EVENT_VIEWED, options.viewed, {
  1122. once: true
  1123. });
  1124. }
  1125. dispatchEvent(element, EVENT_VIEWED, {
  1126. originalImage: _this.images[index],
  1127. index: index,
  1128. image: image
  1129. });
  1130. });
  1131. });
  1132. },
  1133. loadImage: function loadImage(event) {
  1134. var image = event.target;
  1135. var parent = image.parentNode;
  1136. var parentWidth = parent.offsetWidth || 30;
  1137. var parentHeight = parent.offsetHeight || 50;
  1138. var filled = !!getData(image, 'filled');
  1139. getImageNaturalSizes(image, function (naturalWidth, naturalHeight) {
  1140. var aspectRatio = naturalWidth / naturalHeight;
  1141. var width = parentWidth;
  1142. var height = parentHeight;
  1143. if (parentHeight * aspectRatio > parentWidth) {
  1144. if (filled) {
  1145. width = parentHeight * aspectRatio;
  1146. } else {
  1147. height = parentWidth / aspectRatio;
  1148. }
  1149. } else if (filled) {
  1150. height = parentWidth / aspectRatio;
  1151. } else {
  1152. width = parentHeight * aspectRatio;
  1153. }
  1154. setStyle(image, assign({
  1155. width: width,
  1156. height: height
  1157. }, getTransforms({
  1158. translateX: (parentWidth - width) / 2,
  1159. translateY: (parentHeight - height) / 2
  1160. })));
  1161. });
  1162. },
  1163. keydown: function keydown(event) {
  1164. var options = this.options;
  1165. if (!this.fulled || !options.keyboard) {
  1166. return;
  1167. }
  1168. switch (event.keyCode || event.which || event.charCode) {
  1169. // Escape
  1170. case 27:
  1171. if (this.played) {
  1172. this.stop();
  1173. } else if (options.inline) {
  1174. if (this.fulled) {
  1175. this.exit();
  1176. }
  1177. } else {
  1178. this.hide();
  1179. }
  1180. break;
  1181. // Space
  1182. case 32:
  1183. if (this.played) {
  1184. this.stop();
  1185. }
  1186. break;
  1187. // ArrowLeft
  1188. case 37:
  1189. this.prev(options.loop);
  1190. break;
  1191. // ArrowUp
  1192. case 38:
  1193. // Prevent scroll on Firefox
  1194. event.preventDefault(); // Zoom in
  1195. this.zoom(options.zoomRatio, true);
  1196. break;
  1197. // ArrowRight
  1198. case 39:
  1199. this.next(options.loop);
  1200. break;
  1201. // ArrowDown
  1202. case 40:
  1203. // Prevent scroll on Firefox
  1204. event.preventDefault(); // Zoom out
  1205. this.zoom(-options.zoomRatio, true);
  1206. break;
  1207. // Ctrl + 0
  1208. case 48: // Fall through
  1209. // Ctrl + 1
  1210. // eslint-disable-next-line no-fallthrough
  1211. case 49:
  1212. if (event.ctrlKey) {
  1213. event.preventDefault();
  1214. this.toggle();
  1215. }
  1216. break;
  1217. default:
  1218. }
  1219. },
  1220. dragstart: function dragstart(event) {
  1221. if (event.target.tagName.toLowerCase() === 'img') {
  1222. event.preventDefault();
  1223. }
  1224. },
  1225. pointerdown: function pointerdown(event) {
  1226. var options = this.options,
  1227. pointers = this.pointers;
  1228. var buttons = event.buttons,
  1229. button = event.button;
  1230. if (!this.viewed || this.showing || this.viewing || this.hiding // No primary button (Usually the left button)
  1231. // Note that touch events have no `buttons` or `button` property
  1232. || isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0 // Open context menu
  1233. || event.ctrlKey) {
  1234. return;
  1235. } // Prevent default behaviours as page zooming in touch devices.
  1236. event.preventDefault();
  1237. if (event.changedTouches) {
  1238. forEach(event.changedTouches, function (touch) {
  1239. pointers[touch.identifier] = getPointer(touch);
  1240. });
  1241. } else {
  1242. pointers[event.pointerId || 0] = getPointer(event);
  1243. }
  1244. var action = options.movable ? ACTION_MOVE : false;
  1245. if (Object.keys(pointers).length > 1) {
  1246. action = ACTION_ZOOM;
  1247. } else if ((event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {
  1248. action = ACTION_SWITCH;
  1249. }
  1250. if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
  1251. removeClass(this.image, CLASS_TRANSITION);
  1252. }
  1253. this.action = action;
  1254. },
  1255. pointermove: function pointermove(event) {
  1256. var pointers = this.pointers,
  1257. action = this.action;
  1258. if (!this.viewed || !action) {
  1259. return;
  1260. }
  1261. event.preventDefault();
  1262. if (event.changedTouches) {
  1263. forEach(event.changedTouches, function (touch) {
  1264. assign(pointers[touch.identifier] || {}, getPointer(touch, true));
  1265. });
  1266. } else {
  1267. assign(pointers[event.pointerId || 0] || {}, getPointer(event, true));
  1268. }
  1269. this.change(event);
  1270. },
  1271. pointerup: function pointerup(event) {
  1272. var _this2 = this;
  1273. var options = this.options,
  1274. action = this.action,
  1275. pointers = this.pointers;
  1276. var pointer;
  1277. if (event.changedTouches) {
  1278. forEach(event.changedTouches, function (touch) {
  1279. pointer = pointers[touch.identifier];
  1280. delete pointers[touch.identifier];
  1281. });
  1282. } else {
  1283. pointer = pointers[event.pointerId || 0];
  1284. delete pointers[event.pointerId || 0];
  1285. }
  1286. if (!action) {
  1287. return;
  1288. }
  1289. event.preventDefault();
  1290. if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
  1291. addClass(this.image, CLASS_TRANSITION);
  1292. }
  1293. this.action = false; // Emulate click and double click in touch devices to support backdrop and image zooming (#210).
  1294. if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) {
  1295. clearTimeout(this.clickCanvasTimeout);
  1296. clearTimeout(this.doubleClickImageTimeout);
  1297. if (options.toggleOnDblclick && this.viewed && event.target === this.image) {
  1298. if (this.imageClicked) {
  1299. this.imageClicked = false; // This timeout will be cleared later when a native dblclick event is triggering
  1300. this.doubleClickImageTimeout = setTimeout(function () {
  1301. dispatchEvent(_this2.image, EVENT_DBLCLICK);
  1302. }, 50);
  1303. } else {
  1304. this.imageClicked = true; // The default timing of a double click in Windows is 500 ms
  1305. this.doubleClickImageTimeout = setTimeout(function () {
  1306. _this2.imageClicked = false;
  1307. }, 500);
  1308. }
  1309. } else {
  1310. this.imageClicked = false;
  1311. if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) {
  1312. // This timeout will be cleared later when a native click event is triggering
  1313. this.clickCanvasTimeout = setTimeout(function () {
  1314. dispatchEvent(_this2.canvas, EVENT_CLICK);
  1315. }, 50);
  1316. }
  1317. }
  1318. }
  1319. },
  1320. resize: function resize() {
  1321. var _this3 = this;
  1322. if (!this.isShown || this.hiding) {
  1323. return;
  1324. }
  1325. this.initContainer();
  1326. this.initViewer();
  1327. this.renderViewer();
  1328. this.renderList();
  1329. if (this.viewed) {
  1330. this.initImage(function () {
  1331. _this3.renderImage();
  1332. });
  1333. }
  1334. if (this.played) {
  1335. if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
  1336. this.stop();
  1337. return;
  1338. }
  1339. forEach(this.player.getElementsByTagName('img'), function (image) {
  1340. addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), {
  1341. once: true
  1342. });
  1343. dispatchEvent(image, EVENT_LOAD);
  1344. });
  1345. }
  1346. },
  1347. wheel: function wheel(event) {
  1348. var _this4 = this;
  1349. if (!this.viewed) {
  1350. return;
  1351. }
  1352. event.preventDefault(); // Limit wheel speed to prevent zoom too fast
  1353. if (this.wheeling) {
  1354. return;
  1355. }
  1356. this.wheeling = true;
  1357. setTimeout(function () {
  1358. _this4.wheeling = false;
  1359. }, 50);
  1360. var ratio = Number(this.options.zoomRatio) || 0.1;
  1361. var delta = 1;
  1362. if (event.deltaY) {
  1363. delta = event.deltaY > 0 ? 1 : -1;
  1364. } else if (event.wheelDelta) {
  1365. delta = -event.wheelDelta / 120;
  1366. } else if (event.detail) {
  1367. delta = event.detail > 0 ? 1 : -1;
  1368. }
  1369. this.zoom(-delta * ratio, true, event);
  1370. }
  1371. };
  1372. var methods = {
  1373. /** Show the viewer (only available in modal mode)
  1374. * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not.
  1375. * @returns {Viewer} this
  1376. */
  1377. show: function show() {
  1378. var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  1379. var element = this.element,
  1380. options = this.options;
  1381. if (options.inline || this.showing || this.isShown || this.showing) {
  1382. return this;
  1383. }
  1384. if (!this.ready) {
  1385. this.build();
  1386. if (this.ready) {
  1387. this.show(immediate);
  1388. }
  1389. return this;
  1390. }
  1391. if (isFunction(options.show)) {
  1392. addListener(element, EVENT_SHOW, options.show, {
  1393. once: true
  1394. });
  1395. }
  1396. if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) {
  1397. return this;
  1398. }
  1399. if (this.hiding) {
  1400. this.transitioning.abort();
  1401. }
  1402. this.showing = true;
  1403. this.open();
  1404. var viewer = this.viewer;
  1405. removeClass(viewer, CLASS_HIDE);
  1406. if (options.transition && !immediate) {
  1407. var shown = this.shown.bind(this);
  1408. this.transitioning = {
  1409. abort: function abort() {
  1410. removeListener(viewer, EVENT_TRANSITION_END, shown);
  1411. removeClass(viewer, CLASS_IN);
  1412. }
  1413. };
  1414. addClass(viewer, CLASS_TRANSITION); // Force reflow to enable CSS3 transition
  1415. // eslint-disable-next-line
  1416. viewer.offsetWidth;
  1417. addListener(viewer, EVENT_TRANSITION_END, shown, {
  1418. once: true
  1419. });
  1420. addClass(viewer, CLASS_IN);
  1421. } else {
  1422. addClass(viewer, CLASS_IN);
  1423. this.shown();
  1424. }
  1425. return this;
  1426. },
  1427. /**
  1428. * Hide the viewer (only available in modal mode)
  1429. * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not.
  1430. * @returns {Viewer} this
  1431. */
  1432. hide: function hide() {
  1433. var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  1434. var element = this.element,
  1435. options = this.options;
  1436. if (options.inline || this.hiding || !(this.isShown || this.showing)) {
  1437. return this;
  1438. }
  1439. if (isFunction(options.hide)) {
  1440. addListener(element, EVENT_HIDE, options.hide, {
  1441. once: true
  1442. });
  1443. }
  1444. if (dispatchEvent(element, EVENT_HIDE) === false) {
  1445. return this;
  1446. }
  1447. if (this.showing) {
  1448. this.transitioning.abort();
  1449. }
  1450. this.hiding = true;
  1451. if (this.played) {
  1452. this.stop();
  1453. } else if (this.viewing) {
  1454. this.viewing.abort();
  1455. }
  1456. var viewer = this.viewer;
  1457. if (options.transition && !immediate) {
  1458. var hidden = this.hidden.bind(this);
  1459. var hide = function hide() {
  1460. addListener(viewer, EVENT_TRANSITION_END, hidden, {
  1461. once: true
  1462. });
  1463. removeClass(viewer, CLASS_IN);
  1464. };
  1465. this.transitioning = {
  1466. abort: function abort() {
  1467. if (this.viewed) {
  1468. removeListener(this.image, EVENT_TRANSITION_END, hide);
  1469. } else {
  1470. removeListener(viewer, EVENT_TRANSITION_END, hidden);
  1471. }
  1472. }
  1473. };
  1474. if (this.viewed) {
  1475. addListener(this.image, EVENT_TRANSITION_END, hide, {
  1476. once: true
  1477. });
  1478. this.zoomTo(0, false, false, true);
  1479. } else {
  1480. hide();
  1481. }
  1482. } else {
  1483. removeClass(viewer, CLASS_IN);
  1484. this.hidden();
  1485. }
  1486. return this;
  1487. },
  1488. /**
  1489. * View one of the images with image's index
  1490. * @param {number} index - The index of the image to view.
  1491. * @returns {Viewer} this
  1492. */
  1493. view: function view() {
  1494. var _this = this;
  1495. var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex;
  1496. index = Number(index) || 0;
  1497. if (!this.isShown) {
  1498. this.index = index;
  1499. return this.show();
  1500. }
  1501. if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) {
  1502. return this;
  1503. }
  1504. if (this.viewing) {
  1505. this.viewing.abort();
  1506. }
  1507. var element = this.element,
  1508. options = this.options,
  1509. title = this.title,
  1510. canvas = this.canvas;
  1511. var item = this.items[index];
  1512. var img = item.querySelector('img');
  1513. var url = getData(img, 'originalUrl');
  1514. var alt = img.getAttribute('alt');
  1515. var image = document.createElement('img');
  1516. image.src = url;
  1517. image.alt = alt;
  1518. if (isFunction(options.view)) {
  1519. addListener(element, EVENT_VIEW, options.view, {
  1520. once: true
  1521. });
  1522. }
  1523. if (dispatchEvent(element, EVENT_VIEW, {
  1524. originalImage: this.images[index],
  1525. index: index,
  1526. image: image
  1527. }) === false || !this.isShown || this.hiding || this.played) {
  1528. return this;
  1529. }
  1530. this.image = image;
  1531. removeClass(this.items[this.index], CLASS_ACTIVE);
  1532. addClass(item, CLASS_ACTIVE);
  1533. this.viewed = false;
  1534. this.index = index;
  1535. this.imageData = {};
  1536. addClass(image, CLASS_INVISIBLE);
  1537. if (options.loading) {
  1538. addClass(canvas, CLASS_LOADING);
  1539. }
  1540. canvas.innerHTML = '';
  1541. canvas.appendChild(image); // Center current item
  1542. this.renderList(); // Clear title
  1543. title.innerHTML = ''; // Generate title after viewed
  1544. var onViewed = function onViewed() {
  1545. var imageData = _this.imageData;
  1546. var render = Array.isArray(options.title) ? options.title[1] : options.title;
  1547. title.innerHTML = isFunction(render) ? render.call(_this, image, imageData) : "".concat(alt, " (").concat(imageData.naturalWidth, " \xD7 ").concat(imageData.naturalHeight, ")");
  1548. };
  1549. var onLoad;
  1550. addListener(element, EVENT_VIEWED, onViewed, {
  1551. once: true
  1552. });
  1553. this.viewing = {
  1554. abort: function abort() {
  1555. removeListener(element, EVENT_VIEWED, onViewed);
  1556. if (image.complete) {
  1557. if (this.imageRendering) {
  1558. this.imageRendering.abort();
  1559. } else if (this.imageInitializing) {
  1560. this.imageInitializing.abort();
  1561. }
  1562. } else {
  1563. removeListener(image, EVENT_LOAD, onLoad);
  1564. if (this.timeout) {
  1565. clearTimeout(this.timeout);
  1566. }
  1567. }
  1568. }
  1569. };
  1570. if (image.complete) {
  1571. this.load();
  1572. } else {
  1573. addListener(image, EVENT_LOAD, onLoad = this.load.bind(this), {
  1574. once: true
  1575. });
  1576. if (this.timeout) {
  1577. clearTimeout(this.timeout);
  1578. } // Make the image visible if it fails to load within 1s
  1579. this.timeout = setTimeout(function () {
  1580. removeClass(image, CLASS_INVISIBLE);
  1581. _this.timeout = false;
  1582. }, 1000);
  1583. }
  1584. return this;
  1585. },
  1586. /**
  1587. * View the previous image
  1588. * @param {boolean} [loop=false] - Indicate if view the last one
  1589. * when it is the first one at present.
  1590. * @returns {Viewer} this
  1591. */
  1592. prev: function prev() {
  1593. var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  1594. var index = this.index - 1;
  1595. if (index < 0) {
  1596. index = loop ? this.length - 1 : 0;
  1597. }
  1598. this.view(index);
  1599. return this;
  1600. },
  1601. /**
  1602. * View the next image
  1603. * @param {boolean} [loop=false] - Indicate if view the first one
  1604. * when it is the last one at present.
  1605. * @returns {Viewer} this
  1606. */
  1607. next: function next() {
  1608. var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  1609. var maxIndex = this.length - 1;
  1610. var index = this.index + 1;
  1611. if (index > maxIndex) {
  1612. index = loop ? 0 : maxIndex;
  1613. }
  1614. this.view(index);
  1615. return this;
  1616. },
  1617. /**
  1618. * Move the image with relative offsets.
  1619. * @param {number} offsetX - The relative offset distance on the x-axis.
  1620. * @param {number} offsetY - The relative offset distance on the y-axis.
  1621. * @returns {Viewer} this
  1622. */
  1623. move: function move(offsetX, offsetY) {
  1624. var imageData = this.imageData;
  1625. this.moveTo(isUndefined(offsetX) ? offsetX : imageData.left + Number(offsetX), isUndefined(offsetY) ? offsetY : imageData.top + Number(offsetY));
  1626. return this;
  1627. },
  1628. /**
  1629. * Move the image to an absolute point.
  1630. * @param {number} x - The x-axis coordinate.
  1631. * @param {number} [y=x] - The y-axis coordinate.
  1632. * @returns {Viewer} this
  1633. */
  1634. moveTo: function moveTo(x) {
  1635. var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
  1636. var imageData = this.imageData;
  1637. x = Number(x);
  1638. y = Number(y);
  1639. if (this.viewed && !this.played && this.options.movable) {
  1640. var changed = false;
  1641. if (isNumber(x)) {
  1642. imageData.left = x;
  1643. changed = true;
  1644. }
  1645. if (isNumber(y)) {
  1646. imageData.top = y;
  1647. changed = true;
  1648. }
  1649. if (changed) {
  1650. this.renderImage();
  1651. }
  1652. }
  1653. return this;
  1654. },
  1655. /**
  1656. * Zoom the image with a relative ratio.
  1657. * @param {number} ratio - The target ratio.
  1658. * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not.
  1659. * @param {Event} [_originalEvent=null] - The original event if any.
  1660. * @returns {Viewer} this
  1661. */
  1662. zoom: function zoom(ratio) {
  1663. var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  1664. var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
  1665. var imageData = this.imageData;
  1666. ratio = Number(ratio);
  1667. if (ratio < 0) {
  1668. ratio = 1 / (1 - ratio);
  1669. } else {
  1670. ratio = 1 + ratio;
  1671. }
  1672. this.zoomTo(imageData.width * ratio / imageData.naturalWidth, hasTooltip, _originalEvent);
  1673. return this;
  1674. },
  1675. /**
  1676. * Zoom the image to an absolute ratio.
  1677. * @param {number} ratio - The target ratio.
  1678. * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not.
  1679. * @param {Event} [_originalEvent=null] - The original event if any.
  1680. * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not.
  1681. * @returns {Viewer} this
  1682. */
  1683. zoomTo: function zoomTo(ratio) {
  1684. var _this2 = this;
  1685. var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  1686. var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
  1687. var _zoomable = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
  1688. var element = this.element,
  1689. options = this.options,
  1690. pointers = this.pointers,
  1691. imageData = this.imageData;
  1692. var width = imageData.width,
  1693. height = imageData.height,
  1694. left = imageData.left,
  1695. top = imageData.top,
  1696. naturalWidth = imageData.naturalWidth,
  1697. naturalHeight = imageData.naturalHeight;
  1698. ratio = Math.max(0, ratio);
  1699. if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) {
  1700. if (!_zoomable) {
  1701. var minZoomRatio = Math.max(0.01, options.minZoomRatio);
  1702. var maxZoomRatio = Math.min(100, options.maxZoomRatio);
  1703. ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio);
  1704. }
  1705. if (_originalEvent && ratio > 0.95 && ratio < 1.05) {
  1706. ratio = 1;
  1707. }
  1708. var newWidth = naturalWidth * ratio;
  1709. var newHeight = naturalHeight * ratio;
  1710. var offsetWidth = newWidth - width;
  1711. var offsetHeight = newHeight - height;
  1712. var oldRatio = width / naturalWidth;
  1713. if (isFunction(options.zoom)) {
  1714. addListener(element, EVENT_ZOOM, options.zoom, {
  1715. once: true
  1716. });
  1717. }
  1718. if (dispatchEvent(element, EVENT_ZOOM, {
  1719. ratio: ratio,
  1720. oldRatio: oldRatio,
  1721. originalEvent: _originalEvent
  1722. }) === false) {
  1723. return this;
  1724. }
  1725. this.zooming = true;
  1726. if (_originalEvent) {
  1727. var offset = getOffset(this.viewer);
  1728. var center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : {
  1729. pageX: _originalEvent.pageX,
  1730. pageY: _originalEvent.pageY
  1731. }; // Zoom from the triggering point of the event
  1732. imageData.left -= offsetWidth * ((center.pageX - offset.left - left) / width);
  1733. imageData.top -= offsetHeight * ((center.pageY - offset.top - top) / height);
  1734. } else {
  1735. // Zoom from the center of the image
  1736. imageData.left -= offsetWidth / 2;
  1737. imageData.top -= offsetHeight / 2;
  1738. }
  1739. imageData.width = newWidth;
  1740. imageData.height = newHeight;
  1741. imageData.ratio = ratio;
  1742. this.renderImage(function () {
  1743. _this2.zooming = false;
  1744. if (isFunction(options.zoomed)) {
  1745. addListener(element, EVENT_ZOOMED, options.zoomed, {
  1746. once: true
  1747. });
  1748. }
  1749. dispatchEvent(element, EVENT_ZOOMED, {
  1750. ratio: ratio,
  1751. oldRatio: oldRatio,
  1752. originalEvent: _originalEvent
  1753. });
  1754. });
  1755. if (hasTooltip) {
  1756. this.tooltip();
  1757. }
  1758. }
  1759. return this;
  1760. },
  1761. /**
  1762. * Rotate the image with a relative degree.
  1763. * @param {number} degree - The rotate degree.
  1764. * @returns {Viewer} this
  1765. */
  1766. rotate: function rotate(degree) {
  1767. this.rotateTo((this.imageData.rotate || 0) + Number(degree));
  1768. return this;
  1769. },
  1770. /**
  1771. * Rotate the image to an absolute degree.
  1772. * @param {number} degree - The rotate degree.
  1773. * @returns {Viewer} this
  1774. */
  1775. rotateTo: function rotateTo(degree) {
  1776. var imageData = this.imageData;
  1777. degree = Number(degree);
  1778. if (isNumber(degree) && this.viewed && !this.played && this.options.rotatable) {
  1779. imageData.rotate = degree;
  1780. this.renderImage();
  1781. }
  1782. return this;
  1783. },
  1784. /**
  1785. * Scale the image on the x-axis.
  1786. * @param {number} scaleX - The scale ratio on the x-axis.
  1787. * @returns {Viewer} this
  1788. */
  1789. scaleX: function scaleX(_scaleX) {
  1790. this.scale(_scaleX, this.imageData.scaleY);
  1791. return this;
  1792. },
  1793. /**
  1794. * Scale the image on the y-axis.
  1795. * @param {number} scaleY - The scale ratio on the y-axis.
  1796. * @returns {Viewer} this
  1797. */
  1798. scaleY: function scaleY(_scaleY) {
  1799. this.scale(this.imageData.scaleX, _scaleY);
  1800. return this;
  1801. },
  1802. /**
  1803. * Scale the image.
  1804. * @param {number} scaleX - The scale ratio on the x-axis.
  1805. * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.
  1806. * @returns {Viewer} this
  1807. */
  1808. scale: function scale(scaleX) {
  1809. var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX;
  1810. var imageData = this.imageData;
  1811. scaleX = Number(scaleX);
  1812. scaleY = Number(scaleY);
  1813. if (this.viewed && !this.played && this.options.scalable) {
  1814. var changed = false;
  1815. if (isNumber(scaleX)) {
  1816. imageData.scaleX = scaleX;
  1817. changed = true;
  1818. }
  1819. if (isNumber(scaleY)) {
  1820. imageData.scaleY = scaleY;
  1821. changed = true;
  1822. }
  1823. if (changed) {
  1824. this.renderImage();
  1825. }
  1826. }
  1827. return this;
  1828. },
  1829. /**
  1830. * Play the images
  1831. * @param {boolean} [fullscreen=false] - Indicate if request fullscreen or not.
  1832. * @returns {Viewer} this
  1833. */
  1834. play: function play() {
  1835. var _this3 = this;
  1836. var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  1837. if (!this.isShown || this.played) {
  1838. return this;
  1839. }
  1840. var options = this.options,
  1841. player = this.player;
  1842. var onLoad = this.loadImage.bind(this);
  1843. var list = [];
  1844. var total = 0;
  1845. var index = 0;
  1846. this.played = true;
  1847. this.onLoadWhenPlay = onLoad;
  1848. if (fullscreen) {
  1849. this.requestFullscreen();
  1850. }
  1851. addClass(player, CLASS_SHOW);
  1852. forEach(this.items, function (item, i) {
  1853. var img = item.querySelector('img');
  1854. var image = document.createElement('img');
  1855. image.src = getData(img, 'originalUrl');
  1856. image.alt = img.getAttribute('alt');
  1857. total += 1;
  1858. addClass(image, CLASS_FADE);
  1859. toggleClass(image, CLASS_TRANSITION, options.transition);
  1860. if (hasClass(item, CLASS_ACTIVE)) {
  1861. addClass(image, CLASS_IN);
  1862. index = i;
  1863. }
  1864. list.push(image);
  1865. addListener(image, EVENT_LOAD, onLoad, {
  1866. once: true
  1867. });
  1868. player.appendChild(image);
  1869. });
  1870. if (isNumber(options.interval) && options.interval > 0) {
  1871. var play = function play() {
  1872. _this3.playing = setTimeout(function () {
  1873. removeClass(list[index], CLASS_IN);
  1874. index += 1;
  1875. index = index < total ? index : 0;
  1876. addClass(list[index], CLASS_IN);
  1877. play();
  1878. }, options.interval);
  1879. };
  1880. if (total > 1) {
  1881. play();
  1882. }
  1883. }
  1884. return this;
  1885. },
  1886. // Stop play
  1887. stop: function stop() {
  1888. var _this4 = this;
  1889. if (!this.played) {
  1890. return this;
  1891. }
  1892. var player = this.player;
  1893. this.played = false;
  1894. clearTimeout(this.playing);
  1895. forEach(player.getElementsByTagName('img'), function (image) {
  1896. removeListener(image, EVENT_LOAD, _this4.onLoadWhenPlay);
  1897. });
  1898. removeClass(player, CLASS_SHOW);
  1899. player.innerHTML = '';
  1900. this.exitFullscreen();
  1901. return this;
  1902. },
  1903. // Enter modal mode (only available in inline mode)
  1904. full: function full() {
  1905. var _this5 = this;
  1906. var options = this.options,
  1907. viewer = this.viewer,
  1908. image = this.image,
  1909. list = this.list;
  1910. if (!this.isShown || this.played || this.fulled || !options.inline) {
  1911. return this;
  1912. }
  1913. this.fulled = true;
  1914. this.open();
  1915. addClass(this.button, CLASS_FULLSCREEN_EXIT);
  1916. if (options.transition) {
  1917. removeClass(list, CLASS_TRANSITION);
  1918. if (this.viewed) {
  1919. removeClass(image, CLASS_TRANSITION);
  1920. }
  1921. }
  1922. addClass(viewer, CLASS_FIXED);
  1923. viewer.setAttribute('style', '');
  1924. setStyle(viewer, {
  1925. zIndex: options.zIndex
  1926. });
  1927. this.initContainer();
  1928. this.viewerData = assign({}, this.containerData);
  1929. this.renderList();
  1930. if (this.viewed) {
  1931. this.initImage(function () {
  1932. _this5.renderImage(function () {
  1933. if (options.transition) {
  1934. setTimeout(function () {
  1935. addClass(image, CLASS_TRANSITION);
  1936. addClass(list, CLASS_TRANSITION);
  1937. }, 0);
  1938. }
  1939. });
  1940. });
  1941. }
  1942. return this;
  1943. },
  1944. // Exit modal mode (only available in inline mode)
  1945. exit: function exit() {
  1946. var _this6 = this;
  1947. var options = this.options,
  1948. viewer = this.viewer,
  1949. image = this.image,
  1950. list = this.list;
  1951. if (!this.isShown || this.played || !this.fulled || !options.inline) {
  1952. return this;
  1953. }
  1954. this.fulled = false;
  1955. this.close();
  1956. removeClass(this.button, CLASS_FULLSCREEN_EXIT);
  1957. if (options.transition) {
  1958. removeClass(list, CLASS_TRANSITION);
  1959. if (this.viewed) {
  1960. removeClass(image, CLASS_TRANSITION);
  1961. }
  1962. }
  1963. removeClass(viewer, CLASS_FIXED);
  1964. setStyle(viewer, {
  1965. zIndex: options.zIndexInline
  1966. });
  1967. this.viewerData = assign({}, this.parentData);
  1968. this.renderViewer();
  1969. this.renderList();
  1970. if (this.viewed) {
  1971. this.initImage(function () {
  1972. _this6.renderImage(function () {
  1973. if (options.transition) {
  1974. setTimeout(function () {
  1975. addClass(image, CLASS_TRANSITION);
  1976. addClass(list, CLASS_TRANSITION);
  1977. }, 0);
  1978. }
  1979. });
  1980. });
  1981. }
  1982. return this;
  1983. },
  1984. // Show the current ratio of the image with percentage
  1985. tooltip: function tooltip() {
  1986. var _this7 = this;
  1987. var options = this.options,
  1988. tooltipBox = this.tooltipBox,
  1989. imageData = this.imageData;
  1990. if (!this.viewed || this.played || !options.tooltip) {
  1991. return this;
  1992. }
  1993. tooltipBox.textContent = "".concat(Math.round(imageData.ratio * 100), "%");
  1994. if (!this.tooltipping) {
  1995. if (options.transition) {
  1996. if (this.fading) {
  1997. dispatchEvent(tooltipBox, EVENT_TRANSITION_END);
  1998. }
  1999. addClass(tooltipBox, CLASS_SHOW);
  2000. addClass(tooltipBox, CLASS_FADE);
  2001. addClass(tooltipBox, CLASS_TRANSITION); // Force reflow to enable CSS3 transition
  2002. // eslint-disable-next-line
  2003. tooltipBox.offsetWidth;
  2004. addClass(tooltipBox, CLASS_IN);
  2005. } else {
  2006. addClass(tooltipBox, CLASS_SHOW);
  2007. }
  2008. } else {
  2009. clearTimeout(this.tooltipping);
  2010. }
  2011. this.tooltipping = setTimeout(function () {
  2012. if (options.transition) {
  2013. addListener(tooltipBox, EVENT_TRANSITION_END, function () {
  2014. removeClass(tooltipBox, CLASS_SHOW);
  2015. removeClass(tooltipBox, CLASS_FADE);
  2016. removeClass(tooltipBox, CLASS_TRANSITION);
  2017. _this7.fading = false;
  2018. }, {
  2019. once: true
  2020. });
  2021. removeClass(tooltipBox, CLASS_IN);
  2022. _this7.fading = true;
  2023. } else {
  2024. removeClass(tooltipBox, CLASS_SHOW);
  2025. }
  2026. _this7.tooltipping = false;
  2027. }, 1000);
  2028. return this;
  2029. },
  2030. // Toggle the image size between its natural size and initial size
  2031. toggle: function toggle() {
  2032. if (this.imageData.ratio === 1) {
  2033. this.zoomTo(this.initialImageData.ratio, true);
  2034. } else {
  2035. this.zoomTo(1, true);
  2036. }
  2037. return this;
  2038. },
  2039. // Reset the image to its initial state
  2040. reset: function reset() {
  2041. if (this.viewed && !this.played) {
  2042. this.imageData = assign({}, this.initialImageData);
  2043. this.renderImage();
  2044. }
  2045. return this;
  2046. },
  2047. // Update viewer when images changed
  2048. update: function update() {
  2049. var element = this.element,
  2050. options = this.options,
  2051. isImg = this.isImg; // Destroy viewer if the target image was deleted
  2052. if (isImg && !element.parentNode) {
  2053. return this.destroy();
  2054. }
  2055. var images = [];
  2056. forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {
  2057. if (options.filter) {
  2058. if (options.filter(image)) {
  2059. images.push(image);
  2060. }
  2061. } else {
  2062. images.push(image);
  2063. }
  2064. });
  2065. if (!images.length) {
  2066. return this;
  2067. }
  2068. this.images = images;
  2069. this.length = images.length;
  2070. if (this.ready) {
  2071. var indexes = [];
  2072. forEach(this.items, function (item, i) {
  2073. var img = item.querySelector('img');
  2074. var image = images[i];
  2075. if (image) {
  2076. if (image.src !== img.src) {
  2077. indexes.push(i);
  2078. }
  2079. } else {
  2080. indexes.push(i);
  2081. }
  2082. });
  2083. setStyle(this.list, {
  2084. width: 'auto'
  2085. });
  2086. this.initList();
  2087. if (this.isShown) {
  2088. if (this.length) {
  2089. if (this.viewed) {
  2090. var index = indexes.indexOf(this.index);
  2091. if (index >= 0) {
  2092. this.viewed = false;
  2093. this.view(Math.max(this.index - (index + 1), 0));
  2094. } else {
  2095. addClass(this.items[this.index], CLASS_ACTIVE);
  2096. }
  2097. }
  2098. } else {
  2099. this.image = null;
  2100. this.viewed = false;
  2101. this.index = 0;
  2102. this.imageData = {};
  2103. this.canvas.innerHTML = '';
  2104. this.title.innerHTML = '';
  2105. }
  2106. }
  2107. } else {
  2108. this.build();
  2109. }
  2110. return this;
  2111. },
  2112. // Destroy the viewer
  2113. destroy: function destroy() {
  2114. var element = this.element,
  2115. options = this.options;
  2116. if (!element[NAMESPACE]) {
  2117. return this;
  2118. }
  2119. this.destroyed = true;
  2120. if (this.ready) {
  2121. if (this.played) {
  2122. this.stop();
  2123. }
  2124. if (options.inline) {
  2125. if (this.fulled) {
  2126. this.exit();
  2127. }
  2128. this.unbind();
  2129. } else if (this.isShown) {
  2130. if (this.viewing) {
  2131. if (this.imageRendering) {
  2132. this.imageRendering.abort();
  2133. } else if (this.imageInitializing) {
  2134. this.imageInitializing.abort();
  2135. }
  2136. }
  2137. if (this.hiding) {
  2138. this.transitioning.abort();
  2139. }
  2140. this.hidden();
  2141. } else if (this.showing) {
  2142. this.transitioning.abort();
  2143. this.hidden();
  2144. }
  2145. this.ready = false;
  2146. this.viewer.parentNode.removeChild(this.viewer);
  2147. } else if (options.inline) {
  2148. if (this.delaying) {
  2149. this.delaying.abort();
  2150. } else if (this.initializing) {
  2151. this.initializing.abort();
  2152. }
  2153. }
  2154. if (!options.inline) {
  2155. removeListener(element, EVENT_CLICK, this.onStart);
  2156. }
  2157. element[NAMESPACE] = undefined;
  2158. return this;
  2159. }
  2160. };
  2161. var others = {
  2162. open: function open() {
  2163. var body = this.body;
  2164. addClass(body, CLASS_OPEN);
  2165. body.style.paddingRight = "".concat(this.scrollbarWidth + (parseFloat(this.initialBodyPaddingRight) || 0), "px");
  2166. },
  2167. close: function close() {
  2168. var body = this.body;
  2169. removeClass(body, CLASS_OPEN);
  2170. body.style.paddingRight = this.initialBodyPaddingRight;
  2171. },
  2172. shown: function shown() {
  2173. var element = this.element,
  2174. options = this.options;
  2175. this.fulled = true;
  2176. this.isShown = true;
  2177. this.render();
  2178. this.bind();
  2179. this.showing = false;
  2180. if (isFunction(options.shown)) {
  2181. addListener(element, EVENT_SHOWN, options.shown, {
  2182. once: true
  2183. });
  2184. }
  2185. if (dispatchEvent(element, EVENT_SHOWN) === false) {
  2186. return;
  2187. }
  2188. if (this.ready && this.isShown && !this.hiding) {
  2189. this.view(this.index);
  2190. }
  2191. },
  2192. hidden: function hidden() {
  2193. var element = this.element,
  2194. options = this.options;
  2195. this.fulled = false;
  2196. this.viewed = false;
  2197. this.isShown = false;
  2198. this.close();
  2199. this.unbind();
  2200. addClass(this.viewer, CLASS_HIDE);
  2201. this.resetList();
  2202. this.resetImage();
  2203. this.hiding = false;
  2204. if (!this.destroyed) {
  2205. if (isFunction(options.hidden)) {
  2206. addListener(element, EVENT_HIDDEN, options.hidden, {
  2207. once: true
  2208. });
  2209. }
  2210. dispatchEvent(element, EVENT_HIDDEN);
  2211. }
  2212. },
  2213. requestFullscreen: function requestFullscreen() {
  2214. var document = this.element.ownerDocument;
  2215. if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
  2216. var documentElement = document.documentElement; // Element.requestFullscreen()
  2217. if (documentElement.requestFullscreen) {
  2218. documentElement.requestFullscreen();
  2219. } else if (documentElement.webkitRequestFullscreen) {
  2220. documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
  2221. } else if (documentElement.mozRequestFullScreen) {
  2222. documentElement.mozRequestFullScreen();
  2223. } else if (documentElement.msRequestFullscreen) {
  2224. documentElement.msRequestFullscreen();
  2225. }
  2226. }
  2227. },
  2228. exitFullscreen: function exitFullscreen() {
  2229. var document = this.element.ownerDocument;
  2230. if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
  2231. // Document.exitFullscreen()
  2232. if (document.exitFullscreen) {
  2233. document.exitFullscreen();
  2234. } else if (document.webkitExitFullscreen) {
  2235. document.webkitExitFullscreen();
  2236. } else if (document.mozCancelFullScreen) {
  2237. document.mozCancelFullScreen();
  2238. } else if (document.msExitFullscreen) {
  2239. document.msExitFullscreen();
  2240. }
  2241. }
  2242. },
  2243. change: function change(event) {
  2244. var options = this.options,
  2245. pointers = this.pointers;
  2246. var pointer = pointers[Object.keys(pointers)[0]];
  2247. var offsetX = pointer.endX - pointer.startX;
  2248. var offsetY = pointer.endY - pointer.startY;
  2249. switch (this.action) {
  2250. // Move the current image
  2251. case ACTION_MOVE:
  2252. this.move(offsetX, offsetY);
  2253. break;
  2254. // Zoom the current image
  2255. case ACTION_ZOOM:
  2256. this.zoom(getMaxZoomRatio(pointers), false, event);
  2257. break;
  2258. case ACTION_SWITCH:
  2259. {
  2260. this.action = 'switched';
  2261. var absoluteOffsetX = Math.abs(offsetX);
  2262. if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) {
  2263. // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers.
  2264. this.pointers = {};
  2265. if (offsetX > 1) {
  2266. this.prev(options.loop);
  2267. } else if (offsetX < -1) {
  2268. this.next(options.loop);
  2269. }
  2270. }
  2271. break;
  2272. }
  2273. default:
  2274. } // Override
  2275. forEach(pointers, function (p) {
  2276. p.startX = p.endX;
  2277. p.startY = p.endY;
  2278. });
  2279. },
  2280. isSwitchable: function isSwitchable() {
  2281. var imageData = this.imageData,
  2282. viewerData = this.viewerData;
  2283. return this.length > 1 && imageData.left >= 0 && imageData.top >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height;
  2284. }
  2285. };
  2286. var AnotherViewer = WINDOW.Viewer;
  2287. var Viewer =
  2288. /*#__PURE__*/
  2289. function () {
  2290. /**
  2291. * Create a new Viewer.
  2292. * @param {Element} element - The target element for viewing.
  2293. * @param {Object} [options={}] - The configuration options.
  2294. */
  2295. function Viewer(element) {
  2296. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  2297. _classCallCheck(this, Viewer);
  2298. if (!element || element.nodeType !== 1) {
  2299. throw new Error('The first argument is required and must be an element.');
  2300. }
  2301. this.element = element;
  2302. this.options = assign({}, DEFAULTS, isPlainObject(options) && options);
  2303. this.action = false;
  2304. this.fading = false;
  2305. this.fulled = false;
  2306. this.hiding = false;
  2307. this.imageClicked = false;
  2308. this.imageData = {};
  2309. this.index = this.options.initialViewIndex;
  2310. this.isImg = false;
  2311. this.isShown = false;
  2312. this.length = 0;
  2313. this.played = false;
  2314. this.playing = false;
  2315. this.pointers = {};
  2316. this.ready = false;
  2317. this.showing = false;
  2318. this.timeout = false;
  2319. this.tooltipping = false;
  2320. this.viewed = false;
  2321. this.viewing = false;
  2322. this.wheeling = false;
  2323. this.zooming = false;
  2324. this.init();
  2325. }
  2326. _createClass(Viewer, [{
  2327. key: "init",
  2328. value: function init() {
  2329. var _this = this;
  2330. var element = this.element,
  2331. options = this.options;
  2332. if (element[NAMESPACE]) {
  2333. return;
  2334. }
  2335. element[NAMESPACE] = this;
  2336. var isImg = element.tagName.toLowerCase() === 'img';
  2337. var images = [];
  2338. forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {
  2339. if (isFunction(options.filter)) {
  2340. if (options.filter.call(_this, image)) {
  2341. images.push(image);
  2342. }
  2343. } else {
  2344. images.push(image);
  2345. }
  2346. });
  2347. this.isImg = isImg;
  2348. this.length = images.length;
  2349. this.images = images;
  2350. var ownerDocument = element.ownerDocument;
  2351. var body = ownerDocument.body || ownerDocument.documentElement;
  2352. this.body = body;
  2353. this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth;
  2354. this.initialBodyPaddingRight = window.getComputedStyle(body).paddingRight; // Override `transition` option if it is not supported
  2355. if (isUndefined(document.createElement(NAMESPACE).style.transition)) {
  2356. options.transition = false;
  2357. }
  2358. if (options.inline) {
  2359. var count = 0;
  2360. var progress = function progress() {
  2361. count += 1;
  2362. if (count === _this.length) {
  2363. var timeout;
  2364. _this.initializing = false;
  2365. _this.delaying = {
  2366. abort: function abort() {
  2367. clearTimeout(timeout);
  2368. }
  2369. }; // build asynchronously to keep `this.viewer` is accessible in `ready` event handler.
  2370. timeout = setTimeout(function () {
  2371. _this.delaying = false;
  2372. _this.build();
  2373. }, 0);
  2374. }
  2375. };
  2376. this.initializing = {
  2377. abort: function abort() {
  2378. forEach(images, function (image) {
  2379. if (!image.complete) {
  2380. removeListener(image, EVENT_LOAD, progress);
  2381. }
  2382. });
  2383. }
  2384. };
  2385. forEach(images, function (image) {
  2386. if (image.complete) {
  2387. progress();
  2388. } else {
  2389. addListener(image, EVENT_LOAD, progress, {
  2390. once: true
  2391. });
  2392. }
  2393. });
  2394. } else {
  2395. addListener(element, EVENT_CLICK, this.onStart = function (_ref) {
  2396. var target = _ref.target;
  2397. if (target.tagName.toLowerCase() === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) {
  2398. _this.view(_this.images.indexOf(target));
  2399. }
  2400. });
  2401. }
  2402. }
  2403. }, {
  2404. key: "build",
  2405. value: function build() {
  2406. if (this.ready) {
  2407. return;
  2408. }
  2409. var element = this.element,
  2410. options = this.options;
  2411. var parent = element.parentNode;
  2412. var template = document.createElement('div');
  2413. template.innerHTML = TEMPLATE;
  2414. var viewer = template.querySelector(".".concat(NAMESPACE, "-container"));
  2415. var title = viewer.querySelector(".".concat(NAMESPACE, "-title"));
  2416. var toolbar = viewer.querySelector(".".concat(NAMESPACE, "-toolbar"));
  2417. var navbar = viewer.querySelector(".".concat(NAMESPACE, "-navbar"));
  2418. var button = viewer.querySelector(".".concat(NAMESPACE, "-button"));
  2419. var canvas = viewer.querySelector(".".concat(NAMESPACE, "-canvas"));
  2420. this.parent = parent;
  2421. this.viewer = viewer;
  2422. this.title = title;
  2423. this.toolbar = toolbar;
  2424. this.navbar = navbar;
  2425. this.button = button;
  2426. this.canvas = canvas;
  2427. this.footer = viewer.querySelector(".".concat(NAMESPACE, "-footer"));
  2428. this.tooltipBox = viewer.querySelector(".".concat(NAMESPACE, "-tooltip"));
  2429. this.player = viewer.querySelector(".".concat(NAMESPACE, "-player"));
  2430. this.list = viewer.querySelector(".".concat(NAMESPACE, "-list"));
  2431. addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title));
  2432. addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));
  2433. toggleClass(button, CLASS_HIDE, !options.button);
  2434. if (options.backdrop) {
  2435. addClass(viewer, "".concat(NAMESPACE, "-backdrop"));
  2436. if (!options.inline && options.backdrop !== 'static') {
  2437. setData(canvas, DATA_ACTION, 'hide');
  2438. }
  2439. }
  2440. if (isString(options.className) && options.className) {
  2441. // In case there are multiple class names
  2442. options.className.split(REGEXP_SPACES).forEach(function (className) {
  2443. addClass(viewer, className);
  2444. });
  2445. }
  2446. if (options.toolbar) {
  2447. var list = document.createElement('ul');
  2448. var custom = isPlainObject(options.toolbar);
  2449. var zoomButtons = BUTTONS.slice(0, 3);
  2450. var rotateButtons = BUTTONS.slice(7, 9);
  2451. var scaleButtons = BUTTONS.slice(9);
  2452. if (!custom) {
  2453. addClass(toolbar, getResponsiveClass(options.toolbar));
  2454. }
  2455. forEach(custom ? options.toolbar : BUTTONS, function (value, index) {
  2456. var deep = custom && isPlainObject(value);
  2457. var name = custom ? hyphenate(index) : value;
  2458. var show = deep && !isUndefined(value.show) ? value.show : value;
  2459. if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) {
  2460. return;
  2461. }
  2462. var size = deep && !isUndefined(value.size) ? value.size : value;
  2463. var click = deep && !isUndefined(value.click) ? value.click : value;
  2464. var item = document.createElement('li');
  2465. item.setAttribute('role', 'button');
  2466. addClass(item, "".concat(NAMESPACE, "-").concat(name));
  2467. if (!isFunction(click)) {
  2468. setData(item, DATA_ACTION, name);
  2469. }
  2470. if (isNumber(show)) {
  2471. addClass(item, getResponsiveClass(show));
  2472. }
  2473. if (['small', 'large'].indexOf(size) !== -1) {
  2474. addClass(item, "".concat(NAMESPACE, "-").concat(size));
  2475. } else if (name === 'play') {
  2476. addClass(item, "".concat(NAMESPACE, "-large"));
  2477. }
  2478. if (isFunction(click)) {
  2479. addListener(item, EVENT_CLICK, click);
  2480. }
  2481. list.appendChild(item);
  2482. });
  2483. toolbar.appendChild(list);
  2484. } else {
  2485. addClass(toolbar, CLASS_HIDE);
  2486. }
  2487. if (!options.rotatable) {
  2488. var rotates = toolbar.querySelectorAll('li[class*="rotate"]');
  2489. addClass(rotates, CLASS_INVISIBLE);
  2490. forEach(rotates, function (rotate) {
  2491. toolbar.appendChild(rotate);
  2492. });
  2493. }
  2494. if (options.inline) {
  2495. addClass(button, CLASS_FULLSCREEN);
  2496. setStyle(viewer, {
  2497. zIndex: options.zIndexInline
  2498. });
  2499. if (window.getComputedStyle(parent).position === 'static') {
  2500. setStyle(parent, {
  2501. position: 'relative'
  2502. });
  2503. }
  2504. parent.insertBefore(viewer, element.nextSibling);
  2505. } else {
  2506. addClass(button, CLASS_CLOSE);
  2507. addClass(viewer, CLASS_FIXED);
  2508. addClass(viewer, CLASS_FADE);
  2509. addClass(viewer, CLASS_HIDE);
  2510. setStyle(viewer, {
  2511. zIndex: options.zIndex
  2512. });
  2513. var container = options.container;
  2514. if (isString(container)) {
  2515. container = element.ownerDocument.querySelector(container);
  2516. }
  2517. if (!container) {
  2518. container = this.body;
  2519. }
  2520. container.appendChild(viewer);
  2521. }
  2522. if (options.inline) {
  2523. this.render();
  2524. this.bind();
  2525. this.isShown = true;
  2526. }
  2527. this.ready = true;
  2528. if (isFunction(options.ready)) {
  2529. addListener(element, EVENT_READY, options.ready, {
  2530. once: true
  2531. });
  2532. }
  2533. if (dispatchEvent(element, EVENT_READY) === false) {
  2534. this.ready = false;
  2535. return;
  2536. }
  2537. if (this.ready && options.inline) {
  2538. this.view(this.index);
  2539. }
  2540. }
  2541. /**
  2542. * Get the no conflict viewer class.
  2543. * @returns {Viewer} The viewer class.
  2544. */
  2545. }], [{
  2546. key: "noConflict",
  2547. value: function noConflict() {
  2548. window.Viewer = AnotherViewer;
  2549. return Viewer;
  2550. }
  2551. /**
  2552. * Change the default options.
  2553. * @param {Object} options - The new default options.
  2554. */
  2555. }, {
  2556. key: "setDefaults",
  2557. value: function setDefaults(options) {
  2558. assign(DEFAULTS, isPlainObject(options) && options);
  2559. }
  2560. }]);
  2561. return Viewer;
  2562. }();
  2563. assign(Viewer.prototype, render, events, handlers, methods, others);
  2564. return Viewer;
  2565. })));