UNPKG

viewerjs-optimize

Version:
1,725 lines (1,468 loc) 89.4 kB
/*! * ViewerOptimize.js v1.3.21 * https://github.com/ThinkDuan/viewerjs-optimize * * Copyright 2022 ThinkDuan * Released under the MIT license * * Date: 2022-06-20T12:39:55.833Z */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.ViewerOptimize = factory()); }(this, (function () { 'use strict'; function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } var DEFAULTS = { /** * Enable a modal backdrop, specify `static` for a backdrop * which doesn't close the modal on click. * @type {boolean} */ backdrop: true, /** * Show the button on the top-right of the viewer. * @type {boolean} */ button: true, /** * Show the navbar. * @type {boolean | number} */ navbar: true, /** * Specify the visibility and the content of the title. * @type {boolean | number | Function | Array} */ title: true, /** * Show the toolbar. * @type {boolean | number | Object} */ toolbar: true, /** * Custom class name(s) to add to the viewer's root element. * @type {string} */ className: '', /** * Define where to put the viewer in modal mode. * @type {string | Element} */ container: 'body', /** * Filter the images for viewing. Return true if the image is viewable. * @type {Function} */ filter: null, /** * Enable to request fullscreen when play. * @type {boolean} */ fullscreen: true, /** * Define the initial index of image for viewing. * @type {number} */ initialViewIndex: 0, /** * Enable inline mode. * @type {boolean} */ inline: false, /** * The amount of time to delay between automatically cycling an image when playing. * @type {number} */ interval: 5000, /** * Enable keyboard support. * @type {boolean} */ keyboard: true, /** * Indicate if show a loading spinner when load image or not. * @type {boolean} */ loading: true, /** * Indicate if enable loop viewing or not. * @type {boolean} */ loop: true, /** * Min width of the viewer in inline mode. * @type {number} */ minWidth: 200, /** * Min height of the viewer in inline mode. * @type {number} */ minHeight: 100, /** * Enable to move the image. * @type {boolean} */ movable: true, /** * Enable to zoom the image. * @type {boolean} */ zoomable: true, /** * Enable to rotate the image. * @type {boolean} */ rotatable: true, /** * Enable to scale the image. * @type {boolean} */ scalable: true, /** * Indicate if toggle the image size between its natural size * and initial size when double click on the image or not. * @type {boolean} */ toggleOnDblclick: true, /** * Show the tooltip with image ratio (percentage) when zoom in or zoom out. * @type {boolean} */ tooltip: true, /** * Enable CSS3 Transition for some special elements. * @type {boolean} */ transition: true, /** * Define the CSS `z-index` value of viewer in modal mode. * @type {number} */ zIndex: 2015, /** * Define the CSS `z-index` value of viewer in inline mode. * @type {number} */ zIndexInline: 0, /** * Define the ratio when zoom the image by wheeling mouse. * @type {number} */ zoomRatio: 0.1, /** * Define the min ratio of the image when zoom out. * @type {number} */ minZoomRatio: 0.01, /** * Define the max ratio of the image when zoom in. * @type {number} */ maxZoomRatio: 100, /** * Define where to get the original image URL for viewing. * @type {string | Function} */ url: 'src', /** * Event shortcuts. * @type {Function} */ ready: null, show: null, shown: null, hide: null, hidden: null, view: null, viewed: null, zoom: null, zoomed: null }; var TEMPLATE = '<div class="viewer-container" touch-action="none">' + '<div class="viewer-container-box">' + '<div class="viewer-canvas" id="viewer-canvas"></div>' + '<div class="viewer-footer">' + '<div class="viewer-title"></div>' + '<div class="viewer-toolbar"></div>' + '<div class="viewer-navbar">' + '<ul class="viewer-list"></ul>' + '</div>' + '</div>' + '<div class="viewer-tooltip"></div>' + '<div class="viewer-button-box" title="按住鼠标鼠标左键可拖动">' + '<div role="button" class="viewer-button" data-viewer-action="mix"></div>' + '</div>' + '<div class="viewer-player"></div>' + '</div>' + '</div>'; var IS_BROWSER = typeof window !== 'undefined'; var WINDOW = IS_BROWSER ? window : {}; var IS_TOUCH_DEVICE = IS_BROWSER ? 'ontouchstart' in WINDOW.document.documentElement : false; var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false; var NAMESPACE = 'viewer'; // Actions var ACTION_MOVE = 'move'; var ACTION_SWITCH = 'switch'; var ACTION_ZOOM = 'zoom'; // Classes var CLASS_ACTIVE = "".concat(NAMESPACE, "-active"); var CLASS_CLOSE = "".concat(NAMESPACE, "-close"); var CLASS_FADE = "".concat(NAMESPACE, "-fade"); var CLASS_FIXED = "".concat(NAMESPACE, "-fixed"); var CLASS_FULLSCREEN = "".concat(NAMESPACE, "-fullscreen"); var CLASS_FULLSCREEN_EXIT = "".concat(NAMESPACE, "-fullscreen-exit"); var CLASS_HIDE = "".concat(NAMESPACE, "-hide"); var CLASS_HIDE_MD_DOWN = "".concat(NAMESPACE, "-hide-md-down"); var CLASS_HIDE_SM_DOWN = "".concat(NAMESPACE, "-hide-sm-down"); var CLASS_HIDE_XS_DOWN = "".concat(NAMESPACE, "-hide-xs-down"); var CLASS_IN = "".concat(NAMESPACE, "-in"); var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible"); var CLASS_LOADING = "".concat(NAMESPACE, "-loading"); var CLASS_MOVE = "".concat(NAMESPACE, "-move"); var CLASS_OPEN = "".concat(NAMESPACE, "-open"); var CLASS_SHOW = "".concat(NAMESPACE, "-show"); var CLASS_TRANSITION = "".concat(NAMESPACE, "-transition"); // Events var EVENT_CLICK = 'click'; var EVENT_DBLCLICK = 'dblclick'; var EVENT_DRAG_START = 'dragstart'; var EVENT_HIDDEN = 'hidden'; var EVENT_HIDE = 'hide'; var EVENT_KEY_DOWN = 'keydown'; var EVENT_LOAD = 'load'; var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown'; var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove'; var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup'; var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START; var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE; var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END; var EVENT_READY = 'ready'; var EVENT_RESIZE = 'resize'; var EVENT_SHOW = 'show'; var EVENT_SHOWN = 'shown'; var EVENT_TRANSITION_END = 'transitionend'; var EVENT_VIEW = 'view'; var EVENT_VIEWED = 'viewed'; var EVENT_WHEEL = 'wheel'; var EVENT_ZOOM = 'zoom'; var EVENT_ZOOMED = 'zoomed'; // Data keys var DATA_ACTION = "".concat(NAMESPACE, "Action"); // RegExps var REGEXP_SPACES = /\s\s*/; // Misc var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical']; /** * Check if the given value is a string. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is a string, else `false`. */ function isString(value) { return typeof value === 'string'; } /** * Check if the given value is not a number. */ var isNaN = Number.isNaN || WINDOW.isNaN; /** * Check if the given value is a number. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is a number, else `false`. */ function isNumber(value) { return typeof value === 'number' && !isNaN(value); } /** * Check if the given value is undefined. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is undefined, else `false`. */ function isUndefined(value) { return typeof value === 'undefined'; } /** * Check if the given value is an object. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is an object, else `false`. */ function isObject(value) { return _typeof(value) === 'object' && value !== null; } var hasOwnProperty = Object.prototype.hasOwnProperty; /** * Check if the given value is a plain object. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is a plain object, else `false`. */ function isPlainObject(value) { if (!isObject(value)) { return false; } try { var _constructor = value.constructor; var prototype = _constructor.prototype; return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf'); } catch (error) { return false; } } /** * Check if the given value is a function. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is a function, else `false`. */ function isFunction(value) { return typeof value === 'function'; } /** * Iterate the given data. * @param {*} data - The data to iterate. * @param {Function} callback - The process function for each element. * @returns {*} The original data. */ function forEach(data, callback) { if (data && isFunction(callback)) { if (Array.isArray(data) || isNumber(data.length) /* array-like */ ) { var length = data.length; var i; for (i = 0; i < length; i += 1) { if (callback.call(data, data[i], i, data) === false) { break; } } } else if (isObject(data)) { Object.keys(data).forEach(function (key) { callback.call(data, data[key], key, data); }); } } return data; } /** * Extend the given object. * @param {*} obj - The object to be extended. * @param {*} args - The rest objects which will be merged to the first object. * @returns {Object} The extended object. */ var assign = Object.assign || function assign(obj) { for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } if (isObject(obj) && args.length > 0) { args.forEach(function (arg) { if (isObject(arg)) { Object.keys(arg).forEach(function (key) { obj[key] = arg[key]; }); } }); } return obj; }; var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/; /** * Apply styles to the given element. * @param {Element} element - The target element. * @param {Object} styles - The styles for applying. */ function setStyle(element, styles) { var style = element.style; forEach(styles, function (value, property) { if (REGEXP_SUFFIX.test(property) && isNumber(value)) { value += 'px'; } style[property] = value; }); } /** * Escape a string for using in HTML. * @param {String} value - The string to escape. * @returns {String} Returns the escaped string. */ function escapeHTMLEntities(value) { return isString(value) ? value.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : value; } /** * Check if the given element has a special class. * @param {Element} element - The element to check. * @param {string} value - The class to search. * @returns {boolean} Returns `true` if the special class was found. */ function hasClass(element, value) { return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1; } /** * Add classes to the given element. * @param {Element} element - The target element. * @param {string} value - The classes to be added. */ function addClass(element, value) { if (!value) { return; } if (isNumber(element.length)) { forEach(element, function (elem) { addClass(elem, value); }); return; } if (element.classList) { element.classList.add(value); return; } var className = element.className.trim(); if (!className) { element.className = value; } else if (className.indexOf(value) < 0) { element.className = "".concat(className, " ").concat(value); } } /** * Remove classes from the given element. * @param {Element} element - The target element. * @param {string} value - The classes to be removed. */ function removeClass(element, value) { if (!value) { return; } if (isNumber(element.length)) { forEach(element, function (elem) { removeClass(elem, value); }); return; } if (element.classList) { element.classList.remove(value); return; } if (element.className.indexOf(value) >= 0) { element.className = element.className.replace(value, ''); } } /** * Add or remove classes from the given element. * @param {Element} element - The target element. * @param {string} value - The classes to be toggled. * @param {boolean} added - Add only. */ function toggleClass(element, value, added) { if (!value) { return; } if (isNumber(element.length)) { forEach(element, function (elem) { toggleClass(elem, value, added); }); return; } // IE10-11 doesn't support the second parameter of `classList.toggle` if (added) { addClass(element, value); } else { removeClass(element, value); } } var REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g; /** * Transform the given string from camelCase to kebab-case * @param {string} value - The value to transform. * @returns {string} The transformed value. */ function hyphenate(value) { return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase(); } /** * Get data from the given element. * @param {Element} element - The target element. * @param {string} name - The data key to get. * @returns {string} The data value. */ function getData(element, name) { if (isObject(element[name])) { return element[name]; } if (element.dataset) { return element.dataset[name]; } return element.getAttribute("data-".concat(hyphenate(name))); } /** * Set data to the given element. * @param {Element} element - The target element. * @param {string} name - The data key to set. * @param {string} data - The data value. */ function setData(element, name, data) { if (isObject(data)) { element[name] = data; } else if (element.dataset) { element.dataset[name] = data; } else { element.setAttribute("data-".concat(hyphenate(name)), data); } } var onceSupported = function () { var supported = false; if (IS_BROWSER) { var once = false; var listener = function listener() {}; var options = Object.defineProperty({}, 'once', { get: function get() { supported = true; return once; }, /** * This setter can fix a `TypeError` in strict mode * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only} * @param {boolean} value - The value to set */ set: function set(value) { once = value; } }); WINDOW.addEventListener('test', listener, options); WINDOW.removeEventListener('test', listener, options); } return supported; }(); /** * Remove event listener from the target element. * @param {Element} element - The event target. * @param {string} type - The event type(s). * @param {Function} listener - The event listener. * @param {Object} options - The event options. */ function removeListener(element, type, listener) { var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var handler = listener; type.trim().split(REGEXP_SPACES).forEach(function (event) { if (!onceSupported) { var listeners = element.listeners; if (listeners && listeners[event] && listeners[event][listener]) { handler = listeners[event][listener]; delete listeners[event][listener]; if (Object.keys(listeners[event]).length === 0) { delete listeners[event]; } if (Object.keys(listeners).length === 0) { delete element.listeners; } } } element.removeEventListener(event, handler, options); }); } /** * Add event listener to the target element. * @param {Element} element - The event target. * @param {string} type - The event type(s). * @param {Function} listener - The event listener. * @param {Object} options - The event options. */ function addListener(element, type, listener) { var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var _handler = listener; type.trim().split(REGEXP_SPACES).forEach(function (event) { if (options.once && !onceSupported) { var _element$listeners = element.listeners, listeners = _element$listeners === void 0 ? {} : _element$listeners; _handler = function handler() { delete listeners[event][listener]; element.removeEventListener(event, _handler, options); for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } listener.apply(element, args); }; if (!listeners[event]) { listeners[event] = {}; } if (listeners[event][listener]) { element.removeEventListener(event, listeners[event][listener], options); } listeners[event][listener] = _handler; element.listeners = listeners; } element.addEventListener(event, _handler, options); }); } /** * Dispatch event on the target element. * @param {Element} element - The event target. * @param {string} type - The event type(s). * @param {Object} data - The additional event data. * @returns {boolean} Indicate if the event is default prevented or not. */ function dispatchEvent(element, type, data) { var event; // Event and CustomEvent on IE9-11 are global objects, not constructors if (isFunction(Event) && isFunction(CustomEvent)) { event = new CustomEvent(type, { detail: data, bubbles: true, cancelable: true }); } else { event = document.createEvent('CustomEvent'); event.initCustomEvent(type, true, true, data); } return element.dispatchEvent(event); } /** * Get the offset base on the document. * @param {Element} element - The target element. * @returns {Object} The offset data. */ function getOffset(element) { var box = element.getBoundingClientRect(); return { left: box.left + (window.pageXOffset - document.documentElement.clientLeft), top: box.top + (window.pageYOffset - document.documentElement.clientTop) }; } /** * Get transforms base on the given object. * @param {Object} obj - The target object. * @returns {string} A string contains transform values. */ function getTransforms(_ref) { var rotate = _ref.rotate, scaleX = _ref.scaleX, scaleY = _ref.scaleY, translateX = _ref.translateX, translateY = _ref.translateY; var values = []; if (isNumber(translateX) && translateX !== 0) { values.push("translateX(".concat(translateX, "px)")); } if (isNumber(translateY) && translateY !== 0) { values.push("translateY(".concat(translateY, "px)")); } // Rotate should come first before scale to match orientation transform if (isNumber(rotate) && rotate !== 0) { values.push("rotate(".concat(rotate, "deg)")); } if (isNumber(scaleX) && scaleX !== 1) { values.push("scaleX(".concat(scaleX, ")")); } if (isNumber(scaleY) && scaleY !== 1) { values.push("scaleY(".concat(scaleY, ")")); } var transform = values.length ? values.join(' ') : 'none'; return { WebkitTransform: transform, msTransform: transform, transform: transform }; } /** * Get an image name from an image url. * @param {string} url - The target url. * @example * // picture.jpg * getImageNameFromURL('http://domain.com/path/to/picture.jpg?size=1280×960') * @returns {string} A string contains the image name. */ function getImageNameFromURL(url) { return isString(url) ? url.replace(/^.*\//, '').replace(/[?&#].*$/, '') : ''; } var IS_SAFARI = WINDOW.navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(WINDOW.navigator.userAgent); /** * Get an image's natural sizes. * @param {string} image - The target image. * @param {Function} callback - The callback function. * @returns {HTMLImageElement} The new image. */ function getImageNaturalSizes(image, callback) { var newImage = document.createElement('img'); // Modern browsers (except Safari) if (image.naturalWidth && !IS_SAFARI) { callback(image.naturalWidth, image.naturalHeight); return newImage; } var body = document.body || document.documentElement; newImage.onload = function () { callback(newImage.width, newImage.height); if (!IS_SAFARI) { body.removeChild(newImage); } }; newImage.src = image.src; // iOS Safari will convert the image automatically // with its orientation once append it into DOM if (!IS_SAFARI) { 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;'; body.appendChild(newImage); } return newImage; } /** * Get the related class name of a responsive type number. * @param {string} type - The responsive type. * @returns {string} The related class name. */ function getResponsiveClass(type) { switch (type) { case 2: return CLASS_HIDE_XS_DOWN; case 3: return CLASS_HIDE_SM_DOWN; case 4: return CLASS_HIDE_MD_DOWN; default: return ''; } } /** * Get the max ratio of a group of pointers. * @param {string} pointers - The target pointers. * @returns {number} The result ratio. */ function getMaxZoomRatio(pointers) { var pointers2 = assign({}, pointers); var ratios = []; forEach(pointers, function (pointer, pointerId) { delete pointers2[pointerId]; forEach(pointers2, function (pointer2) { var x1 = Math.abs(pointer.startX - pointer2.startX); var y1 = Math.abs(pointer.startY - pointer2.startY); var x2 = Math.abs(pointer.endX - pointer2.endX); var y2 = Math.abs(pointer.endY - pointer2.endY); var z1 = Math.sqrt(x1 * x1 + y1 * y1); var z2 = Math.sqrt(x2 * x2 + y2 * y2); var ratio = (z2 - z1) / z1; ratios.push(ratio); }); }); ratios.sort(function (a, b) { return Math.abs(a) < Math.abs(b); }); return ratios[0]; } /** * Get a pointer from an event object. * @param {Object} event - The target event object. * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not. * @returns {Object} The result pointer contains start and/or end point coordinates. */ function getPointer(_ref2, endOnly) { var pageX = _ref2.pageX, pageY = _ref2.pageY; var end = { endX: pageX, endY: pageY }; return endOnly ? end : assign({ timeStamp: Date.now(), startX: pageX, startY: pageY }, end); } /** * Get the center point coordinate of a group of pointers. * @param {Object} pointers - The target pointers. * @returns {Object} The center point coordinate. */ function getPointersCenter(pointers) { var pageX = 0; var pageY = 0; var count = 0; forEach(pointers, function (_ref3) { var startX = _ref3.startX, startY = _ref3.startY; pageX += startX; pageY += startY; count += 1; }); pageX /= count; pageY /= count; return { pageX: pageX, pageY: pageY }; } var render = { render: function render() { this.initContainer(); this.initViewer(); this.initList(); this.renderViewer(); }, initContainer: function initContainer() { this.containerData = { width: window.innerWidth, height: window.innerHeight }; }, initViewer: function initViewer() { var options = this.options, parent = this.parent; var viewerData; if (options.inline) { viewerData = { width: Math.max(parent.offsetWidth, options.minWidth), height: Math.max(parent.offsetHeight, options.minHeight) }; this.parentData = viewerData; } var containerBox = document.querySelector('#viewer-canvas'); viewerData = { width: Math.max(containerBox.offsetWidth, options.minWidth), height: Math.max(containerBox.offsetHeight, options.minHeight) }; // eslint-disable-next-line prefer-template var elementClassName = '.' + options.className.split(' ')[1]; var div1 = document.querySelector(elementClassName); div1.onmousedown = function mouseDownFunction(ev) { var oevent = ev || window.event; var distanceX = oevent.clientX - div1.offsetLeft; var distanceY = oevent.clientY - div1.offsetTop; document.onmousemove = function mouseMoveFunction(evs) { var oevents = evs || window.event; div1.style.left = "".concat(oevents.clientX - distanceX, "px"); div1.style.top = "".concat(oevents.clientY - distanceY, "px"); }; document.onmouseup = function mouseUpFunction() { document.onmousemove = null; document.onmouseup = null; }; }; // if (this.fulled || !viewerData) { // viewerData = this.containerData; // } this.viewerData = assign({}, viewerData); }, renderViewer: function renderViewer() { if (this.options.inline && !this.fulled) { setStyle(this.viewer, this.viewerData); } }, initList: function initList() { var _this = this; var element = this.element, options = this.options, list = this.list; var items = []; forEach(this.images, function (image, i) { var src = escapeHTMLEntities(image.src); var alt = escapeHTMLEntities(image.alt || getImageNameFromURL(src)); var url = options.url; if (isString(url)) { url = escapeHTMLEntities(image.getAttribute(url)); } else if (isFunction(url)) { url = escapeHTMLEntities(url.call(_this, image)); } if (src || url) { items.push('<li>' + '<img' + " src=\"".concat(src || url, "\"") + ' role="button"' + ' data-viewer-action="view"' + " data-index=\"".concat(i, "\"") + " data-original-url=\"".concat(url || src, "\"") + " alt=\"".concat(alt, "\"") + '>' + '</li>'); } }); list.innerHTML = items.join(''); this.items = list.getElementsByTagName('li'); forEach(this.items, function (item) { var image = item.firstElementChild; setData(image, 'filled', true); if (options.loading) { addClass(item, CLASS_LOADING); } addListener(image, EVENT_LOAD, function (event) { if (options.loading) { removeClass(item, CLASS_LOADING); } _this.loadImage(event); }, { once: true }); }); if (options.transition) { addListener(element, EVENT_VIEWED, function () { addClass(list, CLASS_TRANSITION); }, { once: true }); } }, renderList: function renderList(index) { var i = index || this.index; var width = this.items[i].offsetWidth || 30; var outerWidth = width + 1; // 1 pixel of `margin-left` width // Place the active item in the center of the screen setStyle(this.list, assign({ width: outerWidth * this.length }, getTransforms({ translateX: 0 // translateX: ((this.viewerData.width - width) / 2) - (outerWidth * i), }))); }, resetList: function resetList() { var list = this.list; list.innerHTML = ''; removeClass(list, CLASS_TRANSITION); setStyle(list, getTransforms({ translateX: 0 })); }, initImage: function initImage(done) { var _this2 = this; var options = this.options, image = this.image, viewerData = this.viewerData; var footerHeight = this.footer.offsetHeight; var viewerWidth = viewerData.width; var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight); var oldImageData = this.imageData || {}; var sizingImage; this.imageInitializing = { abort: function abort() { sizingImage.onload = null; } }; sizingImage = getImageNaturalSizes(image, function (naturalWidth, naturalHeight) { var aspectRatio = naturalWidth / naturalHeight; var width = viewerWidth; var height = viewerHeight; _this2.imageInitializing = false; if (viewerHeight * aspectRatio > viewerWidth) { height = viewerWidth / aspectRatio; } else { width = viewerHeight * aspectRatio; } width = Math.min(width * 0.9, naturalWidth); height = Math.min(height * 0.9, naturalHeight); var imageData = { naturalWidth: naturalWidth, naturalHeight: naturalHeight, aspectRatio: aspectRatio, ratio: width / naturalWidth, width: width, height: height, left: (viewerWidth - width) / 2, top: (viewerHeight - height) / 2 }; var initialImageData = assign({}, imageData); if (options.rotatable) { imageData.rotate = oldImageData.rotate || 0; initialImageData.rotate = 0; } if (options.scalable) { imageData.scaleX = oldImageData.scaleX || 1; imageData.scaleY = oldImageData.scaleY || 1; initialImageData.scaleX = 1; initialImageData.scaleY = 1; } _this2.imageData = imageData; _this2.initialImageData = initialImageData; if (done) { done(); } }); }, renderImage: function renderImage(done) { var _this3 = this; var image = this.image, imageData = this.imageData; setStyle(image, assign({ width: imageData.width, height: imageData.height, // XXX: Not to use translateX/Y to avoid image shaking when zooming marginLeft: imageData.left, marginTop: imageData.top }, getTransforms(imageData))); if (done) { if ((this.viewing || this.zooming) && this.options.transition) { var onTransitionEnd = function onTransitionEnd() { _this3.imageRendering = false; done(); }; this.imageRendering = { abort: function abort() { removeListener(image, EVENT_TRANSITION_END, onTransitionEnd); } }; addListener(image, EVENT_TRANSITION_END, onTransitionEnd, { once: true }); } else { done(); } } }, resetImage: function resetImage() { // this.image only defined after viewed if (this.viewing || this.viewed) { var image = this.image; if (this.viewing) { this.viewing.abort(); } image.parentNode.removeChild(image); this.image = null; } } }; var events = { bind: function bind() { var options = this.options, viewer = this.viewer, canvas = this.canvas; var document = this.element.ownerDocument; addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this)); addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), { passive: false, capture: true }); addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this)); addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this)); addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this)); addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this)); addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this)); addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this)); if (options.toggleOnDblclick) { addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this)); } }, unbind: function unbind() { var options = this.options, viewer = this.viewer, canvas = this.canvas; var document = this.element.ownerDocument; removeListener(viewer, EVENT_CLICK, this.onClick); removeListener(viewer, EVENT_WHEEL, this.onWheel, { passive: false, capture: true }); removeListener(viewer, EVENT_DRAG_START, this.onDragStart); removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown); removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove); removeListener(document, EVENT_POINTER_UP, this.onPointerUp); removeListener(document, EVENT_KEY_DOWN, this.onKeyDown); removeListener(window, EVENT_RESIZE, this.onResize); if (options.toggleOnDblclick) { removeListener(canvas, EVENT_DBLCLICK, this.onDblclick); } } }; var handlers = { click: function click(event) { var target = event.target; var options = this.options, imageData = this.imageData; var action = getData(target, DATA_ACTION); // Cancel the emulated click when the native click event was triggered. if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) { clearTimeout(this.clickCanvasTimeout); } switch (action) { case 'mix': if (this.played) { this.stop(); } else if (options.inline) { if (this.fulled) { this.exit(); } else { this.full(); } } else { this.hide(); } break; case 'hide': this.hide(); break; case 'view': this.view(getData(target, 'index')); break; case 'zoom-in': this.zoom(0.1, true); break; case 'zoom-out': this.zoom(-0.1, true); break; case 'one-to-one': this.toggle(); break; case 'reset': this.reset(); break; case 'prev': this.prev(options.loop); break; case 'play': this.play(options.fullscreen); break; case 'next': this.next(options.loop); break; case 'rotate-left': this.rotate(-90); break; case 'rotate-right': this.rotate(90); break; case 'flip-horizontal': this.scaleX(-imageData.scaleX || -1); break; case 'flip-vertical': this.scaleY(-imageData.scaleY || -1); break; default: if (this.played) { this.stop(); } } }, dblclick: function dblclick(event) { event.preventDefault(); if (this.viewed && event.target === this.image) { // Cancel the emulated double click when the native dblclick event was triggered. if (IS_TOUCH_DEVICE && event.isTrusted) { clearTimeout(this.doubleClickImageTimeout); } this.toggle(); } }, load: function load() { var _this = this; if (this.timeout) { clearTimeout(this.timeout); this.timeout = false; } var element = this.element, options = this.options, image = this.image, index = this.index, viewerData = this.viewerData; removeClass(image, CLASS_INVISIBLE); if (options.loading) { removeClass(this.canvas, CLASS_LOADING); } image.style.cssText = 'height:0;' + "margin-left:".concat(viewerData.width / 2, "px;") + "margin-top:".concat(viewerData.height / 2, "px;") + 'max-width:none!important;' + 'position:absolute;' + 'width:0;'; this.initImage(function () { toggleClass(image, CLASS_MOVE, options.movable); toggleClass(image, CLASS_TRANSITION, options.transition); _this.renderImage(function () { _this.viewed = true; _this.viewing = false; if (isFunction(options.viewed)) { addListener(element, EVENT_VIEWED, options.viewed, { once: true }); } dispatchEvent(element, EVENT_VIEWED, { originalImage: _this.images[index], index: index, image: image }); }); }); }, loadImage: function loadImage(event) { var image = event.target; var parent = image.parentNode; var parentWidth = parent.offsetWidth || 30; var parentHeight = parent.offsetHeight || 50; var filled = !!getData(image, 'filled'); getImageNaturalSizes(image, function (naturalWidth, naturalHeight) { var aspectRatio = naturalWidth / naturalHeight; var width = parentWidth; var height = parentHeight; if (parentHeight * aspectRatio > parentWidth) { if (filled) { width = parentHeight * aspectRatio; } else { height = parentWidth / aspectRatio; } } else if (filled) { height = parentWidth / aspectRatio; } else { width = parentHeight * aspectRatio; } setStyle(image, assign({ width: width, height: height }, getTransforms({ translateX: (parentWidth - width) / 2, translateY: (parentHeight - height) / 2 }))); }); }, keydown: function keydown(event) { var options = this.options; if (!this.fulled || !options.keyboard) { return; } switch (event.keyCode || event.which || event.charCode) { // Escape case 27: if (this.played) { this.stop(); } else if (options.inline) { if (this.fulled) { this.exit(); } } else { this.hide(); } break; // Space case 32: if (this.played) { this.stop(); } break; // ArrowLeft case 37: this.prev(options.loop); break; // ArrowUp case 38: // Prevent scroll on Firefox event.preventDefault(); // Zoom in this.zoom(options.zoomRatio, true); break; // ArrowRight case 39: this.next(options.loop); break; // ArrowDown case 40: // Prevent scroll on Firefox event.preventDefault(); // Zoom out this.zoom(-options.zoomRatio, true); break; // Ctrl + 0 case 48: // Fall through // Ctrl + 1 // eslint-disable-next-line no-fallthrough case 49: if (event.ctrlKey) { event.preventDefault(); this.toggle(); } break; } }, dragstart: function dragstart(event) { if (event.target.tagName.toLowerCase() === 'img') { event.preventDefault(); } }, pointerdown: function pointerdown(event) { var options = this.options, pointers = this.pointers; var buttons = event.buttons, button = event.button; if (!this.viewed || this.showing || this.viewing || this.hiding // No primary button (Usually the left button) // Note that touch events have no `buttons` or `button` property || isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0 // Open context menu || event.ctrlKey) { return; } // Prevent default behaviours as page zooming in touch devices. event.preventDefault(); if (event.changedTouches) { forEach(event.changedTouches, function (touch) { pointers[touch.identifier] = getPointer(touch); }); } else { pointers[event.pointerId || 0] = getPointer(event); } var action = options.movable ? ACTION_MOVE : false; if (Object.keys(pointers).length > 1) { action = ACTION_ZOOM; } else if ((event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) { action = ACTION_SWITCH; } if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) { removeClass(this.image, CLASS_TRANSITION); } this.action = action; }, pointermove: function pointermove(event) { var pointers = this.pointers, action = this.action; if (!this.viewed || !action) { return; } event.preventDefault(); if (event.changedTouches) { forEach(event.changedTouches, function (touch) { assign(pointers[touch.identifier] || {}, getPointer(touch, true)); }); } else { assign(pointers[event.pointerId || 0] || {}, getPointer(event, true)); } this.change(event); }, pointerup: function pointerup(event) { var _this2 = this; var options = this.options, action = this.action, pointers = this.pointers; var pointer; if (event.changedTouches) { forEach(event.changedTouches, function (touch) { pointer = pointers[touch.identifier]; delete pointers[touch.identifier]; }); } else { pointer = pointers[event.pointerId || 0]; delete pointers[event.pointerId || 0]; } if (!action) { return; } event.preventDefault(); if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) { addClass(this.image, CLASS_TRANSITION); } this.action = false; // Emulate click and double click in touch devices to support backdrop and image zooming (#210). if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) { clearTimeout(this.clickCanvasTimeout); clearTimeout(this.doubleClickImageTimeout); if (options.toggleOnDblclick && this.viewed && event.target === this.image) { if (this.imageClicked) { this.imageClicked = false; // This timeout will be cleared later when a native dblclick event is triggering this.doubleClickImageTimeout = setTimeout(function () { dispatchEvent(_this2.image, EVENT_DBLCLICK); }, 50); } else { this.imageClicked = true; // The default timing of a double click in Windows is 500 ms this.doubleClickImageTimeout = setTimeout(function () { _this2.imageClicked = false; }, 500); } } else { this.imageClicked = false; if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) { // This timeout will be cleared later when a native click event is triggering this.clickCanvasTimeout = setTimeout(function () { dispatchEvent(_this2.canvas, EVENT_CLICK); }, 50); } } } }, resize: function resize() { var _this3 = this; if (!this.isShown || this.hiding) { return; } this.initContainer(); this.initViewer(); this.renderViewer(); this.renderList(); if (this.viewed) { this.initImage(function () { _this3.renderImage(); }); } if (this.played) { if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { this.stop(); return; } forEach(this.player.getElementsByTagName('img'), function (image) { addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), { once: true }); dispatchEvent(image, EVENT_LOAD); }); } }, wheel: function wheel(event) { var _this4 = this; if (!this.viewed) { return; } event.preventDefault(); // Limit wheel speed to prevent zoom too fast if (this.wheeling) { return; } this.wheeling = true; setTimeout(function () { _this4.wheeling = false; }, 50); var ratio = Number(this.options.zoomRatio) || 0.1; var delta = 1; if (event.deltaY) { delta = event.deltaY > 0 ? 1 : -1; } else if (event.wheelDelta) { delta = -event.wheelDelta / 120; } else if (event.detail) { delta = event.detail > 0 ? 1 : -1; } this.zoom(-delta * ratio, true, event); } }; var methods = { /** Show the viewer (only available in modal mode) * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not. * @returns {Viewer} this */ show: function show() { var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var element = this.element, options = this.options; if (options.inline || this.showing || this.isShown || this.showing) { return this; } if (!this.ready) { this.build(); if (this.ready) { this.show(immediate); } return this; } if (isFunction(options.show)) { addListener(element, EVENT_SHOW, options.show, { once: true }); } if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) { return this; } if (this.hiding) { this.transitioning.abort(); } this.showing = true; this.open(); var viewer = this.viewer; removeClass(viewer, CLASS_HIDE); if (options.transition && !immediate) { var shown = this.shown.bind(this); this.transitioning = { abort: function abort() { removeListener(viewer, EVENT_TRANSITION_END, shown); removeClass(viewer, CLASS_IN); } }; addClass(viewer, CLASS_TRANSITION); // Force reflow to enable CSS3 transition // eslint-disable-next-line viewer.offsetWidth; addListener(viewer, EVENT_TRANSITION_END, shown, { once: true }); addClass(viewer, CLASS_IN); } else { addClass(viewer, CLASS_IN); this.shown(); } return this; }, /** * Hide the viewer (only available in modal mode) * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not. * @returns {Viewer} this */ hide: function hide() { var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var element = this.element, options = this.options; if (options.inline || this.hiding || !(this.isShown || this.showing)) { return this; } if (isFunction(options.hide)) { addListener(element, EVENT_HIDE, options.hide, { once: true });