/*!
* viewer v0.5.1
* https://github.com/fengyuanchen/viewer
*
* copyright (c) 2015-2016 fengyuan chen
* released under the mit license
*
* date: 2016-03-11t07:57:59.486z
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// amd. register as anonymous module.
define('viewer', ['jquery'], factory);
} else if (typeof exports === 'object') {
// node / commonjs
factory(require('jquery'));
} else {
// browser globals.
factory(jquery);
}
})(function ($) {
'use strict';
var $window = $(window);
var $document = $(document);
// constants
var namespace = 'viewer';
var element_viewer = document.createelement(namespace);
// classes
var class_fixed = 'viewer-fixed';
var class_open = 'viewer-open';
var class_show = 'viewer-show';
var class_hide = 'viewer-hide';
var class_hide_xs_down = 'viewer-hide-xs-down';
var class_hide_sm_down = 'viewer-hide-sm-down';
var class_hide_md_down = 'viewer-hide-md-down';
var class_fade = 'viewer-fade';
var class_in = 'viewer-in';
var class_move = 'viewer-move';
var class_active = 'viewer-active';
var class_invisible = 'viewer-invisible';
var class_transition = 'viewer-transition';
var class_fullscreen = 'viewer-fullscreen';
var class_fullscreen_exit = 'viewer-fullscreen-exit';
var class_close = 'viewer-close';
// selectors
var selector_img = 'img';
// events
var event_mousedown = 'mousedown touchstart pointerdown mspointerdown';
var event_mousemove = 'mousemove touchmove pointermove mspointermove';
var event_mouseup = 'mouseup touchend touchcancel pointerup pointercancel mspointerup mspointercancel';
var event_wheel = 'wheel mousewheel dommousescroll';
var event_transitionend = 'transitionend';
var event_load = 'load.' + namespace;
var event_keydown = 'keydown.' + namespace;
var event_click = 'click.' + namespace;
var event_resize = 'resize.' + namespace;
var event_build = 'build.' + namespace;
var event_built = 'built.' + namespace;
var event_show = 'show.' + namespace;
var event_shown = 'shown.' + namespace;
var event_hide = 'hide.' + namespace;
var event_hidden = 'hidden.' + namespace;
var event_view = 'view.' + namespace;
var event_viewed = 'viewed.' + namespace;
// supports
var support_transition = typeof element_viewer.style.transition !== 'undefined';
// others
var round = math.round;
var sqrt = math.sqrt;
var abs = math.abs;
var min = math.min;
var max = math.max;
var num = number;
function isstring(s) {
return typeof s === 'string';
}
function isnumber(n) {
return typeof n === 'number' && !isnan(n);
}
function isundefined(u) {
return typeof u === 'undefined';
}
function toarray(obj, offset) {
var args = [];
if (isnumber(offset)) { // it's necessary for ie8
args.push(offset);
}
return args.slice.apply(obj, args);
}
// custom proxy to avoid jquery's guid
function proxy(fn, context) {
var args = toarray(arguments, 2);
return function () {
return fn.apply(context, args.concat(toarray(arguments)));
};
}
function gettransform(options) {
var transforms = [];
var rotate = options.rotate;
var scalex = options.scalex;
var scaley = options.scaley;
if (isnumber(rotate)) {
transforms.push('rotate(' + rotate + 'deg)');
}
if (isnumber(scalex) && isnumber(scaley)) {
transforms.push('scale(' + scalex + ',' + scaley + ')');
}
return transforms.length ? transforms.join(' ') : 'none';
}
// force reflow to enable css3 transition
function forcereflow(element) {
return element.offsetwidth;
}
// e.g.: http://domain.com/path/to/picture.jpg?size=1280×960 -> picture.jpg
function getimagename(url) {
return isstring(url) ? url.replace(/^.*\//, '').replace(/[\?].*$/, '') : '';
}
function getimagesize(image, callback) {
var newimage;
// modern browsers
if (image.naturalwidth) {
return callback(image.naturalwidth, image.naturalheight);
}
// ie8: don't use `new image()` here
newimage = document.createelement('img');
newimage.onload = function () {
callback(this.width, this.height);
};
newimage.src = image.src;
}
function gettouchescenter(touches) {
var length = touches.length;
var pagex = 0;
var pagey = 0;
if (length) {
$.each(touches, function (i, touch) {
pagex += touch.pagex;
pagey += touch.pagey;
});
pagex /= length;
pagey /= length;
}
return {
pagex: pagex,
pagey: pagey
};
}
function getresponsiveclass(option) {
switch (option) {
case 2:
return class_hide_xs_down;
case 3:
return class_hide_sm_down;
case 4:
return class_hide_md_down;
}
}
function viewer(element, options) {
this.$element = $(element);
this.options = $.extend({}, viewer.defaults, $.isplainobject(options) && options);
this.isimg = false;
this.isbuilt = false;
this.isshown = false;
this.isviewed = false;
this.isfulled = false;
this.isplayed = false;
this.wheeling = false;
this.playing = false;
this.fading = false;
this.tooltiping = false;
this.transitioning = false;
this.action = false;
this.target = false;
this.timeout = false;
this.index = 0;
this.length = 0;
this.init();
}
viewer.prototype = {
constructor: viewer,
init: function () {
var options = this.options;
var $this = this.$element;
var isimg = $this.is(selector_img);
var $images = isimg ? $this : $this.find(selector_img);
var length = $images.length;
var ready = $.proxy(this.ready, this);
if (!length) {
return;
}
if ($.isfunction(options.build)) {
$this.one(event_build, options.build);
}
if (this.trigger(event_build).isdefaultprevented()) {
return;
}
// override `transition` option if it is not supported
if (!support_transition) {
options.transition = false;
}
this.isimg = isimg;
this.length = length;
this.count = 0;
this.$images = $images;
this.$body = $('body');
if (options.inline) {
$this.one(event_built, $.proxy(function () {
this.view();
}, this));
$images.each(function () {
if (this.complete) {
ready();
} else {
$(this).one(event_load, ready);
}
});
} else {
$this.on(event_click, $.proxy(this.start, this));
}
},
ready: function () {
this.count++;
if (this.count === this.length) {
this.build();
}
},
build: function () {
var options = this.options;
var $this = this.$element;
var $parent;
var $viewer;
var $title;
var $toolbar;
var $navbar;
var $button;
if (this.isbuilt) {
return;
}
this.$parent = $parent = $this.parent();
this.$viewer = $viewer = $(viewer.template);
this.$canvas = $viewer.find('.viewer-canvas');
this.$footer = $viewer.find('.viewer-footer');
this.$title = $title = $viewer.find('.viewer-title');
this.$toolbar = $toolbar = $viewer.find('.viewer-toolbar');
this.$navbar = $navbar = $viewer.find('.viewer-navbar');
this.$button = $button = $viewer.find('.viewer-button');
this.$tooltip = $viewer.find('.viewer-tooltip');
this.$player = $viewer.find('.viewer-player');
this.$list = $viewer.find('.viewer-list');
$title.addclass(!options.title ? class_hide : getresponsiveclass(options.title));
$toolbar.addclass(!options.toolbar ? class_hide : getresponsiveclass(options.toolbar));
$toolbar.find('li[class*=zoom]').toggleclass(class_invisible, !options.zoomable);
$toolbar.find('li[class*=flip]').toggleclass(class_invisible, !options.scalable);
if (!options.rotatable) {
$toolbar.find('li[class*=rotate]').addclass(class_invisible).appendto($toolbar);
}
$navbar.addclass(!options.navbar ? class_hide : getresponsiveclass(options.navbar));
$button.toggleclass(class_hide, !options.button);
if (options.inline) {
$button.addclass(class_fullscreen);
$viewer.css('z-index', options.zindexinline);
if ($parent.css('position') === 'static') {
$parent.css('position', 'relative');
}
} else {
$button.addclass(class_close);
$viewer.
css('z-index', options.zindex).
addclass([class_fixed, class_fade, class_hide].join(' '));
}
$this.after($viewer);
if (options.inline) {
this.render();
this.bind();
this.isshown = true;
}
this.isbuilt = true;
if ($.isfunction(options.built)) {
$this.one(event_built, options.built);
}
this.trigger(event_built);
},
unbuild: function () {
if (!this.isbuilt) {
return;
}
this.isbuilt = false;
this.$viewer.remove();
},
bind: function () {
var options = this.options;
var $this = this.$element;
if ($.isfunction(options.view)) {
$this.on(event_view, options.view);
}
if ($.isfunction(options.viewed)) {
$this.on(event_viewed, options.viewed);
}
this.$viewer.
on(event_click, $.proxy(this.click, this)).
on(event_wheel, $.proxy(this.wheel, this));
this.$canvas.on(event_mousedown, $.proxy(this.mousedown, this));
$document.
on(event_mousemove, (this._mousemove = proxy(this.mousemove, this))).
on(event_mouseup, (this._mouseup = proxy(this.mouseup, this))).
on(event_keydown, (this._keydown = proxy(this.keydown, this)));
$window.on(event_resize, (this._resize = proxy(this.resize, this)));
},
unbind: function () {
var options = this.options;
var $this = this.$element;
if ($.isfunction(options.view)) {
$this.off(event_view, options.view);
}
if ($.isfunction(options.viewed)) {
$this.off(event_viewed, options.viewed);
}
this.$viewer.
off(event_click, this.click).
off(event_wheel, this.wheel);
this.$canvas.off(event_mousedown, this.mousedown);
$document.
off(event_mousemove, this._mousemove).
off(event_mouseup, this._mouseup).
off(event_keydown, this._keydown);
$window.off(event_resize, this._resize);
},
render: function () {
this.initcontainer();
this.initviewer();
this.initlist();
this.renderviewer();
},
initcontainer: function () {
this.container = {
width: $window.innerwidth(),
height: $window.innerheight()
};
},
initviewer: function () {
var options = this.options;
var $parent = this.$parent;
var viewer;
if (options.inline) {
this.parent = viewer = {
width: max($parent.width(), options.minwidth),
height: max($parent.height(), options.minheight)
};
}
if (this.isfulled || !viewer) {
viewer = this.container;
}
this.viewer = $.extend({}, viewer);
},
renderviewer: function () {
if (this.options.inline && !this.isfulled) {
this.$viewer.css(this.viewer);
}
},
initlist: function () {
var options = this.options;
var $this = this.$element;
var $list = this.$list;
var list = [];
this.$images.each(function (i) {
var src = this.src;
var alt = this.alt || getimagename(src);
var url = options.url;
if (!src) {
return;
}
if (isstring(url)) {
url = this.getattribute(url);
} else if ($.isfunction(url)) {
url = url.call(this, this);
}
list.push(
'
' +
'
' +
''
);
});
$list.html(list.join('')).find(selector_img).one(event_load, {
filled: true
}, $.proxy(this.loadimage, this));
this.$items = $list.children();
if (options.transition) {
$this.one(event_viewed, function () {
$list.addclass(class_transition);
});
}
},
renderlist: function (index) {
var i = index || this.index;
var width = this.$items.eq(i).width();
var outerwidth = width + 1; // 1 pixel of `margin-left` width
// place the active item in the center of the screen
this.$list.css({
width: outerwidth * this.length,
marginleft: (this.viewer.width - width) / 2 - outerwidth * i
});
},
resetlist: function () {
this.$list.empty().removeclass(class_transition).css('margin-left', 0);
},
initimage: function (callback) {
var options = this.options;
var $image = this.$image;
var viewer = this.viewer;
var footerheight = this.$footer.height();
var viewerwidth = viewer.width;
var viewerheight = max(viewer.height - footerheight, footerheight);
var oldimage = this.image || {};
getimagesize($image[0], $.proxy(function (naturalwidth, naturalheight) {
var aspectratio = naturalwidth / naturalheight;
var width = viewerwidth;
var height = viewerheight;
var initialimage;
var image;
if (viewerheight * aspectratio > viewerwidth) {
height = viewerwidth / aspectratio;
} else {
width = viewerheight * aspectratio;
}
width = min(width * 0.9, naturalwidth);
height = min(height * 0.9, naturalheight);
image = {
naturalwidth: naturalwidth,
naturalheight: naturalheight,
aspectratio: aspectratio,
ratio: width / naturalwidth,
width: width,
height: height,
left: (viewerwidth - width) / 2,
top: (viewerheight - height) / 2
};
initialimage = $.extend({}, image);
if (options.rotatable) {
image.rotate = oldimage.rotate || 0;
initialimage.rotate = 0;
}
if (options.scalable) {
image.scalex = oldimage.scalex || 1;
image.scaley = oldimage.scaley || 1;
initialimage.scalex = 1;
initialimage.scaley = 1;
}
this.image = image;
this.initialimage = initialimage;
if ($.isfunction(callback)) {
callback();
}
}, this));
},
renderimage: function (callback) {
var image = this.image;
var $image = this.$image;
$image.css({
width: image.width,
height: image.height,
marginleft: image.left,
margintop: image.top,
transform: gettransform(image)
});
if ($.isfunction(callback)) {
if (this.transitioning) {
$image.one(event_transitionend, callback);
} else {
callback();
}
}
},
resetimage: function () {
if (this.$image) {
this.$image.remove();
this.$image = null;
}
},
start: function (e) {
var target = e.target;
if ($(target).is('img')) {
this.target = target;
this.show();
}
},
click: function (e) {
var $target = $(e.target);
var action = $target.data('action');
var image = this.image;
switch (action) {
case 'mix':
if (this.isplayed) {
this.stop();
} else {
if (this.options.inline) {
if (this.isfulled) {
this.exit();
} else {
this.full();
}
} else {
this.hide();
}
}
break;
case 'view':
this.view($target.data('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();
break;
case 'play':
this.play();
break;
case 'next':
this.next();
break;
case 'rotate-left':
this.rotate(-90);
break;
case 'rotate-right':
this.rotate(90);
break;
case 'flip-horizontal':
this.scalex(-image.scalex || -1);
break;
case 'flip-vertical':
this.scaley(-image.scaley || -1);
break;
default:
if (this.isplayed) {
this.stop();
}
}
},
load: function () {
var options = this.options;
var viewer = this.viewer;
var $image = this.$image;
if (this.timeout) {
cleartimeout(this.timeout);
this.timeout = false;
}
$image.removeclass(class_invisible).css('csstext', (
'width:0;' +
'height:0;' +
'margin-left:' + viewer.width / 2 + 'px;' +
'margin-top:' + viewer.height / 2 + 'px;' +
'max-width:none!important;' +
'visibility:visible;'
));
this.initimage($.proxy(function () {
$image.
toggleclass(class_transition, options.transition).
toggleclass(class_move, options.movable);
this.renderimage($.proxy(function () {
this.isviewed = true;
this.trigger(event_viewed);
}, this));
}, this));
},
loadimage: function (e) {
var image = e.target;
var $image = $(image);
var $parent = $image.parent();
var parentwidth = $parent.width();
var parentheight = $parent.height();
var filled = e.data && e.data.filled;
getimagesize(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;
}
}
$image.css({
width: width,
height: height,
marginleft: (parentwidth - width) / 2,
margintop: (parentheight - height) / 2
});
});
},
resize: function () {
this.initcontainer();
this.initviewer();
this.renderviewer();
this.renderlist();
if (this.isviewed) {
this.initimage($.proxy(function () {
this.renderimage();
}, this));
}
if (this.isplayed) {
this.$player.
find(selector_img).
one(event_load, $.proxy(this.loadimage, this)).
trigger(event_load);
}
},
wheel: function (event) {
var e = event.originalevent || event;
var ratio = num(this.options.zoomratio) || 0.1;
var delta = 1;
if (!this.isviewed) {
return;
}
event.preventdefault();
// limit wheel speed to prevent zoom too fast
if (this.wheeling) {
return;
}
this.wheeling = true;
settimeout($.proxy(function () {
this.wheeling = false;
}, this), 50);
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;
}
this.zoom(-delta * ratio, true, event);
},
keydown: function (e) {
var options = this.options;
var which = e.which;
if (!this.isfulled || !options.keyboard) {
return;
}
switch (which) {
// (key: esc)
case 27:
if (this.isplayed) {
this.stop();
} else {
if (options.inline) {
if (this.isfulled) {
this.exit();
}
} else {
this.hide();
}
}
break;
// (key: space)
case 32:
if (this.isplayed) {
this.stop();
}
break;
// view previous (key: ←)
case 37:
this.prev();
break;
// zoom in (key: ↑)
case 38:
// prevent scroll on firefox
e.preventdefault();
this.zoom(options.zoomratio, true);
break;
// view next (key: →)
case 39:
this.next();
break;
// zoom out (key: ↓)
case 40:
// prevent scroll on firefox
e.preventdefault();
this.zoom(-options.zoomratio, true);
break;
// zoom out to initial size (key: ctrl + 0)
case 48:
// go to next
// zoom in to natural size (key: ctrl + 1)
case 49:
if (e.ctrlkey || e.shiftkey) {
e.preventdefault();
this.toggle();
}
break;
// no default
}
},
mousedown: function (event) {
var options = this.options;
var originalevent = event.originalevent;
var touches = originalevent && originalevent.touches;
var e = event;
var action = options.movable ? 'move' : false;
var toucheslength;
if (!this.isviewed) {
return;
}
if (touches) {
toucheslength = touches.length;
if (toucheslength > 1) {
if (options.zoomable && toucheslength === 2) {
e = touches[1];
this.startx2 = e.pagex;
this.starty2 = e.pagey;
action = 'zoom';
} else {
return;
}
} else {
if (this.isswitchable()) {
action = 'switch';
}
}
e = touches[0];
}
if (action) {
event.preventdefault();
this.action = action;
// ie8 has `event.pagex/y`, but not `event.originalevent.pagex/y`
// ie10 has `event.originalevent.pagex/y`, but not `event.pagex/y`
this.startx = e.pagex || originalevent && originalevent.pagex;
this.starty = e.pagey || originalevent && originalevent.pagey;
}
},
mousemove: function (event) {
var options = this.options;
var action = this.action;
var $image = this.$image;
var originalevent = event.originalevent;
var touches = originalevent && originalevent.touches;
var e = event;
var toucheslength;
if (!this.isviewed) {
return;
}
if (touches) {
toucheslength = touches.length;
if (toucheslength > 1) {
if (options.zoomable && toucheslength === 2) {
e = touches[1];
this.endx2 = e.pagex;
this.endy2 = e.pagey;
} else {
return;
}
}
e = touches[0];
}
if (action) {
event.preventdefault();
if (action === 'move' && options.transition && $image.hasclass(class_transition)) {
$image.removeclass(class_transition);
}
this.endx = e.pagex || originalevent && originalevent.pagex;
this.endy = e.pagey || originalevent && originalevent.pagey;
this.change(event);
}
},
mouseup: function (event) {
var action = this.action;
if (action) {
event.preventdefault();
if (action === 'move' && this.options.transition) {
this.$image.addclass(class_transition);
}
this.action = false;
}
},
// show the viewer (only available in modal mode)
show: function () {
var options = this.options;
var $viewer;
if (options.inline || this.transitioning) {
return;
}
if (!this.isbuilt) {
this.build();
}
if ($.isfunction(options.show)) {
this.$element.one(event_show, options.show);
}
if (this.trigger(event_show).isdefaultprevented()) {
return;
}
this.$body.addclass(class_open);
$viewer = this.$viewer.removeclass(class_hide);
this.$element.one(event_shown, $.proxy(function () {
this.view(this.target ? this.$images.index(this.target) : this.index);
this.target = false;
}, this));
if (options.transition) {
this.transitioning = true;
$viewer.addclass(class_transition);
forcereflow($viewer[0]);
$viewer.one(event_transitionend, $.proxy(this.shown, this)).addclass(class_in);
} else {
$viewer.addclass(class_in);
this.shown();
}
},
// hide the viewer (only available in modal mode)
hide: function () {
var options = this.options;
var $viewer = this.$viewer;
if (options.inline || this.transitioning || !this.isshown) {
return;
}
if ($.isfunction(options.hide)) {
this.$element.one(event_hide, options.hide);
}
if (this.trigger(event_hide).isdefaultprevented()) {
return;
}
if (this.isviewed && options.transition) {
this.transitioning = true;
this.$image.one(event_transitionend, $.proxy(function () {
$viewer.one(event_transitionend, $.proxy(this.hidden, this)).removeclass(class_in);
}, this));
this.zoomto(0, false, false, true);
} else {
$viewer.removeclass(class_in);
this.hidden();
}
},
/**
* view one of the images with image's index
*
* @param {number} index
*/
view: function (index) {
var $title = this.$title;
var $image;
var $item;
var $img;
var url;
var alt;
index = number(index) || 0;
if (!this.isshown || this.isplayed || index < 0 || index >= this.length ||
this.isviewed && index === this.index) {
return;
}
if (this.trigger(event_view).isdefaultprevented()) {
return;
}
$item = this.$items.eq(index);
$img = $item.find(selector_img);
url = $img.data('originalurl');
alt = $img.attr('alt');
this.$image = $image = $('
');
if (this.isviewed) {
this.$items.eq(this.index).removeclass(class_active);
}
$item.addclass(class_active);
this.isviewed = false;
this.index = index;
this.image = null;
this.$canvas.html($image.addclass(class_invisible));
// center current item
this.renderlist();
// clear title
$title.empty();
// generate title after viewed
this.$element.one(event_viewed, $.proxy(function () {
var image = this.image;
var width = image.naturalwidth;
var height = image.naturalheight;
$title.html(alt);
}, this));
if ($image[0].complete) {
this.load();
} else {
$image.one(event_load, $.proxy(this.load, this));
if (this.timeout) {
cleartimeout(this.timeout);
}
// make the image visible if it fails to load within 1s
this.timeout = settimeout($.proxy(function () {
$image.removeclass(class_invisible);
this.timeout = false;
}, this), 1000);
}
},
// view the previous image
prev: function () {
this.view(max(this.index - 1, 0));
},
// view the next image
next: function () {
this.view(min(this.index + 1, this.length - 1));
},
/**
* move the image with relative offsets
*
* @param {number} offsetx
* @param {number} offsety (optional)
*/
move: function (offsetx, offsety) {
var image = this.image;
this.moveto(
isundefined(offsetx) ? offsetx : image.left + num(offsetx),
isundefined(offsety) ? offsety : image.top + num(offsety)
);
},
/**
* move the image to an absolute point
*
* @param {number} x
* @param {number} y (optional)
*/
moveto: function (x, y) {
var image = this.image;
var changed = false;
// if "y" is not present, its default value is "x"
if (isundefined(y)) {
y = x;
}
x = num(x);
y = num(y);
if (this.isviewed && !this.isplayed && this.options.movable) {
if (isnumber(x)) {
image.left = x;
changed = true;
}
if (isnumber(y)) {
image.top = y;
changed = true;
}
if (changed) {
this.renderimage();
}
}
},
/**
* zoom the image with a relative ratio
*
* @param {number} ratio
* @param {boolean} hastooltip (optional)
* @param {jquery event} _event (private)
*/
zoom: function (ratio, hastooltip, _event) {
var image = this.image;
ratio = num(ratio);
if (ratio < 0) {
ratio = 1 / (1 - ratio);
} else {
ratio = 1 + ratio;
}
this.zoomto(image.width * ratio / image.naturalwidth, hastooltip, _event);
},
/**
* zoom the image to an absolute ratio
*
* @param {number} ratio
* @param {boolean} hastooltip (optional)
* @param {jquery event} _event (private)
* @param {boolean} _zoomable (private)
*/
zoomto: function (ratio, hastooltip, _event, _zoomable) {
var options = this.options;
var minzoomratio = 0.01;
var maxzoomratio = 100;
var image = this.image;
var width = image.width;
var height = image.height;
var originalevent;
var newwidth;
var newheight;
var offset;
var center;
ratio = max(0, ratio);
if (isnumber(ratio) && this.isviewed && !this.isplayed && (_zoomable || options.zoomable)) {
if (!_zoomable) {
minzoomratio = max(minzoomratio, options.minzoomratio);
maxzoomratio = min(maxzoomratio, options.maxzoomratio);
ratio = min(max(ratio, minzoomratio), maxzoomratio);
}
if (ratio > 0.95 && ratio < 1.05) {
ratio = 1;
}
newwidth = image.naturalwidth * ratio;
newheight = image.naturalheight * ratio;
if (_event && (originalevent = _event.originalevent)) {
offset = this.$viewer.offset();
center = originalevent.touches ? gettouchescenter(originalevent.touches) : {
pagex: _event.pagex || originalevent.pagex || 0,
pagey: _event.pagey || originalevent.pagey || 0
};
// zoom from the triggering point of the event
image.left -= (newwidth - width) * (
((center.pagex - offset.left) - image.left) / width
);
image.top -= (newheight - height) * (
((center.pagey - offset.top) - image.top) / height
);
} else {
// zoom from the center of the image
image.left -= (newwidth - width) / 2;
image.top -= (newheight - height) / 2;
}
image.width = newwidth;
image.height = newheight;
image.ratio = ratio;
this.renderimage();
if (hastooltip) {
this.tooltip();
}
}
},
/**
* rotate the image with a relative degree
*
* @param {number} degree
*/
rotate: function (degree) {
this.rotateto((this.image.rotate || 0) + num(degree));
},
/**
* rotate the image to an absolute degree
* https://developer.mozilla.org/en-us/docs/web/css/transform-function#rotate()
*
* @param {number} degree
*/
rotateto: function (degree) {
var image = this.image;
degree = num(degree);
if (isnumber(degree) && this.isviewed && !this.isplayed && this.options.rotatable) {
image.rotate = degree;
this.renderimage();
}
},
/**
* scale the image
* https://developer.mozilla.org/en-us/docs/web/css/transform-function#scale()
*
* @param {number} scalex
* @param {number} scaley (optional)
*/
scale: function (scalex, scaley) {
var image = this.image;
var changed = false;
// if "scaley" is not present, its default value is "scalex"
if (isundefined(scaley)) {
scaley = scalex;
}
scalex = num(scalex);
scaley = num(scaley);
if (this.isviewed && !this.isplayed && this.options.scalable) {
if (isnumber(scalex)) {
image.scalex = scalex;
changed = true;
}
if (isnumber(scaley)) {
image.scaley = scaley;
changed = true;
}
if (changed) {
this.renderimage();
}
}
},
/**
* scale the abscissa of the image
*
* @param {number} scalex
*/
scalex: function (scalex) {
this.scale(scalex, this.image.scaley);
},
/**
* scale the ordinate of the image
*
* @param {number} scaley
*/
scaley: function (scaley) {
this.scale(this.image.scalex, scaley);
},
// play the images
play: function () {
var options = this.options;
var $player = this.$player;
var load = $.proxy(this.loadimage, this);
var list = [];
var total = 0;
var index = 0;
var playing;
if (!this.isshown || this.isplayed) {
return;
}
if (options.fullscreen) {
this.requestfullscreen();
}
this.isplayed = true;
$player.addclass(class_show);
this.$items.each(function (i) {
var $this = $(this);
var $img = $this.find(selector_img);
var $image = $('
');
total++;
$image.addclass(class_fade).toggleclass(class_transition, options.transition);
if ($this.hasclass(class_active)) {
$image.addclass(class_in);
index = i;
}
list.push($image);
$image.one(event_load, {
filled: false
}, load);
$player.append($image);
});
if (isnumber(options.interval) && options.interval > 0) {
playing = $.proxy(function () {
this.playing = settimeout(function () {
list[index].removeclass(class_in);
index++;
index = index < total ? index : 0;
list[index].addclass(class_in);
playing();
}, options.interval);
}, this);
if (total > 1) {
playing();
}
}
},
// stop play
stop: function () {
if (!this.isplayed) {
return;
}
if (this.options.fullscreen) {
this.exitfullscreen();
}
this.isplayed = false;
cleartimeout(this.playing);
this.$player.removeclass(class_show).empty();
},
// enter modal mode (only available in inline mode)
full: function () {
var options = this.options;
var $image = this.$image;
var $list = this.$list;
if (!this.isshown || this.isplayed || this.isfulled || !options.inline) {
return;
}
this.isfulled = true;
this.$body.addclass(class_open);
this.$button.addclass(class_fullscreen_exit);
if (options.transition) {
$image.removeclass(class_transition);
$list.removeclass(class_transition);
}
this.$viewer.addclass(class_fixed).removeattr('style').css('z-index', options.zindex);
this.initcontainer();
this.viewer = $.extend({}, this.container);
this.renderlist();
this.initimage($.proxy(function () {
this.renderimage(function () {
if (options.transition) {
settimeout(function () {
$image.addclass(class_transition);
$list.addclass(class_transition);
}, 0);
}
});
}, this));
},
// exit modal mode (only available in inline mode)
exit: function () {
var options = this.options;
var $image = this.$image;
var $list = this.$list;
if (!this.isfulled) {
return;
}
this.isfulled = false;
this.$body.removeclass(class_open);
this.$button.removeclass(class_fullscreen_exit);
if (options.transition) {
$image.removeclass(class_transition);
$list.removeclass(class_transition);
}
this.$viewer.removeclass(class_fixed).css('z-index', options.zindexinline);
this.viewer = $.extend({}, this.parent);
this.renderviewer();
this.renderlist();
this.initimage($.proxy(function () {
this.renderimage(function () {
if (options.transition) {
settimeout(function () {
$image.addclass(class_transition);
$list.addclass(class_transition);
}, 0);
}
});
}, this));
},
// show the current ratio of the image with percentage
tooltip: function () {
var options = this.options;
var $tooltip = this.$tooltip;
var image = this.image;
var classes = [
class_show,
class_fade,
class_transition
].join(' ');
if (!this.isviewed || this.isplayed || !options.tooltip) {
return;
}
$tooltip.text(round(image.ratio * 100) + '%');
if (!this.tooltiping) {
if (options.transition) {
if (this.fading) {
$tooltip.trigger(event_transitionend);
}
$tooltip.addclass(classes);
forcereflow($tooltip[0]);
$tooltip.addclass(class_in);
} else {
$tooltip.addclass(class_show);
}
} else {
cleartimeout(this.tooltiping);
}
this.tooltiping = settimeout($.proxy(function () {
if (options.transition) {
$tooltip.one(event_transitionend, $.proxy(function () {
$tooltip.removeclass(classes);
this.fading = false;
}, this)).removeclass(class_in);
this.fading = true;
} else {
$tooltip.removeclass(class_show);
}
this.tooltiping = false;
}, this), 1000);
},
// toggle the image size between its natural size and initial size
toggle: function () {
if (this.image.ratio === 1) {
this.zoomto(this.initialimage.ratio, true);
} else {
this.zoomto(1, true);
}
},
// reset the image to its initial state
reset: function () {
if (this.isviewed && !this.isplayed) {
this.image = $.extend({}, this.initialimage);
this.renderimage();
}
},
// update viewer when images changed
update: function () {
var $this = this.$element;
var $images = this.$images;
var indexes = [];
var index;
if (this.isimg) {
// destroy viewer if the target image was deleted
if (!$this.parent().length) {
return this.destroy();
}
} else {
this.$images = $images = $this.find(selector_img);
this.length = $images.length;
}
if (this.isbuilt) {
$.each(this.$items, function (i) {
var img = $(this).find('img')[0];
var image = $images[i];
if (image) {
if (image.src !== img.src) {
indexes.push(i);
}
} else {
indexes.push(i);
}
});
this.$list.width('auto');
this.initlist();
if (this.isshown) {
if (this.length) {
if (this.isviewed) {
index = $.inarray(this.index, indexes);
if (index >= 0) {
this.isviewed = false;
this.view(max(this.index - (index + 1), 0));
} else {
this.$items.eq(this.index).addclass(class_active);
}
}
} else {
this.$image = null;
this.isviewed = false;
this.index = 0;
this.image = null;
this.$canvas.empty();
this.$title.empty();
}
}
}
},
// destroy the viewer
destroy: function () {
var $this = this.$element;
if (this.options.inline) {
this.unbind();
} else {
if (this.isshown) {
this.unbind();
}
$this.off(event_click, this.start);
}
this.unbuild();
$this.removedata(namespace);
},
// a shortcut for triggering custom events
trigger: function (type, data) {
var e = $.event(type, data);
this.$element.trigger(e);
return e;
},
shown: function () {
var options = this.options;
this.transitioning = false;
this.isfulled = true;
this.isshown = true;
this.isvisible = true;
this.render();
this.bind();
if ($.isfunction(options.shown)) {
this.$element.one(event_shown, options.shown);
}
this.trigger(event_shown);
},
hidden: function () {
var options = this.options;
this.transitioning = false;
this.isviewed = false;
this.isfulled = false;
this.isshown = false;
this.isvisible = false;
this.unbind();
this.$body.removeclass(class_open);
this.$viewer.addclass(class_hide);
this.resetlist();
this.resetimage();
if ($.isfunction(options.hidden)) {
this.$element.one(event_hidden, options.hidden);
}
this.trigger(event_hidden);
},
requestfullscreen: function () {
var documentelement = document.documentelement;
if (this.isfulled && !document.fullscreenelement && !document.mozfullscreenelement &&
!document.webkitfullscreenelement && !document.msfullscreenelement) {
if (documentelement.requestfullscreen) {
documentelement.requestfullscreen();
} else if (documentelement.msrequestfullscreen) {
documentelement.msrequestfullscreen();
} else if (documentelement.mozrequestfullscreen) {
documentelement.mozrequestfullscreen();
} else if (documentelement.webkitrequestfullscreen) {
documentelement.webkitrequestfullscreen(element.allow_keyboard_input);
}
}
},
exitfullscreen: function () {
if (this.isfulled) {
if (document.exitfullscreen) {
document.exitfullscreen();
} else if (document.msexitfullscreen) {
document.msexitfullscreen();
} else if (document.mozcancelfullscreen) {
document.mozcancelfullscreen();
} else if (document.webkitexitfullscreen) {
document.webkitexitfullscreen();
}
}
},
change: function (event) {
var offsetx = this.endx - this.startx;
var offsety = this.endy - this.starty;
switch (this.action) {
// move the current image
case 'move':
this.move(offsetx, offsety);
break;
// zoom the current image
case 'zoom':
this.zoom(function (x1, y1, x2, y2) {
var z1 = sqrt(x1 * x1 + y1 * y1);
var z2 = sqrt(x2 * x2 + y2 * y2);
return (z2 - z1) / z1;
}(
abs(this.startx - this.startx2),
abs(this.starty - this.starty2),
abs(this.endx - this.endx2),
abs(this.endy - this.endy2)
), false, event);
this.startx2 = this.endx2;
this.starty2 = this.endy2;
break;
case 'switch':
this.action = 'switched';
if (abs(offsetx) > abs(offsety)) {
if (offsetx > 1) {
this.prev();
} else if (offsetx < -1) {
this.next();
}
}
break;
// no default
}
// override
this.startx = this.endx;
this.starty = this.endy;
},
isswitchable: function () {
var image = this.image;
var viewer = this.viewer;
return (image.left >= 0 && image.top >= 0 && image.width <= viewer.width &&
image.height <= viewer.height);
}
};
viewer.defaults = {
// enable inline mode
inline: false,
// show the button on the top-right of the viewer
button: true,
// show the navbar
navbar: true,
// show the title
title: true,
// show the toolbar
toolbar: true,
// show the tooltip with image ratio (percentage) when zoom in or zoom out
tooltip: true,
// enable to move the image
movable: true,
// enable to zoom the image
zoomable: true,
// enable to rotate the image
rotatable: true,
// enable to scale the image
scalable: true,
// enable css3 transition for some special elements
transition: true,
// enable to request fullscreen when play
fullscreen: true,
// enable keyboard support
keyboard: true,
// define interval of each image when playing
interval: 5000,
// min width of the viewer in inline mode
minwidth: 200,
// min height of the viewer in inline mode
minheight: 100,
// define the ratio when zoom the image by wheeling mouse
zoomratio: 0.1,
// define the min ratio of the image when zoom out
minzoomratio: 0.01,
// define the max ratio of the image when zoom in
maxzoomratio: 100,
// define the css `z-index` value of viewer in modal mode.
zindex: 2015,
// define the css `z-index` value of viewer in inline mode.
zindexinline: 0,
// define where to get the original image url for viewing
// type: string (an image attribute) or function (should return an image url)
url: 'src',
// event shortcuts
build: null,
built: null,
show: null,
shown: null,
hide: null,
hidden: null,
view: null,
viewed: null
};
viewer.setdefaults = function (options) {
$.extend(viewer.defaults, options);
};
viewer.template = (
'' +
'
' +
'' +
'
' +
'
' +
'
' +
'
'
);
// save the other viewer
viewer.other = $.fn.viewer;
// register as jquery plugin
$.fn.viewer = function (options) {
var args = toarray(arguments, 1);
var result;
this.each(function () {
var $this = $(this);
var data = $this.data(namespace);
var fn;
if (!data) {
if (/destroy|hide|exit|stop|reset/.test(options)) {
return;
}
$this.data(namespace, (data = new viewer(this, options)));
}
if (isstring(options) && $.isfunction(fn = data[options])) {
result = fn.apply(data, args);
}
});
return isundefined(result) ? this : result;
};
$.fn.viewer.constructor = viewer;
$.fn.viewer.setdefaults = viewer.setdefaults;
// no conflict
$.fn.viewer.noconflict = function () {
$.fn.viewer = viewer.other;
return this;
};
});