viewer.js

viewer

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/446062/1058082/viewerjs.js

  1. /*!
  2. * Viewer.js v1.10.5
  3. * https://fengyuanchen.github.io/viewerjs
  4. *
  5. * Copyright 2015-present Chen Fengyuan
  6. * Released under the MIT license
  7. *
  8. * Date: 2022-04-05T08:21:02.491Z
  9. */
  10.  
  11. (function (global, factory) {
  12. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  13. typeof define === 'function' && define.amd ? define(factory) :
  14. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Viewer = factory());
  15. })(this, (function () { 'use strict';
  16.  
  17. function ownKeys(object, enumerableOnly) {
  18. var keys = Object.keys(object);
  19.  
  20. if (Object.getOwnPropertySymbols) {
  21. var symbols = Object.getOwnPropertySymbols(object);
  22. enumerableOnly && (symbols = symbols.filter(function (sym) {
  23. return Object.getOwnPropertyDescriptor(object, sym).enumerable;
  24. })), keys.push.apply(keys, symbols);
  25. }
  26.  
  27. return keys;
  28. }
  29.  
  30. function _objectSpread2(target) {
  31. for (var i = 1; i < arguments.length; i++) {
  32. var source = null != arguments[i] ? arguments[i] : {};
  33. i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
  34. _defineProperty(target, key, source[key]);
  35. }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
  36. Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
  37. });
  38. }
  39.  
  40. return target;
  41. }
  42.  
  43. function _typeof(obj) {
  44. "@babel/helpers - typeof";
  45.  
  46. return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
  47. return typeof obj;
  48. } : function (obj) {
  49. return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
  50. }, _typeof(obj);
  51. }
  52.  
  53. function _classCallCheck(instance, Constructor) {
  54. if (!(instance instanceof Constructor)) {
  55. throw new TypeError("Cannot call a class as a function");
  56. }
  57. }
  58.  
  59. function _defineProperties(target, props) {
  60. for (var i = 0; i < props.length; i++) {
  61. var descriptor = props[i];
  62. descriptor.enumerable = descriptor.enumerable || false;
  63. descriptor.configurable = true;
  64. if ("value" in descriptor) descriptor.writable = true;
  65. Object.defineProperty(target, descriptor.key, descriptor);
  66. }
  67. }
  68.  
  69. function _createClass(Constructor, protoProps, staticProps) {
  70. if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  71. if (staticProps) _defineProperties(Constructor, staticProps);
  72. Object.defineProperty(Constructor, "prototype", {
  73. writable: false
  74. });
  75. return Constructor;
  76. }
  77.  
  78. function _defineProperty(obj, key, value) {
  79. if (key in obj) {
  80. Object.defineProperty(obj, key, {
  81. value: value,
  82. enumerable: true,
  83. configurable: true,
  84. writable: true
  85. });
  86. } else {
  87. obj[key] = value;
  88. }
  89.  
  90. return obj;
  91. }
  92.  
  93. var DEFAULTS = {
  94. /**
  95. * Enable a modal backdrop, specify `static` for a backdrop
  96. * which doesn't close the modal on click.
  97. * @type {boolean}
  98. */
  99. backdrop: true,
  100.  
  101. /**
  102. * Show the button on the top-right of the viewer.
  103. * @type {boolean}
  104. */
  105. button: true,
  106.  
  107. /**
  108. * Show the navbar.
  109. * @type {boolean | number}
  110. */
  111. navbar: true,
  112.  
  113. /**
  114. * Specify the visibility and the content of the title.
  115. * @type {boolean | number | Function | Array}
  116. */
  117. title: true,
  118.  
  119. /**
  120. * Show the toolbar.
  121. * @type {boolean | number | Object}
  122. */
  123. toolbar: true,
  124.  
  125. /**
  126. * Custom class name(s) to add to the viewer's root element.
  127. * @type {string}
  128. */
  129. className: '',
  130.  
  131. /**
  132. * Define where to put the viewer in modal mode.
  133. * @type {string | Element}
  134. */
  135. container: 'body',
  136.  
  137. /**
  138. * Filter the images for viewing. Return true if the image is viewable.
  139. * @type {Function}
  140. */
  141. filter: null,
  142.  
  143. /**
  144. * Enable to request fullscreen when play.
  145. * {@link https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions}
  146. * @type {boolean|FullscreenOptions}
  147. */
  148. fullscreen: true,
  149.  
  150. /**
  151. * Define the extra attributes to inherit from the original image.
  152. * @type {Array}
  153. */
  154. inheritedAttributes: ['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap'],
  155.  
  156. /**
  157. * Define the initial index of image for viewing.
  158. * @type {number}
  159. */
  160. initialViewIndex: 0,
  161.  
  162. /**
  163. * Enable inline mode.
  164. * @type {boolean}
  165. */
  166. inline: false,
  167.  
  168. /**
  169. * The amount of time to delay between automatically cycling an image when playing.
  170. * @type {number}
  171. */
  172. interval: 5000,
  173.  
  174. /**
  175. * Enable keyboard support.
  176. * @type {boolean}
  177. */
  178. keyboard: true,
  179.  
  180. /**
  181. * Focus the viewer when initialized.
  182. * @type {boolean}
  183. */
  184. focus: true,
  185.  
  186. /**
  187. * Indicate if show a loading spinner when load image or not.
  188. * @type {boolean}
  189. */
  190. loading: true,
  191.  
  192. /**
  193. * Indicate if enable loop viewing or not.
  194. * @type {boolean}
  195. */
  196. loop: true,
  197.  
  198. /**
  199. * Min width of the viewer in inline mode.
  200. * @type {number}
  201. */
  202. minWidth: 200,
  203.  
  204. /**
  205. * Min height of the viewer in inline mode.
  206. * @type {number}
  207. */
  208. minHeight: 100,
  209.  
  210. /**
  211. * Enable to move the image.
  212. * @type {boolean}
  213. */
  214. movable: true,
  215.  
  216. /**
  217. * Enable to rotate the image.
  218. * @type {boolean}
  219. */
  220. rotatable: true,
  221.  
  222. /**
  223. * Enable to scale the image.
  224. * @type {boolean}
  225. */
  226. scalable: true,
  227.  
  228. /**
  229. * Enable to zoom the image.
  230. * @type {boolean}
  231. */
  232. zoomable: true,
  233.  
  234. /**
  235. * Enable to zoom the current image by dragging on the touch screen.
  236. * @type {boolean}
  237. */
  238. zoomOnTouch: true,
  239.  
  240. /**
  241. * Enable to zoom the image by wheeling mouse.
  242. * @type {boolean}
  243. */
  244. zoomOnWheel: true,
  245.  
  246. /**
  247. * Enable to slide to the next or previous image by swiping on the touch screen.
  248. * @type {boolean}
  249. */
  250. slideOnTouch: true,
  251.  
  252. /**
  253. * Indicate if toggle the image size between its natural size
  254. * and initial size when double click on the image or not.
  255. * @type {boolean}
  256. */
  257. toggleOnDblclick: true,
  258.  
  259. /**
  260. * Show the tooltip with image ratio (percentage) when zoom in or zoom out.
  261. * @type {boolean}
  262. */
  263. tooltip: true,
  264.  
  265. /**
  266. * Enable CSS3 Transition for some special elements.
  267. * @type {boolean}
  268. */
  269. transition: true,
  270.  
  271. /**
  272. * Define the CSS `z-index` value of viewer in modal mode.
  273. * @type {number}
  274. */
  275. zIndex: 2015,
  276.  
  277. /**
  278. * Define the CSS `z-index` value of viewer in inline mode.
  279. * @type {number}
  280. */
  281. zIndexInline: 0,
  282.  
  283. /**
  284. * Define the ratio when zoom the image by wheeling mouse.
  285. * @type {number}
  286. */
  287. zoomRatio: 0.1,
  288.  
  289. /**
  290. * Define the min ratio of the image when zoom out.
  291. * @type {number}
  292. */
  293. minZoomRatio: 0.01,
  294.  
  295. /**
  296. * Define the max ratio of the image when zoom in.
  297. * @type {number}
  298. */
  299. maxZoomRatio: 100,
  300.  
  301. /**
  302. * Define where to get the original image URL for viewing.
  303. * @type {string | Function}
  304. */
  305. url: 'src',
  306.  
  307. /**
  308. * Event shortcuts.
  309. * @type {Function}
  310. */
  311. ready: null,
  312. show: null,
  313. shown: null,
  314. hide: null,
  315. hidden: null,
  316. view: null,
  317. viewed: null,
  318. move: null,
  319. moved: null,
  320. rotate: null,
  321. rotated: null,
  322. scale: null,
  323. scaled: null,
  324. zoom: null,
  325. zoomed: null,
  326. play: null,
  327. stop: null
  328. };
  329.  
  330. var TEMPLATE = '<div class="viewer-container" tabindex="-1" 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" role="navigation"></ul>' + '</div>' + '</div>' + '<div class="viewer-tooltip" role="alert" aria-hidden="true"></div>' + '<div class="viewer-button" data-viewer-action="mix" role="button"></div>' + '<div class="viewer-player"></div>' + '</div>';
  331.  
  332. var IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
  333. var WINDOW = IS_BROWSER ? window : {};
  334. var IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? 'ontouchstart' in WINDOW.document.documentElement : false;
  335. var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;
  336. var NAMESPACE = 'viewer'; // Actions
  337.  
  338. var ACTION_MOVE = 'move';
  339. var ACTION_SWITCH = 'switch';
  340. var ACTION_ZOOM = 'zoom'; // Classes
  341.  
  342. var CLASS_ACTIVE = "".concat(NAMESPACE, "-active");
  343. var CLASS_CLOSE = "".concat(NAMESPACE, "-close");
  344. var CLASS_FADE = "".concat(NAMESPACE, "-fade");
  345. var CLASS_FIXED = "".concat(NAMESPACE, "-fixed");
  346. var CLASS_FULLSCREEN = "".concat(NAMESPACE, "-fullscreen");
  347. var CLASS_FULLSCREEN_EXIT = "".concat(NAMESPACE, "-fullscreen-exit");
  348. var CLASS_HIDE = "".concat(NAMESPACE, "-hide");
  349. var CLASS_HIDE_MD_DOWN = "".concat(NAMESPACE, "-hide-md-down");
  350. var CLASS_HIDE_SM_DOWN = "".concat(NAMESPACE, "-hide-sm-down");
  351. var CLASS_HIDE_XS_DOWN = "".concat(NAMESPACE, "-hide-xs-down");
  352. var CLASS_IN = "".concat(NAMESPACE, "-in");
  353. var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible");
  354. var CLASS_LOADING = "".concat(NAMESPACE, "-loading");
  355. var CLASS_MOVE = "".concat(NAMESPACE, "-move");
  356. var CLASS_OPEN = "".concat(NAMESPACE, "-open");
  357. var CLASS_SHOW = "".concat(NAMESPACE, "-show");
  358. var CLASS_TRANSITION = "".concat(NAMESPACE, "-transition"); // Native events
  359.  
  360. var EVENT_CLICK = 'click';
  361. var EVENT_DBLCLICK = 'dblclick';
  362. var EVENT_DRAG_START = 'dragstart';
  363. var EVENT_FOCUSIN = 'focusin';
  364. var EVENT_KEY_DOWN = 'keydown';
  365. var EVENT_LOAD = 'load';
  366. var EVENT_ERROR = 'error';
  367. var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';
  368. var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';
  369. var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';
  370. var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;
  371. var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;
  372. var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;
  373. var EVENT_RESIZE = 'resize';
  374. var EVENT_TRANSITION_END = 'transitionend';
  375. var EVENT_WHEEL = 'wheel'; // Custom events
  376.  
  377. var EVENT_READY = 'ready';
  378. var EVENT_SHOW = 'show';
  379. var EVENT_SHOWN = 'shown';
  380. var EVENT_HIDE = 'hide';
  381. var EVENT_HIDDEN = 'hidden';
  382. var EVENT_VIEW = 'view';
  383. var EVENT_VIEWED = 'viewed';
  384. var EVENT_MOVE = 'move';
  385. var EVENT_MOVED = 'moved';
  386. var EVENT_ROTATE = 'rotate';
  387. var EVENT_ROTATED = 'rotated';
  388. var EVENT_SCALE = 'scale';
  389. var EVENT_SCALED = 'scaled';
  390. var EVENT_ZOOM = 'zoom';
  391. var EVENT_ZOOMED = 'zoomed';
  392. var EVENT_PLAY = 'play';
  393. var EVENT_STOP = 'stop'; // Data keys
  394.  
  395. var DATA_ACTION = "".concat(NAMESPACE, "Action"); // RegExps
  396.  
  397. var REGEXP_SPACES = /\s\s*/; // Misc
  398.  
  399. var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical'];
  400.  
  401. /**
  402. * Check if the given value is a string.
  403. * @param {*} value - The value to check.
  404. * @returns {boolean} Returns `true` if the given value is a string, else `false`.
  405. */
  406.  
  407. function isString(value) {
  408. return typeof value === 'string';
  409. }
  410. /**
  411. * Check if the given value is not a number.
  412. */
  413.  
  414. var isNaN = Number.isNaN || WINDOW.isNaN;
  415. /**
  416. * Check if the given value is a number.
  417. * @param {*} value - The value to check.
  418. * @returns {boolean} Returns `true` if the given value is a number, else `false`.
  419. */
  420.  
  421. function isNumber(value) {
  422. return typeof value === 'number' && !isNaN(value);
  423. }
  424. /**
  425. * Check if the given value is undefined.
  426. * @param {*} value - The value to check.
  427. * @returns {boolean} Returns `true` if the given value is undefined, else `false`.
  428. */
  429.  
  430. function isUndefined(value) {
  431. return typeof value === 'undefined';
  432. }
  433. /**
  434. * Check if the given value is an object.
  435. * @param {*} value - The value to check.
  436. * @returns {boolean} Returns `true` if the given value is an object, else `false`.
  437. */
  438.  
  439. function isObject(value) {
  440. return _typeof(value) === 'object' && value !== null;
  441. }
  442. var hasOwnProperty = Object.prototype.hasOwnProperty;
  443. /**
  444. * Check if the given value is a plain object.
  445. * @param {*} value - The value to check.
  446. * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.
  447. */
  448.  
  449. function isPlainObject(value) {
  450. if (!isObject(value)) {
  451. return false;
  452. }
  453.  
  454. try {
  455. var _constructor = value.constructor;
  456. var prototype = _constructor.prototype;
  457. return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');
  458. } catch (error) {
  459. return false;
  460. }
  461. }
  462. /**
  463. * Check if the given value is a function.
  464. * @param {*} value - The value to check.
  465. * @returns {boolean} Returns `true` if the given value is a function, else `false`.
  466. */
  467.  
  468. function isFunction(value) {
  469. return typeof value === 'function';
  470. }
  471. /**
  472. * Iterate the given data.
  473. * @param {*} data - The data to iterate.
  474. * @param {Function} callback - The process function for each element.
  475. * @returns {*} The original data.
  476. */
  477.  
  478. function forEach(data, callback) {
  479. if (data && isFunction(callback)) {
  480. if (Array.isArray(data) || isNumber(data.length)
  481. /* array-like */
  482. ) {
  483. var length = data.length;
  484. var i;
  485.  
  486. for (i = 0; i < length; i += 1) {
  487. if (callback.call(data, data[i], i, data) === false) {
  488. break;
  489. }
  490. }
  491. } else if (isObject(data)) {
  492. Object.keys(data).forEach(function (key) {
  493. callback.call(data, data[key], key, data);
  494. });
  495. }
  496. }
  497.  
  498. return data;
  499. }
  500. /**
  501. * Extend the given object.
  502. * @param {*} obj - The object to be extended.
  503. * @param {*} args - The rest objects which will be merged to the first object.
  504. * @returns {Object} The extended object.
  505. */
  506.  
  507. var assign = Object.assign || function assign(obj) {
  508. for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  509. args[_key - 1] = arguments[_key];
  510. }
  511.  
  512. if (isObject(obj) && args.length > 0) {
  513. args.forEach(function (arg) {
  514. if (isObject(arg)) {
  515. Object.keys(arg).forEach(function (key) {
  516. obj[key] = arg[key];
  517. });
  518. }
  519. });
  520. }
  521.  
  522. return obj;
  523. };
  524. var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;
  525. /**
  526. * Apply styles to the given element.
  527. * @param {Element} element - The target element.
  528. * @param {Object} styles - The styles for applying.
  529. */
  530.  
  531. function setStyle(element, styles) {
  532. var style = element.style;
  533. forEach(styles, function (value, property) {
  534. if (REGEXP_SUFFIX.test(property) && isNumber(value)) {
  535. value += 'px';
  536. }
  537.  
  538. style[property] = value;
  539. });
  540. }
  541. /**
  542. * Escape a string for using in HTML.
  543. * @param {String} value - The string to escape.
  544. * @returns {String} Returns the escaped string.
  545. */
  546.  
  547. function escapeHTMLEntities(value) {
  548. return isString(value) ? value.replace(/&(?!amp;|quot;|#39;|lt;|gt;)/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : value;
  549. }
  550. /**
  551. * Check if the given element has a special class.
  552. * @param {Element} element - The element to check.
  553. * @param {string} value - The class to search.
  554. * @returns {boolean} Returns `true` if the special class was found.
  555. */
  556.  
  557. function hasClass(element, value) {
  558. if (!element || !value) {
  559. return false;
  560. }
  561.  
  562. return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1;
  563. }
  564. /**
  565. * Add classes to the given element.
  566. * @param {Element} element - The target element.
  567. * @param {string} value - The classes to be added.
  568. */
  569.  
  570. function addClass(element, value) {
  571. if (!element || !value) {
  572. return;
  573. }
  574.  
  575. if (isNumber(element.length)) {
  576. forEach(element, function (elem) {
  577. addClass(elem, value);
  578. });
  579. return;
  580. }
  581.  
  582. if (element.classList) {
  583. element.classList.add(value);
  584. return;
  585. }
  586.  
  587. var className = element.className.trim();
  588.  
  589. if (!className) {
  590. element.className = value;
  591. } else if (className.indexOf(value) < 0) {
  592. element.className = "".concat(className, " ").concat(value);
  593. }
  594. }
  595. /**
  596. * Remove classes from the given element.
  597. * @param {Element} element - The target element.
  598. * @param {string} value - The classes to be removed.
  599. */
  600.  
  601. function removeClass(element, value) {
  602. if (!element || !value) {
  603. return;
  604. }
  605.  
  606. if (isNumber(element.length)) {
  607. forEach(element, function (elem) {
  608. removeClass(elem, value);
  609. });
  610. return;
  611. }
  612.  
  613. if (element.classList) {
  614. element.classList.remove(value);
  615. return;
  616. }
  617.  
  618. if (element.className.indexOf(value) >= 0) {
  619. element.className = element.className.replace(value, '');
  620. }
  621. }
  622. /**
  623. * Add or remove classes from the given element.
  624. * @param {Element} element - The target element.
  625. * @param {string} value - The classes to be toggled.
  626. * @param {boolean} added - Add only.
  627. */
  628.  
  629. function toggleClass(element, value, added) {
  630. if (!value) {
  631. return;
  632. }
  633.  
  634. if (isNumber(element.length)) {
  635. forEach(element, function (elem) {
  636. toggleClass(elem, value, added);
  637. });
  638. return;
  639. } // IE10-11 doesn't support the second parameter of `classList.toggle`
  640.  
  641.  
  642. if (added) {
  643. addClass(element, value);
  644. } else {
  645. removeClass(element, value);
  646. }
  647. }
  648. var REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g;
  649. /**
  650. * Transform the given string from camelCase to kebab-case
  651. * @param {string} value - The value to transform.
  652. * @returns {string} The transformed value.
  653. */
  654.  
  655. function hyphenate(value) {
  656. return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();
  657. }
  658. /**
  659. * Get data from the given element.
  660. * @param {Element} element - The target element.
  661. * @param {string} name - The data key to get.
  662. * @returns {string} The data value.
  663. */
  664.  
  665. function getData(element, name) {
  666. if (isObject(element[name])) {
  667. return element[name];
  668. }
  669.  
  670. if (element.dataset) {
  671. return element.dataset[name];
  672. }
  673.  
  674. return element.getAttribute("data-".concat(hyphenate(name)));
  675. }
  676. /**
  677. * Set data to the given element.
  678. * @param {Element} element - The target element.
  679. * @param {string} name - The data key to set.
  680. * @param {string} data - The data value.
  681. */
  682.  
  683. function setData(element, name, data) {
  684. if (isObject(data)) {
  685. element[name] = data;
  686. } else if (element.dataset) {
  687. element.dataset[name] = data;
  688. } else {
  689. element.setAttribute("data-".concat(hyphenate(name)), data);
  690. }
  691. }
  692.  
  693. var onceSupported = function () {
  694. var supported = false;
  695.  
  696. if (IS_BROWSER) {
  697. var once = false;
  698.  
  699. var listener = function listener() {};
  700.  
  701. var options = Object.defineProperty({}, 'once', {
  702. get: function get() {
  703. supported = true;
  704. return once;
  705. },
  706.  
  707. /**
  708. * This setter can fix a `TypeError` in strict mode
  709. * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only}
  710. * @param {boolean} value - The value to set
  711. */
  712. set: function set(value) {
  713. once = value;
  714. }
  715. });
  716. WINDOW.addEventListener('test', listener, options);
  717. WINDOW.removeEventListener('test', listener, options);
  718. }
  719.  
  720. return supported;
  721. }();
  722. /**
  723. * Remove event listener from the target element.
  724. * @param {Element} element - The event target.
  725. * @param {string} type - The event type(s).
  726. * @param {Function} listener - The event listener.
  727. * @param {Object} options - The event options.
  728. */
  729.  
  730.  
  731. function removeListener(element, type, listener) {
  732. var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  733. var handler = listener;
  734. type.trim().split(REGEXP_SPACES).forEach(function (event) {
  735. if (!onceSupported) {
  736. var listeners = element.listeners;
  737.  
  738. if (listeners && listeners[event] && listeners[event][listener]) {
  739. handler = listeners[event][listener];
  740. delete listeners[event][listener];
  741.  
  742. if (Object.keys(listeners[event]).length === 0) {
  743. delete listeners[event];
  744. }
  745.  
  746. if (Object.keys(listeners).length === 0) {
  747. delete element.listeners;
  748. }
  749. }
  750. }
  751.  
  752. element.removeEventListener(event, handler, options);
  753. });
  754. }
  755. /**
  756. * Add event listener to the target element.
  757. * @param {Element} element - The event target.
  758. * @param {string} type - The event type(s).
  759. * @param {Function} listener - The event listener.
  760. * @param {Object} options - The event options.
  761. */
  762.  
  763. function addListener(element, type, listener) {
  764. var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  765. var _handler = listener;
  766. type.trim().split(REGEXP_SPACES).forEach(function (event) {
  767. if (options.once && !onceSupported) {
  768. var _element$listeners = element.listeners,
  769. listeners = _element$listeners === void 0 ? {} : _element$listeners;
  770.  
  771. _handler = function handler() {
  772. delete listeners[event][listener];
  773. element.removeEventListener(event, _handler, options);
  774.  
  775. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  776. args[_key2] = arguments[_key2];
  777. }
  778.  
  779. listener.apply(element, args);
  780. };
  781.  
  782. if (!listeners[event]) {
  783. listeners[event] = {};
  784. }
  785.  
  786. if (listeners[event][listener]) {
  787. element.removeEventListener(event, listeners[event][listener], options);
  788. }
  789.  
  790. listeners[event][listener] = _handler;
  791. element.listeners = listeners;
  792. }
  793.  
  794. element.addEventListener(event, _handler, options);
  795. });
  796. }
  797. /**
  798. * Dispatch event on the target element.
  799. * @param {Element} element - The event target.
  800. * @param {string} type - The event type(s).
  801. * @param {Object} data - The additional event data.
  802. * @param {Object} options - The additional event options.
  803. * @returns {boolean} Indicate if the event is default prevented or not.
  804. */
  805.  
  806. function dispatchEvent(element, type, data, options) {
  807. var event; // Event and CustomEvent on IE9-11 are global objects, not constructors
  808.  
  809. if (isFunction(Event) && isFunction(CustomEvent)) {
  810. event = new CustomEvent(type, _objectSpread2({
  811. bubbles: true,
  812. cancelable: true,
  813. detail: data
  814. }, options));
  815. } else {
  816. event = document.createEvent('CustomEvent');
  817. event.initCustomEvent(type, true, true, data);
  818. }
  819.  
  820. return element.dispatchEvent(event);
  821. }
  822. /**
  823. * Get the offset base on the document.
  824. * @param {Element} element - The target element.
  825. * @returns {Object} The offset data.
  826. */
  827.  
  828. function getOffset(element) {
  829. var box = element.getBoundingClientRect();
  830. return {
  831. left: box.left + (window.pageXOffset - document.documentElement.clientLeft),
  832. top: box.top + (window.pageYOffset - document.documentElement.clientTop)
  833. };
  834. }
  835. /**
  836. * Get transforms base on the given object.
  837. * @param {Object} obj - The target object.
  838. * @returns {string} A string contains transform values.
  839. */
  840.  
  841. function getTransforms(_ref) {
  842. var rotate = _ref.rotate,
  843. scaleX = _ref.scaleX,
  844. scaleY = _ref.scaleY,
  845. translateX = _ref.translateX,
  846. translateY = _ref.translateY;
  847. var values = [];
  848.  
  849. if (isNumber(translateX) && translateX !== 0) {
  850. values.push("translateX(".concat(translateX, "px)"));
  851. }
  852.  
  853. if (isNumber(translateY) && translateY !== 0) {
  854. values.push("translateY(".concat(translateY, "px)"));
  855. } // Rotate should come first before scale to match orientation transform
  856.  
  857.  
  858. if (isNumber(rotate) && rotate !== 0) {
  859. values.push("rotate(".concat(rotate, "deg)"));
  860. }
  861.  
  862. if (isNumber(scaleX) && scaleX !== 1) {
  863. values.push("scaleX(".concat(scaleX, ")"));
  864. }
  865.  
  866. if (isNumber(scaleY) && scaleY !== 1) {
  867. values.push("scaleY(".concat(scaleY, ")"));
  868. }
  869.  
  870. var transform = values.length ? values.join(' ') : 'none';
  871. return {
  872. WebkitTransform: transform,
  873. msTransform: transform,
  874. transform: transform
  875. };
  876. }
  877. /**
  878. * Get an image name from an image url.
  879. * @param {string} url - The target url.
  880. * @example
  881. * // picture.jpg
  882. * getImageNameFromURL('https://domain.com/path/to/picture.jpg?size=1280×960')
  883. * @returns {string} A string contains the image name.
  884. */
  885.  
  886. function getImageNameFromURL(url) {
  887. return isString(url) ? decodeURIComponent(url.replace(/^.*\//, '').replace(/[?&#].*$/, '')) : '';
  888. }
  889. var IS_SAFARI = WINDOW.navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(WINDOW.navigator.userAgent);
  890. /**
  891. * Get an image's natural sizes.
  892. * @param {string} image - The target image.
  893. * @param {Object} options - The viewer options.
  894. * @param {Function} callback - The callback function.
  895. * @returns {HTMLImageElement} The new image.
  896. */
  897.  
  898. function getImageNaturalSizes(image, options, callback) {
  899. var newImage = document.createElement('img'); // Modern browsers (except Safari)
  900.  
  901. if (image.naturalWidth && !IS_SAFARI) {
  902. callback(image.naturalWidth, image.naturalHeight);
  903. return newImage;
  904. }
  905.  
  906. var body = document.body || document.documentElement;
  907.  
  908. newImage.onload = function () {
  909. callback(newImage.width, newImage.height);
  910.  
  911. if (!IS_SAFARI) {
  912. body.removeChild(newImage);
  913. }
  914. };
  915.  
  916. forEach(options.inheritedAttributes, function (name) {
  917. var value = image.getAttribute(name);
  918.  
  919. if (value !== null) {
  920. newImage.setAttribute(name, value);
  921. }
  922. });
  923. newImage.src = image.src; // iOS Safari will convert the image automatically
  924. // with its orientation once append it into DOM
  925.  
  926. if (!IS_SAFARI) {
  927. 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;';
  928. body.appendChild(newImage);
  929. }
  930.  
  931. return newImage;
  932. }
  933. /**
  934. * Get the related class name of a responsive type number.
  935. * @param {string} type - The responsive type.
  936. * @returns {string} The related class name.
  937. */
  938.  
  939. function getResponsiveClass(type) {
  940. switch (type) {
  941. case 2:
  942. return CLASS_HIDE_XS_DOWN;
  943.  
  944. case 3:
  945. return CLASS_HIDE_SM_DOWN;
  946.  
  947. case 4:
  948. return CLASS_HIDE_MD_DOWN;
  949.  
  950. default:
  951. return '';
  952. }
  953. }
  954. /**
  955. * Get the max ratio of a group of pointers.
  956. * @param {string} pointers - The target pointers.
  957. * @returns {number} The result ratio.
  958. */
  959.  
  960. function getMaxZoomRatio(pointers) {
  961. var pointers2 = _objectSpread2({}, pointers);
  962.  
  963. var ratios = [];
  964. forEach(pointers, function (pointer, pointerId) {
  965. delete pointers2[pointerId];
  966. forEach(pointers2, function (pointer2) {
  967. var x1 = Math.abs(pointer.startX - pointer2.startX);
  968. var y1 = Math.abs(pointer.startY - pointer2.startY);
  969. var x2 = Math.abs(pointer.endX - pointer2.endX);
  970. var y2 = Math.abs(pointer.endY - pointer2.endY);
  971. var z1 = Math.sqrt(x1 * x1 + y1 * y1);
  972. var z2 = Math.sqrt(x2 * x2 + y2 * y2);
  973. var ratio = (z2 - z1) / z1;
  974. ratios.push(ratio);
  975. });
  976. });
  977. ratios.sort(function (a, b) {
  978. return Math.abs(a) < Math.abs(b);
  979. });
  980. return ratios[0];
  981. }
  982. /**
  983. * Get a pointer from an event object.
  984. * @param {Object} event - The target event object.
  985. * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.
  986. * @returns {Object} The result pointer contains start and/or end point coordinates.
  987. */
  988.  
  989. function getPointer(_ref2, endOnly) {
  990. var pageX = _ref2.pageX,
  991. pageY = _ref2.pageY;
  992. var end = {
  993. endX: pageX,
  994. endY: pageY
  995. };
  996. return endOnly ? end : _objectSpread2({
  997. timeStamp: Date.now(),
  998. startX: pageX,
  999. startY: pageY
  1000. }, end);
  1001. }
  1002. /**
  1003. * Get the center point coordinate of a group of pointers.
  1004. * @param {Object} pointers - The target pointers.
  1005. * @returns {Object} The center point coordinate.
  1006. */
  1007.  
  1008. function getPointersCenter(pointers) {
  1009. var pageX = 0;
  1010. var pageY = 0;
  1011. var count = 0;
  1012. forEach(pointers, function (_ref3) {
  1013. var startX = _ref3.startX,
  1014. startY = _ref3.startY;
  1015. pageX += startX;
  1016. pageY += startY;
  1017. count += 1;
  1018. });
  1019. pageX /= count;
  1020. pageY /= count;
  1021. return {
  1022. pageX: pageX,
  1023. pageY: pageY
  1024. };
  1025. }
  1026.  
  1027. var render = {
  1028. render: function render() {
  1029. this.initContainer();
  1030. this.initViewer();
  1031. this.initList();
  1032. this.renderViewer();
  1033. },
  1034. initBody: function initBody() {
  1035. var ownerDocument = this.element.ownerDocument;
  1036. var body = ownerDocument.body || ownerDocument.documentElement;
  1037. this.body = body;
  1038. this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth;
  1039. this.initialBodyPaddingRight = body.style.paddingRight;
  1040. this.initialBodyComputedPaddingRight = window.getComputedStyle(body).paddingRight;
  1041. },
  1042. initContainer: function initContainer() {
  1043. this.containerData = {
  1044. width: window.innerWidth,
  1045. height: window.innerHeight
  1046. };
  1047. },
  1048. initViewer: function initViewer() {
  1049. var options = this.options,
  1050. parent = this.parent;
  1051. var viewerData;
  1052.  
  1053. if (options.inline) {
  1054. viewerData = {
  1055. width: Math.max(parent.offsetWidth, options.minWidth),
  1056. height: Math.max(parent.offsetHeight, options.minHeight)
  1057. };
  1058. this.parentData = viewerData;
  1059. }
  1060.  
  1061. if (this.fulled || !viewerData) {
  1062. viewerData = this.containerData;
  1063. }
  1064.  
  1065. this.viewerData = assign({}, viewerData);
  1066. },
  1067. renderViewer: function renderViewer() {
  1068. if (this.options.inline && !this.fulled) {
  1069. setStyle(this.viewer, this.viewerData);
  1070. }
  1071. },
  1072. initList: function initList() {
  1073. var _this = this;
  1074.  
  1075. var element = this.element,
  1076. options = this.options,
  1077. list = this.list;
  1078. var items = []; // initList may be called in this.update, so should keep idempotent
  1079.  
  1080. list.innerHTML = '';
  1081. forEach(this.images, function (image, index) {
  1082. var src = image.src;
  1083. var alt = image.alt || getImageNameFromURL(src);
  1084.  
  1085. var url = _this.getImageURL(image);
  1086.  
  1087. if (src || url) {
  1088. var item = document.createElement('li');
  1089. var img = document.createElement('img');
  1090. forEach(options.inheritedAttributes, function (name) {
  1091. var value = image.getAttribute(name);
  1092.  
  1093. if (value !== null) {
  1094. img.setAttribute(name, value);
  1095. }
  1096. });
  1097. img.src = src || url;
  1098. img.alt = alt;
  1099. img.setAttribute('data-original-url', url || src);
  1100. item.setAttribute('data-index', index);
  1101. item.setAttribute('data-viewer-action', 'view');
  1102. item.setAttribute('role', 'button');
  1103.  
  1104. if (options.keyboard) {
  1105. item.setAttribute('tabindex', 0);
  1106. }
  1107.  
  1108. item.appendChild(img);
  1109. list.appendChild(item);
  1110. items.push(item);
  1111. }
  1112. });
  1113. this.items = items;
  1114. forEach(items, function (item) {
  1115. var image = item.firstElementChild;
  1116. var onLoad;
  1117. var onError;
  1118. setData(image, 'filled', true);
  1119.  
  1120. if (options.loading) {
  1121. addClass(item, CLASS_LOADING);
  1122. }
  1123.  
  1124. addListener(image, EVENT_LOAD, onLoad = function onLoad(event) {
  1125. removeListener(image, EVENT_ERROR, onError);
  1126.  
  1127. if (options.loading) {
  1128. removeClass(item, CLASS_LOADING);
  1129. }
  1130.  
  1131. _this.loadImage(event);
  1132. }, {
  1133. once: true
  1134. });
  1135. addListener(image, EVENT_ERROR, onError = function onError() {
  1136. removeListener(image, EVENT_LOAD, onLoad);
  1137.  
  1138. if (options.loading) {
  1139. removeClass(item, CLASS_LOADING);
  1140. }
  1141. }, {
  1142. once: true
  1143. });
  1144. });
  1145.  
  1146. if (options.transition) {
  1147. addListener(element, EVENT_VIEWED, function () {
  1148. addClass(list, CLASS_TRANSITION);
  1149. }, {
  1150. once: true
  1151. });
  1152. }
  1153. },
  1154. renderList: function renderList() {
  1155. var index = this.index;
  1156. var item = this.items[index];
  1157.  
  1158. if (!item) {
  1159. return;
  1160. }
  1161.  
  1162. var next = item.nextElementSibling;
  1163. var gutter = parseInt(window.getComputedStyle(next || item).marginLeft, 10);
  1164. var offsetWidth = item.offsetWidth;
  1165. var outerWidth = offsetWidth + gutter; // Place the active item in the center of the screen
  1166.  
  1167. setStyle(this.list, assign({
  1168. width: outerWidth * this.length - gutter
  1169. }, getTransforms({
  1170. translateX: (this.viewerData.width - offsetWidth) / 2 - outerWidth * index
  1171. })));
  1172. },
  1173. resetList: function resetList() {
  1174. var list = this.list;
  1175. list.innerHTML = '';
  1176. removeClass(list, CLASS_TRANSITION);
  1177. setStyle(list, getTransforms({
  1178. translateX: 0
  1179. }));
  1180. },
  1181. initImage: function initImage(done) {
  1182. var _this2 = this;
  1183.  
  1184. var options = this.options,
  1185. image = this.image,
  1186. viewerData = this.viewerData;
  1187. var footerHeight = this.footer.offsetHeight;
  1188. var viewerWidth = viewerData.width;
  1189. var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);
  1190. var oldImageData = this.imageData || {};
  1191. var sizingImage;
  1192. this.imageInitializing = {
  1193. abort: function abort() {
  1194. sizingImage.onload = null;
  1195. }
  1196. };
  1197. sizingImage = getImageNaturalSizes(image, options, function (naturalWidth, naturalHeight) {
  1198. var aspectRatio = naturalWidth / naturalHeight;
  1199. var width = viewerWidth;
  1200. var height = viewerHeight;
  1201. _this2.imageInitializing = false;
  1202.  
  1203. if (viewerHeight * aspectRatio > viewerWidth) {
  1204. height = viewerWidth / aspectRatio;
  1205. } else {
  1206. width = viewerHeight * aspectRatio;
  1207. }
  1208.  
  1209. width = Math.min(width * 0.9, naturalWidth);
  1210. height = Math.min(height * 0.9, naturalHeight);
  1211. var left = (viewerWidth - width) / 2;
  1212. var top = (viewerHeight - height) / 2;
  1213. var imageData = {
  1214. left: left,
  1215. top: top,
  1216. x: left,
  1217. y: top,
  1218. width: width,
  1219. height: height,
  1220. oldRatio: 1,
  1221. ratio: width / naturalWidth,
  1222. aspectRatio: aspectRatio,
  1223. naturalWidth: naturalWidth,
  1224. naturalHeight: naturalHeight
  1225. };
  1226. var initialImageData = assign({}, imageData);
  1227.  
  1228. if (options.rotatable) {
  1229. imageData.rotate = oldImageData.rotate || 0;
  1230. initialImageData.rotate = 0;
  1231. }
  1232.  
  1233. if (options.scalable) {
  1234. imageData.scaleX = oldImageData.scaleX || 1;
  1235. imageData.scaleY = oldImageData.scaleY || 1;
  1236. initialImageData.scaleX = 1;
  1237. initialImageData.scaleY = 1;
  1238. }
  1239.  
  1240. _this2.imageData = imageData;
  1241. _this2.initialImageData = initialImageData;
  1242.  
  1243. if (done) {
  1244. done();
  1245. }
  1246. });
  1247. },
  1248. renderImage: function renderImage(done) {
  1249. var _this3 = this;
  1250.  
  1251. var image = this.image,
  1252. imageData = this.imageData;
  1253. setStyle(image, assign({
  1254. width: imageData.width,
  1255. height: imageData.height,
  1256. // XXX: Not to use translateX/Y to avoid image shaking when zooming
  1257. marginLeft: imageData.x,
  1258. marginTop: imageData.y
  1259. }, getTransforms(imageData)));
  1260.  
  1261. if (done) {
  1262. if ((this.viewing || this.moving || this.rotating || this.scaling || this.zooming) && this.options.transition && hasClass(image, CLASS_TRANSITION)) {
  1263. var onTransitionEnd = function onTransitionEnd() {
  1264. _this3.imageRendering = false;
  1265. done();
  1266. };
  1267.  
  1268. this.imageRendering = {
  1269. abort: function abort() {
  1270. removeListener(image, EVENT_TRANSITION_END, onTransitionEnd);
  1271. }
  1272. };
  1273. addListener(image, EVENT_TRANSITION_END, onTransitionEnd, {
  1274. once: true
  1275. });
  1276. } else {
  1277. done();
  1278. }
  1279. }
  1280. },
  1281. resetImage: function resetImage() {
  1282. // this.image only defined after viewed
  1283. if (this.viewing || this.viewed) {
  1284. var image = this.image;
  1285.  
  1286. if (this.viewing) {
  1287. this.viewing.abort();
  1288. }
  1289.  
  1290. image.parentNode.removeChild(image);
  1291. this.image = null;
  1292. }
  1293. }
  1294. };
  1295.  
  1296. var events = {
  1297. bind: function bind() {
  1298. var options = this.options,
  1299. viewer = this.viewer,
  1300. canvas = this.canvas;
  1301. var document = this.element.ownerDocument;
  1302. addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this));
  1303. addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this));
  1304. addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this));
  1305. addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this));
  1306. addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this));
  1307. addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this));
  1308. addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this));
  1309.  
  1310. if (options.zoomable && options.zoomOnWheel) {
  1311. addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), {
  1312. passive: false,
  1313. capture: true
  1314. });
  1315. }
  1316.  
  1317. if (options.toggleOnDblclick) {
  1318. addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this));
  1319. }
  1320. },
  1321. unbind: function unbind() {
  1322. var options = this.options,
  1323. viewer = this.viewer,
  1324. canvas = this.canvas;
  1325. var document = this.element.ownerDocument;
  1326. removeListener(viewer, EVENT_CLICK, this.onClick);
  1327. removeListener(viewer, EVENT_DRAG_START, this.onDragStart);
  1328. removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown);
  1329. removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove);
  1330. removeListener(document, EVENT_POINTER_UP, this.onPointerUp);
  1331. removeListener(document, EVENT_KEY_DOWN, this.onKeyDown);
  1332. removeListener(window, EVENT_RESIZE, this.onResize);
  1333.  
  1334. if (options.zoomable && options.zoomOnWheel) {
  1335. removeListener(viewer, EVENT_WHEEL, this.onWheel, {
  1336. passive: false,
  1337. capture: true
  1338. });
  1339. }
  1340.  
  1341. if (options.toggleOnDblclick) {
  1342. removeListener(canvas, EVENT_DBLCLICK, this.onDblclick);
  1343. }
  1344. }
  1345. };
  1346.  
  1347. var handlers = {
  1348. click: function click(event) {
  1349. var options = this.options,
  1350. imageData = this.imageData;
  1351. var target = event.target;
  1352. var action = getData(target, DATA_ACTION);
  1353.  
  1354. if (!action && target.localName === 'img' && target.parentElement.localName === 'li') {
  1355. target = target.parentElement;
  1356. action = getData(target, DATA_ACTION);
  1357. } // Cancel the emulated click when the native click event was triggered.
  1358.  
  1359.  
  1360. if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) {
  1361. clearTimeout(this.clickCanvasTimeout);
  1362. }
  1363.  
  1364. switch (action) {
  1365. case 'mix':
  1366. if (this.played) {
  1367. this.stop();
  1368. } else if (options.inline) {
  1369. if (this.fulled) {
  1370. this.exit();
  1371. } else {
  1372. this.full();
  1373. }
  1374. } else {
  1375. this.hide();
  1376. }
  1377.  
  1378. break;
  1379.  
  1380. case 'hide':
  1381. this.hide();
  1382. break;
  1383.  
  1384. case 'view':
  1385. this.view(getData(target, 'index'));
  1386. break;
  1387.  
  1388. case 'zoom-in':
  1389. this.zoom(0.1, true);
  1390. break;
  1391.  
  1392. case 'zoom-out':
  1393. this.zoom(-0.1, true);
  1394. break;
  1395.  
  1396. case 'one-to-one':
  1397. this.toggle();
  1398. break;
  1399.  
  1400. case 'reset':
  1401. this.reset();
  1402. break;
  1403.  
  1404. case 'prev':
  1405. this.prev(options.loop);
  1406. break;
  1407.  
  1408. case 'play':
  1409. this.play(options.fullscreen);
  1410. break;
  1411.  
  1412. case 'next':
  1413. this.next(options.loop);
  1414. break;
  1415.  
  1416. case 'rotate-left':
  1417. this.rotate(-90);
  1418. break;
  1419.  
  1420. case 'rotate-right':
  1421. this.rotate(90);
  1422. break;
  1423.  
  1424. case 'flip-horizontal':
  1425. this.scaleX(-imageData.scaleX || -1);
  1426. break;
  1427.  
  1428. case 'flip-vertical':
  1429. this.scaleY(-imageData.scaleY || -1);
  1430. break;
  1431.  
  1432. default:
  1433. if (this.played) {
  1434. this.stop();
  1435. }
  1436.  
  1437. }
  1438. },
  1439. dblclick: function dblclick(event) {
  1440. event.preventDefault();
  1441.  
  1442. if (this.viewed && event.target === this.image) {
  1443. // Cancel the emulated double click when the native dblclick event was triggered.
  1444. if (IS_TOUCH_DEVICE && event.isTrusted) {
  1445. clearTimeout(this.doubleClickImageTimeout);
  1446. } // XXX: No pageX/Y properties in custom event, fallback to the original event.
  1447.  
  1448.  
  1449. this.toggle(event.isTrusted ? event : event.detail && event.detail.originalEvent);
  1450. }
  1451. },
  1452. load: function load() {
  1453. var _this = this;
  1454.  
  1455. if (this.timeout) {
  1456. clearTimeout(this.timeout);
  1457. this.timeout = false;
  1458. }
  1459.  
  1460. var element = this.element,
  1461. options = this.options,
  1462. image = this.image,
  1463. index = this.index,
  1464. viewerData = this.viewerData;
  1465. removeClass(image, CLASS_INVISIBLE);
  1466.  
  1467. if (options.loading) {
  1468. removeClass(this.canvas, CLASS_LOADING);
  1469. }
  1470.  
  1471. image.style.cssText = 'height:0;' + "margin-left:".concat(viewerData.width / 2, "px;") + "margin-top:".concat(viewerData.height / 2, "px;") + 'max-width:none!important;' + 'position:relative;' + 'width:0;';
  1472. this.initImage(function () {
  1473. toggleClass(image, CLASS_MOVE, options.movable);
  1474. toggleClass(image, CLASS_TRANSITION, options.transition);
  1475.  
  1476. _this.renderImage(function () {
  1477. _this.viewed = true;
  1478. _this.viewing = false;
  1479.  
  1480. if (isFunction(options.viewed)) {
  1481. addListener(element, EVENT_VIEWED, options.viewed, {
  1482. once: true
  1483. });
  1484. }
  1485.  
  1486. dispatchEvent(element, EVENT_VIEWED, {
  1487. originalImage: _this.images[index],
  1488. index: index,
  1489. image: image
  1490. }, {
  1491. cancelable: false
  1492. });
  1493. });
  1494. });
  1495. },
  1496. loadImage: function loadImage(event) {
  1497. var image = event.target;
  1498. var parent = image.parentNode;
  1499. var parentWidth = parent.offsetWidth || 30;
  1500. var parentHeight = parent.offsetHeight || 50;
  1501. var filled = !!getData(image, 'filled');
  1502. getImageNaturalSizes(image, this.options, function (naturalWidth, naturalHeight) {
  1503. var aspectRatio = naturalWidth / naturalHeight;
  1504. var width = parentWidth;
  1505. var height = parentHeight;
  1506.  
  1507. if (parentHeight * aspectRatio > parentWidth) {
  1508. if (filled) {
  1509. width = parentHeight * aspectRatio;
  1510. } else {
  1511. height = parentWidth / aspectRatio;
  1512. }
  1513. } else if (filled) {
  1514. height = parentWidth / aspectRatio;
  1515. } else {
  1516. width = parentHeight * aspectRatio;
  1517. }
  1518.  
  1519. setStyle(image, assign({
  1520. width: width,
  1521. height: height
  1522. }, getTransforms({
  1523. translateX: (parentWidth - width) / 2,
  1524. translateY: (parentHeight - height) / 2
  1525. })));
  1526. });
  1527. },
  1528. keydown: function keydown(event) {
  1529. var options = this.options;
  1530.  
  1531. if (!options.keyboard) {
  1532. return;
  1533. }
  1534.  
  1535. var keyCode = event.keyCode || event.which || event.charCode;
  1536.  
  1537. switch (keyCode) {
  1538. // Enter
  1539. case 13:
  1540. if (this.viewer.contains(event.target)) {
  1541. this.click(event);
  1542. }
  1543.  
  1544. break;
  1545. }
  1546.  
  1547. if (!this.fulled) {
  1548. return;
  1549. }
  1550.  
  1551. switch (keyCode) {
  1552. // Escape
  1553. case 27:
  1554. if (this.played) {
  1555. this.stop();
  1556. } else if (options.inline) {
  1557. if (this.fulled) {
  1558. this.exit();
  1559. }
  1560. } else {
  1561. this.hide();
  1562. }
  1563.  
  1564. break;
  1565. // Space
  1566.  
  1567. case 32:
  1568. if (this.played) {
  1569. this.stop();
  1570. }
  1571.  
  1572. break;
  1573. // ArrowLeft
  1574.  
  1575. case 37:
  1576. this.prev(options.loop);
  1577. break;
  1578. // ArrowUp
  1579.  
  1580. case 38:
  1581. // Prevent scroll on Firefox
  1582. event.preventDefault(); // Zoom in
  1583.  
  1584. this.zoom(options.zoomRatio, true);
  1585. break;
  1586. // ArrowRight
  1587.  
  1588. case 39:
  1589. this.next(options.loop);
  1590. break;
  1591. // ArrowDown
  1592.  
  1593. case 40:
  1594. // Prevent scroll on Firefox
  1595. event.preventDefault(); // Zoom out
  1596.  
  1597. this.zoom(-options.zoomRatio, true);
  1598. break;
  1599. // Ctrl + 0
  1600.  
  1601. case 48: // Fall through
  1602. // Ctrl + 1
  1603. // eslint-disable-next-line no-fallthrough
  1604.  
  1605. case 49:
  1606. if (event.ctrlKey) {
  1607. event.preventDefault();
  1608. this.toggle();
  1609. }
  1610.  
  1611. break;
  1612. }
  1613. },
  1614. dragstart: function dragstart(event) {
  1615. if (event.target.localName === 'img') {
  1616. event.preventDefault();
  1617. }
  1618. },
  1619. pointerdown: function pointerdown(event) {
  1620. var options = this.options,
  1621. pointers = this.pointers;
  1622. var buttons = event.buttons,
  1623. button = event.button;
  1624.  
  1625. if (!this.viewed || this.showing || this.viewing || this.hiding // Handle mouse event and pointer event and ignore touch event
  1626. || (event.type === 'mousedown' || event.type === 'pointerdown' && event.pointerType === 'mouse') && ( // No primary button (Usually the left button)
  1627. isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0 // Open context menu
  1628. || event.ctrlKey)) {
  1629. return;
  1630. } // Prevent default behaviours as page zooming in touch devices.
  1631.  
  1632.  
  1633. event.preventDefault();
  1634.  
  1635. if (event.changedTouches) {
  1636. forEach(event.changedTouches, function (touch) {
  1637. pointers[touch.identifier] = getPointer(touch);
  1638. });
  1639. } else {
  1640. pointers[event.pointerId || 0] = getPointer(event);
  1641. }
  1642.  
  1643. var action = options.movable ? ACTION_MOVE : false;
  1644.  
  1645. if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) {
  1646. action = ACTION_ZOOM;
  1647. } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {
  1648. action = ACTION_SWITCH;
  1649. }
  1650.  
  1651. if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
  1652. removeClass(this.image, CLASS_TRANSITION);
  1653. }
  1654.  
  1655. this.action = action;
  1656. },
  1657. pointermove: function pointermove(event) {
  1658. var pointers = this.pointers,
  1659. action = this.action;
  1660.  
  1661. if (!this.viewed || !action) {
  1662. return;
  1663. }
  1664.  
  1665. event.preventDefault();
  1666.  
  1667. if (event.changedTouches) {
  1668. forEach(event.changedTouches, function (touch) {
  1669. assign(pointers[touch.identifier] || {}, getPointer(touch, true));
  1670. });
  1671. } else {
  1672. assign(pointers[event.pointerId || 0] || {}, getPointer(event, true));
  1673. }
  1674.  
  1675. this.change(event);
  1676. },
  1677. pointerup: function pointerup(event) {
  1678. var _this2 = this;
  1679.  
  1680. var options = this.options,
  1681. action = this.action,
  1682. pointers = this.pointers;
  1683. var pointer;
  1684.  
  1685. if (event.changedTouches) {
  1686. forEach(event.changedTouches, function (touch) {
  1687. pointer = pointers[touch.identifier];
  1688. delete pointers[touch.identifier];
  1689. });
  1690. } else {
  1691. pointer = pointers[event.pointerId || 0];
  1692. delete pointers[event.pointerId || 0];
  1693. }
  1694.  
  1695. if (!action) {
  1696. return;
  1697. }
  1698.  
  1699. event.preventDefault();
  1700.  
  1701. if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
  1702. addClass(this.image, CLASS_TRANSITION);
  1703. }
  1704.  
  1705. this.action = false; // Emulate click and double click in touch devices to support backdrop and image zooming (#210).
  1706.  
  1707. if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) {
  1708. clearTimeout(this.clickCanvasTimeout);
  1709. clearTimeout(this.doubleClickImageTimeout);
  1710.  
  1711. if (options.toggleOnDblclick && this.viewed && event.target === this.image) {
  1712. if (this.imageClicked) {
  1713. this.imageClicked = false; // This timeout will be cleared later when a native dblclick event is triggering
  1714.  
  1715. this.doubleClickImageTimeout = setTimeout(function () {
  1716. dispatchEvent(_this2.image, EVENT_DBLCLICK, {
  1717. originalEvent: event
  1718. });
  1719. }, 50);
  1720. } else {
  1721. this.imageClicked = true; // The default timing of a double click in Windows is 500 ms
  1722.  
  1723. this.doubleClickImageTimeout = setTimeout(function () {
  1724. _this2.imageClicked = false;
  1725. }, 500);
  1726. }
  1727. } else {
  1728. this.imageClicked = false;
  1729.  
  1730. if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) {
  1731. // This timeout will be cleared later when a native click event is triggering
  1732. this.clickCanvasTimeout = setTimeout(function () {
  1733. dispatchEvent(_this2.canvas, EVENT_CLICK, {
  1734. originalEvent: event
  1735. });
  1736. }, 50);
  1737. }
  1738. }
  1739. }
  1740. },
  1741. resize: function resize() {
  1742. var _this3 = this;
  1743.  
  1744. if (!this.isShown || this.hiding) {
  1745. return;
  1746. }
  1747.  
  1748. if (this.fulled) {
  1749. this.close();
  1750. this.initBody();
  1751. this.open();
  1752. }
  1753.  
  1754. this.initContainer();
  1755. this.initViewer();
  1756. this.renderViewer();
  1757. this.renderList();
  1758.  
  1759. if (this.viewed) {
  1760. this.initImage(function () {
  1761. _this3.renderImage();
  1762. });
  1763. }
  1764.  
  1765. if (this.played) {
  1766. if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
  1767. this.stop();
  1768. return;
  1769. }
  1770.  
  1771. forEach(this.player.getElementsByTagName('img'), function (image) {
  1772. addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), {
  1773. once: true
  1774. });
  1775. dispatchEvent(image, EVENT_LOAD);
  1776. });
  1777. }
  1778. },
  1779. wheel: function wheel(event) {
  1780. var _this4 = this;
  1781.  
  1782. if (!this.viewed) {
  1783. return;
  1784. }
  1785.  
  1786. event.preventDefault(); // Limit wheel speed to prevent zoom too fast
  1787.  
  1788. if (this.wheeling) {
  1789. return;
  1790. }
  1791.  
  1792. this.wheeling = true;
  1793. setTimeout(function () {
  1794. _this4.wheeling = false;
  1795. }, 50);
  1796. var ratio = Number(this.options.zoomRatio) || 0.1;
  1797. var delta = 1;
  1798.  
  1799. if (event.deltaY) {
  1800. delta = event.deltaY > 0 ? 1 : -1;
  1801. } else if (event.wheelDelta) {
  1802. delta = -event.wheelDelta / 120;
  1803. } else if (event.detail) {
  1804. delta = event.detail > 0 ? 1 : -1;
  1805. }
  1806.  
  1807. this.zoom(-delta * ratio, true, event);
  1808. }
  1809. };
  1810.  
  1811. var methods = {
  1812. /** Show the viewer (only available in modal mode)
  1813. * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not.
  1814. * @returns {Viewer} this
  1815. */
  1816. show: function show() {
  1817. var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  1818. var element = this.element,
  1819. options = this.options;
  1820.  
  1821. if (options.inline || this.showing || this.isShown || this.showing) {
  1822. return this;
  1823. }
  1824.  
  1825. if (!this.ready) {
  1826. this.build();
  1827.  
  1828. if (this.ready) {
  1829. this.show(immediate);
  1830. }
  1831.  
  1832. return this;
  1833. }
  1834.  
  1835. if (isFunction(options.show)) {
  1836. addListener(element, EVENT_SHOW, options.show, {
  1837. once: true
  1838. });
  1839. }
  1840.  
  1841. if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) {
  1842. return this;
  1843. }
  1844.  
  1845. if (this.hiding) {
  1846. this.transitioning.abort();
  1847. }
  1848.  
  1849. this.showing = true;
  1850. this.open();
  1851. var viewer = this.viewer;
  1852. removeClass(viewer, CLASS_HIDE);
  1853. viewer.setAttribute('role', 'dialog');
  1854. viewer.setAttribute('aria-labelledby', this.title.id);
  1855. viewer.setAttribute('aria-modal', true);
  1856. viewer.removeAttribute('aria-hidden');
  1857.  
  1858. if (options.transition && !immediate) {
  1859. var shown = this.shown.bind(this);
  1860. this.transitioning = {
  1861. abort: function abort() {
  1862. removeListener(viewer, EVENT_TRANSITION_END, shown);
  1863. removeClass(viewer, CLASS_IN);
  1864. }
  1865. };
  1866. addClass(viewer, CLASS_TRANSITION); // Force reflow to enable CSS3 transition
  1867.  
  1868. viewer.initialOffsetWidth = viewer.offsetWidth;
  1869. addListener(viewer, EVENT_TRANSITION_END, shown, {
  1870. once: true
  1871. });
  1872. addClass(viewer, CLASS_IN);
  1873. } else {
  1874. addClass(viewer, CLASS_IN);
  1875. this.shown();
  1876. }
  1877.  
  1878. return this;
  1879. },
  1880.  
  1881. /**
  1882. * Hide the viewer (only available in modal mode)
  1883. * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not.
  1884. * @returns {Viewer} this
  1885. */
  1886. hide: function hide() {
  1887. var _this = this;
  1888.  
  1889. var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  1890. var element = this.element,
  1891. options = this.options;
  1892.  
  1893. if (options.inline || this.hiding || !(this.isShown || this.showing)) {
  1894. return this;
  1895. }
  1896.  
  1897. if (isFunction(options.hide)) {
  1898. addListener(element, EVENT_HIDE, options.hide, {
  1899. once: true
  1900. });
  1901. }
  1902.  
  1903. if (dispatchEvent(element, EVENT_HIDE) === false) {
  1904. return this;
  1905. }
  1906.  
  1907. if (this.showing) {
  1908. this.transitioning.abort();
  1909. }
  1910.  
  1911. this.hiding = true;
  1912.  
  1913. if (this.played) {
  1914. this.stop();
  1915. } else if (this.viewing) {
  1916. this.viewing.abort();
  1917. }
  1918.  
  1919. var viewer = this.viewer,
  1920. image = this.image;
  1921.  
  1922. var hideImmediately = function hideImmediately() {
  1923. removeClass(viewer, CLASS_IN);
  1924.  
  1925. _this.hidden();
  1926. };
  1927.  
  1928. if (options.transition && !immediate) {
  1929. var onViewerTransitionEnd = function onViewerTransitionEnd(event) {
  1930. // Ignore all propagating `transitionend` events (#275).
  1931. if (event && event.target === viewer) {
  1932. removeListener(viewer, EVENT_TRANSITION_END, onViewerTransitionEnd);
  1933.  
  1934. _this.hidden();
  1935. }
  1936. };
  1937.  
  1938. var onImageTransitionEnd = function onImageTransitionEnd() {
  1939. // In case of show the viewer by `viewer.show(true)` previously (#407).
  1940. if (hasClass(viewer, CLASS_TRANSITION)) {
  1941. addListener(viewer, EVENT_TRANSITION_END, onViewerTransitionEnd);
  1942. removeClass(viewer, CLASS_IN);
  1943. } else {
  1944. hideImmediately();
  1945. }
  1946. };
  1947.  
  1948. this.transitioning = {
  1949. abort: function abort() {
  1950. if (_this.viewed && hasClass(image, CLASS_TRANSITION)) {
  1951. removeListener(image, EVENT_TRANSITION_END, onImageTransitionEnd);
  1952. } else if (hasClass(viewer, CLASS_TRANSITION)) {
  1953. removeListener(viewer, EVENT_TRANSITION_END, onViewerTransitionEnd);
  1954. }
  1955. }
  1956. }; // In case of hiding the viewer when holding on the image (#255),
  1957. // note that the `CLASS_TRANSITION` class will be removed on pointer down.
  1958.  
  1959. if (this.viewed && hasClass(image, CLASS_TRANSITION)) {
  1960. addListener(image, EVENT_TRANSITION_END, onImageTransitionEnd, {
  1961. once: true
  1962. });
  1963. this.zoomTo(0, false, null, true);
  1964. } else {
  1965. onImageTransitionEnd();
  1966. }
  1967. } else {
  1968. hideImmediately();
  1969. }
  1970.  
  1971. return this;
  1972. },
  1973.  
  1974. /**
  1975. * View one of the images with image's index
  1976. * @param {number} index - The index of the image to view.
  1977. * @returns {Viewer} this
  1978. */
  1979. view: function view() {
  1980. var _this2 = this;
  1981.  
  1982. var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex;
  1983. index = Number(index) || 0;
  1984.  
  1985. if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) {
  1986. return this;
  1987. }
  1988.  
  1989. if (!this.isShown) {
  1990. this.index = index;
  1991. return this.show();
  1992. }
  1993.  
  1994. if (this.viewing) {
  1995. this.viewing.abort();
  1996. }
  1997.  
  1998. var element = this.element,
  1999. options = this.options,
  2000. title = this.title,
  2001. canvas = this.canvas;
  2002. var item = this.items[index];
  2003. var img = item.querySelector('img');
  2004. var url = getData(img, 'originalUrl');
  2005. var alt = img.getAttribute('alt');
  2006. var image = document.createElement('img');
  2007. forEach(options.inheritedAttributes, function (name) {
  2008. var value = img.getAttribute(name);
  2009.  
  2010. if (value !== null) {
  2011. image.setAttribute(name, value);
  2012. }
  2013. });
  2014. image.src = url;
  2015. image.alt = alt;
  2016.  
  2017. if (isFunction(options.view)) {
  2018. addListener(element, EVENT_VIEW, options.view, {
  2019. once: true
  2020. });
  2021. }
  2022.  
  2023. if (dispatchEvent(element, EVENT_VIEW, {
  2024. originalImage: this.images[index],
  2025. index: index,
  2026. image: image
  2027. }) === false || !this.isShown || this.hiding || this.played) {
  2028. return this;
  2029. }
  2030.  
  2031. var activeItem = this.items[this.index];
  2032.  
  2033. if (activeItem) {
  2034. removeClass(activeItem, CLASS_ACTIVE);
  2035. activeItem.removeAttribute('aria-selected');
  2036. }
  2037.  
  2038. addClass(item, CLASS_ACTIVE);
  2039. item.setAttribute('aria-selected', true);
  2040.  
  2041. if (options.focus) {
  2042. item.focus();
  2043. }
  2044.  
  2045. this.image = image;
  2046. this.viewed = false;
  2047. this.index = index;
  2048. this.imageData = {};
  2049. addClass(image, CLASS_INVISIBLE);
  2050.  
  2051. if (options.loading) {
  2052. addClass(canvas, CLASS_LOADING);
  2053. }
  2054.  
  2055. canvas.innerHTML = '';
  2056. canvas.appendChild(image); // Center current item
  2057.  
  2058. this.renderList(); // Clear title
  2059.  
  2060. title.innerHTML = ''; // Generate title after viewed
  2061.  
  2062. var onViewed = function onViewed() {
  2063. var imageData = _this2.imageData;
  2064. var render = Array.isArray(options.title) ? options.title[1] : options.title;
  2065. title.innerHTML = escapeHTMLEntities(isFunction(render) ? render.call(_this2, image, imageData) : "".concat(alt, " (").concat(imageData.naturalWidth, " \xD7 ").concat(imageData.naturalHeight, ")"));
  2066. };
  2067.  
  2068. var onLoad;
  2069. var onError;
  2070. addListener(element, EVENT_VIEWED, onViewed, {
  2071. once: true
  2072. });
  2073. this.viewing = {
  2074. abort: function abort() {
  2075. removeListener(element, EVENT_VIEWED, onViewed);
  2076.  
  2077. if (image.complete) {
  2078. if (_this2.imageRendering) {
  2079. _this2.imageRendering.abort();
  2080. } else if (_this2.imageInitializing) {
  2081. _this2.imageInitializing.abort();
  2082. }
  2083. } else {
  2084. // Cancel download to save bandwidth.
  2085. image.src = '';
  2086. removeListener(image, EVENT_LOAD, onLoad);
  2087.  
  2088. if (_this2.timeout) {
  2089. clearTimeout(_this2.timeout);
  2090. }
  2091. }
  2092. }
  2093. };
  2094.  
  2095. if (image.complete) {
  2096. this.load();
  2097. } else {
  2098. addListener(image, EVENT_LOAD, onLoad = function onLoad() {
  2099. removeListener(image, EVENT_ERROR, onError);
  2100.  
  2101. _this2.load();
  2102. }, {
  2103. once: true
  2104. });
  2105. addListener(image, EVENT_ERROR, onError = function onError() {
  2106. removeListener(image, EVENT_LOAD, onLoad);
  2107.  
  2108. if (_this2.timeout) {
  2109. clearTimeout(_this2.timeout);
  2110. _this2.timeout = false;
  2111. }
  2112.  
  2113. removeClass(image, CLASS_INVISIBLE);
  2114.  
  2115. if (options.loading) {
  2116. removeClass(_this2.canvas, CLASS_LOADING);
  2117. }
  2118. }, {
  2119. once: true
  2120. });
  2121.  
  2122. if (this.timeout) {
  2123. clearTimeout(this.timeout);
  2124. } // Make the image visible if it fails to load within 1s
  2125.  
  2126.  
  2127. this.timeout = setTimeout(function () {
  2128. removeClass(image, CLASS_INVISIBLE);
  2129. _this2.timeout = false;
  2130. }, 1000);
  2131. }
  2132.  
  2133. return this;
  2134. },
  2135.  
  2136. /**
  2137. * View the previous image
  2138. * @param {boolean} [loop=false] - Indicate if view the last one
  2139. * when it is the first one at present.
  2140. * @returns {Viewer} this
  2141. */
  2142. prev: function prev() {
  2143. var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  2144. var index = this.index - 1;
  2145.  
  2146. if (index < 0) {
  2147. index = loop ? this.length - 1 : 0;
  2148. }
  2149.  
  2150. this.view(index);
  2151. return this;
  2152. },
  2153.  
  2154. /**
  2155. * View the next image
  2156. * @param {boolean} [loop=false] - Indicate if view the first one
  2157. * when it is the last one at present.
  2158. * @returns {Viewer} this
  2159. */
  2160. next: function next() {
  2161. var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  2162. var maxIndex = this.length - 1;
  2163. var index = this.index + 1;
  2164.  
  2165. if (index > maxIndex) {
  2166. index = loop ? 0 : maxIndex;
  2167. }
  2168.  
  2169. this.view(index);
  2170. return this;
  2171. },
  2172.  
  2173. /**
  2174. * Move the image with relative offsets.
  2175. * @param {number} x - The moving distance in the horizontal direction.
  2176. * @param {number} [y=x] The moving distance in the vertical direction.
  2177. * @returns {Viewer} this
  2178. */
  2179. move: function move(x) {
  2180. var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
  2181. var imageData = this.imageData;
  2182. this.moveTo(isUndefined(x) ? x : imageData.x + Number(x), isUndefined(y) ? y : imageData.y + Number(y));
  2183. return this;
  2184. },
  2185.  
  2186. /**
  2187. * Move the image to an absolute point.
  2188. * @param {number} x - The new position in the horizontal direction.
  2189. * @param {number} [y=x] - The new position in the vertical direction.
  2190. * @param {Event} [_originalEvent=null] - The original event if any.
  2191. * @returns {Viewer} this
  2192. */
  2193. moveTo: function moveTo(x) {
  2194. var _this3 = this;
  2195.  
  2196. var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
  2197.  
  2198. var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
  2199.  
  2200. var element = this.element,
  2201. options = this.options,
  2202. imageData = this.imageData;
  2203. x = Number(x);
  2204. y = Number(y);
  2205.  
  2206. if (this.viewed && !this.played && options.movable) {
  2207. var oldX = imageData.x;
  2208. var oldY = imageData.y;
  2209. var changed = false;
  2210.  
  2211. if (isNumber(x)) {
  2212. changed = true;
  2213. } else {
  2214. x = oldX;
  2215. }
  2216.  
  2217. if (isNumber(y)) {
  2218. changed = true;
  2219. } else {
  2220. y = oldY;
  2221. }
  2222.  
  2223. if (changed) {
  2224. if (isFunction(options.move)) {
  2225. addListener(element, EVENT_MOVE, options.move, {
  2226. once: true
  2227. });
  2228. }
  2229.  
  2230. if (dispatchEvent(element, EVENT_MOVE, {
  2231. x: x,
  2232. y: y,
  2233. oldX: oldX,
  2234. oldY: oldY,
  2235. originalEvent: _originalEvent
  2236. }) === false) {
  2237. return this;
  2238. }
  2239.  
  2240. imageData.x = x;
  2241. imageData.y = y;
  2242. imageData.left = x;
  2243. imageData.top = y;
  2244. this.moving = true;
  2245. this.renderImage(function () {
  2246. _this3.moving = false;
  2247.  
  2248. if (isFunction(options.moved)) {
  2249. addListener(element, EVENT_MOVED, options.moved, {
  2250. once: true
  2251. });
  2252. }
  2253.  
  2254. dispatchEvent(element, EVENT_MOVED, {
  2255. x: x,
  2256. y: y,
  2257. oldX: oldX,
  2258. oldY: oldY,
  2259. originalEvent: _originalEvent
  2260. }, {
  2261. cancelable: false
  2262. });
  2263. });
  2264. }
  2265. }
  2266.  
  2267. return this;
  2268. },
  2269.  
  2270. /**
  2271. * Rotate the image with a relative degree.
  2272. * @param {number} degree - The rotate degree.
  2273. * @returns {Viewer} this
  2274. */
  2275. rotate: function rotate(degree) {
  2276. this.rotateTo((this.imageData.rotate || 0) + Number(degree));
  2277. return this;
  2278. },
  2279.  
  2280. /**
  2281. * Rotate the image to an absolute degree.
  2282. * @param {number} degree - The rotate degree.
  2283. * @returns {Viewer} this
  2284. */
  2285. rotateTo: function rotateTo(degree) {
  2286. var _this4 = this;
  2287.  
  2288. var element = this.element,
  2289. options = this.options,
  2290. imageData = this.imageData;
  2291. degree = Number(degree);
  2292.  
  2293. if (isNumber(degree) && this.viewed && !this.played && options.rotatable) {
  2294. var oldDegree = imageData.rotate;
  2295.  
  2296. if (isFunction(options.rotate)) {
  2297. addListener(element, EVENT_ROTATE, options.rotate, {
  2298. once: true
  2299. });
  2300. }
  2301.  
  2302. if (dispatchEvent(element, EVENT_ROTATE, {
  2303. degree: degree,
  2304. oldDegree: oldDegree
  2305. }) === false) {
  2306. return this;
  2307. }
  2308.  
  2309. imageData.rotate = degree;
  2310. this.rotating = true;
  2311. this.renderImage(function () {
  2312. _this4.rotating = false;
  2313.  
  2314. if (isFunction(options.rotated)) {
  2315. addListener(element, EVENT_ROTATED, options.rotated, {
  2316. once: true
  2317. });
  2318. }
  2319.  
  2320. dispatchEvent(element, EVENT_ROTATED, {
  2321. degree: degree,
  2322. oldDegree: oldDegree
  2323. }, {
  2324. cancelable: false
  2325. });
  2326. });
  2327. }
  2328.  
  2329. return this;
  2330. },
  2331.  
  2332. /**
  2333. * Scale the image on the x-axis.
  2334. * @param {number} scaleX - The scale ratio on the x-axis.
  2335. * @returns {Viewer} this
  2336. */
  2337. scaleX: function scaleX(_scaleX) {
  2338. this.scale(_scaleX, this.imageData.scaleY);
  2339. return this;
  2340. },
  2341.  
  2342. /**
  2343. * Scale the image on the y-axis.
  2344. * @param {number} scaleY - The scale ratio on the y-axis.
  2345. * @returns {Viewer} this
  2346. */
  2347. scaleY: function scaleY(_scaleY) {
  2348. this.scale(this.imageData.scaleX, _scaleY);
  2349. return this;
  2350. },
  2351.  
  2352. /**
  2353. * Scale the image.
  2354. * @param {number} scaleX - The scale ratio on the x-axis.
  2355. * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.
  2356. * @returns {Viewer} this
  2357. */
  2358. scale: function scale(scaleX) {
  2359. var _this5 = this;
  2360.  
  2361. var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX;
  2362. var element = this.element,
  2363. options = this.options,
  2364. imageData = this.imageData;
  2365. scaleX = Number(scaleX);
  2366. scaleY = Number(scaleY);
  2367.  
  2368. if (this.viewed && !this.played && options.scalable) {
  2369. var oldScaleX = imageData.scaleX;
  2370. var oldScaleY = imageData.scaleY;
  2371. var changed = false;
  2372.  
  2373. if (isNumber(scaleX)) {
  2374. changed = true;
  2375. } else {
  2376. scaleX = oldScaleX;
  2377. }
  2378.  
  2379. if (isNumber(scaleY)) {
  2380. changed = true;
  2381. } else {
  2382. scaleY = oldScaleY;
  2383. }
  2384.  
  2385. if (changed) {
  2386. if (isFunction(options.scale)) {
  2387. addListener(element, EVENT_SCALE, options.scale, {
  2388. once: true
  2389. });
  2390. }
  2391.  
  2392. if (dispatchEvent(element, EVENT_SCALE, {
  2393. scaleX: scaleX,
  2394. scaleY: scaleY,
  2395. oldScaleX: oldScaleX,
  2396. oldScaleY: oldScaleY
  2397. }) === false) {
  2398. return this;
  2399. }
  2400.  
  2401. imageData.scaleX = scaleX;
  2402. imageData.scaleY = scaleY;
  2403. this.scaling = true;
  2404. this.renderImage(function () {
  2405. _this5.scaling = false;
  2406.  
  2407. if (isFunction(options.scaled)) {
  2408. addListener(element, EVENT_SCALED, options.scaled, {
  2409. once: true
  2410. });
  2411. }
  2412.  
  2413. dispatchEvent(element, EVENT_SCALED, {
  2414. scaleX: scaleX,
  2415. scaleY: scaleY,
  2416. oldScaleX: oldScaleX,
  2417. oldScaleY: oldScaleY
  2418. }, {
  2419. cancelable: false
  2420. });
  2421. });
  2422. }
  2423. }
  2424.  
  2425. return this;
  2426. },
  2427.  
  2428. /**
  2429. * Zoom the image with a relative ratio.
  2430. * @param {number} ratio - The target ratio.
  2431. * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not.
  2432. * @param {Event} [_originalEvent=null] - The original event if any.
  2433. * @returns {Viewer} this
  2434. */
  2435. zoom: function zoom(ratio) {
  2436. var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  2437.  
  2438. var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
  2439.  
  2440. var imageData = this.imageData;
  2441. ratio = Number(ratio);
  2442.  
  2443. if (ratio < 0) {
  2444. ratio = 1 / (1 - ratio);
  2445. } else {
  2446. ratio = 1 + ratio;
  2447. }
  2448.  
  2449. this.zoomTo(imageData.width * ratio / imageData.naturalWidth, hasTooltip, _originalEvent);
  2450. return this;
  2451. },
  2452.  
  2453. /**
  2454. * Zoom the image to an absolute ratio.
  2455. * @param {number} ratio - The target ratio.
  2456. * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not.
  2457. * @param {Event} [_originalEvent=null] - The original event if any.
  2458. * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not.
  2459. * @returns {Viewer} this
  2460. */
  2461. zoomTo: function zoomTo(ratio) {
  2462. var _this6 = this;
  2463.  
  2464. var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  2465.  
  2466. var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
  2467.  
  2468. var _zoomable = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
  2469.  
  2470. var element = this.element,
  2471. options = this.options,
  2472. pointers = this.pointers,
  2473. imageData = this.imageData;
  2474. var x = imageData.x,
  2475. y = imageData.y,
  2476. width = imageData.width,
  2477. height = imageData.height,
  2478. naturalWidth = imageData.naturalWidth,
  2479. naturalHeight = imageData.naturalHeight;
  2480. ratio = Math.max(0, ratio);
  2481.  
  2482. if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) {
  2483. if (!_zoomable) {
  2484. var minZoomRatio = Math.max(0.01, options.minZoomRatio);
  2485. var maxZoomRatio = Math.min(100, options.maxZoomRatio);
  2486. ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio);
  2487. }
  2488.  
  2489. if (_originalEvent) {
  2490. switch (_originalEvent.type) {
  2491. case 'wheel':
  2492. if (options.zoomRatio >= 0.055 && ratio > 0.95 && ratio < 1.05) {
  2493. ratio = 1;
  2494. }
  2495.  
  2496. break;
  2497.  
  2498. case 'pointermove':
  2499. case 'touchmove':
  2500. case 'mousemove':
  2501. if (ratio > 0.99 && ratio < 1.01) {
  2502. ratio = 1;
  2503. }
  2504.  
  2505. break;
  2506. }
  2507. }
  2508.  
  2509. var newWidth = naturalWidth * ratio;
  2510. var newHeight = naturalHeight * ratio;
  2511. var offsetWidth = newWidth - width;
  2512. var offsetHeight = newHeight - height;
  2513. var oldRatio = imageData.ratio;
  2514.  
  2515. if (isFunction(options.zoom)) {
  2516. addListener(element, EVENT_ZOOM, options.zoom, {
  2517. once: true
  2518. });
  2519. }
  2520.  
  2521. if (dispatchEvent(element, EVENT_ZOOM, {
  2522. ratio: ratio,
  2523. oldRatio: oldRatio,
  2524. originalEvent: _originalEvent
  2525. }) === false) {
  2526. return this;
  2527. }
  2528.  
  2529. this.zooming = true;
  2530.  
  2531. if (_originalEvent) {
  2532. var offset = getOffset(this.viewer);
  2533. var center = pointers && Object.keys(pointers).length > 0 ? getPointersCenter(pointers) : {
  2534. pageX: _originalEvent.pageX,
  2535. pageY: _originalEvent.pageY
  2536. }; // Zoom from the triggering point of the event
  2537.  
  2538. imageData.x -= offsetWidth * ((center.pageX - offset.left - x) / width);
  2539. imageData.y -= offsetHeight * ((center.pageY - offset.top - y) / height);
  2540. } else {
  2541. // Zoom from the center of the image
  2542. imageData.x -= offsetWidth / 2;
  2543. imageData.y -= offsetHeight / 2;
  2544. }
  2545.  
  2546. imageData.left = imageData.x;
  2547. imageData.top = imageData.y;
  2548. imageData.width = newWidth;
  2549. imageData.height = newHeight;
  2550. imageData.oldRatio = oldRatio;
  2551. imageData.ratio = ratio;
  2552. this.renderImage(function () {
  2553. _this6.zooming = false;
  2554.  
  2555. if (isFunction(options.zoomed)) {
  2556. addListener(element, EVENT_ZOOMED, options.zoomed, {
  2557. once: true
  2558. });
  2559. }
  2560.  
  2561. dispatchEvent(element, EVENT_ZOOMED, {
  2562. ratio: ratio,
  2563. oldRatio: oldRatio,
  2564. originalEvent: _originalEvent
  2565. }, {
  2566. cancelable: false
  2567. });
  2568. });
  2569.  
  2570. if (hasTooltip) {
  2571. this.tooltip();
  2572. }
  2573. }
  2574.  
  2575. return this;
  2576. },
  2577.  
  2578. /**
  2579. * Play the images
  2580. * @param {boolean|FullscreenOptions} [fullscreen=false] - Indicate if request fullscreen or not.
  2581. * @returns {Viewer} this
  2582. */
  2583. play: function play() {
  2584. var _this7 = this;
  2585.  
  2586. var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  2587.  
  2588. if (!this.isShown || this.played) {
  2589. return this;
  2590. }
  2591.  
  2592. var element = this.element,
  2593. options = this.options;
  2594.  
  2595. if (isFunction(options.play)) {
  2596. addListener(element, EVENT_PLAY, options.play, {
  2597. once: true
  2598. });
  2599. }
  2600.  
  2601. if (dispatchEvent(element, EVENT_PLAY) === false) {
  2602. return this;
  2603. }
  2604.  
  2605. var player = this.player;
  2606. var onLoad = this.loadImage.bind(this);
  2607. var list = [];
  2608. var total = 0;
  2609. var index = 0;
  2610. this.played = true;
  2611. this.onLoadWhenPlay = onLoad;
  2612.  
  2613. if (fullscreen) {
  2614. this.requestFullscreen(fullscreen);
  2615. }
  2616.  
  2617. addClass(player, CLASS_SHOW);
  2618. forEach(this.items, function (item, i) {
  2619. var img = item.querySelector('img');
  2620. var image = document.createElement('img');
  2621. image.src = getData(img, 'originalUrl');
  2622. image.alt = img.getAttribute('alt');
  2623. image.referrerPolicy = img.referrerPolicy;
  2624. total += 1;
  2625. addClass(image, CLASS_FADE);
  2626. toggleClass(image, CLASS_TRANSITION, options.transition);
  2627.  
  2628. if (hasClass(item, CLASS_ACTIVE)) {
  2629. addClass(image, CLASS_IN);
  2630. index = i;
  2631. }
  2632.  
  2633. list.push(image);
  2634. addListener(image, EVENT_LOAD, onLoad, {
  2635. once: true
  2636. });
  2637. player.appendChild(image);
  2638. });
  2639.  
  2640. if (isNumber(options.interval) && options.interval > 0) {
  2641. var play = function play() {
  2642. _this7.playing = setTimeout(function () {
  2643. removeClass(list[index], CLASS_IN);
  2644. index += 1;
  2645. index = index < total ? index : 0;
  2646. addClass(list[index], CLASS_IN);
  2647. play();
  2648. }, options.interval);
  2649. };
  2650.  
  2651. if (total > 1) {
  2652. play();
  2653. }
  2654. }
  2655.  
  2656. return this;
  2657. },
  2658. // Stop play
  2659. stop: function stop() {
  2660. var _this8 = this;
  2661.  
  2662. if (!this.played) {
  2663. return this;
  2664. }
  2665.  
  2666. var element = this.element,
  2667. options = this.options;
  2668.  
  2669. if (isFunction(options.stop)) {
  2670. addListener(element, EVENT_STOP, options.stop, {
  2671. once: true
  2672. });
  2673. }
  2674.  
  2675. if (dispatchEvent(element, EVENT_STOP) === false) {
  2676. return this;
  2677. }
  2678.  
  2679. var player = this.player;
  2680. this.played = false;
  2681. clearTimeout(this.playing);
  2682. forEach(player.getElementsByTagName('img'), function (image) {
  2683. removeListener(image, EVENT_LOAD, _this8.onLoadWhenPlay);
  2684. });
  2685. removeClass(player, CLASS_SHOW);
  2686. player.innerHTML = '';
  2687. this.exitFullscreen();
  2688. return this;
  2689. },
  2690. // Enter modal mode (only available in inline mode)
  2691. full: function full() {
  2692. var _this9 = this;
  2693.  
  2694. var options = this.options,
  2695. viewer = this.viewer,
  2696. image = this.image,
  2697. list = this.list;
  2698.  
  2699. if (!this.isShown || this.played || this.fulled || !options.inline) {
  2700. return this;
  2701. }
  2702.  
  2703. this.fulled = true;
  2704. this.open();
  2705. addClass(this.button, CLASS_FULLSCREEN_EXIT);
  2706.  
  2707. if (options.transition) {
  2708. removeClass(list, CLASS_TRANSITION);
  2709.  
  2710. if (this.viewed) {
  2711. removeClass(image, CLASS_TRANSITION);
  2712. }
  2713. }
  2714.  
  2715. addClass(viewer, CLASS_FIXED);
  2716. viewer.setAttribute('role', 'dialog');
  2717. viewer.setAttribute('aria-labelledby', this.title.id);
  2718. viewer.setAttribute('aria-modal', true);
  2719. viewer.removeAttribute('style');
  2720. setStyle(viewer, {
  2721. zIndex: options.zIndex
  2722. });
  2723.  
  2724. if (options.focus) {
  2725. this.enforceFocus();
  2726. }
  2727.  
  2728. this.initContainer();
  2729. this.viewerData = assign({}, this.containerData);
  2730. this.renderList();
  2731.  
  2732. if (this.viewed) {
  2733. this.initImage(function () {
  2734. _this9.renderImage(function () {
  2735. if (options.transition) {
  2736. setTimeout(function () {
  2737. addClass(image, CLASS_TRANSITION);
  2738. addClass(list, CLASS_TRANSITION);
  2739. }, 0);
  2740. }
  2741. });
  2742. });
  2743. }
  2744.  
  2745. return this;
  2746. },
  2747. // Exit modal mode (only available in inline mode)
  2748. exit: function exit() {
  2749. var _this10 = this;
  2750.  
  2751. var options = this.options,
  2752. viewer = this.viewer,
  2753. image = this.image,
  2754. list = this.list;
  2755.  
  2756. if (!this.isShown || this.played || !this.fulled || !options.inline) {
  2757. return this;
  2758. }
  2759.  
  2760. this.fulled = false;
  2761. this.close();
  2762. removeClass(this.button, CLASS_FULLSCREEN_EXIT);
  2763.  
  2764. if (options.transition) {
  2765. removeClass(list, CLASS_TRANSITION);
  2766.  
  2767. if (this.viewed) {
  2768. removeClass(image, CLASS_TRANSITION);
  2769. }
  2770. }
  2771.  
  2772. if (options.focus) {
  2773. this.clearEnforceFocus();
  2774. }
  2775.  
  2776. viewer.removeAttribute('role');
  2777. viewer.removeAttribute('aria-labelledby');
  2778. viewer.removeAttribute('aria-modal');
  2779. removeClass(viewer, CLASS_FIXED);
  2780. setStyle(viewer, {
  2781. zIndex: options.zIndexInline
  2782. });
  2783. this.viewerData = assign({}, this.parentData);
  2784. this.renderViewer();
  2785. this.renderList();
  2786.  
  2787. if (this.viewed) {
  2788. this.initImage(function () {
  2789. _this10.renderImage(function () {
  2790. if (options.transition) {
  2791. setTimeout(function () {
  2792. addClass(image, CLASS_TRANSITION);
  2793. addClass(list, CLASS_TRANSITION);
  2794. }, 0);
  2795. }
  2796. });
  2797. });
  2798. }
  2799.  
  2800. return this;
  2801. },
  2802. // Show the current ratio of the image with percentage
  2803. tooltip: function tooltip() {
  2804. var _this11 = this;
  2805.  
  2806. var options = this.options,
  2807. tooltipBox = this.tooltipBox,
  2808. imageData = this.imageData;
  2809.  
  2810. if (!this.viewed || this.played || !options.tooltip) {
  2811. return this;
  2812. }
  2813.  
  2814. tooltipBox.textContent = "".concat(Math.round(imageData.ratio * 100), "%");
  2815.  
  2816. if (!this.tooltipping) {
  2817. if (options.transition) {
  2818. if (this.fading) {
  2819. dispatchEvent(tooltipBox, EVENT_TRANSITION_END);
  2820. }
  2821.  
  2822. addClass(tooltipBox, CLASS_SHOW);
  2823. addClass(tooltipBox, CLASS_FADE);
  2824. addClass(tooltipBox, CLASS_TRANSITION);
  2825. tooltipBox.removeAttribute('aria-hidden'); // Force reflow to enable CSS3 transition
  2826.  
  2827. tooltipBox.initialOffsetWidth = tooltipBox.offsetWidth;
  2828. addClass(tooltipBox, CLASS_IN);
  2829. } else {
  2830. addClass(tooltipBox, CLASS_SHOW);
  2831. tooltipBox.removeAttribute('aria-hidden');
  2832. }
  2833. } else {
  2834. clearTimeout(this.tooltipping);
  2835. }
  2836.  
  2837. this.tooltipping = setTimeout(function () {
  2838. if (options.transition) {
  2839. addListener(tooltipBox, EVENT_TRANSITION_END, function () {
  2840. removeClass(tooltipBox, CLASS_SHOW);
  2841. removeClass(tooltipBox, CLASS_FADE);
  2842. removeClass(tooltipBox, CLASS_TRANSITION);
  2843. tooltipBox.setAttribute('aria-hidden', true);
  2844. _this11.fading = false;
  2845. }, {
  2846. once: true
  2847. });
  2848. removeClass(tooltipBox, CLASS_IN);
  2849. _this11.fading = true;
  2850. } else {
  2851. removeClass(tooltipBox, CLASS_SHOW);
  2852. tooltipBox.setAttribute('aria-hidden', true);
  2853. }
  2854.  
  2855. _this11.tooltipping = false;
  2856. }, 1000);
  2857. return this;
  2858. },
  2859.  
  2860. /**
  2861. * Toggle the image size between its current size and natural size
  2862. * @param {Event} [_originalEvent=null] - The original event if any.
  2863. * @returns {Viewer} this
  2864. */
  2865. toggle: function toggle() {
  2866. var _originalEvent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
  2867.  
  2868. if (this.imageData.ratio === 1) {
  2869. this.zoomTo(this.imageData.oldRatio, true, _originalEvent);
  2870. } else {
  2871. this.zoomTo(1, true, _originalEvent);
  2872. }
  2873.  
  2874. return this;
  2875. },
  2876. // Reset the image to its initial state
  2877. reset: function reset() {
  2878. if (this.viewed && !this.played) {
  2879. this.imageData = assign({}, this.initialImageData);
  2880. this.renderImage();
  2881. }
  2882.  
  2883. return this;
  2884. },
  2885. // Update viewer when images changed
  2886. update: function update() {
  2887. var _this12 = this;
  2888.  
  2889. var element = this.element,
  2890. options = this.options,
  2891. isImg = this.isImg; // Destroy viewer if the target image was deleted
  2892.  
  2893. if (isImg && !element.parentNode) {
  2894. return this.destroy();
  2895. }
  2896.  
  2897. var images = [];
  2898. forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {
  2899. if (isFunction(options.filter)) {
  2900. if (options.filter.call(_this12, image)) {
  2901. images.push(image);
  2902. }
  2903. } else if (_this12.getImageURL(image)) {
  2904. images.push(image);
  2905. }
  2906. });
  2907.  
  2908. if (!images.length) {
  2909. return this;
  2910. }
  2911.  
  2912. this.images = images;
  2913. this.length = images.length;
  2914.  
  2915. if (this.ready) {
  2916. var changedIndexes = [];
  2917. forEach(this.items, function (item, i) {
  2918. var img = item.querySelector('img');
  2919. var image = images[i];
  2920.  
  2921. if (image && img) {
  2922. if (image.src !== img.src // Title changed (#408)
  2923. || image.alt !== img.alt) {
  2924. changedIndexes.push(i);
  2925. }
  2926. } else {
  2927. changedIndexes.push(i);
  2928. }
  2929. });
  2930. setStyle(this.list, {
  2931. width: 'auto'
  2932. });
  2933. this.initList();
  2934.  
  2935. if (this.isShown) {
  2936. if (this.length) {
  2937. if (this.viewed) {
  2938. var changedIndex = changedIndexes.indexOf(this.index);
  2939.  
  2940. if (changedIndex >= 0) {
  2941. this.viewed = false;
  2942. this.view(Math.max(Math.min(this.index - changedIndex, this.length - 1), 0));
  2943. } else {
  2944. var activeItem = this.items[this.index]; // Reactivate the current viewing item after reset the list.
  2945.  
  2946. addClass(activeItem, CLASS_ACTIVE);
  2947. activeItem.setAttribute('aria-selected', true);
  2948. }
  2949. }
  2950. } else {
  2951. this.image = null;
  2952. this.viewed = false;
  2953. this.index = 0;
  2954. this.imageData = {};
  2955. this.canvas.innerHTML = '';
  2956. this.title.innerHTML = '';
  2957. }
  2958. }
  2959. } else {
  2960. this.build();
  2961. }
  2962.  
  2963. return this;
  2964. },
  2965. // Destroy the viewer
  2966. destroy: function destroy() {
  2967. var element = this.element,
  2968. options = this.options;
  2969.  
  2970. if (!element[NAMESPACE]) {
  2971. return this;
  2972. }
  2973.  
  2974. this.destroyed = true;
  2975.  
  2976. if (this.ready) {
  2977. if (this.played) {
  2978. this.stop();
  2979. }
  2980.  
  2981. if (options.inline) {
  2982. if (this.fulled) {
  2983. this.exit();
  2984. }
  2985.  
  2986. this.unbind();
  2987. } else if (this.isShown) {
  2988. if (this.viewing) {
  2989. if (this.imageRendering) {
  2990. this.imageRendering.abort();
  2991. } else if (this.imageInitializing) {
  2992. this.imageInitializing.abort();
  2993. }
  2994. }
  2995.  
  2996. if (this.hiding) {
  2997. this.transitioning.abort();
  2998. }
  2999.  
  3000. this.hidden();
  3001. } else if (this.showing) {
  3002. this.transitioning.abort();
  3003. this.hidden();
  3004. }
  3005.  
  3006. this.ready = false;
  3007. this.viewer.parentNode.removeChild(this.viewer);
  3008. } else if (options.inline) {
  3009. if (this.delaying) {
  3010. this.delaying.abort();
  3011. } else if (this.initializing) {
  3012. this.initializing.abort();
  3013. }
  3014. }
  3015.  
  3016. if (!options.inline) {
  3017. removeListener(element, EVENT_CLICK, this.onStart);
  3018. }
  3019.  
  3020. element[NAMESPACE] = undefined;
  3021. return this;
  3022. }
  3023. };
  3024.  
  3025. var others = {
  3026. getImageURL: function getImageURL(image) {
  3027. var url = this.options.url;
  3028.  
  3029. if (isString(url)) {
  3030. url = image.getAttribute(url);
  3031. } else if (isFunction(url)) {
  3032. url = url.call(this, image);
  3033. } else {
  3034. url = '';
  3035. }
  3036.  
  3037. return url;
  3038. },
  3039. enforceFocus: function enforceFocus() {
  3040. var _this = this;
  3041.  
  3042. this.clearEnforceFocus();
  3043. addListener(document, EVENT_FOCUSIN, this.onFocusin = function (event) {
  3044. var viewer = _this.viewer;
  3045. var target = event.target;
  3046.  
  3047. if (target === document || target === viewer || viewer.contains(target)) {
  3048. return;
  3049. }
  3050.  
  3051. while (target) {
  3052. // Avoid conflicts with other modals (#474, #540)
  3053. if (target.getAttribute('tabindex') !== null || target.getAttribute('aria-modal') === 'true') {
  3054. return;
  3055. }
  3056.  
  3057. target = target.parentElement;
  3058. }
  3059.  
  3060. viewer.focus();
  3061. });
  3062. },
  3063. clearEnforceFocus: function clearEnforceFocus() {
  3064. if (this.onFocusin) {
  3065. removeListener(document, EVENT_FOCUSIN, this.onFocusin);
  3066. this.onFocusin = null;
  3067. }
  3068. },
  3069. open: function open() {
  3070. var body = this.body;
  3071. addClass(body, CLASS_OPEN);
  3072. body.style.paddingRight = "".concat(this.scrollbarWidth + (parseFloat(this.initialBodyComputedPaddingRight) || 0), "px");
  3073. },
  3074. close: function close() {
  3075. var body = this.body;
  3076. removeClass(body, CLASS_OPEN);
  3077. body.style.paddingRight = this.initialBodyPaddingRight;
  3078. },
  3079. shown: function shown() {
  3080. var element = this.element,
  3081. options = this.options,
  3082. viewer = this.viewer;
  3083. this.fulled = true;
  3084. this.isShown = true;
  3085. this.render();
  3086. this.bind();
  3087. this.showing = false;
  3088.  
  3089. if (options.focus) {
  3090. viewer.focus();
  3091. this.enforceFocus();
  3092. }
  3093.  
  3094. if (isFunction(options.shown)) {
  3095. addListener(element, EVENT_SHOWN, options.shown, {
  3096. once: true
  3097. });
  3098. }
  3099.  
  3100. if (dispatchEvent(element, EVENT_SHOWN) === false) {
  3101. return;
  3102. }
  3103.  
  3104. if (this.ready && this.isShown && !this.hiding) {
  3105. this.view(this.index);
  3106. }
  3107. },
  3108. hidden: function hidden() {
  3109. var element = this.element,
  3110. options = this.options,
  3111. viewer = this.viewer;
  3112.  
  3113. if (options.fucus) {
  3114. this.clearEnforceFocus();
  3115. }
  3116.  
  3117. this.fulled = false;
  3118. this.viewed = false;
  3119. this.isShown = false;
  3120. this.close();
  3121. this.unbind();
  3122. addClass(viewer, CLASS_HIDE);
  3123. viewer.removeAttribute('role');
  3124. viewer.removeAttribute('aria-labelledby');
  3125. viewer.removeAttribute('aria-modal');
  3126. viewer.setAttribute('aria-hidden', true);
  3127. this.resetList();
  3128. this.resetImage();
  3129. this.hiding = false;
  3130.  
  3131. if (!this.destroyed) {
  3132. if (isFunction(options.hidden)) {
  3133. addListener(element, EVENT_HIDDEN, options.hidden, {
  3134. once: true
  3135. });
  3136. }
  3137.  
  3138. dispatchEvent(element, EVENT_HIDDEN, null, {
  3139. cancelable: false
  3140. });
  3141. }
  3142. },
  3143. requestFullscreen: function requestFullscreen(options) {
  3144. var document = this.element.ownerDocument;
  3145.  
  3146. if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
  3147. var documentElement = document.documentElement; // Element.requestFullscreen()
  3148.  
  3149. if (documentElement.requestFullscreen) {
  3150. // Avoid TypeError when convert `options` to dictionary
  3151. if (isPlainObject(options)) {
  3152. documentElement.requestFullscreen(options);
  3153. } else {
  3154. documentElement.requestFullscreen();
  3155. }
  3156. } else if (documentElement.webkitRequestFullscreen) {
  3157. documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
  3158. } else if (documentElement.mozRequestFullScreen) {
  3159. documentElement.mozRequestFullScreen();
  3160. } else if (documentElement.msRequestFullscreen) {
  3161. documentElement.msRequestFullscreen();
  3162. }
  3163. }
  3164. },
  3165. exitFullscreen: function exitFullscreen() {
  3166. var document = this.element.ownerDocument;
  3167.  
  3168. if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
  3169. // Document.exitFullscreen()
  3170. if (document.exitFullscreen) {
  3171. document.exitFullscreen();
  3172. } else if (document.webkitExitFullscreen) {
  3173. document.webkitExitFullscreen();
  3174. } else if (document.mozCancelFullScreen) {
  3175. document.mozCancelFullScreen();
  3176. } else if (document.msExitFullscreen) {
  3177. document.msExitFullscreen();
  3178. }
  3179. }
  3180. },
  3181. change: function change(event) {
  3182. var options = this.options,
  3183. pointers = this.pointers;
  3184. var pointer = pointers[Object.keys(pointers)[0]]; // In the case of the `pointers` object is empty (#421)
  3185.  
  3186. if (!pointer) {
  3187. return;
  3188. }
  3189.  
  3190. var offsetX = pointer.endX - pointer.startX;
  3191. var offsetY = pointer.endY - pointer.startY;
  3192.  
  3193. switch (this.action) {
  3194. // Move the current image
  3195. case ACTION_MOVE:
  3196. this.move(offsetX, offsetY, event);
  3197. break;
  3198. // Zoom the current image
  3199.  
  3200. case ACTION_ZOOM:
  3201. this.zoom(getMaxZoomRatio(pointers), false, event);
  3202. break;
  3203.  
  3204. case ACTION_SWITCH:
  3205. {
  3206. this.action = 'switched';
  3207. var absoluteOffsetX = Math.abs(offsetX);
  3208.  
  3209. if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) {
  3210. // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers.
  3211. this.pointers = {};
  3212.  
  3213. if (offsetX > 1) {
  3214. this.prev(options.loop);
  3215. } else if (offsetX < -1) {
  3216. this.next(options.loop);
  3217. }
  3218. }
  3219.  
  3220. break;
  3221. }
  3222. } // Override
  3223.  
  3224.  
  3225. forEach(pointers, function (p) {
  3226. p.startX = p.endX;
  3227. p.startY = p.endY;
  3228. });
  3229. },
  3230. isSwitchable: function isSwitchable() {
  3231. var imageData = this.imageData,
  3232. viewerData = this.viewerData;
  3233. return this.length > 1 && imageData.x >= 0 && imageData.y >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height;
  3234. }
  3235. };
  3236.  
  3237. var AnotherViewer = WINDOW.Viewer;
  3238.  
  3239. var getUniqueID = function (id) {
  3240. return function () {
  3241. id += 1;
  3242. return id;
  3243. };
  3244. }(-1);
  3245.  
  3246. var Viewer = /*#__PURE__*/function () {
  3247. /**
  3248. * Create a new Viewer.
  3249. * @param {Element} element - The target element for viewing.
  3250. * @param {Object} [options={}] - The configuration options.
  3251. */
  3252. function Viewer(element) {
  3253. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  3254.  
  3255. _classCallCheck(this, Viewer);
  3256.  
  3257. if (!element || element.nodeType !== 1) {
  3258. throw new Error('The first argument is required and must be an element.');
  3259. }
  3260.  
  3261. this.element = element;
  3262. this.options = assign({}, DEFAULTS, isPlainObject(options) && options);
  3263. this.action = false;
  3264. this.fading = false;
  3265. this.fulled = false;
  3266. this.hiding = false;
  3267. this.imageClicked = false;
  3268. this.imageData = {};
  3269. this.index = this.options.initialViewIndex;
  3270. this.isImg = false;
  3271. this.isShown = false;
  3272. this.length = 0;
  3273. this.moving = false;
  3274. this.played = false;
  3275. this.playing = false;
  3276. this.pointers = {};
  3277. this.ready = false;
  3278. this.rotating = false;
  3279. this.scaling = false;
  3280. this.showing = false;
  3281. this.timeout = false;
  3282. this.tooltipping = false;
  3283. this.viewed = false;
  3284. this.viewing = false;
  3285. this.wheeling = false;
  3286. this.zooming = false;
  3287. this.id = getUniqueID();
  3288. this.init();
  3289. }
  3290.  
  3291. _createClass(Viewer, [{
  3292. key: "init",
  3293. value: function init() {
  3294. var _this = this;
  3295.  
  3296. var element = this.element,
  3297. options = this.options;
  3298.  
  3299. if (element[NAMESPACE]) {
  3300. return;
  3301. }
  3302.  
  3303. element[NAMESPACE] = this; // The `focus` option requires the `keyboard` option set to `true`.
  3304.  
  3305. if (options.focus && !options.keyboard) {
  3306. options.focus = false;
  3307. }
  3308.  
  3309. var isImg = element.localName === 'img';
  3310. var images = [];
  3311. forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {
  3312. if (isFunction(options.filter)) {
  3313. if (options.filter.call(_this, image)) {
  3314. images.push(image);
  3315. }
  3316. } else if (_this.getImageURL(image)) {
  3317. images.push(image);
  3318. }
  3319. });
  3320. this.isImg = isImg;
  3321. this.length = images.length;
  3322. this.images = images;
  3323. this.initBody(); // Override `transition` option if it is not supported
  3324.  
  3325. if (isUndefined(document.createElement(NAMESPACE).style.transition)) {
  3326. options.transition = false;
  3327. }
  3328.  
  3329. if (options.inline) {
  3330. var count = 0;
  3331.  
  3332. var progress = function progress() {
  3333. count += 1;
  3334.  
  3335. if (count === _this.length) {
  3336. var timeout;
  3337. _this.initializing = false;
  3338. _this.delaying = {
  3339. abort: function abort() {
  3340. clearTimeout(timeout);
  3341. }
  3342. }; // build asynchronously to keep `this.viewer` is accessible in `ready` event handler.
  3343.  
  3344. timeout = setTimeout(function () {
  3345. _this.delaying = false;
  3346.  
  3347. _this.build();
  3348. }, 0);
  3349. }
  3350. };
  3351.  
  3352. this.initializing = {
  3353. abort: function abort() {
  3354. forEach(images, function (image) {
  3355. if (!image.complete) {
  3356. removeListener(image, EVENT_LOAD, progress);
  3357. removeListener(image, EVENT_ERROR, progress);
  3358. }
  3359. });
  3360. }
  3361. };
  3362. forEach(images, function (image) {
  3363. if (image.complete) {
  3364. progress();
  3365. } else {
  3366. var onLoad;
  3367. var onError;
  3368. addListener(image, EVENT_LOAD, onLoad = function onLoad() {
  3369. removeListener(image, EVENT_ERROR, onError);
  3370. progress();
  3371. }, {
  3372. once: true
  3373. });
  3374. addListener(image, EVENT_ERROR, onError = function onError() {
  3375. removeListener(image, EVENT_LOAD, onLoad);
  3376. progress();
  3377. }, {
  3378. once: true
  3379. });
  3380. }
  3381. });
  3382. } else {
  3383. addListener(element, EVENT_CLICK, this.onStart = function (_ref) {
  3384. var target = _ref.target;
  3385.  
  3386. if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) {
  3387. _this.view(_this.images.indexOf(target));
  3388. }
  3389. });
  3390. }
  3391. }
  3392. }, {
  3393. key: "build",
  3394. value: function build() {
  3395. if (this.ready) {
  3396. return;
  3397. }
  3398.  
  3399. var element = this.element,
  3400. options = this.options;
  3401. var parent = element.parentNode;
  3402. var template = document.createElement('div');
  3403. template.innerHTML = TEMPLATE;
  3404. var viewer = template.querySelector(".".concat(NAMESPACE, "-container"));
  3405. var title = viewer.querySelector(".".concat(NAMESPACE, "-title"));
  3406. var toolbar = viewer.querySelector(".".concat(NAMESPACE, "-toolbar"));
  3407. var navbar = viewer.querySelector(".".concat(NAMESPACE, "-navbar"));
  3408. var button = viewer.querySelector(".".concat(NAMESPACE, "-button"));
  3409. var canvas = viewer.querySelector(".".concat(NAMESPACE, "-canvas"));
  3410. this.parent = parent;
  3411. this.viewer = viewer;
  3412. this.title = title;
  3413. this.toolbar = toolbar;
  3414. this.navbar = navbar;
  3415. this.button = button;
  3416. this.canvas = canvas;
  3417. this.footer = viewer.querySelector(".".concat(NAMESPACE, "-footer"));
  3418. this.tooltipBox = viewer.querySelector(".".concat(NAMESPACE, "-tooltip"));
  3419. this.player = viewer.querySelector(".".concat(NAMESPACE, "-player"));
  3420. this.list = viewer.querySelector(".".concat(NAMESPACE, "-list"));
  3421. viewer.id = "".concat(NAMESPACE).concat(this.id);
  3422. title.id = "".concat(NAMESPACE, "Title").concat(this.id);
  3423. addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title));
  3424. addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));
  3425. toggleClass(button, CLASS_HIDE, !options.button);
  3426.  
  3427. if (options.keyboard) {
  3428. button.setAttribute('tabindex', 0);
  3429. }
  3430.  
  3431. if (options.backdrop) {
  3432. addClass(viewer, "".concat(NAMESPACE, "-backdrop"));
  3433.  
  3434. if (!options.inline && options.backdrop !== 'static') {
  3435. setData(canvas, DATA_ACTION, 'hide');
  3436. }
  3437. }
  3438.  
  3439. if (isString(options.className) && options.className) {
  3440. // In case there are multiple class names
  3441. options.className.split(REGEXP_SPACES).forEach(function (className) {
  3442. addClass(viewer, className);
  3443. });
  3444. }
  3445.  
  3446. if (options.toolbar) {
  3447. var list = document.createElement('ul');
  3448. var custom = isPlainObject(options.toolbar);
  3449. var zoomButtons = BUTTONS.slice(0, 3);
  3450. var rotateButtons = BUTTONS.slice(7, 9);
  3451. var scaleButtons = BUTTONS.slice(9);
  3452.  
  3453. if (!custom) {
  3454. addClass(toolbar, getResponsiveClass(options.toolbar));
  3455. }
  3456.  
  3457. forEach(custom ? options.toolbar : BUTTONS, function (value, index) {
  3458. var deep = custom && isPlainObject(value);
  3459. var name = custom ? hyphenate(index) : value;
  3460. var show = deep && !isUndefined(value.show) ? value.show : value;
  3461.  
  3462. if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) {
  3463. return;
  3464. }
  3465.  
  3466. var size = deep && !isUndefined(value.size) ? value.size : value;
  3467. var click = deep && !isUndefined(value.click) ? value.click : value;
  3468. var item = document.createElement('li');
  3469.  
  3470. if (options.keyboard) {
  3471. item.setAttribute('tabindex', 0);
  3472. }
  3473.  
  3474. item.setAttribute('role', 'button');
  3475. addClass(item, "".concat(NAMESPACE, "-").concat(name));
  3476.  
  3477. if (!isFunction(click)) {
  3478. setData(item, DATA_ACTION, name);
  3479. }
  3480.  
  3481. if (isNumber(show)) {
  3482. addClass(item, getResponsiveClass(show));
  3483. }
  3484.  
  3485. if (['small', 'large'].indexOf(size) !== -1) {
  3486. addClass(item, "".concat(NAMESPACE, "-").concat(size));
  3487. } else if (name === 'play') {
  3488. addClass(item, "".concat(NAMESPACE, "-large"));
  3489. }
  3490.  
  3491. if (isFunction(click)) {
  3492. addListener(item, EVENT_CLICK, click);
  3493. }
  3494.  
  3495. list.appendChild(item);
  3496. });
  3497. toolbar.appendChild(list);
  3498. } else {
  3499. addClass(toolbar, CLASS_HIDE);
  3500. }
  3501.  
  3502. if (!options.rotatable) {
  3503. var rotates = toolbar.querySelectorAll('li[class*="rotate"]');
  3504. addClass(rotates, CLASS_INVISIBLE);
  3505. forEach(rotates, function (rotate) {
  3506. toolbar.appendChild(rotate);
  3507. });
  3508. }
  3509.  
  3510. if (options.inline) {
  3511. addClass(button, CLASS_FULLSCREEN);
  3512. setStyle(viewer, {
  3513. zIndex: options.zIndexInline
  3514. });
  3515.  
  3516. if (window.getComputedStyle(parent).position === 'static') {
  3517. setStyle(parent, {
  3518. position: 'relative'
  3519. });
  3520. }
  3521.  
  3522. parent.insertBefore(viewer, element.nextSibling);
  3523. } else {
  3524. addClass(button, CLASS_CLOSE);
  3525. addClass(viewer, CLASS_FIXED);
  3526. addClass(viewer, CLASS_FADE);
  3527. addClass(viewer, CLASS_HIDE);
  3528. setStyle(viewer, {
  3529. zIndex: options.zIndex
  3530. });
  3531. var container = options.container;
  3532.  
  3533. if (isString(container)) {
  3534. container = element.ownerDocument.querySelector(container);
  3535. }
  3536.  
  3537. if (!container) {
  3538. container = this.body;
  3539. }
  3540.  
  3541. container.appendChild(viewer);
  3542. }
  3543.  
  3544. if (options.inline) {
  3545. this.render();
  3546. this.bind();
  3547. this.isShown = true;
  3548. }
  3549.  
  3550. this.ready = true;
  3551.  
  3552. if (isFunction(options.ready)) {
  3553. addListener(element, EVENT_READY, options.ready, {
  3554. once: true
  3555. });
  3556. }
  3557.  
  3558. if (dispatchEvent(element, EVENT_READY) === false) {
  3559. this.ready = false;
  3560. return;
  3561. }
  3562.  
  3563. if (this.ready && options.inline) {
  3564. this.view(this.index);
  3565. }
  3566. }
  3567. /**
  3568. * Get the no conflict viewer class.
  3569. * @returns {Viewer} The viewer class.
  3570. */
  3571.  
  3572. }], [{
  3573. key: "noConflict",
  3574. value: function noConflict() {
  3575. window.Viewer = AnotherViewer;
  3576. return Viewer;
  3577. }
  3578. /**
  3579. * Change the default options.
  3580. * @param {Object} options - The new default options.
  3581. */
  3582.  
  3583. }, {
  3584. key: "setDefaults",
  3585. value: function setDefaults(options) {
  3586. assign(DEFAULTS, isPlainObject(options) && options);
  3587. }
  3588. }]);
  3589.  
  3590. return Viewer;
  3591. }();
  3592.  
  3593. assign(Viewer.prototype, render, events, handlers, methods, others);
  3594.  
  3595. return Viewer;
  3596.  
  3597. }));