You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2758 lines
96 KiB
2758 lines
96 KiB
/*!
|
|
* ____ __ ______ __________ _ _____________ ____________
|
|
* / __ \/ / / / __ \/_ __/ __ \ | / / _/ ____/ | / / ____/ __ \
|
|
* / /_/ / /_/ / / / / / / / / / / | / // // __/ | | /| / / __/ / /_/ /
|
|
* / ____/ __ / /_/ / / / / /_/ /| |/ // // /___ | |/ |/ / /___/ _, _/
|
|
* /_/ /_/ /_/\____/ /_/ \____/ |___/___/_____/ |__/|__/_____/_/ |_|
|
|
*
|
|
* photoviewer - v3.7.1
|
|
* A JS plugin to view images just like in Windows.
|
|
* https://nzbin.github.io/photoviewer/
|
|
*
|
|
* Copyright (c) 2018 nzbin
|
|
* Released under MIT License
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.photoviewer = factory());
|
|
})(this, (function () { 'use strict';
|
|
|
|
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, _toPropertyKey(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;
|
|
}
|
|
function _toPrimitive(input, hint) {
|
|
if (typeof input !== "object" || input === null) return input;
|
|
var prim = input[Symbol.toPrimitive];
|
|
if (prim !== undefined) {
|
|
var res = prim.call(input, hint || "default");
|
|
if (typeof res !== "object") return res;
|
|
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
}
|
|
return (hint === "string" ? String : Number)(input);
|
|
}
|
|
function _toPropertyKey(arg) {
|
|
var key = _toPrimitive(arg, "string");
|
|
return typeof key === "symbol" ? key : String(key);
|
|
}
|
|
|
|
// Class D
|
|
var D = function (selector, context) {
|
|
return new D.fn.init(selector, context);
|
|
};
|
|
|
|
var document$1 = window.document,
|
|
emptyArray = [],
|
|
concat = emptyArray.concat,
|
|
filter = emptyArray.filter,
|
|
slice = emptyArray.slice,
|
|
elementDisplay = {},
|
|
classCache = {},
|
|
cssNumber = {
|
|
'column-count': 1,
|
|
'columns': 1,
|
|
'font-weight': 1,
|
|
'line-height': 1,
|
|
'opacity': 1,
|
|
'z-index': 1,
|
|
'zoom': 1
|
|
},
|
|
fragmentRE = /^\s*<(\w+|!)[^>]*>/,
|
|
singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
|
|
tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
|
|
rootNodeRE = /^(?:body|html)$/i,
|
|
// special attributes that should be get/set via method calls
|
|
methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
|
|
|
|
table = document$1.createElement('table'),
|
|
tableRow = document$1.createElement('tr'),
|
|
containers = {
|
|
'tr': document$1.createElement('tbody'),
|
|
'tbody': table,
|
|
'thead': table,
|
|
'tfoot': table,
|
|
'td': tableRow,
|
|
'th': tableRow,
|
|
'*': document$1.createElement('div')
|
|
},
|
|
simpleSelectorRE = /^[\w-]*$/,
|
|
class2type = {},
|
|
toString = class2type.toString,
|
|
tempParent = document$1.createElement('div'),
|
|
isArray = Array.isArray || function (arg) {
|
|
return Object.prototype.toString.call(arg) === '[object Array]';
|
|
},
|
|
contains = document$1.documentElement.contains
|
|
? function (parent, node) {
|
|
return parent !== node && parent.contains(node);
|
|
}
|
|
: function (parent, node) {
|
|
while (node && (node = node.parentNode))
|
|
if (node === parent) return true;
|
|
return false;
|
|
};
|
|
|
|
function type(obj) {
|
|
return obj == null
|
|
? String(obj)
|
|
: class2type[toString.call(obj)] || 'object';
|
|
}
|
|
|
|
function isFunction(value) {
|
|
return type(value) == 'function';
|
|
}
|
|
|
|
function isWindow(obj) {
|
|
return obj != null && obj == obj.window;
|
|
}
|
|
|
|
function isDocument(obj) {
|
|
return obj != null && obj.nodeType == obj.DOCUMENT_NODE;
|
|
}
|
|
|
|
function isObject(obj) {
|
|
return type(obj) == 'object';
|
|
}
|
|
|
|
function isPlainObject(obj) {
|
|
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype;
|
|
}
|
|
|
|
function likeArray(obj) {
|
|
var length = !!obj && 'length' in obj && obj.length,
|
|
typeRes = type(obj);
|
|
|
|
return 'function' != typeRes && !isWindow(obj) && (
|
|
'array' == typeRes || length === 0 ||
|
|
(typeof length == 'number' && length > 0 && (length - 1) in obj)
|
|
);
|
|
}
|
|
|
|
function compact(array) {
|
|
return filter.call(array, function (item) {
|
|
return item != null;
|
|
});
|
|
}
|
|
|
|
function dasherize$1(str) {
|
|
return str.replace(/::/g, '/')
|
|
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
|
|
.replace(/_/g, '-')
|
|
.toLowerCase();
|
|
}
|
|
|
|
function maybeAddPx(name, value) {
|
|
return (typeof value == 'number' && !cssNumber[dasherize$1(name)]) ? value + 'px' : value;
|
|
}
|
|
|
|
function camelize(str) {
|
|
return str.replace(/-+(.)?/g, function (match, chr) {
|
|
return chr ? chr.toUpperCase() : '';
|
|
});
|
|
}
|
|
|
|
function classRE(name) {
|
|
return name in classCache ?
|
|
classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'));
|
|
}
|
|
|
|
function defaultDisplay(nodeName) {
|
|
var element, display;
|
|
if (!elementDisplay[nodeName]) {
|
|
element = document$1.createElement(nodeName);
|
|
document$1.body.appendChild(element);
|
|
display = getComputedStyle(element, '').getPropertyValue('display');
|
|
element.parentNode.removeChild(element);
|
|
display == 'none' && (display = 'block');
|
|
elementDisplay[nodeName] = display;
|
|
}
|
|
return elementDisplay[nodeName];
|
|
}
|
|
|
|
function flatten(array) {
|
|
return array.length > 0 ? D.fn.concat.apply([], array) : array;
|
|
}
|
|
|
|
function isD(object) {
|
|
return object instanceof D;
|
|
}
|
|
|
|
function funcArg(context, arg, idx, payload) {
|
|
return isFunction(arg) ? arg.call(context, idx, payload) : arg;
|
|
}
|
|
|
|
function setAttribute(node, name, value) {
|
|
value == null ? node.removeAttribute(name) : node.setAttribute(name, value);
|
|
}
|
|
|
|
// access className property while respecting SVGAnimatedString
|
|
function className(node, value) {
|
|
var klass = node.className || '',
|
|
svg = klass && klass.baseVal !== undefined;
|
|
|
|
if (value === undefined) return svg ? klass.baseVal : klass;
|
|
svg ? (klass.baseVal = value) : (node.className = value);
|
|
}
|
|
|
|
D.fn = D.prototype = {
|
|
constuctor: D,
|
|
length: 0,
|
|
// Because a collection acts like an array
|
|
// copy over these useful array functions.
|
|
forEach: emptyArray.forEach,
|
|
reduce: emptyArray.reduce,
|
|
push: emptyArray.push,
|
|
sort: emptyArray.sort,
|
|
splice: emptyArray.splice,
|
|
indexOf: emptyArray.indexOf,
|
|
// D's counterpart to jQuery's `$.fn.init` and
|
|
// takes a CSS selector and an optional context (and handles various
|
|
// special cases).
|
|
init: function (selector, context) {
|
|
var dom;
|
|
// If nothing given, return an empty D collection
|
|
if (!selector) {
|
|
return this;
|
|
}
|
|
// Optimize for string selectors
|
|
else if (typeof selector == 'string') {
|
|
selector = selector.trim();
|
|
// If it's a html fragment, create nodes from it
|
|
// Note: In both Chrome 21 and Firefox 15, DOM error 12
|
|
// is thrown if the fragment doesn't begin with <
|
|
if (selector[0] == '<' && fragmentRE.test(selector)) {
|
|
dom = D.fragment(selector, RegExp.$1, context);
|
|
selector = null;
|
|
}
|
|
// If there's a context, create a collection on that context first, and select
|
|
// nodes from there
|
|
else if (context !== undefined) {
|
|
return D(context).find(selector);
|
|
}
|
|
// If it's a CSS selector, use it to select nodes.
|
|
else {
|
|
dom = D.qsa(document$1, selector);
|
|
}
|
|
}
|
|
// If a function is given, call it when the DOM is ready
|
|
else if (isFunction(selector)) {
|
|
return D(document$1).ready(selector);
|
|
}
|
|
// If a D collection is given, just return it
|
|
else if (isD(selector)) {
|
|
return selector;
|
|
}
|
|
// normalize array if an array of nodes is given
|
|
else if (isArray(selector)) {
|
|
dom = compact(selector);
|
|
}
|
|
// Wrap DOM nodes.
|
|
else if (isObject(selector)) {
|
|
dom = [selector], selector = null;
|
|
}
|
|
// If there's a context, create a collection on that context first, and select
|
|
// nodes from there
|
|
else if (context !== undefined) {
|
|
return D(context).find(selector);
|
|
}
|
|
// And last but no least, if it's a CSS selector, use it to select nodes.
|
|
else {
|
|
dom = D.qsa(document$1, selector);
|
|
}
|
|
// create a new D collection from the nodes found
|
|
return D.makeArray(dom, selector, this);
|
|
},
|
|
// Modify the collection by adding elements to it
|
|
concat: function () {
|
|
var i, value, args = [];
|
|
for (i = 0; i < arguments.length; i++) {
|
|
value = arguments[i];
|
|
args[i] = isD(value) ? value.toArray() : value;
|
|
}
|
|
return concat.apply(isD(this) ? this.toArray() : this, args);
|
|
},
|
|
// `pluck` is borrowed from Prototype.js
|
|
pluck: function (property) {
|
|
return D.map(this, function (el) { return el[property]; });
|
|
},
|
|
toArray: function () {
|
|
return this.get();
|
|
},
|
|
get: function (idx) {
|
|
return idx === undefined
|
|
? slice.call(this)
|
|
: this[idx >= 0 ? idx : idx + this.length];
|
|
},
|
|
size: function () {
|
|
return this.length;
|
|
},
|
|
each: function (callback) {
|
|
emptyArray.every.call(this, function (el, idx) {
|
|
return callback.call(el, idx, el) !== false;
|
|
});
|
|
return this;
|
|
},
|
|
map: function (fn) {
|
|
return D(D.map(this, function (el, i) { return fn.call(el, i, el); }));
|
|
},
|
|
slice: function () {
|
|
return D(slice.apply(this, arguments));
|
|
},
|
|
first: function () {
|
|
var el = this[0];
|
|
return el && !isObject(el) ? el : D(el);
|
|
},
|
|
last: function () {
|
|
var el = this[this.length - 1];
|
|
return el && !isObject(el) ? el : D(el);
|
|
},
|
|
eq: function (idx) {
|
|
return idx === -1 ? this.slice(idx) : this.slice(idx, +idx + 1);
|
|
}
|
|
};
|
|
|
|
D.extend = D.fn.extend = function () {
|
|
var options, name, src, copy, copyIsArray, clone,
|
|
target = arguments[0] || {},
|
|
i = 1,
|
|
length = arguments.length,
|
|
deep = false;
|
|
|
|
// Handle a deep copy situation
|
|
if (typeof target === 'boolean') {
|
|
deep = target;
|
|
|
|
// Skip the boolean and the target
|
|
target = arguments[i] || {};
|
|
i++;
|
|
}
|
|
// Handle case when target is a string or something (possible in deep copy)
|
|
if (typeof target !== 'object' && !isFunction(target)) {
|
|
target = {};
|
|
}
|
|
// Extend D itself if only one argument is passed
|
|
if (i === length) {
|
|
target = this;
|
|
i--;
|
|
}
|
|
for (; i < length; i++) {
|
|
// Only deal with non-null/undefined values
|
|
if ((options = arguments[i]) != null) {
|
|
// Extend the base object
|
|
for (name in options) {
|
|
src = target[name];
|
|
copy = options[name];
|
|
// Prevent never-ending loop
|
|
if (target === copy) {
|
|
continue;
|
|
}
|
|
// Recurse if we're merging plain objects or arrays
|
|
if (deep && copy && (isPlainObject(copy) ||
|
|
(copyIsArray = isArray(copy)))) {
|
|
if (copyIsArray) {
|
|
copyIsArray = false;
|
|
clone = src && isArray(src) ? src : [];
|
|
} else {
|
|
clone = src && isPlainObject(src) ? src : {};
|
|
}
|
|
// Never move original objects, clone them
|
|
target[name] = D.extend(deep, clone, copy);
|
|
// Don't bring in undefined values
|
|
} else if (copy !== undefined) {
|
|
target[name] = copy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Return the modified object
|
|
return target;
|
|
};
|
|
|
|
D.extend({
|
|
// Make DOM Array
|
|
makeArray: function (dom, selector, me) {
|
|
var i, len = dom ? dom.length : 0;
|
|
for (i = 0; i < len; i++) me[i] = dom[i];
|
|
me.length = len;
|
|
me.selector = selector || '';
|
|
return me;
|
|
},
|
|
// D's CSS selector
|
|
qsa: function (element, selector) {
|
|
var found,
|
|
maybeID = selector[0] == '#',
|
|
maybeClass = !maybeID && selector[0] == '.',
|
|
// Ensure that a 1 char tag name still gets checked
|
|
nameOnly = maybeID || maybeClass ? selector.slice(1) : selector,
|
|
isSimple = simpleSelectorRE.test(nameOnly);
|
|
return (
|
|
// Safari DocumentFragment doesn't have getElementById
|
|
element.getElementById && isSimple && maybeID)
|
|
// eslint-disable-next-line no-cond-assign
|
|
? (found = element.getElementById(nameOnly))
|
|
? [found]
|
|
: []
|
|
: element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11
|
|
? []
|
|
: slice.call(
|
|
// DocumentFragment doesn't have getElementsByClassName/TagName
|
|
isSimple && !maybeID && element.getElementsByClassName
|
|
? maybeClass
|
|
// If it's simple, it could be a class
|
|
? element.getElementsByClassName(nameOnly)
|
|
// Or a tag
|
|
: element.getElementsByTagName(selector)
|
|
// Or it's not simple, and we need to query all
|
|
: element.querySelectorAll(selector)
|
|
);
|
|
},
|
|
// Html -> Node
|
|
fragment: function (html, name, properties) {
|
|
var dom, nodes, container;
|
|
|
|
// A special case optimization for a single tag
|
|
if (singleTagRE.test(html)) dom = D(document$1.createElement(RegExp.$1));
|
|
|
|
if (!dom) {
|
|
if (html.replace) html = html.replace(tagExpanderRE, '<$1></$2>');
|
|
if (name === undefined) name = fragmentRE.test(html) && RegExp.$1;
|
|
if (!(name in containers)) name = '*';
|
|
|
|
container = containers[name];
|
|
container.innerHTML = '' + html;
|
|
dom = D.each(slice.call(container.childNodes), function () {
|
|
container.removeChild(this);
|
|
});
|
|
}
|
|
|
|
if (isPlainObject(properties)) {
|
|
nodes = D(dom);
|
|
D.each(properties, function (key, value) {
|
|
if (methodAttributes.indexOf(key) > -1) nodes[key](value);
|
|
else nodes.attr(key, value);
|
|
});
|
|
}
|
|
|
|
return dom;
|
|
},
|
|
matches: function (element, selector) {
|
|
if (!selector || !element || element.nodeType !== 1) return false;
|
|
var matchesSelector = element.matches || element.webkitMatchesSelector ||
|
|
element.mozMatchesSelector || element.oMatchesSelector ||
|
|
element.matchesSelector;
|
|
if (matchesSelector) return matchesSelector.call(element, selector);
|
|
// fall back to performing a selector:
|
|
var match, parent = element.parentNode,
|
|
temp = !parent;
|
|
if (temp) (parent = tempParent).appendChild(element);
|
|
match = ~D.qsa(parent, selector).indexOf(element);
|
|
temp && tempParent.removeChild(element);
|
|
return match;
|
|
},
|
|
each: function (elements, callback) {
|
|
var i, key;
|
|
if (likeArray(elements)) {
|
|
for (i = 0; i < elements.length; i++)
|
|
if (callback.call(elements[i], i, elements[i]) === false) return elements;
|
|
} else {
|
|
for (key in elements)
|
|
if (callback.call(elements[key], key, elements[key]) === false) return elements;
|
|
}
|
|
|
|
return elements;
|
|
},
|
|
map: function (elements, callback) {
|
|
var value, values = [],
|
|
i, key;
|
|
if (likeArray(elements))
|
|
for (i = 0; i < elements.length; i++) {
|
|
value = callback(elements[i], i);
|
|
if (value != null) values.push(value);
|
|
}
|
|
else
|
|
for (key in elements) {
|
|
value = callback(elements[key], key);
|
|
if (value != null) values.push(value);
|
|
}
|
|
return flatten(values);
|
|
}
|
|
});
|
|
|
|
// Populate the class2type map
|
|
D.each('Boolean Number String Function Array Date RegExp Object Error'.split(' '), function (i, name) {
|
|
class2type['[object ' + name + ']'] = name.toLowerCase();
|
|
});
|
|
|
|
D.fn.init.prototype = D.fn;
|
|
|
|
function noop() {
|
|
}
|
|
|
|
function css(property, value) {
|
|
if (arguments.length < 2) {
|
|
var element = this[0];
|
|
if (typeof property == 'string') {
|
|
if (!element) return;
|
|
return element.style[camelize(property)] || getComputedStyle(element, '').getPropertyValue(property);
|
|
} else if (isArray(property)) {
|
|
if (!element) return;
|
|
var props = {};
|
|
var computedStyle = getComputedStyle(element, '');
|
|
D.each(property, function (_, prop) {
|
|
props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop));
|
|
});
|
|
return props;
|
|
}
|
|
}
|
|
|
|
var css = '';
|
|
if (type(property) == 'string') {
|
|
if (!value && value !== 0) {
|
|
this.each(function () {
|
|
this.style.removeProperty(dasherize$1(property));
|
|
});
|
|
} else {
|
|
css = dasherize$1(property) + ':' + maybeAddPx(property, value);
|
|
}
|
|
} else {
|
|
for (var key in property) {
|
|
if (!property[key] && property[key] !== 0) {
|
|
this.each(function () { this.style.removeProperty(dasherize$1(key)); });
|
|
} else {
|
|
css += dasherize$1(key) + ':' + maybeAddPx(key, property[key]) + ';';
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.each(function () { this.style.cssText += ';' + css; });
|
|
}
|
|
|
|
function hasClass(name) {
|
|
if (!name) return false;
|
|
return emptyArray.some.call(this, function (el) {
|
|
return this.test(className(el));
|
|
}, classRE(name));
|
|
}
|
|
|
|
function addClass(name) {
|
|
var classList = [];
|
|
if (!name) return this;
|
|
return this.each(function (idx) {
|
|
if (!('className' in this)) return;
|
|
classList = [];
|
|
var cls = className(this),
|
|
newName = funcArg(this, name, idx, cls);
|
|
newName.split(/\s+/g).forEach(function (klass) {
|
|
if (!D(this).hasClass(klass)) classList.push(klass);
|
|
}, this);
|
|
classList.length && className(this, cls + (cls ? ' ' : '') + classList.join(' '));
|
|
});
|
|
}
|
|
|
|
function removeClass(name) {
|
|
var classList = [];
|
|
return this.each(function (idx) {
|
|
if (!('className' in this)) return;
|
|
if (name === undefined) return className(this, '');
|
|
classList = className(this);
|
|
funcArg(this, name, idx, classList).split(/\s+/g).forEach(function (klass) {
|
|
classList = classList.replace(classRE(klass), ' ');
|
|
});
|
|
className(this, classList.trim());
|
|
});
|
|
}
|
|
|
|
function offset(coordinates) {
|
|
if (coordinates) return this.each(function (index) {
|
|
var $this = D(this),
|
|
coords = funcArg(this, coordinates, index, $this.offset()),
|
|
parentOffset = $this.offsetParent().offset(),
|
|
props = {
|
|
top: coords.top - parentOffset.top,
|
|
left: coords.left - parentOffset.left
|
|
};
|
|
|
|
if ($this.css('position') == 'static') props['position'] = 'relative';
|
|
$this.css(props);
|
|
});
|
|
if (!this.length) return null;
|
|
if (document$1.documentElement !== this[0] && !contains(document$1.documentElement, this[0]))
|
|
return { top: 0, left: 0 };
|
|
var obj = this[0].getBoundingClientRect();
|
|
return {
|
|
left: obj.left + window.pageXOffset,
|
|
top: obj.top + window.pageYOffset,
|
|
width: Math.round(obj.width),
|
|
height: Math.round(obj.height)
|
|
};
|
|
}
|
|
|
|
function position() {
|
|
if (!this.length) return;
|
|
|
|
var elem = this[0], offset,
|
|
// Get *real* offset parent
|
|
offsetParent = this.offsetParent(),
|
|
parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
|
|
|
|
// `position: fixed` elements are offset from the viewport, which itself always has zero offset
|
|
if (D(elem).css('position') === 'fixed') {
|
|
// Assume `position: fixed` implies availability of getBoundingClientRect
|
|
offset = elem.getBoundingClientRect();
|
|
} else {
|
|
offset = this.offset();
|
|
|
|
// Incorporate borders into its offset, since they are outside its content origin
|
|
parentOffset.top += parseFloat(D(offsetParent[0]).css('border-top-width')) || 0;
|
|
parentOffset.left += parseFloat(D(offsetParent[0]).css('border-left-width')) || 0;
|
|
}
|
|
|
|
// Subtract parent offsets and element margins
|
|
// note: when an element has `margin: auto` the offsetLeft and marginLeft
|
|
// are the same in Safari causing `offset.left` to incorrectly be 0
|
|
return {
|
|
top: offset.top - parentOffset.top - parseFloat(D(elem).css('margin-top')) || 0,
|
|
left: offset.left - parentOffset.left - parseFloat(D(elem).css('margin-left')) || 0
|
|
};
|
|
}
|
|
|
|
function scrollTop(value) {
|
|
if (!this.length) return;
|
|
var hasScrollTop = 'scrollTop' in this[0];
|
|
if (value === undefined) return hasScrollTop
|
|
? this[0].scrollTop
|
|
: isWindow(this[0])
|
|
? this[0].pageYOffset
|
|
: this[0].defaultView.pageYOffset;
|
|
return this.each(hasScrollTop ?
|
|
function () { this.scrollTop = value; } :
|
|
function () { this.scrollTo(this.scrollX, value); });
|
|
}
|
|
|
|
function scrollLeft(value) {
|
|
if (!this.length) return;
|
|
var hasScrollLeft = 'scrollLeft' in this[0];
|
|
if (value === undefined) return hasScrollLeft
|
|
? this[0].scrollLeft
|
|
: isWindow(this[0])
|
|
? this[0].pageXOffset
|
|
: this[0].defaultView.pageXOffset;
|
|
return this.each(hasScrollLeft ?
|
|
function () { this.scrollLeft = value; } :
|
|
function () { this.scrollTo(value, this.scrollY); });
|
|
}
|
|
|
|
function offsetParent() {
|
|
return this.map(function () {
|
|
var parent = this.offsetParent || document$1.body;
|
|
while (parent && !rootNodeRE.test(parent.nodeName) && D(parent).css('position') == 'static')
|
|
parent = parent.offsetParent;
|
|
return parent;
|
|
});
|
|
}
|
|
|
|
function attr(name, value) {
|
|
var result;
|
|
return (typeof name == 'string' && !(1 in arguments))
|
|
? (0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) != null
|
|
? result
|
|
: undefined)
|
|
: this.each(function (idx) {
|
|
if (this.nodeType !== 1) return;
|
|
if (isObject(name))
|
|
for (var key in name) setAttribute(this, key, name[key]);
|
|
else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)));
|
|
});
|
|
}
|
|
|
|
function removeAttr(name) {
|
|
return this.each(function () {
|
|
this.nodeType === 1 && name.split(' ').forEach(function (attribute) {
|
|
setAttribute(this, attribute);
|
|
}, this);
|
|
});
|
|
}
|
|
|
|
function find(selector) {
|
|
var result, $this = this;
|
|
if (!selector) result = D();
|
|
else if (typeof selector == 'object')
|
|
result = D(selector).filter(function () {
|
|
var node = this;
|
|
return emptyArray.some.call($this, function (parent) {
|
|
return contains(parent, node);
|
|
});
|
|
});
|
|
else if (this.length == 1) result = D(D.qsa(this[0], selector));
|
|
else result = this.map(function () { return D.qsa(this, selector); });
|
|
return result;
|
|
}
|
|
|
|
function closest(selector, context) {
|
|
var nodes = [],
|
|
collection = typeof selector == 'object' && D(selector);
|
|
this.each(function (_, node) {
|
|
while (node && !(collection ? collection.indexOf(node) >= 0 : D.matches(node, selector)))
|
|
node = node !== context && !isDocument(node) && node.parentNode;
|
|
if (node && nodes.indexOf(node) < 0) nodes.push(node);
|
|
});
|
|
return D(nodes);
|
|
}
|
|
|
|
function isIE() {
|
|
var ua = window.navigator.userAgent;
|
|
var msie = ua.indexOf('MSIE ');
|
|
return msie > 0 || !!navigator.userAgent.match(/Trident.*rv:11\./);
|
|
}
|
|
|
|
function subtract(el, dimen) {
|
|
var offsetMap = {
|
|
width: ['padding-left', 'padding-right', 'border-left-width', 'border-right-width'],
|
|
height: ['padding-top', 'padding-bottom', 'border-top-width', 'border-bottom-width']
|
|
};
|
|
return el.css('box-sizing') === 'border-box' && !isIE()
|
|
? parseFloat(el.css(dimen))
|
|
- parseFloat(el.css(offsetMap[dimen][0]))
|
|
- parseFloat(el.css(offsetMap[dimen][1]))
|
|
- parseFloat(el.css(offsetMap[dimen][2]))
|
|
- parseFloat(el.css(offsetMap[dimen][3]))
|
|
: parseFloat(el.css(dimen));
|
|
}
|
|
|
|
function calc(dimension, value) {
|
|
var dimensionProperty = dimension.replace(/./, function (m) { return m[0].toUpperCase(); });
|
|
var el = this[0];
|
|
if (value === undefined) return isWindow(el)
|
|
? el.document.documentElement['client' + dimensionProperty]
|
|
: isDocument(el)
|
|
? el.documentElement['scroll' + dimensionProperty]
|
|
: subtract(this, dimension);
|
|
else return this.each(function (idx) {
|
|
el = D(this);
|
|
el.css(dimension, funcArg(this, value, idx, el[dimension]()));
|
|
});
|
|
}
|
|
|
|
// Export
|
|
|
|
function width(value) {
|
|
return calc.call(this, 'width', value);
|
|
}
|
|
|
|
function height(value) {
|
|
return calc.call(this, 'height', value);
|
|
}
|
|
|
|
var traverseNode = function (node, fn) {
|
|
fn(node);
|
|
for (var i = 0, len = node.childNodes.length; i < len; i++)
|
|
traverseNode(node.childNodes[i], fn);
|
|
};
|
|
|
|
// inside => append, prepend
|
|
var domMani = function (elem, args, fn, inside) {
|
|
// arguments can be nodes, arrays of nodes, D objects and HTML strings
|
|
var argType,
|
|
nodes = D.map(args, function (arg) {
|
|
var arr = [];
|
|
argType = type(arg);
|
|
if (argType == 'array') {
|
|
arg.forEach(function (el) {
|
|
if (el.nodeType !== undefined) return arr.push(el);
|
|
else if (isD(el)) return arr = arr.concat(el.get());
|
|
arr = arr.concat(D.fragment(el));
|
|
});
|
|
return arr;
|
|
}
|
|
return argType == 'object' || arg == null ? arg : D.fragment(arg);
|
|
}),
|
|
parent,
|
|
copyByClone = elem.length > 1;
|
|
|
|
if (nodes.length < 1) return elem;
|
|
|
|
return elem.each(function (_, target) {
|
|
parent = inside ? target : target.parentNode;
|
|
var parentInDocument = contains(document$1.documentElement, parent);
|
|
|
|
nodes.forEach(function (node) {
|
|
if (copyByClone) node = node.cloneNode(true);
|
|
else if (!parent) return D(node).remove();
|
|
|
|
fn.call(target, node);
|
|
|
|
if (parentInDocument) {
|
|
traverseNode(node, function (el) {
|
|
if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
|
|
(!el.type || el.type === 'text/javascript') && !el.src) {
|
|
var target = el.ownerDocument ? el.ownerDocument.defaultView : window;
|
|
target['eval'].call(target, el.innerHTML);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
// Export
|
|
|
|
function remove$1() {
|
|
return this.each(function () {
|
|
if (this.parentNode != null)
|
|
this.parentNode.removeChild(this);
|
|
});
|
|
}
|
|
|
|
function empty() {
|
|
return this.each(function () { this.innerHTML = ''; });
|
|
}
|
|
|
|
function html(html) {
|
|
return 0 in arguments
|
|
? this.each(function (idx) {
|
|
var originHtml = this.innerHTML;
|
|
D(this).empty().append(funcArg(this, html, idx, originHtml));
|
|
})
|
|
: (0 in this ? this[0].innerHTML : null);
|
|
}
|
|
|
|
function append() {
|
|
return domMani(this, arguments, function (elem) {
|
|
this.insertBefore(elem, null);
|
|
}, true);
|
|
}
|
|
|
|
var _zid = 1;
|
|
function zid(element) {
|
|
return element._zid || (element._zid = _zid++);
|
|
}
|
|
|
|
function isString(obj) {
|
|
return typeof obj == 'string';
|
|
}
|
|
|
|
var returnTrue = function () { return true; },
|
|
returnFalse = function () { return false; },
|
|
eventMethods = {
|
|
preventDefault: 'isDefaultPrevented',
|
|
stopImmediatePropagation: 'isImmediatePropagationStopped',
|
|
stopPropagation: 'isPropagationStopped'
|
|
};
|
|
|
|
function compatible(event, source) {
|
|
if (source || !event.isDefaultPrevented) {
|
|
source || (source = event);
|
|
|
|
D.each(eventMethods, function (name, predicate) {
|
|
var sourceMethod = source[name];
|
|
event[name] = function () {
|
|
this[predicate] = returnTrue;
|
|
return sourceMethod && sourceMethod.apply(source, arguments);
|
|
};
|
|
event[predicate] = returnFalse;
|
|
});
|
|
|
|
try {
|
|
event.timeStamp || (event.timeStamp = Date.now());
|
|
} catch (ignored) {
|
|
console.warn(ignored);
|
|
}
|
|
|
|
if (source.defaultPrevented !== undefined ? source.defaultPrevented :
|
|
'returnValue' in source ? source.returnValue === false :
|
|
source.getPreventDefault && source.getPreventDefault())
|
|
event.isDefaultPrevented = returnTrue;
|
|
}
|
|
return event;
|
|
}
|
|
|
|
var handlers = {},
|
|
focusinSupported = 'onfocusin' in window,
|
|
focus = { focus: 'focusin', blur: 'focusout' },
|
|
hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' },
|
|
ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/;
|
|
|
|
function parse(event) {
|
|
var parts = ('' + event).split('.');
|
|
return { e: parts[0], ns: parts.slice(1).sort().join(' ') };
|
|
}
|
|
function matcherFor(ns) {
|
|
return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)');
|
|
}
|
|
|
|
function findHandlers(element, event, fn, selector) {
|
|
event = parse(event);
|
|
if (event.ns) var matcher = matcherFor(event.ns);
|
|
return (handlers[zid(element)] || []).filter(function (handler) {
|
|
return handler
|
|
&& (!event.e || handler.e == event.e)
|
|
&& (!event.ns || matcher.test(handler.ns))
|
|
&& (!fn || zid(handler.fn) === zid(fn))
|
|
&& (!selector || handler.sel == selector);
|
|
});
|
|
}
|
|
|
|
function eventCapture(handler, captureSetting) {
|
|
return handler.del &&
|
|
(!focusinSupported && (handler.e in focus)) ||
|
|
!!captureSetting;
|
|
}
|
|
|
|
function realEvent(type) {
|
|
return hover[type] || (focusinSupported && focus[type]) || type;
|
|
}
|
|
|
|
function add(element, events, fn, data, selector, delegator, capture) {
|
|
var id = zid(element), set = (handlers[id] || (handlers[id] = []));
|
|
events.split(/\s/).forEach(function (event) {
|
|
if (event == 'ready') return D(document$1).ready(fn);
|
|
var handler = parse(event);
|
|
handler.fn = fn;
|
|
handler.sel = selector;
|
|
// emulate mouseenter, mouseleave
|
|
if (handler.e in hover) fn = function (e) {
|
|
var related = e.relatedTarget;
|
|
if (!related || (related !== this && !contains(this, related)))
|
|
return handler.fn.apply(this, arguments);
|
|
};
|
|
handler.del = delegator;
|
|
var callback = delegator || fn;
|
|
handler.proxy = function (e) {
|
|
e = compatible(e);
|
|
if (e.isImmediatePropagationStopped()) return;
|
|
e.data = data;
|
|
var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args));
|
|
if (result === false) e.preventDefault(), e.stopPropagation();
|
|
return result;
|
|
};
|
|
handler.i = set.length;
|
|
set.push(handler);
|
|
if ('addEventListener' in element)
|
|
element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture));
|
|
});
|
|
}
|
|
|
|
function remove(element, events, fn, selector, capture) {
|
|
var id = zid(element);
|
|
(events || '').split(/\s/).forEach(function (event) {
|
|
findHandlers(element, event, fn, selector).forEach(function (handler) {
|
|
delete handlers[id][handler.i];
|
|
if ('removeEventListener' in element)
|
|
element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture));
|
|
});
|
|
});
|
|
}
|
|
|
|
function createProxy(event) {
|
|
var key, proxy = { originalEvent: event };
|
|
for (key in event)
|
|
if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key];
|
|
|
|
return compatible(proxy, event);
|
|
}
|
|
|
|
var on = function (event, selector, data, callback, one) {
|
|
var autoRemove, delegator, $this = this;
|
|
if (event && !isString(event)) {
|
|
D.each(event, function (type, fn) {
|
|
$this.on(type, selector, data, fn, one);
|
|
});
|
|
return $this;
|
|
}
|
|
|
|
if (!isString(selector) && !isFunction(callback) && callback !== false)
|
|
callback = data, data = selector, selector = undefined;
|
|
if (callback === undefined || data === false)
|
|
callback = data, data = undefined;
|
|
|
|
if (callback === false) callback = returnFalse;
|
|
|
|
return $this.each(function (_, element) {
|
|
if (one) autoRemove = function (e) {
|
|
remove(element, e.type, callback);
|
|
return callback.apply(this, arguments);
|
|
};
|
|
|
|
if (selector) delegator = function (e) {
|
|
var evt, match = D(e.target).closest(selector, element).get(0);
|
|
if (match && match !== element) {
|
|
evt = D.extend(createProxy(e), { currentTarget: match, liveFired: element });
|
|
return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)));
|
|
}
|
|
};
|
|
|
|
add(element, event, callback, data, selector, delegator || autoRemove);
|
|
});
|
|
};
|
|
|
|
var off = function (event, selector, callback) {
|
|
var $this = this;
|
|
if (event && !isString(event)) {
|
|
D.each(event, function (type, fn) {
|
|
$this.off(type, selector, fn);
|
|
});
|
|
return $this;
|
|
}
|
|
|
|
if (!isString(selector) && !isFunction(callback) && callback !== false)
|
|
callback = selector, selector = undefined;
|
|
|
|
if (callback === false) callback = returnFalse;
|
|
|
|
return $this.each(function () {
|
|
remove(this, event, callback, selector);
|
|
});
|
|
};
|
|
|
|
var prefix = '',
|
|
eventPrefix,
|
|
vendors = { Webkit: 'webkit', Moz: '', O: 'o' },
|
|
testEl = document$1.createElement('div'),
|
|
testTransitionProperty = testEl.style.transitionProperty;
|
|
|
|
if (testEl.style.transform === undefined) D.each(vendors, function (vendor, event) {
|
|
if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
|
|
prefix = '-' + vendor.toLowerCase() + '-';
|
|
eventPrefix = event;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
testEl = null;
|
|
|
|
// fx cannot seperate
|
|
function normalizeEvent(name) {
|
|
return eventPrefix ? eventPrefix + name : name.toLowerCase();
|
|
}
|
|
|
|
D.fx = {
|
|
off: (eventPrefix === undefined && testTransitionProperty === undefined),
|
|
speeds: { _default: 400, fast: 200, slow: 600 },
|
|
cssPrefix: prefix,
|
|
transitionEnd: normalizeEvent('TransitionEnd'),
|
|
animationEnd: normalizeEvent('AnimationEnd')
|
|
};
|
|
|
|
var supportedTransforms = /^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,
|
|
transform,
|
|
transitionProperty, transitionDuration, transitionTiming, transitionDelay,
|
|
animationName, animationDuration, animationTiming, animationDelay,
|
|
cssReset = {};
|
|
|
|
function dasherize(str) { return str.replace(/([A-Z])/g, '-$1').toLowerCase(); }
|
|
|
|
transform = prefix + 'transform';
|
|
cssReset[transitionProperty = prefix + 'transition-property'] =
|
|
cssReset[transitionDuration = prefix + 'transition-duration'] =
|
|
cssReset[transitionDelay = prefix + 'transition-delay'] =
|
|
cssReset[transitionTiming = prefix + 'transition-timing-function'] =
|
|
cssReset[animationName = prefix + 'animation-name'] =
|
|
cssReset[animationDuration = prefix + 'animation-duration'] =
|
|
cssReset[animationDelay = prefix + 'animation-delay'] =
|
|
cssReset[animationTiming = prefix + 'animation-timing-function'] = '';
|
|
|
|
var anim$1 = function (properties, duration, ease, callback, delay) {
|
|
var key, cssValues = {}, cssProperties, transforms = '',
|
|
that = this, wrappedCallback, endEvent = D.fx.transitionEnd,
|
|
fired = false;
|
|
|
|
if (duration === undefined) duration = D.fx.speeds._default / 1000;
|
|
if (delay === undefined) delay = 0;
|
|
if (D.fx.off) duration = 0;
|
|
|
|
if (typeof properties == 'string') {
|
|
// keyframe animation
|
|
cssValues[animationName] = properties;
|
|
cssValues[animationDuration] = duration + 's';
|
|
cssValues[animationDelay] = delay + 's';
|
|
cssValues[animationTiming] = (ease || 'linear');
|
|
endEvent = D.fx.animationEnd;
|
|
} else {
|
|
cssProperties = [];
|
|
// CSS transitions
|
|
for (key in properties)
|
|
if (supportedTransforms.test(key)) transforms += key + '(' + properties[key] + ') ';
|
|
else cssValues[key] = properties[key], cssProperties.push(dasherize(key));
|
|
|
|
if (transforms) cssValues[transform] = transforms, cssProperties.push(transform);
|
|
if (duration > 0 && typeof properties === 'object') {
|
|
cssValues[transitionProperty] = cssProperties.join(', ');
|
|
cssValues[transitionDuration] = duration + 's';
|
|
cssValues[transitionDelay] = delay + 's';
|
|
cssValues[transitionTiming] = (ease || 'linear');
|
|
}
|
|
}
|
|
|
|
wrappedCallback = function (event) {
|
|
if (typeof event !== 'undefined') {
|
|
if (event.target !== event.currentTarget) return; // makes sure the event didn't bubble from "below"
|
|
D(event.target).off(endEvent, wrappedCallback);
|
|
} else
|
|
D(this).off(endEvent, wrappedCallback); // triggered by setTimeout
|
|
|
|
fired = true;
|
|
D(this).css(cssReset);
|
|
callback && callback.call(this);
|
|
};
|
|
if (duration > 0) {
|
|
this.on(endEvent, wrappedCallback);
|
|
// transitionEnd is not always firing on older Android phones
|
|
// so make sure it gets fired
|
|
setTimeout(function () {
|
|
if (fired) return;
|
|
wrappedCallback.call(that);
|
|
}, ((duration + delay) * 1000) + 25);
|
|
}
|
|
|
|
// trigger page reflow so new elements can animate
|
|
this.size() && this.get(0).clientLeft;
|
|
|
|
this.css(cssValues);
|
|
|
|
if (duration <= 0) setTimeout(function () {
|
|
that.each(function () { wrappedCallback.call(this); });
|
|
}, 0);
|
|
|
|
return this;
|
|
};
|
|
|
|
var animate = function (properties, duration, ease, callback, delay) {
|
|
if (isFunction(duration))
|
|
callback = duration, ease = undefined, duration = undefined;
|
|
if (isFunction(ease))
|
|
callback = ease, ease = undefined;
|
|
if (isPlainObject(duration))
|
|
ease = duration.easing, callback = duration.complete, delay = duration.delay, duration = duration.duration;
|
|
if (duration) duration = (typeof duration == 'number' ? duration :
|
|
(D.fx.speeds[duration] || D.fx.speeds._default)) / 1000;
|
|
if (delay) delay = parseFloat(delay) / 1000;
|
|
return this.anim(properties, duration, ease, callback, delay);
|
|
};
|
|
|
|
var origShow = function () {
|
|
return this.each(function () {
|
|
this.style.display == 'none' && (this.style.display = '');
|
|
if (getComputedStyle(this, '').getPropertyValue('display') == 'none')
|
|
this.style.display = defaultDisplay(this.nodeName);
|
|
});
|
|
};
|
|
|
|
var origHide = function () {
|
|
return this.css('display', 'none');
|
|
};
|
|
|
|
function anim(el, speed, opacity, scale, callback) {
|
|
if (typeof speed == 'function' && !callback) callback = speed, speed = undefined;
|
|
var props = { opacity: opacity };
|
|
if (scale) {
|
|
props.scale = scale;
|
|
el.css(D.fx.cssPrefix + 'transform-origin', '0 0');
|
|
}
|
|
return el.animate(props, speed, null, callback);
|
|
}
|
|
|
|
function hideHelper(el, speed, scale, callback) {
|
|
return anim(el, speed, 0, scale, function () {
|
|
origHide.call(D(this));
|
|
callback && callback.call(this);
|
|
});
|
|
}
|
|
|
|
// Export
|
|
|
|
var show = function (speed, callback) {
|
|
origShow.call(this);
|
|
if (speed === undefined) speed = 0;
|
|
else this.css('opacity', 0);
|
|
return anim(this, speed, 1, '1,1', callback);
|
|
};
|
|
|
|
var hide = function (speed, callback) {
|
|
if (speed === undefined) return origHide.call(this);
|
|
else return hideHelper(this, speed, '0,0', callback);
|
|
};
|
|
|
|
var fadeTo = function (speed, opacity, callback) {
|
|
return anim(this, speed, opacity, null, callback);
|
|
};
|
|
|
|
var fadeIn = function (speed, callback) {
|
|
var target = this.css('opacity');
|
|
if (target > 0) this.css('opacity', 0);
|
|
else target = 1;
|
|
return origShow.call(this).fadeTo(speed, target, callback);
|
|
};
|
|
|
|
var $ = D;
|
|
var methods = {
|
|
isPlainObject: isPlainObject,
|
|
isArray: isArray,
|
|
noop: noop
|
|
};
|
|
var fnMethods = {
|
|
find: find,
|
|
closest: closest,
|
|
css: css,
|
|
addClass: addClass,
|
|
hasClass: hasClass,
|
|
removeClass: removeClass,
|
|
attr: attr,
|
|
removeAttr: removeAttr,
|
|
append: append,
|
|
remove: remove$1,
|
|
empty: empty,
|
|
html: html,
|
|
width: width,
|
|
height: height,
|
|
scrollTop: scrollTop,
|
|
scrollLeft: scrollLeft,
|
|
offset: offset,
|
|
offsetParent: offsetParent,
|
|
position: position,
|
|
on: on,
|
|
off: off,
|
|
show: show,
|
|
hide: hide,
|
|
anim: anim$1,
|
|
animate: animate,
|
|
fadeTo: fadeTo,
|
|
fadeIn: fadeIn
|
|
};
|
|
$.extend(methods);
|
|
$.fn.extend(fnMethods);
|
|
|
|
var DEFAULTS = {
|
|
// Whether to enable modal dragging
|
|
draggable: true,
|
|
// Whether to enable modal resizing
|
|
resizable: true,
|
|
// Whether to enable image moving
|
|
movable: true,
|
|
// Whether to enable keyboard navigation
|
|
keyboard: true,
|
|
// Whether to show the title
|
|
title: true,
|
|
// Min width of modal
|
|
modalWidth: 320,
|
|
// Min height of modal
|
|
modalHeight: 320,
|
|
// Whether to change the modal size after image loaded
|
|
fixedModalSize: false,
|
|
// Whether to set maximized on init
|
|
initMaximized: false,
|
|
// Threshold of modal relative to browser window
|
|
gapThreshold: 0.02,
|
|
// Threshold of image ratio
|
|
ratioThreshold: 0.1,
|
|
// Min ratio of image when zoom out
|
|
minRatio: 0.05,
|
|
// Max ratio of image when zoom in
|
|
maxRatio: 16,
|
|
// Toolbar options in header
|
|
headerToolbar: ['maximize', 'close'],
|
|
// Toolbar options in footer
|
|
footerToolbar: ['zoomIn', 'zoomOut', 'prev', 'fullscreen', 'next', 'actualSize', 'rotateRight'],
|
|
// Custom icon for button
|
|
icons: {
|
|
minimize: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M20,14H4V10H20\"></path>\n </svg>",
|
|
maximize: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M4,4H20V20H4V4M6,8V18H18V8H6Z\"></path>\n </svg>",
|
|
close: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12\n L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z\"></path>\n </svg>",
|
|
zoomIn: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M15.5,14L20.5,19L19,20.5L14,15.5V14.71L13.73,14.43\n C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3A6.5,6.5 0 0,1 16,9.5\n C16,11.11 15.41,12.59 14.43,13.73L14.71,14H15.5M9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5\n C7,5 5,7 5,9.5C5,12 7,14 9.5,14M12,10H10V12H9V10H7V9H9V7H10V9H12V10Z\"></path>\n </svg>",
|
|
zoomOut: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M15.5,14H14.71L14.43,13.73C15.41,12.59 16,11.11 16,9.5\n A6.5,6.5 0 0,0 9.5,3A6.5,6.5 0 0,0 3,9.5A6.5,6.5 0 0,0 9.5,16\n C11.11,16 12.59,15.41 13.73,14.43L14,14.71V15.5L19,20.5L20.5,19L15.5,14M9.5,14\n C7,14 5,12 5,9.5C5,7 7,5 9.5,5C12,5 14,7 14,9.5C14,12 12,14 9.5,14M7,9H12V10H7V9Z\"></path>\n </svg>",
|
|
prev: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M6,18V6H8V18H6M9.5,12L18,6V18L9.5,12Z\"></path>\n </svg>",
|
|
next: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M16,18H18V6H16M6,18L14.5,12L6,6V18Z\"></path>\n </svg>",
|
|
fullscreen: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M8.5,12.5L11,15.5L14.5,11L19,17H5M23,18V6A2,2 0 0,0 21,4H3\n A2,2 0 0,0 1,6V18A2,2 0 0,0 3,20H21A2,2 0 0,0 23,18Z\"></path>\n </svg>",
|
|
actualSize: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M9.5,13.09L10.91,14.5L6.41,19H10V21H3V14H5V17.59L9.5,13.09\n M10.91,9.5L9.5,10.91L5,6.41V10H3V3H10V5H6.41L10.91,9.5M14.5,13.09L19,17.59V14H21V21H14V19\n H17.59L13.09,14.5L14.5,13.09M13.09,9.5L17.59,5H14V3H21V10H19V6.41L14.5,10.91\n L13.09,9.5Z\"></path>\n </svg>",
|
|
rotateLeft: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M13,4.07V1L8.45,5.55L13,10V6.09C15.84,6.57 18,9.03 18,12\n C18,14.97 15.84,17.43 13,17.91V19.93C16.95,19.44 20,16.08 20,12C20,7.92 16.95,4.56 13,4.07\n M7.1,18.32C8.26,19.22 9.61,19.76 11,19.93V17.9C10.13,17.75 9.29,17.41 8.54,16.87L7.1,18.32\n M6.09,13H4.07C4.24,14.39 4.79,15.73 5.69,16.89L7.1,15.47C6.58,14.72 6.23,13.88 6.09,13\n M7.11,8.53L5.7,7.11C4.8,8.27 4.24,9.61 4.07,11H6.09C6.23,10.13 6.58,9.28 7.11,8.53Z\"></path>\n </svg>",
|
|
rotateRight: "<svg viewBox=\"0 0 24 24\" class=\"svg-inline-icon\">\n <path fill=\"currentColor\" d=\"M16.89,15.5L18.31,16.89C19.21,15.73 19.76,14.39 19.93,13H17.91\n C17.77,13.87 17.43,14.72 16.89,15.5M13,17.9V19.92C14.39,19.75 15.74,19.21 16.9,18.31\n L15.46,16.87C14.71,17.41 13.87,17.76 13,17.9M19.93,11C19.76,9.61 19.21,8.27 18.31,7.11\n L16.89,8.53C17.43,9.28 17.77,10.13 17.91,11M15.55,5.55L11,1V4.07C7.06,4.56 4,7.92 4,12\n C4,16.08 7.05,19.44 11,19.93V17.91C8.16,17.43 6,14.97 6,12C6,9.03 8.16,6.57 11,6.09V10\n L15.55,5.55Z\"></path>\n </svg>"
|
|
},
|
|
// Custom title for button
|
|
i18n: {
|
|
minimize: 'Minimize',
|
|
maximize: 'Maximize (Alt+X)',
|
|
close: 'Close (Q)',
|
|
zoomIn: 'Zoom-in (+)',
|
|
zoomOut: 'Zoom-out (-)',
|
|
prev: 'Prev (←)',
|
|
next: 'Next (→)',
|
|
fullscreen: 'Fullscreen (F)',
|
|
actualSize: 'Actual-size (Ctrl+Alt+0)',
|
|
rotateLeft: 'Rotate-left (Ctrl+,)',
|
|
rotateRight: 'Rotate-right (Ctrl+.)'
|
|
},
|
|
// Whether to enable multiple instances
|
|
multiInstances: true,
|
|
// Whether to enable modal transform animation
|
|
initAnimation: true,
|
|
// Modal transform animation duration
|
|
animationDuration: 400,
|
|
// Whether to disable modal translate to center after image changed
|
|
fixedModalPos: false,
|
|
// Modal css `z-index`
|
|
zIndex: 1090,
|
|
// Selector of drag handler
|
|
dragHandle: null,
|
|
// Callback events
|
|
callbacks: {
|
|
beforeOpen: $.noop,
|
|
opened: $.noop,
|
|
beforeClose: $.noop,
|
|
closed: $.noop,
|
|
beforeChange: $.noop,
|
|
changed: $.noop
|
|
},
|
|
// Start index of images
|
|
index: 0,
|
|
// Whether to load the image progressively
|
|
progressiveLoading: true,
|
|
// The DOM element which photoviewer will be attached to
|
|
appendTo: 'body',
|
|
// Custom Buttons
|
|
customButtons: {},
|
|
// Whether to set modal css `position: fixed`
|
|
positionFixed: true,
|
|
// Init modal position `{top: 0, right: 0, bottom: 0, left: 0}`
|
|
initModalPos: null
|
|
};
|
|
|
|
var document = window.document;
|
|
|
|
/**
|
|
* Debounce function
|
|
*
|
|
* @param {function} fn - The function will be triggered
|
|
* @param {number} delay - The debounce delay time
|
|
* @return {function}
|
|
*/
|
|
function debounce(fn, delay) {
|
|
var timer = null;
|
|
return function () {
|
|
var context = this;
|
|
var args = arguments;
|
|
clearTimeout(timer);
|
|
timer = setTimeout(function () {
|
|
fn.apply(context, args);
|
|
}, delay);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Preload a image
|
|
*
|
|
* @param {string} src - The image src
|
|
* @param {function} success - The callback of success
|
|
* @param {function} error - The callback of error
|
|
*/
|
|
function preloadImage(src, success, error) {
|
|
var img = new Image();
|
|
img.onload = function () {
|
|
success(img);
|
|
};
|
|
img.onerror = function () {
|
|
error(img);
|
|
};
|
|
img.src = src;
|
|
}
|
|
|
|
/**
|
|
* Request fullscreen
|
|
*
|
|
* @param {type} element
|
|
*/
|
|
function requestFullscreen(element) {
|
|
if (element.requestFullscreen) {
|
|
element.requestFullscreen();
|
|
} else if (element.mozRequestFullScreen) {
|
|
element.mozRequestFullScreen();
|
|
} else if (element.webkitRequestFullscreen) {
|
|
element.webkitRequestFullscreen();
|
|
} else if (element.msRequestFullscreen) {
|
|
element.msRequestFullscreen();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the image name from its url
|
|
*
|
|
* @param {string} url - The image src
|
|
* @return {string}
|
|
*/
|
|
function getImageNameFromUrl(url) {
|
|
var reg = /^.*?\/*([^/?]*)\.[a-z]+(\?.+|$)/gi;
|
|
var txt = url.replace(reg, '$1');
|
|
return txt;
|
|
}
|
|
|
|
/**
|
|
* Set grab cursor when move image
|
|
*
|
|
* @param {object} imageData - The image data
|
|
* @param {object} stageData - The stage data
|
|
* @param {object} $stage - The stage element of domq
|
|
* @param {boolean} isRotate - The image rotated flag
|
|
*/
|
|
function setGrabCursor(imageData, stageData, $stage, isRotated) {
|
|
var imageWidth = !isRotated ? imageData.w : imageData.h;
|
|
var imageHeight = !isRotated ? imageData.h : imageData.w;
|
|
if (imageHeight > stageData.h || imageWidth > stageData.w) {
|
|
$stage.addClass('is-grab');
|
|
}
|
|
if (imageHeight <= stageData.h && imageWidth <= stageData.w) {
|
|
$stage.removeClass('is-grab');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether browser support touch event
|
|
*
|
|
* @return {boolean}
|
|
*/
|
|
function supportTouch() {
|
|
return !!('ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch);
|
|
}
|
|
|
|
/**
|
|
* Check whether element is root node (`body` or `html`)
|
|
*
|
|
* @param {object} elem - The DOM element
|
|
* @return {boolean}
|
|
*/
|
|
function isRootNode(elem) {
|
|
return /^(?:body|html)$/i.test(elem.nodeName);
|
|
}
|
|
|
|
/**
|
|
* Get sum value of CSS props
|
|
*
|
|
* @param {object} $elem - The domq element
|
|
* @param {array} props - The array of CSS props
|
|
* @return {number}
|
|
*/
|
|
function getCSSValueSum($elem, props) {
|
|
return props.reduce(function (acc, cur) {
|
|
return acc + parseFloat($elem.css(cur));
|
|
}, 0);
|
|
}
|
|
|
|
/**
|
|
* Check whether element's CSS `box-sizing` is `border-box`
|
|
*
|
|
* @param {object} $elem - The domq element
|
|
* @return {boolean}
|
|
*/
|
|
function isBorderBox($elem) {
|
|
return $elem.css('box-sizing') === 'border-box';
|
|
}
|
|
|
|
var $W = $(window);
|
|
var $D = $(document);
|
|
var CLICK_EVENT = 'click';
|
|
var RESIZE_EVENT = 'resize';
|
|
var KEYDOWN_EVENT = 'keydown';
|
|
var WHEEL_EVENT = 'wheel mousewheel DOMMouseScroll';
|
|
var TOUCH_START_EVENT = supportTouch() ? 'touchstart' : 'mousedown';
|
|
var TOUCH_MOVE_EVENT = supportTouch() ? 'touchmove' : 'mousemove';
|
|
var TOUCH_END_EVENT = supportTouch() ? 'touchend' : 'mouseup';
|
|
var NS = 'photoviewer';
|
|
var CLASS_NS = '.' + NS;
|
|
var EVENT_NS = '.' + NS;
|
|
var PUBLIC_VARS = {
|
|
// Whether image is moving
|
|
isMoving: false,
|
|
// Whether modal is resizing
|
|
isResizing: false,
|
|
// Modal's `z-index`
|
|
zIndex: 0
|
|
};
|
|
|
|
var draggable = {
|
|
/**
|
|
* Modal draggable
|
|
*
|
|
* @param {object} $modal - The modal element of domq
|
|
* @param {object} dragHandle - The handle element when dragging
|
|
* @param {object} dragCancel - The cancel element when dragging
|
|
*/
|
|
draggable: function draggable($modal, dragHandle, dragCancel) {
|
|
var _this = this;
|
|
var isDragging = false;
|
|
var startX = 0;
|
|
var startY = 0;
|
|
var left = 0;
|
|
var top = 0;
|
|
var dragStart = function dragStart(e) {
|
|
e = e || window.event;
|
|
|
|
// Must be removed
|
|
// e.preventDefault();
|
|
|
|
// Fix focus scroll issue on Chrome
|
|
$modal[0].blur();
|
|
|
|
// Get clicked button
|
|
var elemCancel = $(e.target).closest(dragCancel);
|
|
// Stop modal moving when click buttons
|
|
if (elemCancel.length) {
|
|
return true;
|
|
}
|
|
if (_this.options.multiInstances) {
|
|
$modal.css('z-index', ++PUBLIC_VARS['zIndex']);
|
|
}
|
|
isDragging = true;
|
|
startX = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.clientX;
|
|
startY = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.clientY;
|
|
|
|
// Get current position of the modal
|
|
left = parseFloat($modal.css('left'));
|
|
top = parseFloat($modal.css('top'));
|
|
|
|
// Reset modal position with left and top
|
|
$modal.css({
|
|
left: left,
|
|
top: top,
|
|
right: '',
|
|
bottom: ''
|
|
});
|
|
$D.on(TOUCH_MOVE_EVENT + EVENT_NS, dragMove).on(TOUCH_END_EVENT + EVENT_NS, dragEnd);
|
|
};
|
|
var dragMove = function dragMove(e) {
|
|
e = e || window.event;
|
|
e.preventDefault();
|
|
if (isDragging && !PUBLIC_VARS['isMoving'] && !PUBLIC_VARS['isResizing'] && !_this.isMaximized) {
|
|
var endX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.clientX;
|
|
var endY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.clientY;
|
|
var relativeX = endX - startX;
|
|
var relativeY = endY - startY;
|
|
$modal.css({
|
|
left: relativeX + left,
|
|
top: relativeY + top
|
|
});
|
|
}
|
|
};
|
|
var dragEnd = function dragEnd() {
|
|
$D.off(TOUCH_MOVE_EVENT + EVENT_NS, dragMove).off(TOUCH_END_EVENT + EVENT_NS, dragEnd);
|
|
isDragging = false;
|
|
|
|
// Focus must be executed after drag end
|
|
$modal[0].focus();
|
|
};
|
|
$(dragHandle).on(TOUCH_START_EVENT + EVENT_NS, dragStart);
|
|
}
|
|
};
|
|
|
|
var ELEMS_WITH_GRABBING_CURSOR = "html, body, .".concat(NS, "-modal, .").concat(NS, "-stage, .").concat(NS, "-button, .").concat(NS, "-resizable-handle");
|
|
var movable = {
|
|
/**
|
|
* Image movable
|
|
*
|
|
* 1. No movable
|
|
* 2. Vertical movable
|
|
* 3. Horizontal movable
|
|
* 4. Vertical & Horizontal movable
|
|
*
|
|
* @param {object} $stage - The stage element of domq
|
|
* @param {object} $image - The image element of domq
|
|
*/
|
|
movable: function movable($stage, $image) {
|
|
var _this = this;
|
|
var isDragging = false;
|
|
var startX = 0;
|
|
var startY = 0;
|
|
var left = 0;
|
|
var top = 0;
|
|
var widthDiff = 0;
|
|
var heightDiff = 0;
|
|
var δ = 0;
|
|
var dragStart = function dragStart(e) {
|
|
e = e || window.event;
|
|
e.preventDefault();
|
|
var imageWidth = $image.width();
|
|
var imageHeight = $image.height();
|
|
var stageWidth = $stage.width();
|
|
var stageHeight = $stage.height();
|
|
startX = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.clientX;
|
|
startY = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.clientY;
|
|
|
|
// δ is the difference between image width and height
|
|
δ = !_this.isRotated ? 0 : (imageWidth - imageHeight) / 2;
|
|
|
|
// Width or height difference can be use to limit image right or top position
|
|
widthDiff = !_this.isRotated ? imageWidth - stageWidth : imageHeight - stageWidth;
|
|
heightDiff = !_this.isRotated ? imageHeight - stageHeight : imageWidth - stageHeight;
|
|
|
|
// Modal can be dragging if image is smaller to stage
|
|
isDragging = widthDiff > 0 || heightDiff > 0 ? true : false;
|
|
PUBLIC_VARS['isMoving'] = widthDiff > 0 || heightDiff > 0 ? true : false;
|
|
|
|
// Reclac the element position when mousedown
|
|
// Fix the issue of stage with a border
|
|
left = $image.position().left - δ;
|
|
top = $image.position().top + δ;
|
|
|
|
// Add grabbing cursor
|
|
if ($stage.hasClass('is-grab')) {
|
|
$(ELEMS_WITH_GRABBING_CURSOR).addClass('is-grabbing');
|
|
}
|
|
$D.on(TOUCH_MOVE_EVENT + EVENT_NS, dragMove).on(TOUCH_END_EVENT + EVENT_NS, dragEnd);
|
|
};
|
|
var dragMove = function dragMove(e) {
|
|
e = e || window.event;
|
|
e.preventDefault();
|
|
if (isDragging) {
|
|
var endX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.clientX;
|
|
var endY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.clientY;
|
|
var relativeX = endX - startX;
|
|
var relativeY = endY - startY;
|
|
var newLeft = relativeX + left;
|
|
var newTop = relativeY + top;
|
|
|
|
// Vertical limit
|
|
if (heightDiff > 0) {
|
|
if (relativeY + top > δ) {
|
|
newTop = δ;
|
|
} else if (relativeY + top < -heightDiff + δ) {
|
|
newTop = -heightDiff + δ;
|
|
}
|
|
} else {
|
|
newTop = top;
|
|
}
|
|
// Horizontal limit
|
|
if (widthDiff > 0) {
|
|
if (relativeX + left > -δ) {
|
|
newLeft = -δ;
|
|
} else if (relativeX + left < -widthDiff - δ) {
|
|
newLeft = -widthDiff - δ;
|
|
}
|
|
} else {
|
|
newLeft = left;
|
|
}
|
|
$image.css({
|
|
left: newLeft,
|
|
top: newTop
|
|
});
|
|
|
|
// Update image initial data
|
|
$.extend(_this.imageData, {
|
|
left: newLeft,
|
|
top: newTop
|
|
});
|
|
}
|
|
};
|
|
var dragEnd = function dragEnd() {
|
|
$D.off(TOUCH_MOVE_EVENT + EVENT_NS, dragMove).off(TOUCH_END_EVENT + EVENT_NS, dragEnd);
|
|
isDragging = false;
|
|
PUBLIC_VARS['isMoving'] = false;
|
|
|
|
// Remove grabbing cursor
|
|
$(ELEMS_WITH_GRABBING_CURSOR).removeClass('is-grabbing');
|
|
};
|
|
$stage.on(TOUCH_START_EVENT + EVENT_NS, dragStart);
|
|
}
|
|
};
|
|
|
|
var ELEMS_WITH_RESIZE_CURSOR = "html, body, .".concat(NS, "-modal, .").concat(NS, "-stage, .").concat(NS, "-button");
|
|
var resizable = {
|
|
/**
|
|
* Modal resizable
|
|
*
|
|
* 1. Can be resized in 8 directions
|
|
* 2. Keep image in stage center
|
|
* 3. Other restrictions for image
|
|
*
|
|
* @param {object} $modal - The modal element of domq
|
|
* @param {object} $stage - The stage element of domq
|
|
* @param {object} $image - The image element of domq
|
|
* @param {number} minWidth - The modalWidth option
|
|
* @param {number} minHeight - The modalHeight option
|
|
*/
|
|
resizable: function resizable($modal, $stage, $image, minWidth, minHeight) {
|
|
var _this = this;
|
|
var resizableHandleE = $("<div class=\"".concat(NS, "-resizable-handle ").concat(NS, "-resizable-handle-e\"></div>"));
|
|
var resizableHandleW = $("<div class=\"".concat(NS, "-resizable-handle ").concat(NS, "-resizable-handle-w\"></div>"));
|
|
var resizableHandleS = $("<div class=\"".concat(NS, "-resizable-handle ").concat(NS, "-resizable-handle-s\"></div>"));
|
|
var resizableHandleN = $("<div class=\"".concat(NS, "-resizable-handle ").concat(NS, "-resizable-handle-n\"></div>"));
|
|
var resizableHandleSE = $("<div class=\"".concat(NS, "-resizable-handle ").concat(NS, "-resizable-handle-se\"></div>"));
|
|
var resizableHandleSW = $("<div class=\"".concat(NS, "-resizable-handle ").concat(NS, "-resizable-handle-sw\"></div>"));
|
|
var resizableHandleNE = $("<div class=\"".concat(NS, "-resizable-handle ").concat(NS, "-resizable-handle-ne\"></div>"));
|
|
var resizableHandleNW = $("<div class=\"".concat(NS, "-resizable-handle ").concat(NS, "-resizable-handle-nw\"></div>"));
|
|
var resizableHandles = {
|
|
e: resizableHandleE,
|
|
s: resizableHandleS,
|
|
se: resizableHandleSE,
|
|
n: resizableHandleN,
|
|
w: resizableHandleW,
|
|
nw: resizableHandleNW,
|
|
ne: resizableHandleNE,
|
|
sw: resizableHandleSW
|
|
};
|
|
$modal.append(resizableHandleE, resizableHandleW, resizableHandleS, resizableHandleN, resizableHandleSE, resizableHandleSW, resizableHandleNE, resizableHandleNW);
|
|
var isDragging = false;
|
|
var startX = 0;
|
|
var startY = 0;
|
|
var modalData = {
|
|
w: 0,
|
|
h: 0,
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
var stageData = {
|
|
w: 0,
|
|
h: 0,
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
var imageData = {
|
|
w: 0,
|
|
h: 0,
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
// δ is the difference between image width and height
|
|
var δ = 0;
|
|
var imgWidth = 0;
|
|
var imgHeight = 0;
|
|
var direction = '';
|
|
|
|
// Modal CSS options
|
|
var getModalOpts = function getModalOpts(dir, offsetX, offsetY) {
|
|
// Modal should not move when its width to the minwidth
|
|
var modalLeft = -offsetX + modalData.w > minWidth ? offsetX + modalData.x : modalData.x + modalData.w - minWidth;
|
|
var modalTop = -offsetY + modalData.h > minHeight ? offsetY + modalData.y : modalData.y + modalData.h - minHeight;
|
|
var opts = {
|
|
e: {
|
|
width: Math.max(offsetX + modalData.w, minWidth)
|
|
},
|
|
s: {
|
|
height: Math.max(offsetY + modalData.h, minHeight)
|
|
},
|
|
se: {
|
|
width: Math.max(offsetX + modalData.w, minWidth),
|
|
height: Math.max(offsetY + modalData.h, minHeight)
|
|
},
|
|
w: {
|
|
width: Math.max(-offsetX + modalData.w, minWidth),
|
|
left: modalLeft
|
|
},
|
|
n: {
|
|
height: Math.max(-offsetY + modalData.h, minHeight),
|
|
top: modalTop
|
|
},
|
|
nw: {
|
|
width: Math.max(-offsetX + modalData.w, minWidth),
|
|
height: Math.max(-offsetY + modalData.h, minHeight),
|
|
top: modalTop,
|
|
left: modalLeft
|
|
},
|
|
ne: {
|
|
width: Math.max(offsetX + modalData.w, minWidth),
|
|
height: Math.max(-offsetY + modalData.h, minHeight),
|
|
top: modalTop
|
|
},
|
|
sw: {
|
|
width: Math.max(-offsetX + modalData.w, minWidth),
|
|
height: Math.max(offsetY + modalData.h, minHeight),
|
|
left: modalLeft
|
|
}
|
|
};
|
|
return opts[dir];
|
|
};
|
|
|
|
// Image CSS options
|
|
var getImageOpts = function getImageOpts(dir, offsetX, offsetY) {
|
|
// Image should not move when modal width to the min width
|
|
// The minwidth is modal width, so we should clac the stage minwidth
|
|
var widthDiff = offsetX + modalData.w > minWidth ? stageData.w - imgWidth + offsetX - δ : minWidth - (modalData.w - stageData.w) - imgWidth - δ;
|
|
var heightDiff = offsetY + modalData.h > minHeight ? stageData.h - imgHeight + offsetY + δ : minHeight - (modalData.h - stageData.h) - imgHeight + δ;
|
|
var widthDiff2 = -offsetX + modalData.w > minWidth ? stageData.w - imgWidth - offsetX - δ : minWidth - (modalData.w - stageData.w) - imgWidth - δ;
|
|
var heightDiff2 = -offsetY + modalData.h > minHeight ? stageData.h - imgHeight - offsetY + δ : minHeight - (modalData.h - stageData.h) - imgHeight + δ;
|
|
|
|
// Get image position in dragging
|
|
var imgLeft = (widthDiff > 0 ? $image.position().left : $image.position().left < 0 ? $image.position().left : 0) - δ;
|
|
var imgTop = (heightDiff > 0 ? $image.position().top : $image.position().top < 0 ? $image.position().top : 0) + δ;
|
|
var imgLeft2 = (widthDiff2 > 0 ? $image.position().left : $image.position().left < 0 ? $image.position().left : 0) - δ;
|
|
var imgTop2 = (heightDiff2 > 0 ? $image.position().top : $image.position().top < 0 ? $image.position().top : 0) + δ;
|
|
var opts = {
|
|
e: {
|
|
left: widthDiff >= -δ ? (widthDiff - δ) / 2 : imgLeft > widthDiff ? imgLeft : widthDiff
|
|
},
|
|
s: {
|
|
top: heightDiff >= δ ? (heightDiff + δ) / 2 : imgTop > heightDiff ? imgTop : heightDiff
|
|
},
|
|
se: {
|
|
top: heightDiff >= δ ? (heightDiff + δ) / 2 : imgTop > heightDiff ? imgTop : heightDiff,
|
|
left: widthDiff >= -δ ? (widthDiff - δ) / 2 : imgLeft > widthDiff ? imgLeft : widthDiff
|
|
},
|
|
w: {
|
|
left: widthDiff2 >= -δ ? (widthDiff2 - δ) / 2 : imgLeft2 > widthDiff2 ? imgLeft2 : widthDiff2
|
|
},
|
|
n: {
|
|
top: heightDiff2 >= δ ? (heightDiff2 + δ) / 2 : imgTop2 > heightDiff2 ? imgTop2 : heightDiff2
|
|
},
|
|
nw: {
|
|
top: heightDiff2 >= δ ? (heightDiff2 + δ) / 2 : imgTop2 > heightDiff2 ? imgTop2 : heightDiff2,
|
|
left: widthDiff2 >= -δ ? (widthDiff2 - δ) / 2 : imgLeft2 > widthDiff2 ? imgLeft2 : widthDiff2
|
|
},
|
|
ne: {
|
|
top: heightDiff2 >= δ ? (heightDiff2 + δ) / 2 : imgTop2 > heightDiff2 ? imgTop2 : heightDiff2,
|
|
left: widthDiff >= -δ ? (widthDiff - δ) / 2 : imgLeft > widthDiff ? imgLeft : widthDiff
|
|
},
|
|
sw: {
|
|
top: heightDiff >= δ ? (heightDiff + δ) / 2 : imgTop > heightDiff ? imgTop : heightDiff,
|
|
left: widthDiff2 >= -δ ? (widthDiff2 - δ) / 2 : imgLeft2 > widthDiff2 ? imgLeft2 : widthDiff2
|
|
}
|
|
};
|
|
return opts[dir];
|
|
};
|
|
var dragStart = function dragStart(dir, e) {
|
|
e = e || window.event;
|
|
e.preventDefault();
|
|
isDragging = true;
|
|
PUBLIC_VARS['isResizing'] = true;
|
|
startX = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.clientX;
|
|
startY = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.clientY;
|
|
|
|
// Reclac the modal data when mousedown
|
|
modalData = {
|
|
w: $modal.width() + (isBorderBox($modal) ? _this._modalEdgeValue.horizontal : 0),
|
|
h: $modal.height() + (isBorderBox($modal) ? _this._modalEdgeValue.vertical : 0),
|
|
x: $modal.position().left,
|
|
y: $modal.position().top
|
|
};
|
|
stageData = {
|
|
w: $stage.width(),
|
|
h: $stage.height(),
|
|
x: $stage.position().left,
|
|
y: $stage.position().top
|
|
};
|
|
imageData = {
|
|
w: $image.width(),
|
|
h: $image.height(),
|
|
x: $image.position().left,
|
|
y: $image.position().top
|
|
};
|
|
|
|
// δ is the difference between image width and height
|
|
δ = !_this.isRotated ? 0 : (imageData.w - imageData.h) / 2;
|
|
imgWidth = !_this.isRotated ? imageData.w : imageData.h;
|
|
imgHeight = !_this.isRotated ? imageData.h : imageData.w;
|
|
direction = dir;
|
|
|
|
// Add resizable cursor
|
|
$(ELEMS_WITH_RESIZE_CURSOR).css('cursor', dir + '-resize');
|
|
$D.on(TOUCH_MOVE_EVENT + EVENT_NS, dragMove).on(TOUCH_END_EVENT + EVENT_NS, dragEnd);
|
|
};
|
|
var dragMove = function dragMove(e) {
|
|
e = e || window.event;
|
|
e.preventDefault();
|
|
if (isDragging && !_this.isMaximized) {
|
|
var endX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.clientX;
|
|
var endY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.clientY;
|
|
var relativeX = endX - startX;
|
|
var relativeY = endY - startY;
|
|
var modalOpts = getModalOpts(direction, relativeX, relativeY);
|
|
$modal.css(modalOpts);
|
|
var imageOpts = getImageOpts(direction, relativeX, relativeY);
|
|
$image.css(imageOpts);
|
|
_this.isDoResize = true;
|
|
}
|
|
};
|
|
var dragEnd = function dragEnd() {
|
|
$D.off(TOUCH_MOVE_EVENT + EVENT_NS, dragMove).off(TOUCH_END_EVENT + EVENT_NS, dragEnd);
|
|
|
|
// Set grab cursor
|
|
if (PUBLIC_VARS['isResizing']) {
|
|
setGrabCursor({
|
|
w: imgWidth,
|
|
h: imgHeight
|
|
}, {
|
|
w: $stage.width(),
|
|
h: $stage.height()
|
|
}, $stage);
|
|
}
|
|
isDragging = false;
|
|
PUBLIC_VARS['isResizing'] = false;
|
|
|
|
// Remove resizable cursor
|
|
$(ELEMS_WITH_RESIZE_CURSOR).css('cursor', '');
|
|
|
|
// Update image initial data
|
|
var scale = _this.getImageScale($stage.width(), $stage.height());
|
|
$.extend(_this.imageData, {
|
|
initWidth: _this.img.width * scale,
|
|
initHeight: _this.img.height * scale,
|
|
initLeft: ($stage.width() - _this.img.width * scale) / 2,
|
|
initTop: ($stage.height() - _this.img.height * scale) / 2
|
|
});
|
|
};
|
|
$.each(resizableHandles, function (dir, handle) {
|
|
handle.on(TOUCH_START_EVENT + EVENT_NS, function (e) {
|
|
dragStart(dir, e);
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* PhotoViewer class
|
|
*/
|
|
var PhotoViewer = /*#__PURE__*/function () {
|
|
function PhotoViewer(items, options, el) {
|
|
_classCallCheck(this, PhotoViewer);
|
|
this.options = $.extend(true, {}, DEFAULTS, options);
|
|
if (options && $.isArray(options.footerToolbar)) {
|
|
this.options.footerToolbar = options.footerToolbar;
|
|
}
|
|
if (options && $.isArray(options.headerToolbar)) {
|
|
this.options.headerToolbar = options.headerToolbar;
|
|
}
|
|
|
|
// Store element of clicked
|
|
this.$el = $(el);
|
|
|
|
// As we have multiple instances, so every instance has the following variables.
|
|
|
|
// Whether modal opened
|
|
this.isOpened = false;
|
|
|
|
// Whether modal maximized
|
|
this.isMaximized = false;
|
|
|
|
// Whether image rotated (`90*(2n+1)`)
|
|
this.isRotated = false;
|
|
|
|
// Image rotation degree
|
|
this.rotationDegree = 0;
|
|
|
|
// Whether modal do resize
|
|
this.isDoResize = false;
|
|
|
|
// Store image data in every instance
|
|
this.imageData = {};
|
|
|
|
// Store modal data in every instance
|
|
this.modalData = {
|
|
width: null,
|
|
height: null,
|
|
left: null,
|
|
top: null
|
|
};
|
|
|
|
// Used for time comparison
|
|
this._lastTimestamp = 0;
|
|
this.init(items, this.options);
|
|
}
|
|
_createClass(PhotoViewer, [{
|
|
key: "init",
|
|
value: function init(items, opts) {
|
|
this.groupData = items;
|
|
this.groupIndex = opts['index'];
|
|
|
|
// Reset public z-index with option
|
|
PUBLIC_VARS['zIndex'] = PUBLIC_VARS['zIndex'] === 0 ? opts['zIndex'] : PUBLIC_VARS['zIndex'];
|
|
|
|
// Get image src
|
|
var imgSrc = items[this.groupIndex]['src'];
|
|
this.open();
|
|
this.loadImage(imgSrc);
|
|
if (opts.draggable) {
|
|
this.draggable(this.$photoviewer, this.dragHandle, CLASS_NS + '-button');
|
|
}
|
|
if (opts.movable) {
|
|
this.movable(this.$stage, this.$image);
|
|
}
|
|
if (opts.resizable) {
|
|
this.resizable(this.$photoviewer, this.$stage, this.$image, opts.modalWidth, opts.modalHeight);
|
|
}
|
|
}
|
|
}, {
|
|
key: "_createBtns",
|
|
value: function _createBtns(toolbar) {
|
|
var _this = this;
|
|
var btns = ['minimize', 'maximize', 'close', 'zoomIn', 'zoomOut', 'prev', 'next', 'fullscreen', 'actualSize', 'rotateLeft', 'rotateRight'];
|
|
var btnsHTML = '';
|
|
$.each(toolbar, function (index, item) {
|
|
var btnClass = "".concat(NS, "-button ").concat(NS, "-button-").concat(item);
|
|
if (btns.indexOf(item) >= 0) {
|
|
btnsHTML += "<button class=\"".concat(btnClass, "\" title=\"").concat(_this.options.i18n[item], "\">\n ").concat(_this.options.icons[item], "\n </button>");
|
|
} else if (_this.options.customButtons[item]) {
|
|
btnsHTML += "<button class=\"".concat(btnClass, "\" title=\"").concat(_this.options.customButtons[item].title || '', "\">\n ").concat(_this.options.customButtons[item].text, "\n </button>");
|
|
}
|
|
});
|
|
return btnsHTML;
|
|
}
|
|
}, {
|
|
key: "_createTitle",
|
|
value: function _createTitle() {
|
|
return this.options.title ? "<div class=\"".concat(NS, "-title\"></div>") : '';
|
|
}
|
|
}, {
|
|
key: "_createTemplate",
|
|
value: function _createTemplate() {
|
|
// PhotoViewer base HTML
|
|
var photoviewerHTML = "<div class=\"".concat(NS, "-modal\" tabindex=\"0\">\n <div class=\"").concat(NS, "-inner\">\n <div class=\"").concat(NS, "-header\">\n <div class=\"").concat(NS, "-toolbar ").concat(NS, "-toolbar-header\">\n ").concat(this._createBtns(this.options.headerToolbar), "\n </div>\n ").concat(this._createTitle(), "\n </div>\n <div class=\"").concat(NS, "-stage\">\n <img class=\"").concat(NS, "-image\" src=\"\" alt=\"\" />\n </div>\n <div class=\"").concat(NS, "-footer\">\n <div class=\"").concat(NS, "-toolbar ").concat(NS, "-toolbar-footer\">\n ").concat(this._createBtns(this.options.footerToolbar), "\n </div>\n </div>\n </div>\n </div>");
|
|
return photoviewerHTML;
|
|
}
|
|
}, {
|
|
key: "build",
|
|
value: function build() {
|
|
// Create PhotoViewer HTML string
|
|
var photoviewerHTML = this._createTemplate();
|
|
|
|
// Make PhotoViewer HTML string to jQuery element
|
|
var $photoviewer = $(photoviewerHTML);
|
|
|
|
// Get all PhotoViewer elements
|
|
this.$photoviewer = $photoviewer;
|
|
this.$stage = $photoviewer.find(CLASS_NS + '-stage');
|
|
this.$title = $photoviewer.find(CLASS_NS + '-title');
|
|
this.$image = $photoviewer.find(CLASS_NS + '-image');
|
|
this.$close = $photoviewer.find(CLASS_NS + '-button-close');
|
|
this.$maximize = $photoviewer.find(CLASS_NS + '-button-maximize');
|
|
this.$minimize = $photoviewer.find(CLASS_NS + '-button-minimize');
|
|
this.$zoomIn = $photoviewer.find(CLASS_NS + '-button-zoomIn');
|
|
this.$zoomOut = $photoviewer.find(CLASS_NS + '-button-zoomOut');
|
|
this.$actualSize = $photoviewer.find(CLASS_NS + '-button-actualSize');
|
|
this.$fullscreen = $photoviewer.find(CLASS_NS + '-button-fullscreen');
|
|
this.$rotateLeft = $photoviewer.find(CLASS_NS + '-button-rotateLeft');
|
|
this.$rotateRight = $photoviewer.find(CLASS_NS + '-button-rotateRight');
|
|
this.$prev = $photoviewer.find(CLASS_NS + '-button-prev');
|
|
this.$next = $photoviewer.find(CLASS_NS + '-button-next');
|
|
|
|
// Add class before image loaded
|
|
this.$stage.addClass('stage-ready');
|
|
this.$image.addClass('image-ready');
|
|
|
|
// Reset modal `z-index` of multiple instances
|
|
this.$photoviewer.css('z-index', PUBLIC_VARS['zIndex']);
|
|
if (this.options.positionFixed) {
|
|
this.$photoviewer.css({
|
|
position: 'fixed'
|
|
});
|
|
}
|
|
|
|
// Set handle element of draggable
|
|
if (!this.options.dragHandle || this.options.dragHandle === CLASS_NS + '-modal') {
|
|
this.dragHandle = this.$photoviewer;
|
|
} else {
|
|
this.dragHandle = this.$photoviewer.find(this.options.dragHandle);
|
|
}
|
|
|
|
// Add PhotoViewer to DOM
|
|
$(this.options.appendTo).eq(0).append(this.$photoviewer);
|
|
|
|
// Store the edge value of stage
|
|
this._stageEdgeValue = {
|
|
horizontal: getCSSValueSum(this.$stage, ['left', 'right', 'border-left-width', 'border-right-width']),
|
|
vertical: getCSSValueSum(this.$stage, ['top', 'bottom', 'border-top-width', 'border-bottom-width'])
|
|
};
|
|
|
|
// Store the edge value of modal
|
|
this._modalEdgeValue = {
|
|
horizontal: getCSSValueSum(this.$photoviewer, ['padding-left', 'padding-right', 'border-left-width', 'border-right-width']),
|
|
vertical: getCSSValueSum(this.$photoviewer, ['padding-top', 'padding-bottom', 'border-top-width', 'border-bottom-width'])
|
|
};
|
|
this._addEvents();
|
|
this._addCustomButtonEvents();
|
|
}
|
|
}, {
|
|
key: "open",
|
|
value: function open() {
|
|
this._triggerHook('beforeOpen', this);
|
|
if (!this.options.multiInstances) {
|
|
$(CLASS_NS + '-modal').eq(0).remove();
|
|
}
|
|
this.build();
|
|
this.setInitModalPos();
|
|
this._triggerHook('opened', this);
|
|
}
|
|
}, {
|
|
key: "close",
|
|
value: function close() {
|
|
this._triggerHook('beforeClose', this);
|
|
|
|
// Remove PhotoViewer instance
|
|
this.$photoviewer.remove();
|
|
this.isOpened = false;
|
|
this.isMaximized = false;
|
|
this.isRotated = false;
|
|
this.rotationDegree = 0;
|
|
if (!$(CLASS_NS + '-modal').length) {
|
|
// Reset `z-index` after close
|
|
if (this.options.multiInstances) {
|
|
PUBLIC_VARS['zIndex'] = this.options.zIndex;
|
|
}
|
|
|
|
// Off resize event
|
|
$W.off(RESIZE_EVENT + EVENT_NS);
|
|
}
|
|
this._triggerHook('closed', this);
|
|
}
|
|
}, {
|
|
key: "_getOffsetParentData",
|
|
value: function _getOffsetParentData() {
|
|
var offsetParent = $(this.options.appendTo)[0];
|
|
return {
|
|
width: this.options.positionFixed || isRootNode(offsetParent) ? $W.width() : offsetParent.clientWidth,
|
|
height: this.options.positionFixed || isRootNode(offsetParent) ? $W.height() : offsetParent.clientHeight,
|
|
scrollLeft: this.options.positionFixed ? 0 : isRootNode(offsetParent) ? $D.scrollLeft() : offsetParent.scrollLeft,
|
|
scrollTop: this.options.positionFixed ? 0 : isRootNode(offsetParent) ? $D.scrollTop() : offsetParent.scrollTop
|
|
};
|
|
}
|
|
}, {
|
|
key: "setModalToCenter",
|
|
value: function setModalToCenter() {
|
|
var initLeft, initTop, initRight, initBottom;
|
|
|
|
// Extra width/height for `content-box`
|
|
var extraWidth = 0,
|
|
extraHeight = 0;
|
|
if (!isBorderBox(this.$photoviewer)) {
|
|
extraWidth += this._modalEdgeValue.horizontal;
|
|
extraHeight += this._modalEdgeValue.vertical;
|
|
}
|
|
if ($.isPlainObject(this.options.initModalPos)) {
|
|
initLeft = this.options.initModalPos.left;
|
|
initTop = this.options.initModalPos.top;
|
|
initRight = this.options.initModalPos.right;
|
|
initBottom = this.options.initModalPos.bottom;
|
|
} else {
|
|
var offsetParentData = this._getOffsetParentData();
|
|
initLeft = (offsetParentData.width - this.options.modalWidth - extraWidth) / 2 + offsetParentData.scrollLeft;
|
|
initTop = (offsetParentData.height - this.options.modalHeight - extraHeight) / 2 + offsetParentData.scrollTop;
|
|
}
|
|
var modalInitCSS = {
|
|
width: this.modalData.width || this.options.modalWidth,
|
|
height: this.modalData.height || this.options.modalHeight,
|
|
left: this.modalData.left || initLeft,
|
|
top: this.modalData.top || initTop,
|
|
right: this.modalData.right || initRight,
|
|
bottom: this.modalData.bottom || initBottom
|
|
};
|
|
this.$photoviewer.css(modalInitCSS);
|
|
}
|
|
}, {
|
|
key: "setInitModalPos",
|
|
value: function setInitModalPos() {
|
|
if (this.options.initMaximized) {
|
|
this.maximize();
|
|
this.isOpened = true;
|
|
} else {
|
|
this.setModalToCenter();
|
|
}
|
|
|
|
// The focus must be set after opening
|
|
this.$photoviewer[0].focus();
|
|
}
|
|
}, {
|
|
key: "setModalSize",
|
|
value: function setModalSize(img) {
|
|
var _this2 = this;
|
|
var offsetParentData = this._getOffsetParentData();
|
|
|
|
// Modal size should calculate with stage css value
|
|
var modalWidth = img.width + this._stageEdgeValue.horizontal;
|
|
var modalHeight = img.height + this._stageEdgeValue.vertical;
|
|
|
|
// Extra width/height for `content-box`
|
|
var extraWidth = 0,
|
|
extraHeight = 0;
|
|
if (isBorderBox(this.$photoviewer)) {
|
|
modalWidth += this._modalEdgeValue.horizontal;
|
|
modalHeight += this._modalEdgeValue.vertical;
|
|
} else {
|
|
extraWidth += this._modalEdgeValue.horizontal;
|
|
extraHeight += this._modalEdgeValue.vertical;
|
|
}
|
|
var gapThreshold = (this.options.gapThreshold > 0 ? this.options.gapThreshold : 0) + 1;
|
|
|
|
// Modal scale relative to parent element
|
|
var scale = Math.min(offsetParentData.width / ((modalWidth + extraWidth) * gapThreshold), offsetParentData.height / ((modalHeight + extraHeight) * gapThreshold), 1);
|
|
var minWidth = Math.max(modalWidth * scale, this.options.modalWidth);
|
|
var minHeight = Math.max(modalHeight * scale, this.options.modalHeight);
|
|
minWidth = this.options.fixedModalSize ? this.options.modalWidth : Math.round(minWidth);
|
|
minHeight = this.options.fixedModalSize ? this.options.modalHeight : Math.round(minHeight);
|
|
var transLeft, transTop, transRight, transBottom;
|
|
if ($.isPlainObject(this.options.initModalPos)) {
|
|
transLeft = this.options.initModalPos.left;
|
|
transTop = this.options.initModalPos.top;
|
|
transRight = this.options.initModalPos.right;
|
|
transBottom = this.options.initModalPos.bottom;
|
|
} else {
|
|
var _offsetParentData = this._getOffsetParentData();
|
|
transLeft = (_offsetParentData.width - minWidth - extraWidth) / 2 + _offsetParentData.scrollLeft;
|
|
transTop = (_offsetParentData.height - minHeight - extraHeight) / 2 + _offsetParentData.scrollTop;
|
|
}
|
|
var modalTransCSS = {
|
|
width: minWidth,
|
|
height: minHeight,
|
|
left: transLeft,
|
|
top: transTop,
|
|
right: transRight,
|
|
bottom: transBottom
|
|
};
|
|
|
|
// Add init animation for modal
|
|
if (this.options.initAnimation) {
|
|
this.$photoviewer.animate(modalTransCSS, this.options.animationDuration, 'ease-in-out', function () {
|
|
_this2.setImageSize(img);
|
|
});
|
|
} else {
|
|
this.$photoviewer.css(modalTransCSS);
|
|
this.setImageSize(img);
|
|
}
|
|
this.isOpened = true;
|
|
}
|
|
}, {
|
|
key: "getImageScale",
|
|
value: function getImageScale(stageWidth, stageHeight) {
|
|
var scale = 1;
|
|
if (!this.isRotated) {
|
|
scale = Math.min(stageWidth / this.img.width, stageHeight / this.img.height, 1);
|
|
} else {
|
|
scale = Math.min(stageWidth / this.img.height, stageHeight / this.img.width, 1);
|
|
}
|
|
return scale;
|
|
}
|
|
}, {
|
|
key: "setImageSize",
|
|
value: function setImageSize(img) {
|
|
var stageData = {
|
|
w: this.$stage.width(),
|
|
h: this.$stage.height()
|
|
};
|
|
var scale = this.getImageScale(stageData.w, stageData.h);
|
|
this.$image.css({
|
|
width: Math.round(img.width * scale),
|
|
height: Math.round(img.height * scale),
|
|
left: (stageData.w - Math.round(img.width * scale)) / 2,
|
|
top: (stageData.h - Math.round(img.height * scale)) / 2
|
|
});
|
|
|
|
// Store image initial data
|
|
$.extend(this.imageData, {
|
|
initWidth: img.width * scale,
|
|
initHeight: img.height * scale,
|
|
initLeft: (stageData.w - img.width * scale) / 2,
|
|
initTop: (stageData.h - img.height * scale) / 2,
|
|
width: img.width * scale,
|
|
height: img.height * scale,
|
|
left: (stageData.w - img.width * scale) / 2,
|
|
top: (stageData.h - img.height * scale) / 2
|
|
});
|
|
|
|
// Set grab cursor
|
|
setGrabCursor({
|
|
w: this.$image.width(),
|
|
h: this.$image.height()
|
|
}, {
|
|
w: this.$stage.width(),
|
|
h: this.$stage.height()
|
|
}, this.$stage, this.isRotated);
|
|
|
|
// Just execute before image loaded
|
|
if (!this.imageLoaded) {
|
|
// Loader end
|
|
this.$photoviewer.find(CLASS_NS + '-loader').remove();
|
|
|
|
// Remove class after image loaded
|
|
this.$stage.removeClass('stage-ready');
|
|
this.$image.removeClass('image-ready');
|
|
|
|
// Add init animation for image
|
|
if (this.options.initAnimation && !this.options.progressiveLoading) {
|
|
this.$image.fadeIn();
|
|
}
|
|
this.imageLoaded = true;
|
|
}
|
|
}
|
|
}, {
|
|
key: "loadImage",
|
|
value: function loadImage(imgSrc, fn, err) {
|
|
var _this3 = this;
|
|
// Reset image
|
|
this.$image.removeAttr('style').attr('src', '');
|
|
this.isRotated = false;
|
|
this.rotationDegree = 0;
|
|
this.imageLoaded = false;
|
|
|
|
// Loader start
|
|
this.$photoviewer.append("<div class=\"".concat(NS, "-loader\"></div>"));
|
|
|
|
// Add class before image loaded
|
|
this.$stage.addClass('stage-ready');
|
|
this.$image.addClass('image-ready');
|
|
if (this.options.initAnimation && !this.options.progressiveLoading) {
|
|
this.$image.hide();
|
|
}
|
|
this.$image.attr('src', imgSrc);
|
|
preloadImage(imgSrc, function (img) {
|
|
// Store HTMLImageElement
|
|
_this3.img = img;
|
|
|
|
// Store original data
|
|
_this3.imageData = {
|
|
originalWidth: img.width,
|
|
originalHeight: img.height
|
|
};
|
|
if (_this3.isMaximized || _this3.isOpened && _this3.options.fixedModalPos) {
|
|
_this3.setImageSize(img);
|
|
} else {
|
|
_this3.setModalSize(img);
|
|
}
|
|
|
|
// Callback of image loaded successfully
|
|
if (fn) {
|
|
fn.call();
|
|
}
|
|
}, function () {
|
|
// Loader end
|
|
_this3.$photoviewer.find(CLASS_NS + '-loader').remove();
|
|
|
|
// Callback of image loading failed
|
|
if (err) {
|
|
err.call();
|
|
}
|
|
});
|
|
if (this.options.title) {
|
|
this.setImageTitle(imgSrc);
|
|
}
|
|
}
|
|
}, {
|
|
key: "setImageTitle",
|
|
value: function setImageTitle(url) {
|
|
var title = this.groupData[this.groupIndex].title || getImageNameFromUrl(url);
|
|
this.$title.html(title);
|
|
}
|
|
}, {
|
|
key: "jump",
|
|
value: function jump(step) {
|
|
this._triggerHook('beforeChange', [this, this.groupIndex]);
|
|
|
|
// Make sure change image after the modal animation has finished
|
|
var now = Date.now();
|
|
if (now - this._lastTimestamp >= this.options.animationDuration) {
|
|
this.groupIndex = this.groupIndex + step;
|
|
this.jumpTo(this.groupIndex);
|
|
this._lastTimestamp = now;
|
|
}
|
|
}
|
|
}, {
|
|
key: "jumpTo",
|
|
value: function jumpTo(index) {
|
|
var _this4 = this;
|
|
index = index % this.groupData.length;
|
|
if (index >= 0) {
|
|
index = index % this.groupData.length;
|
|
} else if (index < 0) {
|
|
index = (this.groupData.length + index) % this.groupData.length;
|
|
}
|
|
this.groupIndex = index;
|
|
this.loadImage(this.groupData[index].src, function () {
|
|
_this4._triggerHook('changed', [_this4, index]);
|
|
}, function () {
|
|
_this4._triggerHook('changed', [_this4, index]);
|
|
});
|
|
}
|
|
}, {
|
|
key: "wheel",
|
|
value: function wheel(e) {
|
|
e.preventDefault();
|
|
var delta = 1;
|
|
if (e.deltaY) {
|
|
delta = e.deltaY > 0 ? 1 : -1;
|
|
} else if (e.wheelDelta) {
|
|
delta = -e.wheelDelta / 120;
|
|
} else if (e.detail) {
|
|
delta = e.detail > 0 ? 1 : -1;
|
|
}
|
|
|
|
// Ratio threshold
|
|
var ratio = -delta * this.options.ratioThreshold;
|
|
|
|
// Mouse point position relative to stage
|
|
var pointer = {
|
|
x: e.clientX - this.$stage.offset().left + $D.scrollLeft(),
|
|
y: e.clientY - this.$stage.offset().top + $D.scrollTop()
|
|
};
|
|
this.zoom(ratio, pointer);
|
|
}
|
|
}, {
|
|
key: "zoom",
|
|
value: function zoom(ratio, origin) {
|
|
// Zoom out ratio & Zoom in ratio
|
|
ratio = ratio < 0 ? 1 / (1 - ratio) : 1 + ratio;
|
|
|
|
// Image ratio
|
|
ratio = this.$image.width() / this.imageData.originalWidth * ratio;
|
|
if (ratio > this.options.maxRatio || ratio < this.options.minRatio) {
|
|
return;
|
|
}
|
|
this.zoomTo(ratio, origin);
|
|
}
|
|
}, {
|
|
key: "zoomTo",
|
|
value: function zoomTo(ratio, origin) {
|
|
var $image = this.$image;
|
|
var $stage = this.$stage;
|
|
var imgData = {
|
|
w: this.imageData.width,
|
|
h: this.imageData.height,
|
|
x: this.imageData.left,
|
|
y: this.imageData.top
|
|
};
|
|
|
|
// Image stage position
|
|
// We will use it to calc the relative position of image
|
|
var stageData = {
|
|
w: $stage.width(),
|
|
h: $stage.height(),
|
|
x: $stage.offset().left,
|
|
y: $stage.offset().top
|
|
};
|
|
|
|
// Set default origin coordinates (center of stage)
|
|
if (origin === void 0) {
|
|
origin = {
|
|
x: $stage.width() / 2,
|
|
y: $stage.height() / 2
|
|
};
|
|
}
|
|
var newWidth = this.imageData.originalWidth * ratio;
|
|
var newHeight = this.imageData.originalHeight * ratio;
|
|
// Think about it for a while
|
|
var newLeft = origin.x - (origin.x - imgData.x) / imgData.w * newWidth;
|
|
var newTop = origin.y - (origin.y - imgData.y) / imgData.h * newHeight;
|
|
|
|
// δ is the difference between image new width and new height
|
|
var δ = !this.isRotated ? 0 : (newWidth - newHeight) / 2;
|
|
var imgNewWidth = !this.isRotated ? newWidth : newHeight;
|
|
var imgNewHeight = !this.isRotated ? newHeight : newWidth;
|
|
var offsetX = stageData.w - newWidth;
|
|
var offsetY = stageData.h - newHeight;
|
|
|
|
// Zoom out & Zoom in condition
|
|
// It's important and it takes me a lot of time to get it
|
|
// The conditions with image rotate 90 degree drive me crazy alomst!
|
|
if (imgNewHeight <= stageData.h) {
|
|
newTop = (stageData.h - newHeight) / 2;
|
|
} else {
|
|
newTop = newTop > δ ? δ : newTop > offsetY - δ ? newTop : offsetY - δ;
|
|
}
|
|
if (imgNewWidth <= stageData.w) {
|
|
newLeft = (stageData.w - newWidth) / 2;
|
|
} else {
|
|
newLeft = newLeft > -δ ? -δ : newLeft > offsetX + δ ? newLeft : offsetX + δ;
|
|
}
|
|
|
|
// Whether the image scale get to the critical point
|
|
if (Math.abs(this.imageData.initWidth - newWidth) < this.imageData.initWidth * 0.05) {
|
|
this.setImageSize(this.img);
|
|
} else {
|
|
$image.css({
|
|
width: Math.round(newWidth),
|
|
height: Math.round(newHeight),
|
|
left: Math.round(newLeft),
|
|
top: Math.round(newTop)
|
|
});
|
|
|
|
// Set grab cursor
|
|
setGrabCursor({
|
|
w: Math.round(imgNewWidth),
|
|
h: Math.round(imgNewHeight)
|
|
}, {
|
|
w: stageData.w,
|
|
h: stageData.h
|
|
}, this.$stage);
|
|
}
|
|
|
|
// Update image initial data
|
|
$.extend(this.imageData, {
|
|
width: newWidth,
|
|
height: newHeight,
|
|
left: newLeft,
|
|
top: newTop
|
|
});
|
|
}
|
|
}, {
|
|
key: "rotate",
|
|
value: function rotate(degree) {
|
|
this.rotationDegree = this.rotationDegree + degree;
|
|
if (this.rotationDegree / 90 % 2 === 0) {
|
|
this.isRotated = false;
|
|
} else {
|
|
this.isRotated = true;
|
|
}
|
|
this.rotateTo(this.rotationDegree);
|
|
}
|
|
}, {
|
|
key: "rotateTo",
|
|
value: function rotateTo(degree) {
|
|
this.$image.css({
|
|
transform: 'rotate(' + degree + 'deg)'
|
|
});
|
|
this.setImageSize({
|
|
width: this.imageData.originalWidth,
|
|
height: this.imageData.originalHeight
|
|
});
|
|
|
|
// Remove grab cursor when rotate
|
|
this.$stage.removeClass('is-grab');
|
|
}
|
|
}, {
|
|
key: "resize",
|
|
value: function resize() {
|
|
if (this.isOpened) {
|
|
if (this.isMaximized) {
|
|
this.setImageSize({
|
|
width: this.imageData.originalWidth,
|
|
height: this.imageData.originalHeight
|
|
});
|
|
} else {
|
|
this.setModalSize({
|
|
width: this.imageData.originalWidth,
|
|
height: this.imageData.originalHeight
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}, {
|
|
key: "maximize",
|
|
value: function maximize() {
|
|
this.$photoviewer.addClass(NS + '-maximized');
|
|
this.$photoviewer.css({
|
|
width: 'auto',
|
|
height: 'auto',
|
|
top: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
left: 0
|
|
});
|
|
this.isMaximized = true;
|
|
}
|
|
}, {
|
|
key: "exitMaximize",
|
|
value: function exitMaximize() {
|
|
this.$photoviewer.removeClass(NS + '-maximized');
|
|
this.setModalToCenter();
|
|
this.isMaximized = false;
|
|
}
|
|
}, {
|
|
key: "toggleMaximize",
|
|
value: function toggleMaximize() {
|
|
if (!this.isMaximized) {
|
|
var originalWidth = this.$photoviewer.width();
|
|
var originalHeight = this.$photoviewer.height();
|
|
if (isBorderBox(this.$photoviewer)) {
|
|
originalWidth += this._modalEdgeValue.horizontal;
|
|
originalHeight += this._modalEdgeValue.vertical;
|
|
}
|
|
|
|
// Store modal's size and position before maximized
|
|
this.modalData = {
|
|
width: originalWidth,
|
|
height: originalHeight,
|
|
left: parseFloat(this.$photoviewer.css('left')),
|
|
top: parseFloat(this.$photoviewer.css('top'))
|
|
};
|
|
this.maximize();
|
|
} else {
|
|
this.exitMaximize();
|
|
}
|
|
this.setImageSize({
|
|
width: this.imageData.originalWidth,
|
|
height: this.imageData.originalHeight
|
|
});
|
|
this.$photoviewer[0].focus();
|
|
}
|
|
}, {
|
|
key: "fullscreen",
|
|
value: function fullscreen() {
|
|
requestFullscreen(this.$photoviewer[0]);
|
|
this.$photoviewer[0].focus();
|
|
}
|
|
}, {
|
|
key: "_keydown",
|
|
value: function _keydown(e) {
|
|
if (!this.options.keyboard) {
|
|
return false;
|
|
}
|
|
e.preventDefault();
|
|
var keyCode = e.keyCode || e.which || e.charCode;
|
|
var ctrlKey = e.ctrlKey || e.metaKey;
|
|
var altKey = e.altKey;
|
|
switch (keyCode) {
|
|
// ←
|
|
case 37:
|
|
this.jump(-1);
|
|
break;
|
|
// →
|
|
case 39:
|
|
this.jump(1);
|
|
break;
|
|
// +
|
|
case 187:
|
|
this.zoom(this.options.ratioThreshold * 3, {
|
|
x: this.$stage.width() / 2,
|
|
y: this.$stage.height() / 2
|
|
}, e);
|
|
break;
|
|
// -
|
|
case 189:
|
|
this.zoom(-this.options.ratioThreshold * 3, {
|
|
x: this.$stage.width() / 2,
|
|
y: this.$stage.height() / 2
|
|
}, e);
|
|
break;
|
|
// + Firefox
|
|
case 61:
|
|
this.zoom(this.options.ratioThreshold * 3, {
|
|
x: this.$stage.width() / 2,
|
|
y: this.$stage.height() / 2
|
|
}, e);
|
|
break;
|
|
// - Firefox
|
|
case 173:
|
|
this.zoom(-this.options.ratioThreshold * 3, {
|
|
x: this.$stage.width() / 2,
|
|
y: this.$stage.height() / 2
|
|
}, e);
|
|
break;
|
|
// Ctrl + Alt + 0
|
|
case 48:
|
|
if (ctrlKey && altKey) {
|
|
this.zoomTo(1, {
|
|
x: this.$stage.width() / 2,
|
|
y: this.$stage.height() / 2
|
|
}, e);
|
|
}
|
|
break;
|
|
// Ctrl + ,
|
|
case 188:
|
|
if (ctrlKey) {
|
|
this.rotate(-90);
|
|
}
|
|
break;
|
|
// Ctrl + .
|
|
case 190:
|
|
if (ctrlKey) {
|
|
this.rotate(90);
|
|
}
|
|
break;
|
|
// Q
|
|
case 81:
|
|
this.close();
|
|
break;
|
|
// Alt + X
|
|
case 88:
|
|
if (altKey) {
|
|
this.toggleMaximize();
|
|
}
|
|
break;
|
|
// F
|
|
case 70:
|
|
this.fullscreen();
|
|
break;
|
|
}
|
|
}
|
|
}, {
|
|
key: "_addEvents",
|
|
value: function _addEvents() {
|
|
var _this5 = this;
|
|
this.$close.off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function () {
|
|
_this5.close();
|
|
});
|
|
this.$stage.off(WHEEL_EVENT + EVENT_NS).on(WHEEL_EVENT + EVENT_NS, function (e) {
|
|
_this5.wheel(e);
|
|
});
|
|
this.$zoomIn.off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function () {
|
|
_this5.zoom(_this5.options.ratioThreshold * 3);
|
|
});
|
|
this.$zoomOut.off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function () {
|
|
_this5.zoom(-_this5.options.ratioThreshold * 3);
|
|
});
|
|
this.$actualSize.off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function () {
|
|
_this5.zoomTo(1);
|
|
});
|
|
this.$prev.off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function () {
|
|
_this5.jump(-1);
|
|
});
|
|
this.$fullscreen.off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function () {
|
|
_this5.fullscreen();
|
|
});
|
|
this.$next.off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function () {
|
|
_this5.jump(1);
|
|
});
|
|
this.$rotateLeft.off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function () {
|
|
_this5.rotate(-90);
|
|
});
|
|
this.$rotateRight.off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function () {
|
|
_this5.rotate(90);
|
|
});
|
|
this.$maximize.off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function () {
|
|
_this5.toggleMaximize();
|
|
});
|
|
this.$photoviewer.off(KEYDOWN_EVENT + EVENT_NS).on(KEYDOWN_EVENT + EVENT_NS, function (e) {
|
|
_this5._keydown(e);
|
|
});
|
|
$W.on(RESIZE_EVENT + EVENT_NS, debounce(function () {
|
|
return _this5.resize();
|
|
}, 500));
|
|
}
|
|
}, {
|
|
key: "_addCustomButtonEvents",
|
|
value: function _addCustomButtonEvents() {
|
|
var _this6 = this;
|
|
var _loop = function _loop(btnKey) {
|
|
_this6.$photoviewer.find(CLASS_NS + '-button-' + btnKey).off(CLICK_EVENT + EVENT_NS).on(CLICK_EVENT + EVENT_NS, function (e) {
|
|
_this6.options.customButtons[btnKey].click.apply(_this6, [_this6, e]);
|
|
});
|
|
};
|
|
for (var btnKey in this.options.customButtons) {
|
|
_loop(btnKey);
|
|
}
|
|
}
|
|
}, {
|
|
key: "_triggerHook",
|
|
value: function _triggerHook(e, data) {
|
|
if (this.options.callbacks[e]) {
|
|
this.options.callbacks[e].apply(this, $.isArray(data) ? data : [data]);
|
|
}
|
|
}
|
|
}]);
|
|
return PhotoViewer;
|
|
}();
|
|
/**
|
|
* Add methods to PhotoViewer
|
|
*/
|
|
$.extend(PhotoViewer.prototype, draggable, movable, resizable);
|
|
|
|
/**
|
|
* Add PhotoViewer to globle
|
|
*/
|
|
window.PhotoViewer = PhotoViewer;
|
|
|
|
return PhotoViewer;
|
|
|
|
}));
|