1 2if (!Array.isArray) { 3 Array.isArray = function (vArg) { 4 return Object.prototype.toString.call(vArg) === "[object Array]"; 5 }; 6} 7 8(function(h,o,g){var p=function(){for(var b=/audio(.min)?.js.*/,a=document.getElementsByTagName("script"),c=0,d=a.length;c<d;c++){var e=a[c].getAttribute("src");if(b.test(e))return e.replace(b,"")}}();g[h]={instanceCount:0,instances:{},flashSource:' <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="$1" width="1" height="1" name="$1" style="position: absolute; left: -1px;"> <param name="movie" value="$2?playerInstance='+h+'.instances[\'$1\']&datetime=$3"> <param name="allowscriptaccess" value="always"> <embed name="$1" src="$2?playerInstance='+ 9h+'.instances[\'$1\']&datetime=$3" width="1" height="1" allowscriptaccess="always"> </object>',settings:{autoplay:false,loop:false,preload:true,imageLocation:p+"player-graphics.gif",swfLocation:p+"audiojs.swf",useFlash:function(){var b=document.createElement("audio");return!(b.canPlayType&&b.canPlayType("audio/mpeg;").replace(/no/,""))}(),hasFlash:function(){if(navigator.plugins&&navigator.plugins.length&&navigator.plugins["Shockwave Flash"])return true;else if(navigator.mimeTypes&&navigator.mimeTypes.length){var b= 10navigator.mimeTypes["application/x-shockwave-flash"];return b&&b.enabledPlugin}else try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash");return true}catch(a){}return false}(),createPlayer:{markup:' <div class="play-pause"> <p class="play"></p> <p class="pause"></p> <p class="loading"></p> <p class="error"></p> </div> <div class="scrubber"> <div class="progress"></div> <div class="loaded"></div> </div> <div class="time"> <em class="played">00:00</em>/<strong class="duration">00:00</strong> </div> <div class="error-message"></div>', 11playPauseClass:"play-pause",scrubberClass:"scrubber",progressClass:"progress",loaderClass:"loaded",timeClass:"time",durationClass:"duration",playedClass:"played",errorMessageClass:"error-message",playingClass:"playing",loadingClass:"loading",errorClass:"error"},css:' .audiojs audio { position: absolute; left: -1px; } .audiojs { width: 460px; height: 36px; background: #404040; overflow: hidden; font-family: monospace; font-size: 12px; background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #444), color-stop(0.5, #555), color-stop(0.51, #444), color-stop(1, #444)); background-image: -moz-linear-gradient(center top, #444 0%, #555 50%, #444 51%, #444 100%); -webkit-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); -moz-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); -o-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); } .audiojs .play-pause { width: 25px; height: 40px; padding: 4px 6px; margin: 0px; float: left; overflow: hidden; border-right: 1px solid #000; } .audiojs p { display: none; width: 25px; height: 40px; margin: 0px; cursor: pointer; } .audiojs .play { display: block; } .audiojs .scrubber { position: relative; float: left; width: 280px; background: #5a5a5a; height: 14px; margin: 10px; border-top: 1px solid #3f3f3f; border-left: 0px; border-bottom: 0px; overflow: hidden; } .audiojs .progress { position: absolute; top: 0px; left: 0px; height: 14px; width: 0px; background: #ccc; z-index: 1; background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ccc), color-stop(0.5, #ddd), color-stop(0.51, #ccc), color-stop(1, #ccc)); background-image: -moz-linear-gradient(center top, #ccc 0%, #ddd 50%, #ccc 51%, #ccc 100%); } .audiojs .loaded { position: absolute; top: 0px; left: 0px; height: 14px; width: 0px; background: #000; background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #222), color-stop(0.5, #333), color-stop(0.51, #222), color-stop(1, #222)); background-image: -moz-linear-gradient(center top, #222 0%, #333 50%, #222 51%, #222 100%); } .audiojs .time { float: left; height: 36px; line-height: 36px; margin: 0px 0px 0px 6px; padding: 0px 6px 0px 12px; border-left: 1px solid #000; color: #ddd; text-shadow: 1px 1px 0px rgba(0, 0, 0, 0.5); } .audiojs .time em { padding: 0px 2px 0px 0px; color: #f9f9f9; font-style: normal; } .audiojs .time strong { padding: 0px 0px 0px 2px; font-weight: normal; } .audiojs .error-message { float: left; display: none; margin: 0px 10px; height: 36px; width: 400px; overflow: hidden; line-height: 36px; white-space: nowrap; color: #fff; text-overflow: ellipsis; -o-text-overflow: ellipsis; -icab-text-overflow: ellipsis; -khtml-text-overflow: ellipsis; -moz-text-overflow: ellipsis; -webkit-text-overflow: ellipsis; } .audiojs .error-message a { color: #eee; text-decoration: none; padding-bottom: 1px; border-bottom: 1px solid #999; white-space: wrap; } .audiojs .play { background: url("$1") -2px -1px no-repeat; } .audiojs .loading { background: url("$1") -2px -31px no-repeat; } .audiojs .error { background: url("$1") -2px -61px no-repeat; } .audiojs .pause { background: url("$1") -2px -91px no-repeat; } .playing .play, .playing .loading, .playing .error { display: none; } .playing .pause { display: block; } .loading .play, .loading .pause, .loading .error { display: none; } .loading .loading { display: block; } .error .time, .error .play, .error .pause, .error .scrubber, .error .loading { display: none; } .error .error { display: block; } .error .play-pause p { cursor: auto; } .error .error-message { display: block; }', 12trackEnded:function(){},flashError:function(){var b=this.settings.createPlayer,a=j(b.errorMessageClass,this.wrapper),c='Missing <a href="http://get.adobe.com/flashplayer/">
12flash player</a> plugin.';if(this.mp3)c+=' <a href="'+this.mp3+'">Download audio file</a>.';g[h].helpers.removeClass(this.wrapper,b.loadingClass);g[h].helpers.addClass(this.wrapper,b.errorClass);a.innerHTML=c},loadError:function(){var b=this.settings.createPlayer,a=j(b.errorMessageClass,this.wrapper);g[h].helpers.removeClass(this.wrapper, 13b.loadingClass);g[h].helpers.addClass(this.wrapper,b.errorClass);a.innerHTML='Error loading: "'+this.mp3+'"'},init:function(){g[h].helpers.addClass(this.wrapper,this.settings.createPlayer.loadingClass)},loadStarted:function(){var b=this.settings.createPlayer,a=j(b.durationClass,this.wrapper),c=Math.floor(this.duration/60),d=Math.floor(this.duration%60);g[h].helpers.removeClass(this.wrapper,b.loadingClass);a.innerHTML=(c<10?"0":"")+c+":"+(d<10?"0":"")+d},loadProgress:function(b){var a=this.settings.createPlayer, 14c=j(a.scrubberClass,this.wrapper);j(a.loaderClass,this.wrapper).style.width=c.offsetWidth*b+"px"},playPause:function(){this.playing?this.settings.play():this.settings.pause()},play:function(){g[h].helpers.addClass(this.wrapper,this.settings.createPlayer.playingClass)},pause:function(){g[h].helpers.removeClass(this.wrapper,this.settings.createPlayer.playingClass)},updatePlayhead:function(b){var a=this.settings.createPlayer,c=j(a.scrubberClass,this.wrapper);j(a.progressClass,this.wrapper).style.width= 15c.offsetWidth*b+"px";a=j(a.playedClass,this.wrapper);c=this.duration*b;b=Math.floor(c/60);c=Math.floor(c%60);a.innerHTML=(b<10?"0":"")+b+":"+(c<10?"0":"")+c}},create:function(b,a){a=a||{};return b.length?this.createAll(a,b):this.newInstance(b,a)},createAll:function(b,a){var c=a||document.getElementsByTagName("audio"),d=[];b=b||{};for(var e=0,i=c.length;e<i;e++)d.push(this.newInstance(c[e],b));return d},newInstance:function(b,a){var c=this.helpers.clone(this.settings),d="audiojs"+this.instanceCount, 16e="audiojs_wrapper"+this.instanceCount;this.instanceCount++;
16if(b.getAttribute("autoplay")!=null)c.autoplay=true;if(b.getAttribute("loop")!=null)c.loop=true;if(b.getAttribute("preload")=="none")c.preload=false;a&&this.helpers.merge(c,a);if(c.createPlayer.markup)b=this.createPlayer(b,c.createPlayer,e);else b.parentNode.setAttribute("id",e);e=new g[o](b,c);c.css&&this.helpers.injectCss(e,c.css);if(c.useFlash&&c.hasFlash){this.injectFlash(e,d);this.attachFlashEvents(e.wrapper,e)}else c.useFlash&&!c.hasFlash&& 17this.settings.flashError.apply(e);if(!c.useFlash||c.useFlash&&c.hasFlash)this.attachEvents(e.wrapper,e);return this.instances[d]=e},createPlayer:function(b,a,c){var d=document.createElement("div"),e=b.cloneNode(true);d.setAttribute("class","audiojs");d.setAttribute("className","audiojs");d.setAttribute("id",c);if(e.outerHTML&&!document.createElement("audio").canPlayType){e=this.helpers.cloneHtml5Node(b);d.innerHTML=a.markup;d.appendChild(e);b.outerHTML=d.outerHTML;d=document.getElementById(c)}else{d.appendChild(e); 18d.innerHTML+=a.markup;b.parentNode.replaceChild(d,b)}return d.getElementsByTagName("audio")[0]},attachEvents:function(b,a){if(a.settings.createPlayer){var c=a.settings.createPlayer,d=j(c.playPauseClass,b),e=j(c.scrubberClass,b);g[h].events.addListener(d,"click",function(){a.playPause.apply(a)});g[h].events.addListener(e,"click",function(i){i=i.clientX;var f=this,k=0;if(f.offsetParent){do k+=f.offsetLeft;while(f=f.offsetParent)}a.skipTo((i-k)/e.offsetWidth)});if(!a.settings.useFlash){g[h].events.trackLoadProgress(a); 19g[h].events.addListener(a.element,"timeupdate",function(){a.updatePlayhead.apply(a)});g[h].events.addListener(a.element,"ended",function(){a.trackEnded.apply(a)});g[h].events.addListener(a.source,"error",function(){clearInterval(a.readyTimer);clearInterval(a.loadTimer);a.settings.loadError.apply(a)})}}},attachFlashEvents:function(b,a){a.swfReady=false;a.load=function(c){a.mp3=c;a.swfReady&&a.element.load(c)};a.loadProgress=function(c,d){a.loadedPercent=c;a.duration=d;a.settings.loadStarted.apply(a); 20a.settings.loadProgress.apply(a,[c])};a.skipTo=function(c){if(!(c>a.loadedPercent)){a.updatePlayhead.call(a,[c]);a.element.skipTo(c)}};a.updatePlayhead=function(c){a.settings.updatePlayhead.apply(a,[c])};a.play=function(){if(!a.settings.preload){a.settings.preload=true;a.element.init(a.mp3)}a.playing=true;a.element.pplay();a.settings.play.apply(a)};a.pause=function(){a.playing=false;a.element.ppause();a.settings.pause.apply(a)};a.setVolume=function(c){a.element.setVolume(c)};a.loadStarted=function(){a.swfReady= 21true;a.settings.preload&&a.element.init(a.mp3);a.settings.autoplay&&a.play.apply(a)}},injectFlash:function(b,a){var c=this.flashSource.replace(/\$1/g,a);c=c.replace(/\$2/g,b.settings.swfLocation);c=c.replace(/\$3/g,+new Date+Math.random());var d=b.wrapper.innerHTML,e=document.createElement("div");e.innerHTML=c+d;b.wrapper.innerHTML=e.innerHTML;b.element=this.helpers.getSwf(a)},helpers:{merge:function(b,a){for(attr in a)if(b.hasOwnProperty(attr)||a.hasOwnProperty(attr))b[attr]=a[attr]},clone:function(b){if(b== 22null||typeof b!=="object")return b;var a=new b.constructor,c;for(c in b)a[c]=arguments.callee(b[c]);return a},addClass:function(b,a){RegExp("(\\s|^)"+a+"(\\s|$)").test(b.className)||(b.className+=" "+a)},removeClass:function(b,a){b.className=b.className.replace(RegExp("(\\s|^)"+a+"(\\s|$)")," ")},injectCss:function(b,a){for(var c="",d=document.getElementsByTagName("style"),e=a.replace(/\$1/g,b.settings.imageLocation),i=0,f=d.length;i<f;i++){var k=d[i].getAttribute("title");if(k&&~k.indexOf("audiojs")){f= 23d[i];if(f.innerHTML===e)return;c=f.innerHTML;break}}d=document.getElementsByTagName("head")[0];i=d.firstChild;f=document.createElement("style");if(d){f.setAttribute("type","text/css");f.setAttribute("title","audiojs");if(f.styleSheet)f.styleSheet.cssText=c+e;else f.appendChild(document.createTextNode(c+e));i?d.insertBefore(f,i):d.appendChild(styleElement)}},cloneHtml5Node:function(b){var a=document.createDocumentFragment(),c=a.createElement?a:document;c.createElement("audio");c=c.createElement("div"); 24a.appendChild(c);c.innerHTML=b.outerHTML;return c.firstChild},getSwf:function(b){b=document[b]||window[b];return b.length>1?b[b.length-1]:b}},events:{memoryLeaking:false,listeners:[],addListener:function(b,a,c){if(b.addEventListener)b.addEventListener(a,c,false);else if(b.attachEvent){this.listeners.push(b);if(!this.memoryLeaking){window.attachEvent("onunload",function(){if(this.listeners)for(var d=0,e=this.listeners.length;d<e;d++)g[h].events.purge(this.listeners[d])});this.memoryLeaking=true}b.attachEvent("on"+ 25a,function(){c.call(b,window.event)})}},trackLoadProgress:function(b){if(b.settings.preload){var a,c;b=b;var d=/(ipod|iphone|ipad)/i.test(navigator.userAgent);a=setInterval(function(){if(b.element.readyState>
25-1)d||b.init.apply(b);if(b.element.readyState>1){b.settings.autoplay&&b.play.apply(b);clearInterval(a);c=setInterval(function(){b.loadProgress.apply(b);b.loadedPercent>=1&&clearInterval(c)})}},10);b.readyTimer=a;b.loadTimer=c}},purge:function(b){var a=b.attributes,c;if(a)for(c=0;c<a.length;c+= 261)if(typeof b[a[c].name]==="function")b[a[c].name]=null;if(a=b.childNodes)for(c=0;c<a.length;c+=1)purge(b.childNodes[c])},ready:function(){return function(b){var a=window,c=false,d=true,e=a.document,i=e.documentElement,f=e.addEventListener?"addEventListener":"attachEvent",k=e.addEventListener?"removeEventListener":"detachEvent",n=e.addEventListener?"":"on",m=function(l){if(!(l.type=="readystatechange"&&e.readyState!="complete")){(l.type=="load"?a:e)[k](n+l.type,m,false);if(!c&&(c=true))b.call(a,l.type|| 27l)}},q=function(){try{i.doScroll("left")}catch(l){setTimeout(q,50);return}m("poll")};if(e.readyState=="complete")b.call(a,"lazy");else{if(e.createEventObject&&i.doScroll){try{d=!a.frameElement}catch(r){}d&&q()}e[f](n+"DOMContentLoaded",m,false);e[f](n+"readystatechange",m,false);a[f](n+"load",m,false)}}}()}};g[o]=function(b,a){this.element=b;this.wrapper=b.parentNode;this.source=b.getElementsByTagName("source")[0]||b;this.mp3=function(c){var d=c.getElementsByTagName("source")[0];return c.getAttribute("src")|| 28(d?d.getAttribute("src"):null)}(b);this.settings=a;this.loadStartedCalled=false;this.loadedPercent=0;this.duration=1;this.playing=false};g[o].prototype={updatePlayhead:function(){this.settings.updatePlayhead.apply(this,[this.element.currentTime/this.duration])},skipTo:function(b){if(!(b>this.loadedPercent)){this.element.currentTime=this.duration*b;this.updatePlayhead()}},load:function(b){this.loadStartedCalled=false;this.source.setAttribute("src",b);this.element.load();this.mp3=b;g[h].events.trackLoadProgress(this)}, 29loadError:function(){this.settings.loadError.apply(this)},init:function(){this.settings.init.apply(this)},loadStarted:function(){if(!this.element.duration)return false;this.duration=this.element.duration;this.updatePlayhead();this.settings.loadStarted.apply(this)},loadProgress:function(){if(this.element.buffered!=null&&this.element.buffered.length){if(!this.loadStartedCalled)this.loadStartedCalled=this.loadStarted();this.loadedPercent=this.element.buffered.end(this.element.buffered.length-1)/this.duration; 30this.settings.loadProgress.apply(this,[this.loadedPercent])}},playPause:function(){this.playing?this.pause():this.play()},play:function(){/(ipod|iphone|ipad)/i.test(navigator.userAgent)&&this.element.readyState==0&&this.init.apply(this);if(!this.settings.preload){this.settings.preload=true;this.element.setAttribute("preload","auto");g[h].events.trackLoadProgress(this)}this.playing=true;this.element.play();this.settings.play.apply(this)},pause:function(){this.playing=false;this.element.pause();this.settings.pause.apply(this)}, 31setVolume:function(b){this.element.volume=b},trackEnded:function(){this.skipTo.apply(this,[0]);this.settings.loop||this.pause.apply(this);this.settings.trackEnded.apply(this)}};var j=function(b,a){var c=[];a=a||document;if(a.getElementsByClassName)c=a.getElementsByClassName(b);else{var d,e,i=a.getElementsByTagName("*"),f=RegExp("(^|\\s)"+b+"(\\s|$)");d=0;for(e=i.length;d<e;d++)f.test(i[d].className)&&c.push(i[d])}return c.length>1?c:c[0]}})("audiojs","audiojsInstance",this); 32 33/*! 34 * JavaScript Cookie v2.0.4 35 * https://github.com/js-cookie/js-cookie 36 * 37 * Copyright 2006, 2015 Klaus Hartl & Fagner Brack 38 * Released under the MIT license 39 */ 40(function (factory) { 41 if (typeof define === 'function' && define.amd) { 42 define(factory); 43 } else if (typeof exports === 'object') { 44 module.exports = factory(); 45 } else { 46 var _OldCookies = window.Cookies; 47 var api = window.Cookies = factory(); 48 api.noConflict = function () { 49 window.Cookies = _OldCookies; 50 return api; 51 }; 52 } 53}(function () { 54 function extend () { 55 var i = 0; 56 var result = {}; 57 for (; i < arguments.length; i++) { 58 var attributes = arguments[ i ]; 59 for (var key in attributes) { 60 result[key] = attributes[key]; 61 } 62 } 63 return result; 64 } 65 66 function init (converter) { 67 function api (key, value, attributes) { 68 var result; 69 70 // Write 71 72 if (arguments.length > 1) { 73 attributes = extend({ 74 path: '/' 75 }, api.defaults, attributes); 76 77 if (typeof attributes.expires === 'number') { 78 var expires = new Date(); 79 expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); 80 attributes.expires = expires; 81 } 82
vendor: 2,067 bytes, lines 83-159
83 try { 84 result = JSON.stringify(value); 85 if (/^[\{\[]/.test(result)) { 86 value = result; 87 } 88 } catch (e) {} 89 90 if (!converter.write) { 91 value = encodeURIComponent(String(value)) 92 .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); 93 } else { 94 value = converter.write(value, key); 95 } 96 97 key = encodeURIComponent(String(key)); 98 key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); 99 key = key.replace(/[\(\)]/g, escape); 100 101 return (document.cookie = [ 102 key, '=', value, 103 attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE 104 attributes.path && '; path=' + attributes.path, 105 attributes.domain && '; domain=' + attributes.domain, 106 attributes.secure ? '; secure' : '' 107 ].join('')); 108 } 109 110 // Read 111 112 if (!key) { 113 result = {}; 114 } 115 116 // To prevent the for loop in the first place assign an empty array 117 // in case there are no cookies at all. Also prevents odd result when 118 // calling "get()" 119 var cookies = document.cookie ? document.cookie.split('; ') : []; 120 var rdecode = /(%[0-9A-Z]{2})+/g; 121 var i = 0; 122 123 for (; i < cookies.length; i++) { 124 var parts = cookies[i].split('='); 125 var name = parts[0].replace(rdecode, decodeURIComponent); 126 var cookie = parts.slice(1).join('='); 127 128 if (cookie.charAt(0) === '"') { 129 cookie = cookie.slice(1, -1); 130 } 131 132 try { 133 cookie = converter.read ? 134 converter.read(cookie, name) : converter(cookie, name) || 135 cookie.replace(rdecode, decodeURIComponent); 136 137 if (this.json) { 138 try { 139 cookie = JSON.parse(cookie); 140 } catch (e) {} 141 } 142 143 if (key === name) { 144 result = cookie; 145 break; 146 } 147 148 if (!key) { 149 result[name] = cookie; 150 } 151 } catch (e) {} 152 } 153 154 return result; 155 } 156 157 api.get = api.set = api; 158 api.getJSON = function () { 159 return api.apply({
160 json: true 161 }, [].slice.call(arguments)); 162 }; 163 api.defaults = {}; 164 165 api.remove = function (key, attributes) { 166 api(key, '', extend(attributes, { 167 expires: -1 168 })); 169 }; 170 171 api.withConverter = init; 172 173 return api; 174 } 175 176 return init(function () {}); 177})); 178/** 179 * Owl carousel 180 * @version 2.0.0 181 * @author Bartosz Wojciechowski 182 * @license The MIT License (MIT) 183 * @todo Lazy Load Icon 184 * @todo prevent animationend bubling 185 * @todo itemsScaleUp 186 * @todo Test Zepto 187 * @todo stagePadding calculate wrong active classes 188 */ 189;(function($, window, document, undefined) { 190 191 var drag, state, e; 192 193 /** 194 * Template for status information about drag and touch events. 195 * @private 196 */ 197 drag = { 198 start: 0, 199 startX: 0, 200 startY: 0, 201 current: 0, 202 currentX: 0, 203 currentY: 0, 204 offsetX: 0, 205 offsetY: 0, 206 distance: null, 207 startTime: 0, 208 endTime: 0, 209 updatedX: 0, 210 targetEl: null 211 }; 212 213 /** 214 * Template for some status informations. 215 * @private 216 */ 217 state = { 218 isTouch: false, 219 isScrolling: false, 220 isSwiping: false, 221 direction: false, 222 inMotion: false 223 }; 224 225 /** 226 * Event functions references. 227 * @private 228 */ 229 e = { 230 _onDragStart: null, 231 _onDragMove: null, 232 _onDragEnd: null, 233 _transitionEnd: null, 234 _resizer: null, 235 _responsiveCall: null, 236 _goToLoop: null, 237 _checkVisibile: null 238 }; 239 240 /** 241 * Creates a carousel. 242 * @class The Owl Carousel. 243 * @public 244 * @param {HTMLElement|jQuery} element - The element to create the carousel for. 245 * @param {Object} [options] - The options 246 */ 247 function Owl(element, options) { 248 249 /** 250 * Current settings for the carousel. 251 * @public 252 */ 253 this.settings = null; 254 255 /** 256 * Current options set by the caller including defaults. 257 * @public 258 */ 259 this.options = $.extend({}, Owl.Defaults, options); 260 261 /** 262 * Plugin element. 263 * @public 264 */ 265 this.$element = $(element); 266 267 /** 268 * Caches informations about drag and touch events. 269 */ 270 this.drag = $.extend({}, drag); 271 272 /** 273 * Caches some status informations. 274 * @protected 275 */ 276 this.state = $.extend({}, state); 277 278 /** 279 * @protected 280 * @todo Must be documented 281 */ 282 this.e = $.extend({}, e); 283 284 /** 285 * References to the running plugins of this carousel. 286 * @protected 287 */ 288 this._plugins = {}; 289 290 /** 291 * Currently suppressed events to prevent them from beeing retriggered. 292 * @protected 293 */ 294 this._supress = {}; 295 296 /** 297 * Absolute current position. 298 * @protected 299 */ 300 this._current = null; 301 302 /** 303 * Animation speed in milliseconds. 304 * @protected 305 */ 306 this._speed = null; 307 308 /** 309 * Coordinates of all items in pixel. 310 * @todo The name of this member is missleading. 311 * @protected 312 */ 313 this._coordinates = []; 314 315 /** 316 * Current breakpoint. 317 * @todo Real media queries would be nice. 318 * @protected 319 */ 320 this._breakpoint = null; 321 322 /** 323 * Current width of the plugin element. 324 */ 325 this._width = null; 326 327 /** 328 * All real items. 329 * @protected 330 */ 331 this._items = []; 332 333 /** 334 * All cloned items. 335 * @protected 336 */ 337 this._clones = []; 338 339 /** 340 * Merge values of all items. 341 * @todo Maybe this could be part of a plugin. 342 * @protected 343 */ 344 this._mergers = []; 345 346 /** 347 * Invalidated parts within the update process. 348 * @protected 349 */ 350 this._invalidated = {}; 351 352 /** 353 * Ordered list of workers for the update process. 354 * @protected 355 */ 356 this._pipe = []; 357 358 $.each(Owl.Plugins, $.proxy(function(key, plugin) { 359 this._plugins[key[0].toLowerCase() + key.slice(1)] 360 = new plugin(this); 361 }, this)); 362 363 $.each(Owl.Pipe, $.proxy(function(priority, worker) { 364 this._pipe.push({ 365 'filter': worker.filter, 366 'run': $.proxy(worker.run, this) 367 }); 368 }, this)); 369 370 this.setup(); 371 this.initialize(); 372 } 373 374 /** 375 * Default options for the carousel. 376 * @public 377 */ 378 Owl.Defaults = { 379 items: 3, 380 loop: false, 381 center: false, 382 383 mouseDrag: true, 384 touchDrag: true, 385 pullDrag: true, 386 freeDrag: false, 387 388 margin: 0, 389 stagePadding: 0, 390 391 merge: false, 392 mergeFit: true, 393 autoWidth: false, 394 395 startPosition: 0, 396 rtl: false, 397 398 smartSpeed: 250, 399 fluidSpeed: false, 400 dragEndSpeed: false, 401 402 responsive: {}, 403 responsiveRefreshRate: 200, 404 responsiveBaseElement: window,
405 responsiveClass: false, 406 407 fallbackEasing: 'swing', 408 409 info: false, 410 411 nestedItemSelector: false, 412 itemElement: 'div', 413 stageElement: 'div', 414 415 // Classes and Names 416 themeClass: 'owl-theme', 417 baseClass: 'owl-carousel', 418 itemClass: 'owl-item', 419 centerClass: 'center', 420 activeClass: 'active' 421 }; 422 423 /** 424 * Enumeration for width. 425 * @public 426 * @readonly 427 * @enum {String} 428 */ 429 Owl.Width = { 430 Default: 'default', 431 Inner: 'inner', 432 Outer: 'outer' 433 }; 434 435 /** 436 * Contains all registered plugins. 437 * @public 438 */ 439 Owl.Plugins = {}; 440 441 /** 442 * Update pipe. 443 */ 444 Owl.Pipe = [ { 445 filter: [ 'width', 'items', 'settings' ], 446 run: function(cache) { 447 cache.current = this._items && this._items[this.relative(this._current)]; 448 } 449 }, { 450 filter: [ 'items', 'settings' ], 451 run: function() { 452 var cached = this._clones, 453 clones = this.$stage.children('.cloned'); 454 455 if (clones.length !== cached.length || (!this.settings.loop && cached.length > 0)) { 456 this.$stage.children('.cloned').remove(); 457 this._clones = []; 458 } 459 } 460 }, { 461 filter: [ 'items', 'settings' ], 462 run: function() { 463 var i, n, 464 clones = this._clones, 465 items = this._items, 466 delta = this.settings.loop ? clones.length - Math.max(this.settings.items * 2, 4) : 0; 467 468 for (i = 0, n = Math.abs(delta / 2); i < n; i++) { 469 if (delta > 0) { 470 this.$stage.children().eq(items.length + clones.length - 1).remove(); 471 clones.pop(); 472 this.$stage.children().eq(0).remove(); 473 clones.pop(); 474 } else { 475 clones.push(clones.length / 2); 476 this.$stage.append(items[clones[clones.length - 1]].clone().addClass('cloned')); 477 clones.push(items.length - 1 - (clones.length - 1) / 2); 478 this.$stage.prepend(items[clones[clones.length - 1]].clone().addClass('cloned')); 479 } 480 } 481 } 482 }, { 483 filter: [ 'width', 'items', 'settings' ], 484 run: function() { 485 var rtl = (this.settings.rtl ? 1 : -1), 486 width = (this.width() / this.settings.items).toFixed(3), 487 coordinate = 0, merge, i, n; 488 489 this._coordinates = []; 490 for (i = 0, n = this._clones.length + this._items.length; i < n; i++) { 491 merge = this._mergers[this.relative(i)]; 492 merge = (this.settings.mergeFit && Math.min(merge, this.settings.items)) || merge; 493 coordinate += (this.settings.autoWidth ? this._items[this.relative(i)].width() + this.settings.margin : width * merge) * rtl; 494 495 this._coordinates.push(coordinate); 496 } 497 } 498 }, { 499 filter: [ 'width', 'items', 'settings' ], 500 run: function() { 501 var i, n, width = (this.width() / this.settings.items).toFixed(3), css = { 502 'width': Math.abs(this._coordinates[this._coordinates.length - 1]) + this.settings.stagePadding * 2, 503 'padding-left': this.settings.stagePadding || '', 504 'padding-right': this.settings.stagePadding || '' 505 }; 506 507 this.$stage.css(css); 508 509 css = { 'width': this.settings.autoWidth ? 'auto' : width - this.settings.margin }; 510 css[this.settings.rtl ? 'margin-left' : 'margin-right'] = this.settings.margin; 511 512 if (!this.settings.autoWidth && $.grep(this._mergers, function(v) { return v > 1 }).length > 0) { 513 for (i = 0, n = this._coordinates.length; i < n; i++) { 514 css.width = Math.abs(this._coordinates[i]) - Math.abs(this._coordinates[i - 1] || 0) - this.settings.margin; 515 this.$stage.children().eq(i).css(css); 516 } 517 } else { 518 this.$stage.children().css(css); 519 } 520 } 521 }, { 522 filter: [ 'width', 'items', 'settings' ], 523 run: function(cache) { 524 cache.current && this.reset(this.$stage.children().index(cache.current)); 525 } 526 }, { 527 filter: [ 'position' ], 528 run: function() { 529 this.animate(this.coordinates(this._current)); 530 } 531 }, { 532 filter: [ 'width', 'position', 'items', 'settings' ], 533 run: function() { 534 var rtl = this.settings.rtl ? 1 : -1, 535 padding = this.settings.stagePadding * 2, 536 begin = this.coordinates(this.current()) + padding, 537 end = begin + this.width() * rtl, 538 inner, outer, matches = [], i, n; 539 540 for (i = 0, n = this._coordinates.length; i < n; i++) { 541 inner = this._coordinates[i - 1] || 0; 542 outer = Math.abs(this._coordinates[i]) + padding * rtl; 543 544 if ((this.op(inner, '<=', begin) && (this.op(inner, '>', end))) 545 || (this.op(outer, '<', begin) && this.op(outer, '>', end))) { 546 matches.push(i); 547 } 548 } 549 550 this.$stage.children('.' + this.settings.activeClass).removeClass(this.settings.activeClass); 551 this.$stage.children(':eq(' + matches.join('), :eq(') + ')').addClass(this.settings.activeClass); 552 553 if (this.settings.center) { 554 this.$stage.children('.' + this.settings.centerClass).removeClass(this.settings.centerClass); 555 this.$stage.children().eq(this.current()).addClass(this.settings.centerClass); 556 } 557 } 558 } ]; 559 560 /** 561 * Initializes the carousel. 562 * @protected 563 */ 564 Owl.prototype.initialize = function() { 565 this.trigger('initialize'); 566 567 this.$element 568 .addClass(this.settings.baseClass) 569 .addClass(this.settings.themeClass) 570 .toggleClass('owl-rtl', this.settings.rtl); 571 572 // check support 573 this.browserSupport(); 574 575 if (this.settings.autoWidth && this.state.imagesLoaded !== true) { 576 var imgs, nestedSelector, width; 577 imgs = this.$element.find('img'); 578 nestedSelector = this.settings.nestedItemSelector ? '.' + this.settings.nestedItemSelector : undefined; 579 width = this.$element.children(nestedSelector).width(); 580 581 if (imgs.length && width <= 0) { 582 this.preloadAutoWidthImages(imgs); 583 return false; 584 } 585 } 586 587 this.$element.addClass('owl-loading'); 588 589 // create stage 590 this.$stage = $('<' + this.settings.stageElement + ' class="owl-stage"/>') 591 .wrap('<div class="owl-stage-outer">'); 592 593 // append stage 594 this.$element.append(this.$stage.parent()); 595 596 // append content 597 this.replace(this.$element.children().not(this.$stage.parent())); 598 599 // set view width 600 this._width = this.$element.width(); 601 602 // update view 603 this.refresh();
604 605 this.$element.removeClass('owl-loading').addClass('owl-loaded'); 606 607 // attach generic events 608 this.eventsCall(); 609 610 // attach generic events 611 this.internalEvents(); 612 613 // attach custom control events 614 this.addTriggerableEvents(); 615 616 this.trigger('initialized'); 617 }; 618 619 /** 620 * Setups the current settings. 621 * @todo Remove responsive classes. Why should adaptive designs be brought into IE8? 622 * @todo Support for media queries by using `matchMedia` would be nice. 623 * @public 624 */ 625 Owl.prototype.setup = function() { 626 var viewport = this.viewport(), 627 overwrites = this.options.responsive, 628 match = -1, 629 settings = null; 630 631 if (!overwrites) { 632 settings = $.extend({}, this.options); 633 } else { 634 $.each(overwrites, function(breakpoint) { 635 if (breakpoint <= viewport && breakpoint > match) { 636 match = Number(breakpoint); 637 } 638 }); 639 640 settings = $.extend({}, this.options, overwrites[match]); 641 delete settings.responsive; 642 643 // responsive class 644 if (settings.responsiveClass) { 645 this.$element.attr('class', function(i, c) { 646 return c.replace(/\b owl-responsive-\S+/g, ''); 647 }).addClass('owl-responsive-' + match); 648 } 649 } 650 651 if (this.settings === null || this._breakpoint !== match) { 652 this.trigger('change', { property: { name: 'settings', value: settings } }); 653 this._breakpoint = match; 654 this.settings = settings; 655 this.invalidate('settings'); 656 this.trigger('changed', { property: { name: 'settings', value: this.settings } }); 657 } 658 }; 659 660 /** 661 * Updates option logic if necessery. 662 * @protected 663 */ 664 Owl.prototype.optionsLogic = function() { 665 // Toggle Center class 666 this.$element.toggleClass('owl-center', this.settings.center); 667 668 // if items number is less than in body 669 if (this.settings.loop && this._items.length < this.settings.items) { 670 this.settings.loop = false; 671 } 672 673 if (this.settings.autoWidth) { 674 this.settings.stagePadding = false; 675 this.settings.merge = false; 676 } 677 }; 678 679 /** 680 * Prepares an item before add. 681 * @todo Rename event parameter `content` to `item`. 682 * @protected 683 * @returns {jQuery|HTMLElement} - The item container. 684 */ 685 Owl.prototype.prepare = function(item) { 686 var event = this.trigger('prepare', { content: item }); 687 688 if (!event.data) { 689 event.data = $('<' + this.settings.itemElement + '/>') 690 .addClass(this.settings.itemClass).append(item) 691 } 692 693 this.trigger('prepared', { content: event.data }); 694 695 return event.data; 696 }; 697 698 /** 699 * Updates the view. 700 * @public 701 */ 702 Owl.prototype.update = function() { 703 var i = 0, 704 n = this._pipe.length, 705 filter = $.proxy(function(p) { return this[p] }, this._invalidated), 706 cache = {}; 707 708 while (i < n) { 709 if (this._invalidated.all || $.grep(this._pipe[i].filter, filter).length > 0) { 710 this._pipe[i].run(cache); 711 } 712 i++; 713 } 714 715 this._invalidated = {}; 716 }; 717 718 /** 719 * Gets the width of the view. 720 * @public 721 * @param {Owl.Width} [dimension=Owl.Width.Default] - The dimension to return. 722 * @returns {Number} - The width of the view in pixel. 723 */ 724 Owl.prototype.width = function(dimension) { 725 dimension = dimension || Owl.Width.Default; 726 switch (dimension) { 727 case Owl.Width.Inner: 728 case Owl.Width.Outer: 729 return this._width; 730 default: 731 return this._width - this.settings.stagePadding * 2 + this.settings.margin; 732 } 733 }; 734 735 /** 736 * Refreshes the carousel primarily for adaptive purposes. 737 * @public 738 */ 739 Owl.prototype.refresh = function() { 740 if (this._items.length === 0) { 741 return false; 742 } 743 744 var start = new Date().getTime(); 745 746 this.trigger('refresh'); 747 748 this.setup(); 749 750 this.optionsLogic(); 751 752 // hide and show methods helps here to set a proper widths, 753 // this prevents scrollbar to be calculated in stage width 754 this.$stage.addClass('owl-refresh'); 755 756 this.update(); 757 758 this.$stage.removeClass('owl-refresh'); 759 760 this.state.orientation = window.orientation; 761 762 this.watchVisibility(); 763 764 this.trigger('refreshed'); 765 }; 766 767 /** 768 * Save internal event references and add event based functions. 769 * @protected 770 */ 771 Owl.prototype.eventsCall = function() { 772 // Save events references 773 this.e._onDragStart = $.proxy(function(e) { 774 this.onDragStart(e); 775 }, this); 776 this.e._onDragMove = $.proxy(function(e) { 777 this.onDragMove(e); 778 }, this); 779 this.e._onDragEnd = $.proxy(function(e) { 780 this.onDragEnd(e); 781 }, this); 782 this.e._onResize = $.proxy(function(e) { 783 this.onResize(e); 784 }, this); 785 this.e._transitionEnd = $.proxy(function(e) { 786 this.transitionEnd(e); 787 }, this); 788 this.e._preventClick = $.proxy(function(e) { 789 this.preventClick(e); 790 }, this); 791 }; 792 793 /** 794 * Checks window `resize` event. 795 * @protected 796 */ 797 Owl.prototype.onThrottledResize = function() { 798 window.clearTimeout(this.resizeTimer); 799 this.resizeTimer = window.setTimeout(this.e._onResize, this.settings.responsiveRefreshRate); 800 }; 801 802 /** 803 * Checks window `resize` event. 804 * @protected 805 */ 806 Owl.prototype.onResize = function() { 807 if (!this._items.length) { 808 return false; 809 } 810 811 if (this._width === this.$element.width()) { 812 return false; 813 } 814 815 if (this.trigger('resize').isDefaultPrevented()) { 816 return false; 817 } 818 819 this._width = this.$element.width(); 820 821 this.invalidate('width'); 822 823 this.refresh();
824 825 this.trigger('resized'); 826 }; 827 828 /** 829 * Checks for touch/mouse drag event type and add run event handlers. 830 * @protected 831 */ 832 Owl.prototype.eventsRouter = function(event) { 833 var type = event.type; 834 835 if (type === "mousedown" || type === "touchstart") { 836 this.onDragStart(event); 837 } else if (type === "mousemove" || type === "touchmove") { 838 this.onDragMove(event); 839 } else if (type === "mouseup" || type === "touchend") { 840 this.onDragEnd(event); 841 } else if (type === "touchcancel") { 842 this.onDragEnd(event); 843 } 844 }; 845 846 /** 847 * Checks for touch/mouse drag options and add necessery event handlers. 848 * @protected 849 */ 850 Owl.prototype.internalEvents = function() { 851 var isTouch = isTouchSupport(), 852 isTouchIE = isTouchSupportIE(); 853 854 if (this.settings.mouseDrag){ 855 this.$stage.on('mousedown', $.proxy(function(event) { this.eventsRouter(event) }, this)); 856 this.$stage.on('dragstart', function() { return false }); 857 this.$stage.get(0).onselectstart = function() { return false }; 858 } else { 859 this.$element.addClass('owl-text-select-on'); 860 } 861 862 if (this.settings.touchDrag && !isTouchIE){ 863 this.$stage.on('touchstart touchcancel', $.proxy(function(event) { this.eventsRouter(event) }, this)); 864 } 865 866 // catch transitionEnd event 867 if (this.transitionEndVendor) { 868 this.on(this.$stage.get(0), this.transitionEndVendor, this.e._transitionEnd, false); 869 } 870 871 // responsive 872 if (this.settings.responsive !== false) { 873 this.on(window, 'resize', $.proxy(this.onThrottledResize, this)); 874 } 875 }; 876 877 /** 878 * Handles touchstart/mousedown event. 879 * @protected 880 * @param {Event} event - The event arguments. 881 */ 882 Owl.prototype.onDragStart = function(event) { 883 var ev, isTouchEvent, pageX, pageY, animatedPos; 884 885 ev = event.originalEvent || event || window.event; 886 887 // prevent right click 888 if (ev.which === 3 || this.state.isTouch) { 889 return false; 890 } 891 892 if (ev.type === 'mousedown') { 893 this.$stage.addClass('owl-grab'); 894 } 895 896 this.trigger('drag'); 897 this.drag.startTime = new Date().getTime(); 898 this.speed(0); 899 this.state.isTouch = true; 900 this.state.isScrolling = false; 901 this.state.isSwiping = false; 902 this.drag.distance = 0; 903 904 pageX = getTouches(ev).x; 905 pageY = getTouches(ev).y; 906 907 // get stage position left 908 this.drag.offsetX = this.$stage.position().left; 909 this.drag.offsetY = this.$stage.position().top; 910 911 if (this.settings.rtl) { 912 this.drag.offsetX = this.$stage.position().left + this.$stage.width() - this.width() 913 + this.settings.margin; 914 } 915 916 // catch position // ie to fix 917 if (this.state.inMotion && this.support3d) { 918 animatedPos = this.getTransformProperty(); 919 this.drag.offsetX = animatedPos; 920 this.animate(animatedPos); 921 this.state.inMotion = true; 922 } else if (this.state.inMotion && !this.support3d) { 923 this.state.inMotion = false; 924 return false; 925 } 926 927 this.drag.startX = pageX - this.drag.offsetX; 928 this.drag.startY = pageY - this.drag.offsetY; 929 930 this.drag.start = pageX - this.drag.startX; 931 this.drag.targetEl = ev.target || ev.srcElement; 932 this.drag.updatedX = this.drag.start; 933 934 // to do/check 935 // prevent links and images dragging; 936 if (this.drag.targetEl.tagName === "IMG" || this.drag.targetEl.tagName === "A") { 937 this.drag.targetEl.draggable = false; 938 } 939 940 $(document).on('mousemove.owl.dragEvents mouseup.owl.dragEvents touchmove.owl.dragEvents touchend.owl.dragEvents', $.proxy(function(event) {this.eventsRouter(event)},this)); 941 }; 942 943 /** 944 * Handles the touchmove/mousemove events. 945 * @todo Simplify 946 * @protected 947 * @param {Event} event - The event arguments. 948 */ 949 Owl.prototype.onDragMove = function(event) { 950 var ev, isTouchEvent, pageX, pageY, minValue, maxValue, pull; 951 952 if (!this.state.isTouch) { 953 return; 954 } 955 956 if (this.state.isScrolling) { 957 return; 958 } 959 960 ev = event.originalEvent || event || window.event; 961 962 pageX = getTouches(ev).x; 963 pageY = getTouches(ev).y; 964 965 // Drag Direction 966 this.drag.currentX = pageX - this.drag.startX; 967 this.drag.currentY = pageY - this.drag.startY; 968 this.drag.distance = this.drag.currentX - this.drag.offsetX; 969 970 // Check move direction 971 if (this.drag.distance < 0) { 972 this.state.direction = this.settings.rtl ? 'right' : 'left'; 973 } else if (this.drag.distance > 0) { 974 this.state.direction = this.settings.rtl ? 'left' : 'right'; 975 } 976 // Loop 977 if (this.settings.loop) { 978 if (this.op(this.drag.currentX, '>', this.coordinates(this.minimum())) && this.state.direction === 'right') { 979 this.drag.currentX -= (this.settings.center && this.coordinates(0)) - this.coordinates(this._items.length); 980 } else if (this.op(this.drag.currentX, '<', this.coordinates(this.maximum())) && this.state.direction === 'left') { 981 this.drag.currentX += (this.settings.center && this.coordinates(0)) - this.coordinates(this._items.length); 982 } 983 } else { 984 // pull 985 minValue = this.settings.rtl ? this.coordinates(this.maximum()) : this.coordinates(this.minimum()); 986 maxValue = this.settings.rtl ? this.coordinates(this.minimum()) : this.coordinates(this.maximum()); 987 pull = this.settings.pullDrag ? this.drag.distance / 5 : 0; 988 this.drag.currentX = Math.max(Math.min(this.drag.currentX, minValue + pull), maxValue + pull); 989 } 990 991 // Lock browser if swiping horizontal 992 993 if ((this.drag.distance > 8 || this.drag.distance < -8)) { 994 if (ev.preventDefault !== undefined) { 995 ev.preventDefault(); 996 } else { 997 ev.returnValue = false; 998 } 999 this.state.isSwiping = true; 1000 } 1001 1002 this.drag.updatedX = this.drag.currentX; 1003 1004 // Lock Owl if scrolling 1005 if ((this.drag.currentY > 16 || this.drag.currentY < -16) && this.state.isSwiping === false) { 1006 this.state.isScrolling = true; 1007 this.drag.updatedX = this.drag.start; 1008 } 1009 1010 this.animate(this.drag.updatedX); 1011 }; 1012 1013 /** 1014 * Handles the touchend/mouseup events. 1015 * @protected 1016 */ 1017 Owl.prototype.onDragEnd = function(event) { 1018 var compareTimes, distanceAbs, closest; 1019 1020 if (!this.state.isTouch) { 1021 return; 1022 } 1023 1024 if (event.type === 'mouseup') { 1025 this.$stage.removeClass('owl-grab'); 1026 } 1027 1028 this.trigger('dragged'); 1029 1030 // prevent links and images dragging; 1031 this.drag.targetEl.removeAttribute("draggable"); 1032 1033 // remove drag event listeners 1034 1035 this.state.isTouch = false; 1036 this.state.isScrolling = false; 1037 this.state.isSwiping = false; 1038 1039 // to check 1040 if (this.drag.distance === 0 && this.state.inMotion !== true) { 1041 this.state.inMotion = false; 1042 return false; 1043 } 1044 1045 // prevent clicks while scrolling 1046 1047 this.drag.endTime = new Date().getTime(); 1048 compareTimes = this.drag.endTime - this.drag.startTime; 1049 distanceAbs = Math.abs(this.drag.distance); 1050 1051 // to test 1052 if (distanceAbs > 3 || compareTimes > 300) { 1053 this.removeClick(this.drag.targetEl); 1054 } 1055 1056 closest = this.closest(this.drag.updatedX); 1057 1058 this.speed(this.settings.dragEndSpeed || this.settings.smartSpeed); 1059 this.current(closest); 1060 this.invalidate('position'); 1061 this.update(); 1062 1063 // if pullDrag is off then fire transitionEnd event manually when stick 1064 // to border 1065 if (!this.settings.pullDrag && this.drag.updatedX === this.coordinates(closest)) { 1066 this.transitionEnd(); 1067 } 1068 1069 this.drag.distance = 0; 1070 1071 $(document).off('.owl.dragEvents'); 1072 }; 1073 1074 /** 1075 * Attaches `preventClick` to disable link while swipping. 1076 * @protected 1077 * @param {HTMLElement} [target] - The target of the `click` event. 1078 */ 1079 Owl.prototype.removeClick = function(target) { 1080 this.drag.targetEl = target; 1081 $(target).on('click.preventClick', this.e._preventClick); 1082 // to make sure click is removed: 1083 window.setTimeout(function() { 1084 $(target).off('click.preventClick'); 1085 }, 300); 1086 }; 1087 1088 /** 1089 * Suppresses click event. 1090 * @protected 1091 * @param {Event} ev - The event arguments. 1092 */ 1093 Owl.prototype.preventClick = function(ev) { 1094 if (ev.preventDefault) { 1095 ev.preventDefault(); 1096 } else { 1097 ev.returnValue = false; 1098 } 1099 if (ev.stopPropagation) { 1100 ev.stopPropagation(); 1101 } 1102 $(ev.target).off('click.preventClick'); 1103 }; 1104 1105 /** 1106 * Catches stage position while animate (only CSS3). 1107 * @protected 1108 * @returns 1109 */ 1110 Owl.prototype.getTransformProperty = function() { 1111 var transform, matrix3d; 1112 1113 transform = window.getComputedStyle(this.$stage.get(0), null).getPropertyValue(this.vendorName + 'transform'); 1114 // var transform = this.$stage.css(this.vendorName + 'transform') 1115 transform = transform.replace(/matrix(3d)?\(|\)/g, '').split(','); 1116 matrix3d = transform.length === 16; 1117 1118 return matrix3d !== true ? transform[4] : transform[12]; 1119 }; 1120 1121 /** 1122 * Gets absolute position of the closest item for a coordinate. 1123 * @todo Setting `freeDrag` makes `closest` not reusable. See #165. 1124 * @protected 1125 * @param {Number} coordinate - The coordinate in pixel. 1126 * @return {Number} - The absolute position of the closest item. 1127 */ 1128 Owl.prototype.closest = function(coordinate) { 1129 var position = -1, pull = 30, width = this.width(), coordinates = this.coordinates();
1130 1131 if (!this.settings.freeDrag) { 1132 // check closest item 1133 $.each(coordinates, $.proxy(function(index, value) { 1134 if (coordinate > value - pull && coordinate < value + pull) { 1135 position = index; 1136 } else if (this.op(coordinate, '<', value) 1137 && this.op(coordinate, '>', coordinates[index + 1] || value - width)) { 1138 position = this.state.direction === 'left' ? index + 1 : index; 1139 } 1140 return position === -1; 1141 }, this)); 1142 } 1143 1144 if (!this.settings.loop) { 1145 // non loop boundries 1146 if (this.op(coordinate, '>', coordinates[this.minimum()])) { 1147 position = coordinate = this.minimum(); 1148 } else if (this.op(coordinate, '<', coordinates[this.maximum()])) { 1149 position = coordinate = this.maximum(); 1150 } 1151 } 1152 1153 return position; 1154 }; 1155 1156 /** 1157 * Animates the stage. 1158 * @public 1159 * @param {Number} coordinate - The coordinate in pixels. 1160 */ 1161 Owl.prototype.animate = function(coordinate) { 1162 this.trigger('translate'); 1163 this.state.inMotion = this.speed() > 0; 1164 1165 if (this.support3d) { 1166 this.$stage.css({ 1167 transform: 'translate3d(' + coordinate + 'px' + ',0px, 0px)', 1168 transition: (this.speed() / 1000) + 's' 1169 }); 1170 } else if (this.state.isTouch) { 1171 this.$stage.css({ 1172 left: coordinate + 'px' 1173 }); 1174 } else { 1175 this.$stage.animate({ 1176 left: coordinate 1177 }, this.speed() / 1000, this.settings.fallbackEasing, $.proxy(function() { 1178 if (this.state.inMotion) { 1179 this.transitionEnd(); 1180 } 1181 }, this)); 1182 } 1183 }; 1184 1185 /** 1186 * Sets the absolute position of the current item. 1187 * @public 1188 * @param {Number} [position] - The new absolute position or nothing to leave it unchanged. 1189 * @returns {Number} - The absolute position of the current item. 1190 */ 1191 Owl.prototype.current = function(position) { 1192 if (position === undefined) { 1193 return this._current; 1194 } 1195 1196 if (this._items.length === 0) { 1197 return undefined; 1198 } 1199 1200 position = this.normalize(position); 1201 1202 if (this._current !== position) { 1203 var event = this.trigger('change', { property: { name: 'position', value: position } }); 1204 1205 if (event.data !== undefined) { 1206 position = this.normalize(event.data); 1207 } 1208 1209 this._current = position; 1210 1211 this.invalidate('position'); 1212 1213 this.trigger('changed', { property: { name: 'position', value: this._current } }); 1214 } 1215 1216 return this._current; 1217 }; 1218 1219 /** 1220 * Invalidates the given part of the update routine. 1221 * @param {String} part - The part to invalidate. 1222 */ 1223 Owl.prototype.invalidate = function(part) { 1224 this._invalidated[part] = true; 1225 } 1226 1227 /** 1228 * Resets the absolute position of the current item. 1229 * @public 1230 * @param {Number} position - The absolute position of the new item. 1231 */ 1232 Owl.prototype.reset = function(position) { 1233 position = this.normalize(position); 1234 1235 if (position === undefined) { 1236 return; 1237 } 1238 1239 this._speed = 0; 1240 this._current = position; 1241 1242 this.suppress([ 'translate', 'translated' ]); 1243 1244 this.animate(this.coordinates(position)); 1245 1246 this.release([ 'translate', 'translated' ]); 1247 }; 1248 1249 /** 1250 * Normalizes an absolute or a relative position for an item. 1251 * @public 1252 * @param {Number} position - The absolute or relative position to normalize. 1253 * @param {Boolean} [relative=false] - Whether the given position is relative or not. 1254 * @returns {Number} - The normalized position. 1255 */ 1256 Owl.prototype.normalize = function(position, relative) { 1257 var n = (relative ? this._items.length : this._items.length + this._clones.length); 1258 1259 if (!$.isNumeric(position) || n < 1) { 1260 return undefined; 1261 } 1262 1263 if (this._clones.length) { 1264 position = ((position % n) + n) % n; 1265 } else { 1266 position = Math.max(this.minimum(relative), Math.min(this.maximum(relative), position)); 1267 } 1268 1269 return position; 1270 }; 1271 1272 /** 1273 * Converts an absolute position for an item into a relative position. 1274 * @public 1275 * @param {Number} position - The absolute position to convert. 1276 * @returns {Number} - The converted position. 1277 */ 1278 Owl.prototype.relative = function(position) { 1279 position = this.normalize(position); 1280 position = position - this._clones.length / 2; 1281 return this.normalize(position, true); 1282 }; 1283 1284 /** 1285 * Gets the maximum position for an item. 1286 * @public 1287 * @param {Boolean} [relative=false] - Whether to return an absolute position or a relative position. 1288 * @returns {Number} 1289 */ 1290 Owl.prototype.maximum = function(relative) { 1291 var maximum, width, i = 0, coordinate, 1292 settings = this.settings; 1293 1294 if (relative) { 1295 return this._items.length - 1; 1296 } 1297 1298 if (!settings.loop && settings.center) { 1299 maximum = this._items.length - 1; 1300 }
1300 else if (!settings.loop && !settings.center) { 1301 maximum = this._items.length - settings.items; 1302 } else if (settings.loop || settings.center) { 1303 maximum = this._items.length + settings.items; 1304 } else if (settings.autoWidth || settings.merge) { 1305 revert = settings.rtl ? 1 : -1; 1306 width = this.$stage.width() - this.$element.width(); 1307 while (coordinate = this.coordinates(i)) { 1308 if (coordinate * revert >= width) { 1309 break; 1310 } 1311 maximum = ++i; 1312 } 1313 } else { 1314 throw 'Can not detect maximum absolute position.' 1315 } 1316 1317 return maximum; 1318 }; 1319 1320 /** 1321 * Gets the minimum position for an item. 1322 * @public 1323 * @param {Boolean} [relative=false] - Whether to return an absolute position or a relative position. 1324 * @returns {Number} 1325 */ 1326 Owl.prototype.minimum = function(relative) { 1327 if (relative) { 1328 return 0; 1329 } 1330 1331 return this._clones.length / 2; 1332 }; 1333 1334 /** 1335 * Gets an item at the specified relative position. 1336 * @public 1337 * @param {Number} [position] - The relative position of the item. 1338 * @return {jQuery|Array.<jQuery>} - The item at the given position or all items if no position was given. 1339 */ 1340 Owl.prototype.items = function(position) { 1341 if (position === undefined) { 1342 return this._items.slice(); 1343 } 1344 1345 position = this.normalize(position, true); 1346 return this._items[position]; 1347 }; 1348 1349 /** 1350 * Gets an item at the specified relative position. 1351 * @public 1352 * @param {Number} [position] - The relative position of the item. 1353 * @return {jQuery|Array.<jQuery>} - The item at the given position or all items if no position was given. 1354 */ 1355 Owl.prototype.mergers = function(position) { 1356 if (position === undefined) { 1357 return this._mergers.slice(); 1358 } 1359 1360 position = this.normalize(position, true); 1361 return this._mergers[position]; 1362 }; 1363 1364 /** 1365 * Gets the absolute positions of clones for an item. 1366 * @public 1367 * @param {Number} [position] - The relative position of the item. 1368 * @returns {Array.<Number>} - The absolute positions of clones for the item or all if no position was given. 1369 */ 1370 Owl.prototype.clones = function(position) { 1371 var odd = this._clones.length / 2, 1372 even = odd + this._items.length, 1373 map = function(index) { return index % 2 === 0 ? even + index / 2 : odd - (index + 1) / 2 }; 1374 1375 if (position === undefined) { 1376 return $.map(this._clones, function(v, i) { return map(i) }); 1377 } 1378 1379 return $.map(this._clones, function(v, i) { return v === position ? map(i) : null }); 1380 }; 1381 1382 /** 1383 * Sets the current animation speed. 1384 * @public 1385 * @param {Number} [speed] - The animation speed in milliseconds or nothing to leave it unchanged. 1386 * @returns {Number} - The current animation speed in milliseconds. 1387 */ 1388 Owl.prototype.speed = function(speed) { 1389 if (speed !== undefined) { 1390 this._speed = speed; 1391 } 1392 1393 return this._speed; 1394 }; 1395 1396 /** 1397 * Gets the coordinate of an item. 1398 * @todo The name of this method is missleanding. 1399 * @public 1400 * @param {Number} position - The absolute position of the item within `minimum()` and `maximum()`. 1401 * @returns {Number|Array.<Number>} - The coordinate of the item in pixel or all coordinates. 1402 */ 1403 Owl.prototype.coordinates = function(position) { 1404 var coordinate = null; 1405 1406 if (position === undefined) { 1407 return $.map(this._coordinates, $.proxy(function(coordinate, index) { 1408 return this.coordinates(index); 1409 }, this)); 1410 } 1411 1412 if (this.settings.center) { 1413 coordinate = this._coordinates[position]; 1414 coordinate += (this.width() - coordinate + (this._coordinates[position - 1] || 0)) / 2 * (this.settings.rtl ? -1 : 1); 1415 } else { 1416 coordinate = this._coordinates[position - 1] || 0; 1417 } 1418 1419 return coordinate; 1420 }; 1421 1422 /** 1423 * Calculates the speed for a translation. 1424 * @protected 1425 * @param {Number} from - The absolute position of the start item. 1426 * @param {Number} to - The absolute position of the target item. 1427 * @param {Number} [factor=undefined] - The time factor in milliseconds. 1428 * @returns {Number} - The time in milliseconds for the translation. 1429 */ 1430 Owl.prototype.duration = function(from, to, factor) { 1431 return Math.min(Math.max(Math.abs(to - from), 1), 6) * Math.abs((factor || this.settings.smartSpeed)); 1432 }; 1433 1434 /** 1435 * Slides to the specified item. 1436 * @public 1437 * @param {Number} position - The position of the item. 1438 * @param {Number}
1438 [speed] - The time in milliseconds for the transition. 1439 */ 1440 Owl.prototype.to = function(position, speed) { 1441 if (this.settings.loop) { 1442 var distance = position - this.relative(this.current()), 1443 revert = this.current(), 1444 before = this.current(), 1445 after = this.current() + distance, 1446 direction = before - after < 0 ? true : false, 1447 items = this._clones.length + this._items.length; 1448 1449 if (after < this.settings.items && direction === false) { 1450 revert = before + this._items.length; 1451 this.reset(revert); 1452 } else if (after >= items - this.settings.items && direction === true) { 1453 revert = before - this._items.length; 1454 this.reset(revert); 1455 } 1456 window.clearTimeout(this.e._goToLoop); 1457 this.e._goToLoop = window.setTimeout($.proxy(function() { 1458 this.speed(this.duration(this.current(), revert + distance, speed)); 1459 this.current(revert + distance); 1460 this.update(); 1461 }, this), 30); 1462 } else { 1463 this.speed(this.duration(this.current(), position, speed)); 1464 this.current(position); 1465 this.update(); 1466 } 1467 }; 1468 1469 /** 1470 * Slides to the next item. 1471 * @public 1472 * @param {Number} [speed] - The time in milliseconds for the transition. 1473 */ 1474 Owl.prototype.next = function(speed) { 1475 speed = speed || false; 1476 this.to(this.relative(this.current()) + 1, speed); 1477 }; 1478 1479 /** 1480 * Slides to the previous item. 1481 * @public 1482 * @param {Number} [speed] - The time in milliseconds for the transition. 1483 */ 1484 Owl.prototype.prev = function(speed) { 1485 speed = speed || false; 1486 this.to(this.relative(this.current()) - 1, speed); 1487 }; 1488 1489 /** 1490 * Handles the end of an animation. 1491 * @protected 1492 * @param {Event} event - The event arguments. 1493 */ 1494 Owl.prototype.transitionEnd = function(event) { 1495 1496 // if css2 animation then event object is undefined 1497 if (event !== undefined) { 1498 event.stopPropagation(); 1499 1500 // Catch only owl-stage transitionEnd event 1501 if ((event.target || event.srcElement || event.originalTarget) !== this.$stage.get(0)) { 1502 return false; 1503 } 1504 } 1505 1506 this.state.inMotion = false; 1507 this.trigger('translated'); 1508 }; 1509 1510 /** 1511 * Gets viewport width. 1512 * @protected 1513 * @return {Number} - The width in pixel. 1514 */ 1515 Owl.prototype.viewport = function() { 1516 var width; 1517 if (this.options.responsiveBaseElement !== window) { 1518 width = $(this.options.responsiveBaseElement).width(); 1519 } else if (window.innerWidth) { 1520 width = window.innerWidth; 1521 } else if (document.documentElement && document.documentElement.clientWidth) { 1522 width = document.documentElement.clientWidth; 1523 } else { 1524 throw 'Can not detect viewport width.'; 1525 } 1526 return width; 1527 }; 1528 1529 /** 1530 * Replaces the current content. 1531 * @public 1532 * @param {HTMLElement|jQuery|String} content - The new content. 1533 */ 1534 Owl.prototype.replace = function(content) { 1535 this.$stage.empty(); 1536 this._items = []; 1537 1538 if (content) { 1539 content = (content instanceof jQuery) ? content : $(content); 1540 } 1541 1542 if (this.settings.nestedItemSelector) { 1543 content = content.find('.' + this.settings.nestedItemSelector); 1544 } 1545 1546 content.filter(function() { 1547 return this.nodeType === 1; 1548 }).each($.proxy(function(index, item) { 1549 item = this.prepare(item); 1550 this.$stage.append(item); 1551 this._items.push(item); 1552 this._mergers.push(item.find('[data-merge]').andSelf('[data-merge]').attr('data-merge') * 1 || 1); 1553 }, this)); 1554 1555 this.reset($.isNumeric(this.settings.startPosition) ? this.settings.startPosition : 0); 1556 1557 this.invalidate('items'); 1558 }; 1559 1560 /** 1561 * Adds an item. 1562 * @todo Use `item` instead of `content` for the event arguments. 1563 * @public 1564 * @param {HTMLElement|jQuery|String} content - The item content to add. 1565 * @param {Number} [position] - The relative position at which to insert the item otherwise the item will be added to the end. 1566 */ 1567 Owl.prototype.add = function(content, position) { 1568 position = position === undefined ? this._items.length : this.normalize(position, true); 1569 1570 this.trigger('add', { content: content, position: position }); 1571 1572 if (this._items.length === 0 || position === this._items.length) { 1573 this.$stage.append(content); 1574 this._items.push(content); 1575 this._mergers.push(content.find('[data-merge]').andSelf('[data-merge]').attr('data-merge') * 1 || 1); 1576 } else { 1577 this._items[position].before(content); 1578 this._items.splice(position, 0, content); 1579 this._mergers.splice(position, 0, content.find('[data-merge]').andSelf('[data-merge]').attr('data-merge') * 1 || 1); 1580 } 1581 1582 this.invalidate('items'); 1583 1584 this.trigger('added', { content: content, position: position }); 1585 }; 1586 1587 /** 1588 * Removes an item by its position. 1589 * @todo Use `item` instead of `content` for the event arguments. 1590 * @public 1591 * @param {Number} position - The relative position of the item to remove. 1592 */ 1593 Owl.prototype.remove = function(position) { 1594 position = this.normalize(position, true); 1595 1596 if (position === undefined) { 1597 return; 1598 } 1599
1600 this.trigger('remove', { content: this._items[position], position: position }); 1601 1602 this._items[position].remove(); 1603 this._items.splice(position, 1); 1604 this._mergers.splice(position, 1); 1605 1606 this.invalidate('items'); 1607 1608 this.trigger('removed', { content: null, position: position }); 1609 }; 1610 1611 /** 1612 * Adds triggerable events. 1613 * @protected 1614 */ 1615 Owl.prototype.addTriggerableEvents = function() { 1616 var handler = $.proxy(function(callback, event) { 1617 return $.proxy(function(e) { 1618 if (e.relatedTarget !== this) { 1619 this.suppress([ event ]); 1620 callback.apply(this, [].slice.call(arguments, 1)); 1621 this.release([ event ]); 1622 } 1623 }, this); 1624 }, this); 1625 1626 $.each({ 1627 'next': this.next, 1628 'prev': this.prev, 1629 'to': this.to, 1630 'destroy': this.destroy, 1631 'refresh': this.refresh, 1632 'replace': this.replace, 1633 'add': this.add, 1634 'remove': this.remove 1635 }, $.proxy(function(event, callback) { 1636 this.$element.on(event + '.owl.carousel', handler(callback, event + '.owl.carousel')); 1637 }, this)); 1638 1639 }; 1640 1641 /** 1642 * Watches the visibility of the carousel element. 1643 * @protected 1644 */ 1645 Owl.prototype.watchVisibility = function() { 1646 1647 // test on zepto 1648 if (!isElVisible(this.$element.get(0))) { 1649 this.$element.addClass('owl-hidden'); 1650 window.clearInterval(this.e._checkVisibile); 1651 this.e._checkVisibile = window.setInterval($.proxy(checkVisible, this), 500); 1652 } 1653 1654 function isElVisible(el) { 1655 return el.offsetWidth > 0 && el.offsetHeight > 0; 1656 } 1657 1658 function checkVisible() { 1659 if (isElVisible(this.$element.get(0))) { 1660 this.$element.removeClass('owl-hidden'); 1661 this.refresh(); 1662 window.clearInterval(this.e._checkVisibile); 1663 } 1664 } 1665 }; 1666 1667 /** 1668 * Preloads images with auto width. 1669 * @protected 1670 * @todo Still to test 1671 */ 1672 Owl.prototype.preloadAutoWidthImages = function(imgs) { 1673 var loaded, that, $el, img; 1674 1675 loaded = 0; 1676 that = this; 1677 imgs.each(function(i, el) { 1678 $el = $(el); 1679 img = new Image(); 1680 1681 img.onload = function() { 1682 loaded++; 1683 $el.attr('src', img.src); 1684 $el.css('opacity', 1); 1685 if (loaded >= imgs.length) { 1686 that.state.imagesLoaded = true; 1687 that.initialize(); 1688 } 1689 }; 1690 1691 img.src = $el.attr('src') || $el.attr('data-src') || $el.attr('data-src-retina'); 1692 }); 1693 }; 1694 1695 /** 1696 * Destroys the carousel. 1697 * @public 1698 */ 1699 Owl.prototype.destroy = function() { 1700 1701 if (this.$element.hasClass(this.settings.themeClass)) { 1702 this.$element.removeClass(this.settings.themeClass); 1703 } 1704 1705 if (this.settings.responsive !== false) { 1706 $(window).off('resize.owl.carousel'); 1707 } 1708 1709 if (this.transitionEndVendor) { 1710 this.off(this.$stage.get(0), this.transitionEndVendor, this.e._transitionEnd); 1711 } 1712 1713 for ( var i in this._plugins) { 1714 this._plugins[i].destroy(); 1715 } 1716 1717 if (this.settings.mouseDrag || this.settings.touchDrag) { 1718 this.$stage.off('mousedown touchstart touchcancel'); 1719 $(document).off('.owl.dragEvents'); 1720 this.$stage.get(0).onselectstart = function() {}; 1721 this.$stage.off('dragstart', function() { return false }); 1722 } 1723 1724 // remove event handlers in the ".owl.carousel" namespace 1725 this.$element.off('.owl'); 1726 1727 this.$stage.children('.cloned').remove(); 1728 this.e = null; 1729 this.$element.removeData('owlCarousel'); 1730 1731 this.$stage.children().contents().unwrap(); 1732 this.$stage.children().unwrap(); 1733 this.$stage.unwrap(); 1734 }; 1735 1736 /** 1737 * Operators to calculate right-to-left and left-to-right. 1738 * @protected 1739 * @param {Number} [a] - The left side operand. 1740 * @param {String} [o] - The operator. 1741 * @param {Number} [b] - The right side operand. 1742 */ 1743 Owl.prototype.op = function(a, o, b) { 1744 var rtl = this.settings.rtl; 1745 switch (o) { 1746 case '<': 1747 return rtl ? a > b : a < b; 1748 case '>': 1749 return rtl ? a < b : a > b; 1750 case '>=': 1751 return rtl ? a <= b : a >= b; 1752 case '<=': 1753 return rtl ? a >= b : a <= b; 1754 default: 1755 break; 1756 } 1757 }; 1758 1759 /** 1760 * Attaches to an internal event. 1761 * @protected 1762 * @param {HTMLElement} element - The event source. 1763 * @param {String} event - The event name. 1764 * @param {Function} listener - The event handler to attach. 1765 * @param {Boolean} capture - Wether the event should be handled at the capturing p
1765hase or not. 1766 */ 1767 Owl.prototype.on = function(element, event, listener, capture) { 1768 if (element.addEventListener) { 1769 element.addEventListener(event, listener, capture); 1770 } else if (element.attachEvent) { 1771 element.attachEvent('on' + event, listener); 1772 } 1773 }; 1774 1775 /** 1776 * Detaches from an internal event. 1777 * @protected 1778 * @param {HTMLElement} element - The event source. 1779 * @param {String} event - The event name. 1780 * @param {Function} listener - The attached event handler to detach. 1781 * @param {Boolean} capture - Wether the attached event handler was registered as a capturing listener or not. 1782 */ 1783 Owl.prototype.off = function(element, event, listener, capture) { 1784 if (element.removeEventListener) { 1785 element.removeEventListener(event, listener, capture); 1786 } else if (element.detachEvent) { 1787 element.detachEvent('on' + event, listener); 1788 } 1789 }; 1790 1791 /** 1792 * Triggers an public event. 1793 * @protected 1794 * @param {String} name - The event name. 1795 * @param {*} [data=null] - The event data. 1796 * @param {String} [namespace=.owl.carousel] - The event namespace. 1797 * @returns {Event} - The event arguments. 1798 */ 1799 Owl.prototype.trigger = function(name, data, namespace) { 1800 var status = { 1801 item: { count: this._items.length, index: this.current() } 1802 }, handler = $.camelCase( 1803 $.grep([ 'on', name, namespace ], function(v) { return v }) 1804 .join('-').toLowerCase() 1805 ), event = $.Event( 1806 [ name, 'owl', namespace || 'carousel' ].join('.').toLowerCase(), 1807 $.extend({ relatedTarget: this }, status, data) 1808 ); 1809 1810 if (!this._supress[name]) { 1811 $.each(this._plugins, function(name, plugin) { 1812 if (plugin.onTrigger) { 1813 plugin.onTrigger(event); 1814 } 1815 }); 1816 1817 this.$element.trigger(event); 1818 1819 if (this.settings && typeof this.settings[handler] === 'function') { 1820 this.settings[handler].apply(this, event); 1821 } 1822 } 1823 1824 return event; 1825 }; 1826 1827 /** 1828 * Suppresses events. 1829 * @protected 1830 * @param {Array.<String>} events - The events to suppress. 1831 */ 1832 Owl.prototype.suppress = function(events) { 1833 $.each(events, $.proxy(function(index, event) { 1834 this._supress[event] = true; 1835 }, this)); 1836 } 1837 1838 /** 1839 * Releases suppressed events. 1840 * @protected 1841 * @param {Array.<String>} events - The events to release. 1842 */ 1843 Owl.prototype.release = function(events) { 1844 $.each(events, $.proxy(function(index, event) { 1845 delete this._supress[event]; 1846 }, this)); 1847 } 1848 1849 /** 1850 * Checks the availability of some browser features. 1851 * @protected 1852 */ 1853 Owl.prototype.browserSupport = function() { 1854 this.support3d = isPerspective(); 1855 1856 if (this.support3d) { 1857 this.transformVendor = isTransform(); 1858 1859 // take transitionend event name by detecting transition 1860 var endVendors = [ 'transitionend', 'webkitTransitionEnd', 'transitionend', 'oTransitionEnd' ]; 1861 this.transitionEndVendor = endVendors[isTransition()]; 1862 1863 // take vendor name from transform name 1864 this.vendorName = this.transformVendor.replace(/Transform/i, ''); 1865 this.vendorName = this.vendorName !== '' ? '-' + this.vendorName.toLowerCase() + '-' : ''; 1866 } 1867 1868 this.state.orientation = window.orientation; 1869 }; 1870 1871 /** 1872 * Get touch/drag coordinats. 1873 * @private 1874 * @param {event} - mousedown/touchstart event 1875 * @returns {object} - Contains X and Y of current mouse/touch position 1876 */ 1877 1878 function getTouches(event) { 1879 if (event.touches !== undefined) { 1880 return { 1881 x: event.touches[0].pageX, 1882 y: event.touches[0].pageY 1883 }; 1884 } 1885 1886 if (event.touches === undefined) { 1887 if (event.pageX !== undefined) { 1888 return { 1889 x: event.pageX, 1890 y: event.pageY 1891 }; 1892 } 1893 1894 if (event.pageX === undefined) { 1895 return { 1896 x: event.clientX, 1897 y: event.clientY 1898 }; 1899 } 1900 } 1901 } 1902 1903 /** 1904 * Checks for CSS support. 1905 * @private 1906 * @param {Array} array - The CSS properties to check for. 1907 * @returns {Array} - Contains the supported CSS property name and its index or `false`. 1908 */ 1909 function isStyleSupported(array) { 1910 var p, s, fake = document.createElement('div'), list = array; 1911 for (p in list) { 1912 s = list[p]; 1913 if (typeof fake.style[s] !== 'undefined') { 1914 fake = null; 1915 return [ s, p ]; 1916 } 1917 } 1918 return [ false ]; 1919 } 1920 1921 /** 1922 * Checks for CSS transition support. 1923 * @private 1924 * @todo Realy bad design 1925 * @returns {Number} 1926 */ 1927 function isTransition() { 1928 return isStyleSupported([ 'transition', 'WebkitTransition', 'MozTransition', 'OTransition' ])[1]; 1929 } 1930 1931 /** 1932 * Checks for CSS transform support. 1933 * @private 1934 * @returns {String} The supported property name or false. 1935 */ 1936 function isTransform() { 1937 return isStyleSupported([ 'transform', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform' ])[0]; 1938 } 1939 1940 /** 1941 * Checks for CSS perspective support. 1942 * @private 1943 * @returns {String} The supported property name or false. 1944 */ 1945 function isPerspective() { 1946 return isStyleSupported([ 'perspective', 'webkitPerspective', 'MozPerspective', 'OPerspective', 'MsPerspective' ])[0]; 1947 } 1948 1949 /** 1950 * Checks wether touch is supported or not. 1951 * @private 1952 * @returns {Boolean} 1953 */ 1954 function isTouchSupport() { 1955 return 'ontouchstart' in window || !!(navigator.msMaxTouchPoints); 1956 } 1957 1958 /** 1959 * Checks wether touch is supported or not for IE. 1960 * @private 1961 * @returns {Boolean} 1962 */ 1963 function isTouchSupportIE() { 1964 return window.navigator.msPointerEnabled; 1965 } 1966 1967 /** 1968 * The jQuery Plugin for the Owl Carousel 1969 * @public 1970 */ 1971 $.fn.owlCarousel = function(options) { 1972 return this.each(function() { 1973 if (!$(this).data('owlCarousel')) { 1974 $(this).data('owlCarousel', new Owl(this, options)); 1975 } 1976 }); 1977 }; 1978 1979 /** 1980 * The constructor for the jQuery Plugin 1981 * @public 1982 */ 1983 $.fn.owlCarousel.Constructor = Owl; 1984 1985})(window.Zepto || window.jQuery, window, document); 1986 1987/** 1988 * Lazy Plugin 1989 * @version 2.0.0 1990 * @author Bartosz Wojciechowski 1991 * @license The MIT License (MIT) 1992 */ 1993;(function($, window, document, undefined) { 1994 1995 /** 1996 * Creates the lazy plugin. 1997 * @class The Lazy Plugin 1998 * @param {Owl} carousel - The Owl Carousel 1999 */ 2000 var Lazy = function(carousel) { 2001 2002 /** 2003 * Reference to the core. 2004 * @protected 2005 * @type {Owl} 2006 */ 2007 this._core = carousel; 2008 2009 /** 2010 * Already loaded items. 2011 * @protected 2012 * @type {Array.<jQuery>} 2013 */ 2014 this._loaded = []; 2015 2016 /** 2017 * Event handlers. 2018 * @protected 2019 * @type {Object} 2020 */ 2021 this._handlers = { 2022 'initialized.owl.carousel change.owl.carousel': $.proxy(function(e) { 2023 if (!e.namespace) { 2024 return; 2025 } 2026 2027 if (!this._core.settings || !this._core.settings.lazyLoad) { 2028 return; 2029 } 2030 2031 if ((e.property && e.property.name == 'position') || e.type == 'initialized') { 2032 var settings = this._core.settings, 2033 n = (settings.center && Math.ceil(settings.items / 2) || settings.items), 2034 i = ((settings.center && n * -1) || 0), 2035 position = ((e.property && e.property.value) || this._core.current()) + i, 2036 clones = this._core.clones().length, 2037 load = $.proxy(function(i, v) { this.load(v) }, this); 2038 2039 while (i++ < n) { 2040 this.load(clones / 2 + this._core.relative(position)); 2041 clones && $.each(this._core.clones(this._core.relative(position++)), load
2041); 2042 } 2043 } 2044 }, this) 2045 }; 2046 2047 // set the default options 2048 this._core.options = $.extend({}, Lazy.Defaults, this._core.options); 2049 2050 // register event handler 2051 this._core.$element.on(this._handlers); 2052 } 2053 2054 /** 2055 * Default options. 2056 * @public 2057 */ 2058 Lazy.Defaults = { 2059 lazyLoad: false 2060 } 2061 2062 /** 2063 * Loads all resources of an item at the specified position. 2064 * @param {Number} position - The absolute position of the item. 2065 * @protected 2066 */ 2067 Lazy.prototype.load = function(position) { 2068 var $item = this._core.$stage.children().eq(position), 2069 $elements = $item && $item.find('.owl-lazy'); 2070 2071 if (!$elements || $.inArray($item.get(0), this._loaded) > -1) { 2072 return; 2073 } 2074 2075 $elements.each($.proxy(function(index, element) { 2076 var $element = $(element), image, 2077 url = (window.devicePixelRatio > 1 && $element.attr('data-src-retina')) || $element.attr('data-src'); 2078 2079 this._core.trigger('load', { element: $element, url: url }, 'lazy'); 2080 2081 if ($element.is('img')) { 2082 $element.one('load.owl.lazy', $.proxy(function() { 2083 $element.css('opacity', 1); 2084 this._core.trigger('loaded', { element: $element, url: url }, 'lazy'); 2085 }, this)).attr('src', url); 2086 } else { 2087 image = new Image(); 2088 image.onload = $.proxy(function() { 2089 $element.css({ 2090 'background-image': 'url(' + url + ')', 2091 'opacity': '1' 2092 }); 2093 this._core.trigger('loaded', { element: $element, url: url }, 'lazy'); 2094 }, this); 2095 image.src = url; 2096 } 2097 }, this)); 2098 2099 this._loaded.push($item.get(0)); 2100 } 2101 2102 /** 2103 * Destroys the plugin. 2104 * @public 2105 */ 2106 Lazy.prototype.destroy = function() { 2107 var handler, property; 2108 2109 for (handler in this.handlers) { 2110 this._core.$element.off(handler, this.handlers[handler]); 2111 } 2112 for (property in Object.getOwnPropertyNames(this)) { 2113 typeof this[property] != 'function' && (this[property] = null); 2114 } 2115 } 2116 2117 $.fn.owlCarousel.Constructor.Plugins.Lazy = Lazy; 2118 2119})(window.Zepto || window.jQuery, window, document); 2120 2121/** 2122 * AutoHeight Plugin 2123 * @version 2.0.0 2124 * @author Bartosz Wojciechowski 2125 * @license The MIT License (MIT) 2126 */ 2127;(function($, window, document, undefined) { 2128 2129 /** 2130 * Creates the auto height plugin. 2131 * @class The Auto Height Plugin 2132 * @param {Owl} carousel - The Owl Carousel 2133 */ 2134 var AutoHeight = function(carousel) { 2135 /** 2136 * Reference to the core. 2137 * @protected 2138 * @type {Owl} 2139 */ 2140 this._core = carousel; 2141 2142 /** 2143 * All event handlers. 2144 * @protected 2145 * @type {Object} 2146 */ 2147 this._handlers = { 2148 'initialized.owl.carousel': $.proxy(function() { 2149 if (this._core.settings.autoHeight) { 2150 this.update(); 2151 } 2152 }, this), 2153 'changed.owl.carousel': $.proxy(function(e) { 2154 if (this._core.settings.autoHeight && e.property.name == 'position'){ 2155 this.update(); 2156 } 2157 }, this), 2158 'loaded.owl.lazy': $.proxy(function(e) { 2159 if (this._core.settings.autoHeight && e.element.closest('.' + this._core.settings.itemClass) 2160 === this._core.$stage.children().eq(this._core.current())) { 2161 this.update(); 2162 } 2163 }, this) 2164 }; 2165 2166 // set default options 2167 this._core.options = $.extend({}, AutoHeight.Defaults, this._core.options); 2168 2169 // register event handlers 2170 this._core.$element.on(this._handlers); 2171 }; 2172 2173 /** 2174 * Default options. 2175 * @public 2176 */ 2177 AutoHeight.Defaults = { 2178 autoHeight: false, 2179 autoHeightClass: 'owl-height' 2180 }; 2181 2182 /** 2183 * Updates the view. 2184 */ 2185 AutoHeight.prototype.update = function() { 2186 this._core.$stage.parent() 2187 .height(this._core.$stage.children().eq(this._core.current()).height()) 2188 .addClass(this._core.settings.autoHeightClass); 2189 }; 2190 2191 AutoHeight.prototype.destroy = function() { 2192 var handler, property; 2193 2194 for (handler in this._handlers) { 2195 this._core.$element.off(handler, this._handlers[handler]); 2196 } 2197 for (property in Object.getOwnPropertyNames(this)) { 2198 typeof this[property] != 'function' && (this[property] = null); 2199 } 2200 }; 2201 2202 $.fn.owlCarousel.Constructor.Plugins.AutoHeight = AutoHeight; 2203 2204})(window.Zepto || window.jQuery, window, document); 2205 2206/** 2207 * Video Plugin 2208 * @version 2.0.0 2209 * @author Bartosz Wojciechowski 2210 * @license The MIT License (MIT) 2211 */ 2212;(function($, window, document, undefined) { 2213 2214 /** 2215 * Creates the video plugin. 2216 * @class The Video Plugin 2217 * @param {Owl} carousel - The Owl Carousel 2218 */ 2219 var Video = function(carousel) { 2220 /** 2221 * Reference to the core. 2222 * @protected 2223 * @type {Owl} 2224 */ 2225 this._core = carousel; 2226 2227 /** 2228 * Cache all video URLs. 2229 * @protected 2230 * @type {Object} 2231 */ 2232 this._videos = {}; 2233 2234 /** 2235 * Current playing item. 2236 * @protected 2237 * @type {jQuery} 2238 */ 2239 this._playing = null; 2240 2241 /** 2242 * Whether this is in fullscreen or not. 2243 * @protected 2244 * @type {Boolean} 2245 */ 2246 this._fullscreen = false; 2247 2248 /** 2249 * All event handlers. 2250 * @protected 2251 * @type {Object} 2252 */ 2253 this._handlers = { 2254 'resize.owl.carousel': $.proxy(function(e) { 2255 if (this._core.settings.video && !this.isInFullScreen()) { 2256 e.preventDefault(); 2257 } 2258 }, this), 2259 'refresh.owl.carousel changed.owl.carousel': $.proxy(function(e) { 2260 if (this._playing) { 2261 this.stop(); 2262 } 2263 }, this), 2264 'prepared.owl.carousel': $.proxy(function(e) { 2265 var $element = $(e.content).find('.owl-video'); 2266 if ($element.length) { 2267 $element.css('display', 'none'); 2268 this.fetch($element, $(e.content)); 2269 } 2270 }, this) 2271 }; 2272 2273 // set default options 2274 this._core.options = $.extend({}, Video.Defaults, this._core.options); 2275 2276 // register event handlers 2277 this._core.$element.on(this._handlers); 2278 2279 this._core.$element.on('click.owl.video', '.owl-video-play-icon', $.proxy(function(e) { 2280 this.play(e); 2281 }, this)); 2282 }; 2283 2284 /** 2285 * Default options. 2286 * @public 2287 */ 2288 Video.Defaults = { 2289 video: false, 2290 videoHeight: false, 2291 videoWidth: false 2292 }; 2293 2294 /** 2295 * Gets the video ID and the type (YouTube/Vimeo only). 2296 * @protected 2297 * @param {jQuery} target - The target containing the video data. 2298 * @param {jQuery} item - The item containing the video. 2299 */ 2300 Video.prototype.fetch = function(target, item) { 2301 2302 var type = target.attr('data-vimeo-id') ? 'vimeo' : 'youtube', 2303 id = target.attr('data-vimeo-id') || target.attr('data-youtube-id'), 2304 width = target.attr('data-width') || this._core.settings.videoWidth, 2305 height = target.attr('data-height') || this._core.settings.videoHeight, 2306 url = target.attr('href'); 2307 2308 if (url) { 2309 id = url.match(/(http:|https:|)\/\/(player.|www.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com))\/(video\/|embed\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/); 2310 2311 if (id[3].indexOf('youtu') > -1) { 2312 type = 'youtube'; 2313 } else if (id[3].indexOf('vimeo') > -1) { 2314 type = 'vimeo'; 2315 } else { 2316 throw new Error('Video URL not supported.'); 2317 } 2318 id = id[6]; 2319 } else { 2320 throw new Error('Missing video URL.'); 2321 } 2322 2323 this._videos[url] = { 2324 type: type, 2325 id: id, 2326 width: width, 2327 height: height 2328 }; 2329 2330 item.attr('data-video', url); 2331 2332 this.thumbnail(target, this._videos[url]);
2333 }; 2334 2335 /** 2336 * Creates video thumbnail. 2337 * @protected 2338 * @param {jQuery} target - The target containing the video data. 2339 * @param {Object} info - The video info object. 2340 * @see `fetch` 2341 */ 2342 Video.prototype.thumbnail = function(target, video) { 2343 2344 var tnLink, 2345 icon, 2346 path, 2347 dimensions = video.width && video.height ? 'style="width:' + video.width + 'px;height:' + video.height + 'px;"' : '', 2348 customTn = target.find('img'), 2349 srcType = 'src', 2350 lazyClass = '', 2351 settings = this._core.settings, 2352 create = function(path) { 2353 icon = '<div class="owl-video-play-icon"></div>'; 2354 2355 if (settings.lazyLoad) { 2356 tnLink = '<div class="owl-video-tn ' + lazyClass + '" ' + srcType + '="' + path + '"></div>'; 2357 } else { 2358 tnLink = '<div class="owl-video-tn" style="opacity:1;background-image:url(' + path + ')"></div>'; 2359 } 2360 target.after(tnLink); 2361 target.after(icon); 2362 }; 2363 2364 // wrap video content into owl-video-wrapper div 2365 target.wrap('<div class="owl-video-wrapper"' + dimensions + '></div>'); 2366 2367 if (this._core.settings.lazyLoad) { 2368 srcType = 'data-src'; 2369 lazyClass = 'owl-lazy'; 2370 } 2371 2372 // custom thumbnail 2373 if (customTn.length) { 2374 create(customTn.attr(srcType)); 2375 customTn.remove(); 2376 return false; 2377 } 2378 2379 if (video.type === 'youtube') { 2380 path = "http://img.youtube.com/vi/" + video.id + "/hqdefault.jpg"; 2381 create(path); 2382 } else if (video.type === 'vimeo') { 2383 $.ajax({ 2384 type: 'GET', 2385 url: 'http://vimeo.com/api/v2/video/' + video.id + '.json', 2386 jsonp: 'callback', 2387 dataType: 'jsonp', 2388 success: function(data) { 2389 path = data[0].thumbnail_large; 2390 create(path); 2391 } 2392 }); 2393 } 2394 }; 2395 2396 /** 2397 * Stops the current video. 2398 * @public 2399 */ 2400 Video.prototype.stop = function() { 2401 this._core.trigger('stop', null, 'video'); 2402 this._playing.find('.owl-video-frame').remove(); 2403 this._playing.removeClass('owl-video-playing'); 2404 this._playing = null; 2405 }; 2406 2407 /** 2408 * Starts the current video. 2409 * @public 2410 * @param {Event} ev - The event arguments. 2411 */ 2412 Video.prototype.play = function(ev) { 2413 this._core.trigger('play', null, 'video'); 2414 2415 if (this._playing) { 2416 this.stop(); 2417 } 2418 2419 var target = $(ev.target || ev.srcElement), 2420 item = target.closest('.' + this._core.settings.itemClass), 2421 video = this._videos[item.attr('data-video')], 2422 width = video.width || '100%', 2423 height = video.height || this._core.$stage.height(), 2424 html, wrap; 2425 2426 if (video.type === 'youtube') { 2427 html = '<iframe width="' + width + '" height="' + height + '" src="http://www.youtube.com/embed/' 2428 + video.id + '?autoplay=1&v=' + video.id + '" frameborder="0" allowfullscreen></iframe>'; 2429 } else if (video.type === 'vimeo') { 2430 html = '<iframe src="http://player.vimeo.com/video/' + video.id + '?autoplay=1" width="' + width 2431 + '" height="' + height 2432 + '" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'; 2433 } 2434 2435 item.addClass('owl-video-playing'); 2436 this._playing = item; 2437 2438 wrap = $('<div style="height:' + height + 'px; width:' + width + 'px" class="owl-video-frame">' 2439 + html + '</div>'); 2440 target.after(wrap); 2441 }; 2442 2443 /** 2444 * Checks whether an video is currently in full screen mode or not. 2445 * @todo Bad style because looks like a readonly method but changes members. 2446 * @protected 2447 * @returns {Boolean} 2448 */ 2449 Video.prototype.isInFullScreen = function() { 2450 2451 // if Vimeo Fullscreen mode 2452 var element = document.fullscreenElement || document.mozFullScreenElement 2453 || document.webkitFullscreenElement; 2454 2455 if (element && $(element).parent().hasClass('owl-video-frame')) { 2456 this._core.speed(0); 2457 this._fullscreen = true; 2458 } 2459 2460 if (element && this._fullscreen && this._playing) { 2461 return false; 2462 } 2463 2464 // comming back from fullscreen 2465 if (this._fullscreen) { 2466 this._fullscreen = false; 2467 return false; 2468 } 2469 2470 // check full screen mode and window orientation 2471 if (this._playing) { 2472 if (this._core.state.orientation !== window.orientation) { 2473 this._core.state.orientation = window.orientation; 2474 return false; 2475 } 2476 } 2477 2478 return true; 2479 }; 2480 2481 /** 2482 * Destroys the plugin. 2483 */ 2484 Video.prototype.destroy = function() { 2485 var handler, property; 2486 2487 this._core.$element.off('click.owl.video'); 2488 2489 for (handler in this._handlers) { 2490 this._core.$element.off(handler, this._handlers[handler]); 2491 } 2492 for (property in Object.getOwnPropertyNames(this)) { 2493 typeof this[property] != 'function' && (this[property] = null); 2494 } 2495 }; 2496 2497 $.fn.owlCarousel.Constructor.Plugins.Video = Video; 2498 2499})(window.Zepto || window.jQuery, window, document); 2500 2501/** 2502 * Animate Plugin 2503 * @version 2.0.0 2504 * @author Bartosz Wojciechowski 2505 * @license The MIT License (MIT) 2506 */ 2507;(function($, window, document, undefined) { 2508 2509 /** 2510 * Creates the animate plugin. 2511 * @class The Navigation Plugin 2512 * @param {Owl} scope - The Owl Carousel 2513 */ 2514 var Animate = function(scope) { 2515 this.core = scope; 2516 this.core.options = $.extend({}, Animate.Defaults, this.core.options); 2517 this.swapping = true; 2518 this.previous = undefined; 2519 this.next = undefined; 2520 2521 this.handlers = { 2522 'change.owl.carousel': $.proxy(function(e) { 2523 if (e.property.name == 'position') { 2524 this.previous = this.core.current(); 2525 this.next = e.property.value; 2526 } 2527 }, this), 2528 'drag.owl.carousel dragged.owl.carousel translated.owl.carousel': $.proxy(function(e) { 2529 this.swapping = e.type == 'translated'; 2530 }, this), 2531 'translate.owl.carousel': $.proxy(function(e) { 2532 if (this.swapping && (this.core.options.animateOut || this.core.options.animateIn)) { 2533 this.swap();
2534 } 2535 }, this) 2536 }; 2537 2538 this.core.$element.on(this.handlers); 2539 }; 2540 2541 /** 2542 * Default options. 2543 * @public 2544 */ 2545 Animate.Defaults = { 2546 animateOut: false, 2547 animateIn: false 2548 }; 2549 2550 /** 2551 * Toggles the animation classes whenever an translations starts. 2552 * @protected 2553 * @returns {Boolean|undefined} 2554 */ 2555 Animate.prototype.swap = function() { 2556 2557 if (this.core.settings.items !== 1 || !this.core.support3d) { 2558 return; 2559 } 2560 2561 this.core.speed(0); 2562 2563 var left, 2564 clear = $.proxy(this.clear, this), 2565 previous = this.core.$stage.children().eq(this.previous), 2566 next = this.core.$stage.children().eq(this.next), 2567 incoming = this.core.settings.animateIn, 2568 outgoing = this.core.settings.animateOut; 2569 2570 if (this.core.current() === this.previous) { 2571 return; 2572 } 2573 2574 if (outgoing) { 2575 left = this.core.coordinates(this.previous) - this.core.coordinates(this.next); 2576 previous.css( { 'left': left + 'px' } ) 2577 .addClass('animated owl-animated-out') 2578 .addClass(outgoing) 2579 .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', clear); 2580 } 2581 2582 if (incoming) { 2583 next.addClass('animated owl-animated-in') 2584 .addClass(incoming) 2585 .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', clear); 2586 } 2587 }; 2588 2589 Animate.prototype.clear = function(e) { 2590 $(e.target).css( { 'left': '' } ) 2591 .removeClass('animated owl-animated-out owl-animated-in') 2592 .removeClass(this.core.settings.animateIn) 2593 .removeClass(this.core.settings.animateOut); 2594 this.core.transitionEnd(); 2595 } 2596 2597 /** 2598 * Destroys the plugin. 2599 * @public 2600 */ 2601 Animate.prototype.destroy = function() { 2602 var handler, property; 2603 2604 for (handler in this.handlers) { 2605 this.core.$element.off(handler, this.handlers[handler]); 2606 } 2607 for (property in Object.getOwnPropertyNames(this)) { 2608 typeof this[property] != 'function' && (this[property] = null); 2609 } 2610 }; 2611 2612 $.fn.owlCarousel.Constructor.Plugins.Animate = Animate; 2613 2614})(window.Zepto || window.jQuery, window, document); 2615 2616/** 2617 * Autoplay Plugin 2618 * @version 2.0.0 2619 * @author Bartosz Wojciechowski 2620 * @license The MIT License (MIT) 2621 */ 2622;(function($, window, document, undefined) { 2623 2624 /** 2625 * Creates the autoplay plugin. 2626 * @class The Autoplay Plugin 2627 * @param {Owl} scope - The Owl Carousel 2628 */ 2629 var Autoplay = function(scope) { 2630 this.core = scope; 2631 this.core.options = $.extend({}, Autoplay.Defaults, this.core.options); 2632 2633 this.handlers = { 2634 'translated.owl.carousel refreshed.owl.carousel': $.proxy(function() { 2635 this.autoplay(); 2636 }, this), 2637 'play.owl.autoplay': $.proxy(function(e, t, s) { 2638 this.play(t, s); 2639 }, this), 2640 'stop.owl.autoplay': $.proxy(function() { 2641 this.stop(); 2642 }, this), 2643 'mouseover.owl.autoplay': $.proxy(function() { 2644 if (this.core.settings.autoplayHoverPause) { 2645 this.pause(); 2646 } 2647 }, this), 2648 'mouseleave.owl.autoplay': $.proxy(function() { 2649 if (this.core.settings.autoplayHoverPause) { 2650 this.autoplay(); 2651 } 2652 }, this) 2653 }; 2654 2655 this.core.$element.on(this.handlers); 2656 }; 2657 2658 /** 2659 * Default options. 2660 * @public 2661 */ 2662 Autoplay.Defaults = { 2663 autoplay: false, 2664 autoplayTimeout: 5000, 2665 autoplayHoverPause: false, 2666 autoplaySpeed: false 2667 }; 2668 2669 /** 2670 * @protected 2671 * @todo Must be documented. 2672 */ 2673 Autoplay.prototype.autoplay = function() { 2674 if (this.core.settings.autoplay && !this.core.state.videoPlay) { 2675 window.clearInterval(this.interval); 2676 2677 this.interval = window.setInterval($.proxy(function() { 2678 this.play(); 2679 }, this), this.core.settings.autoplayTimeout); 2680 } else { 2681 window.clearInterval(this.interval); 2682 } 2683 }; 2684 2685 /** 2686 * Starts the autoplay. 2687 * @public 2688 * @param {Number} [timeout] - ... 2689 * @param {Number} [speed] - ... 2690 * @returns {Boolean|undefined} - ... 2691 * @todo Must be documented. 2692 */ 2693 Autoplay.prototype.play = function(timeout, speed) { 2694 // if tab is inactive - doesnt work in <IE10 2695 if (document.hidden === true) { 2696 return; 2697 } 2698 2699 if (this.core.state.isTouch || this.core.state.isScrolling 2700 || this.core.state.isSwiping || this.core.state.inMotion) { 2701 return; 2702 } 2703 2704 if (this.core.settings.autoplay === false) { 2705 window.clearInterval(this.interval); 2706 return; 2707 } 2708 2709 this.core.next(this.core.settings.autoplaySpeed); 2710 }; 2711 2712 /** 2713 * Stops the autoplay. 2714 * @public 2715 */ 2716 Autoplay.prototype.stop = function() { 2717 window.clearInterval(this.interval); 2718 }; 2719 2720 /** 2721 * Pauses the autoplay. 2722 * @public 2723 */ 2724 Autoplay.prototype.pause = function() { 2725 window.clearInterval(this.interval); 2726 }; 2727 2728 /** 2729 * Destroys the plugin. 2730 */ 2731 Autoplay.prototype.destroy = function() { 2732 var handler, property; 2733 2734 window.clearInterval(this.interval); 2735 2736 for (handler in this.handlers) { 2737 this.core.$element.off(handler, this.handlers[handler]); 2738 } 2739 for (property in Object.getOwnPropertyNames(this)) { 2740 typeof this[property] != 'function' && (this[property] = null); 2741 } 2742 }; 2743 2744 $.fn.owlCarousel.Constructor.Plugins.autoplay = Autoplay; 2745 2746})(window.Zepto || window.jQuery, window, document); 2747 2748/** 2749 * Navigation Plugin 2750 * @version 2.0.0 2751 * @author Artus Kolanowski 2752 * @license The MIT License (MIT) 2753 */ 2754;(function($, window, document, undefined) { 2755 'use strict'; 2756 2757 /** 2758 * Creates the navigation plugin. 2759 * @class The Navigation Plugin 2760 * @param {Owl} carousel - The Owl Carousel. 2761 */ 2762 var Navigation = function(carousel) { 2763 /** 2764 * Reference to the core. 2765 * @protected 2766 * @type {Owl} 2767 */ 2768 this._core = carousel; 2769 2770 /** 2771 * Indicates whether the plugin is initialized or not. 2772 * @protected 2773 * @type {Boolean} 2774 */ 2775 this._initialized = false; 2776 2777 /** 2778 * The current paging indexes. 2779 * @protected 2780 * @type {Array} 2781 */ 2782 this._pages = []; 2783 2784 /** 2785 * All DOM elements of the user interface. 2786 * @protected 2787 * @type {Object} 2788 */ 2789 this._controls = {}; 2790 2791 /** 2792 * Markup for an indicator. 2793 * @protected 2794 * @type {Array.<String>} 2795 */ 2796 this._templates = []; 2797 2798 /** 2799 * The carousel element. 2800 * @type {jQuery} 2801 */ 2802 this.$element = this._core.$element; 2803 2804 /** 2805 * Overridden methods of the carousel. 2806 * @protected 2807 * @type {Object} 2808 */ 2809 this._overrides = { 2810 next: this._core.next, 2811 prev: this._core.prev, 2812 to: this._core.to 2813 }; 2814 2815 /** 2816 * All event handlers. 2817 * @protected 2818 * @type {Object} 2819 */ 2820 this._handlers = { 2821 'prepared.owl.carousel': $.proxy(function(e) { 2822 if (this._core.settings.dotsData) { 2823 this._templates.push($(e.content).find('[data-dot]').andSelf('[data-dot]').attr('data-dot')); 2824 } 2825 }, this), 2826 'add.owl.carousel': $.proxy(function(e) { 2827 if (this._core.settings.dotsData) { 2828 this._templates.splice(e.position, 0, $(e.content).find('[data-dot]').andSelf('[data-dot]').attr('data-dot')); 2829 } 2830 }, this), 2831 'remove.owl.carousel prepared.owl.carousel': $.proxy(function(e) { 2832 if (this._core.settings.dotsData) { 2833 this._templates.splice(e.position, 1); 2834 } 2835 }, this), 2836 'change.owl.carousel': $.proxy(function(e) { 2837 if (e.property.name == 'position') { 2838 if (!this._core.state.revert && !this._core.settings.loop && this._core.settings.navRewind) { 2839 var current = this._core.current(), 2840 maximum = this._core.maximum(), 2841 minimum = this._core.minimum(); 2842 e.data = e.property.value > maximum 2843 ? current >= maximum ? minimum : maximum 2844 : e.property.value < minimum ? maximum : e.property.value; 2845 } 2846 } 2847 }, this), 2848 'changed.owl.carousel': $.proxy(function(e) { 2849 if (e.property.name == 'position') { 2850 this.draw(); 2851 } 2852 }, this), 2853 'refreshed.owl.carousel': $.proxy(function() { 2854 if (!this._initialized) { 2855 this.initialize(); 2856 this._initialized = true; 2857 } 2858 this._core.trigger('refresh', null, 'navigation'); 2859 this.update(); 2860 this.draw(); 2861 this._core.trigger('refreshed', null, 'navigation'); 2862 }, this) 2863 }; 2864 2865 // set default options 2866 this._core.options = $.extend({}, Navigation.Defaults, this._core.options); 2867 2868 // register event handlers 2869 this.$element.on(this._handlers); 2870 } 2871 2872 /** 2873 * Default options. 2874 * @public 2875 * @todo Rename `slideBy` to `navBy` 2876 */ 2877 Navigation.Defaults = { 2878 nav: false, 2879 navRewind: true, 2880 navText: [ 'prev', 'next' ], 2881 navSpeed: false, 2882 navElement: 'div', 2883 navContainer: false, 2884 navContainerClass: 'owl-nav', 2885 navClass: [ 'owl-prev', 'owl-next' ], 2886 slideBy: 1, 2887 dotClass: 'owl-dot', 2888 dotsClass: 'owl-dots', 2889 dots: true, 2890 dotsEach: false, 2891 dotData: false, 2892 dotsSpeed: false, 2893 dotsContainer: false,
2894 controlsClass: 'owl-controls' 2895 } 2896 2897 /** 2898 * Initializes the layout of the plugin and extends the carousel. 2899 * @protected 2900 */ 2901 Navigation.prototype.initialize = function() { 2902 var $container, override, 2903 options = this._core.settings; 2904 2905 // create the indicator template 2906 if (!options.dotsData) { 2907 this._templates = [ $('<div>') 2908 .addClass(options.dotClass) 2909 .append($('<span>')) 2910 .prop('outerHTML') ]; 2911 } 2912 2913 // create controls container if needed 2914 if (!options.navContainer || !options.dotsContainer) { 2915 this._controls.$container = $('<div>') 2916 .addClass(options.controlsClass) 2917 .appendTo(this.$element); 2918 } 2919 2920 // create DOM structure for absolute navigation 2921 this._controls.$indicators = options.dotsContainer ? $(options.dotsContainer) 2922 : $('<div>').hide().addClass(options.dotsClass).appendTo(this._controls.$container); 2923 2924 this._controls.$indicators.on('click', 'div', $.proxy(function(e) { 2925 var index = $(e.target).parent().is(this._controls.$indicators) 2926 ? $(e.target).index() : $(e.target).parent().index(); 2927 2928 e.preventDefault(); 2929 2930 this.to(index, options.dotsSpeed); 2931 }, this)); 2932 2933 // create DOM structure for relative navigation 2934 $container = options.navContainer ? $(options.navContainer) 2935 : $('<div>').addClass(options.navContainerClass).prependTo(this._controls.$container); 2936 2937 this._controls.$next = $('<' + options.navElement + '>'); 2938 this._controls.$previous = this._controls.$next.clone(); 2939 2940 this._controls.$previous 2941 .addClass(options.navClass[0]) 2942 .html(options.navText[0]) 2943 .hide() 2944 .prependTo($container) 2945 .on('click', $.proxy(function(e) { 2946 this.prev(options.navSpeed); 2947 }, this)); 2948 this._controls.$next 2949 .addClass(options.navClass[1]) 2950 .html(options.navText[1]) 2951 .hide() 2952 .appendTo($container) 2953 .on('click', $.proxy(function(e) { 2954 this.next(options.navSpeed); 2955 }, this)); 2956 2957 // override public methods of the carousel 2958 for (override in this._overrides) { 2959 this._core[override] = $.proxy(this[override], this); 2960 } 2961 } 2962 2963 /** 2964 * Destroys the plugin. 2965 * @protected 2966 */ 2967 Navigation.prototype.destroy = function() { 2968 var handler, control, property, override; 2969 2970 for (handler in this._handlers) { 2971 this.$element.off(handler, this._handlers[handler]); 2972 } 2973 for (control in this._controls) { 2974 this._controls[control].remove(); 2975 } 2976 for (override in this.overides) { 2977 this._core[override] = this._overrides[override]; 2978 } 2979 for (property in Object.getOwnPropertyNames(this)) { 2980 typeof this[property] != 'function' && (this[property] = null); 2981 } 2982 } 2983 2984 /** 2985 * Updates the internal state. 2986 * @protected 2987 */ 2988 Navigation.prototype.update = function() { 2989 var i, j, k, 2990 options = this._core.settings, 2991 lower = this._core.clones().length / 2, 2992 upper = lower + this._core.items().length, 2993 size = options.center || options.autoWidth || options.dotData 2994 ? 1 : options.dotsEach || options.items; 2995 2996 if (options.slideBy !== 'page') { 2997 options.slideBy = Math.min(options.slideBy, options.items); 2998 } 2999 3000 if (options.dots || options.slideBy == 'page') { 3001 this._pages = []; 3002 3003 for (i = lower, j = 0, k = 0; i < upper; i++) { 3004 if (j >= size || j === 0) { 3005 this._pages.push({ 3006 start: i - lower, 3007 end: i - lower + size - 1 3008 }); 3009 j = 0, ++k; 3010 } 3011 j += this._core.mergers(this._core.relative(i)); 3012 } 3013 } 3014 } 3015 3016 /** 3017 * Draws the user interface. 3018 * @todo The option `dotData` wont work. 3019 * @protected 3020 */ 3021 Navigation.prototype.draw = function() { 3022 var difference, i, html = '', 3023 options = this._core.settings, 3024 $items = this._core.$stage.children(), 3025 index = this._core.relative(this._core.current()); 3026 3027 if (options.nav && !options.loop && !options.navRewind) { 3028 this._controls.$previous.toggleClass('disabled', index <= 0); 3029 this._controls.$next.toggleClass('disabled', index >= this._core.maximum()); 3030 } 3031 3032 this._controls.$previous.toggle(options.nav); 3033 this._controls.$next.toggle(options.nav); 3034 3035 if (options.dots) { 3036 difference = this._pages.length - this._controls.$indicators.children().length; 3037 3038 if (options.dotData && difference !== 0) { 3039 for (i = 0; i < this._controls.$indicators.children().length; i++) { 3040 html += this._templates[this._core.relative(i)]; 3041 } 3042 this._controls.$indicators.html(html); 3043 } else if (difference > 0) { 3044 html = new Array(difference + 1).join(this._templates[0]); 3045 this._controls.$indicators.append(html); 3046 } else if (difference < 0) { 3047 this._controls.$indicators.children().slice(difference).remove(); 3048 } 3049 3050 this._controls.$indicators.find('.active').removeClass('active'); 3051 this._controls.$indicators.children().eq($.inArray(this.current(), this._pages)).addClass('active'); 3052 } 3053 3054 this._controls.$indicators.toggle(options.dots); 3055 } 3056 3057 /** 3058 * Extends event data. 3059 * @protected 3060 * @param {Event} event - The event object which gets thrown. 3061 */ 3062 Navigation.prototype.onTrigger = function(event) { 3063 var settings = this._core.settings; 3064 3065 event.page = { 3066 index: $.inArray(this.current(), this._pages), 3067 count: this._pages.length,
3068 size: settings && (settings.center || settings.autoWidth || settings.dotData 3069 ? 1 : settings.dotsEach || settings.items) 3070 }; 3071 } 3072 3073 /** 3074 * Gets the current page position of the carousel. 3075 * @protected 3076 * @returns {Number} 3077 */ 3078 Navigation.prototype.current = function() { 3079 var index = this._core.relative(this._core.current()); 3080 return $.grep(this._pages, function(o) { 3081 return o.start <= index && o.end >= index; 3082 }).pop(); 3083 } 3084 3085 /** 3086 * Gets the current succesor/predecessor position. 3087 * @protected 3088 * @returns {Number} 3089 */ 3090 Navigation.prototype.getPosition = function(successor) { 3091 var position, length, 3092 options = this._core.settings; 3093 3094 if (options.slideBy == 'page') { 3095 position = $.inArray(this.current(), this._pages); 3096 length = this._pages.length; 3097 successor ? ++position : --position; 3098 position = this._pages[((position % length) + length) % length].start; 3099 } else { 3100 position = this._core.relative(this._core.current()); 3101 length = this._core.items().length; 3102 successor ? position += options.slideBy : position -= options.slideBy; 3103 } 3104 return position; 3105 } 3106 3107 /** 3108 * Slides to the next item or page. 3109 * @public 3110 * @param {Number} [speed=false] - The time in milliseconds for the transition. 3111 */ 3112 Navigation.prototype.next = function(speed) { 3113 $.proxy(this._overrides.to, this._core)(this.getPosition(true), speed); 3114 } 3115 3116 /** 3117 * Slides to the previous item or page. 3118 * @public 3119 * @param {Number} [speed=false] - The time in milliseconds for the transition. 3120 */ 3121 Navigation.prototype.prev = function(speed) { 3122 $.proxy(this._overrides.to, this._core)(this.getPosition(false), speed); 3123 } 3124 3125 /** 3126 * Slides to the specified item or page. 3127 * @public 3128 * @param {Number} position - The position of the item or page. 3129 * @param {Number} [speed] - The time in milliseconds for the transition. 3130 * @param {Boolean} [standard=false] - Whether to use the standard behaviour or not. 3131 */ 3132 Navigation.prototype.to = function(position, speed, standard) { 3133 var length; 3134 3135 if (!standard) { 3136 length = this._pages.length; 3137 $.proxy(this._overrides.to, this._core)(this._pages[((position % length) + length) % length].start, speed); 3138 } else { 3139 $.proxy(this._overrides.to, this._core)(position, speed); 3140 } 3141 } 3142 3143 $.fn.owlCarousel.Constructor.Plugins.Navigation = Navigation; 3144 3145})(window.Zepto || window.jQuery, window, document); 3146 3147/** 3148 * Hash Plugin 3149 * @version 2.0.0 3150 * @author Artus Kolanowski 3151 * @license The MIT License (MIT) 3152 */ 3153;(function($, window, document, undefined) { 3154 'use strict'; 3155 3156 /** 3157 * Creates the hash plugin. 3158 * @class The Hash Plugin 3159 * @param {Owl} carousel - The Owl Carousel 3160 */ 3161 var Hash = function(carousel) { 3162 /** 3163 * Reference to the core. 3164 * @protected 3165 * @type {Owl} 3166 */ 3167 this._core = carousel; 3168 3169 /** 3170 * Hash table for the hashes. 3171 * @protected 3172 * @type {Object} 3173 */ 3174 this._hashes = {}; 3175 3176 /** 3177 * The carousel element. 3178 * @type {jQuery} 3179 */ 3180 this.$element = this._core.$element; 3181 3182 /** 3183 * All event handlers. 3184 * @protected 3185 * @type {Object} 3186 */ 3187 this._handlers = { 3188 'initialized.owl.carousel': $.proxy(function() { 3189 if (this._core.settings.startPosition == 'URLHash') { 3190 $(window).trigger('hashchange.owl.navigation'); 3191 } 3192 }, this), 3193 'prepared.owl.carousel': $.proxy(function(e) { 3194 var hash = $(e.content).find('[data-hash]').andSelf('[data-hash]').attr('data-hash'); 3195 this._hashes[hash] = e.content; 3196 }, this) 3197 }; 3198 3199 // set default options 3200 this._core.options = $.extend({}, Hash.Defaults, this._core.options); 3201 3202 // register the event handlers 3203 this.$element.on(this._handlers); 3204 3205 // register event listener for hash navigation 3206 $(window).on('hashchange.owl.navigation', $.proxy(function() { 3207 var hash = window.location.hash.substring(1), 3208 items = this._core.$stage.children(), 3209 position = this._hashes[hash] && items.index(this._hashes[hash]) || 0; 3210 3211 if (!hash) { 3212 return false; 3213 } 3214 3215 this._core.to(position, false, true); 3216 }, this)); 3217 } 3218 3219 /** 3220 * Default options. 3221 * @public 3222 */ 3223 Hash.Defaults = { 3224 URLhashListener: false 3225 } 3226 3227 /** 3228 * Destroys the plugin. 3229 * @public 3230 */ 3231 Hash.prototype.destroy = function() { 3232 var handler, property; 3233 3234 $(window).off('hashchange.owl.navigation'); 3235 3236 for (handler in this._handlers) { 3237 this._core.$element.off(handler, this._handlers[handler]); 3238 } 3239 for (property in Object.getOwnPropertyNames(this)) { 3240 typeof this[property] != 'function' && (this[property] = null); 3241 } 3242 } 3243 3244 $.fn.owlCarousel.Constructor.Plugins.Hash = Hash; 3245 3246})(window.Zepto || window.jQuery, window, document); 3247 3248/*! PhotoSwipe - v4.1.0 - 2015-07-23 3249* http://photoswipe.com 3250* Copyright (c) 2015 Dmitry Semenov; */ 3251(function (root, factory) { 3252 if (typeof define === 'function' && define.amd) { 3253 define(factory); 3254 } else if (typeof exports === 'object') { 3255 module.exports = factory(); 3256 } else { 3257 root.PhotoSwipe = factory(); 3258 } 3259})(this, function () { 3260 3261 'use strict'; 3262 var PhotoSwipe = function(template, UiClass, items, options){ 3263 3264/*>>framework-bridge*/ 3265/** 3266 * 3267 * Set of generic functions used by gallery. 3268 * 3269 * You're free to modify anything here as long as functionality is kept. 3270 * 3271 */ 3272var framework = { 3273 features: null, 3274 bind: function(target, type, listener, unbind) { 3275 var methodName = (unbind ? 'remove' : 'add') + 'EventListener'; 3276 type = type.split(' '); 3277 for(var i = 0; i < type.length; i++) { 3278 if(type[i]) { 3279 target[methodName]( type[i], listener, false); 3280 } 3281 } 3282 }, 3283 isArray: function(obj) { 3284 return (obj instanceof Array); 3285 },
3286 createEl: function(classes, tag) { 3287 var el = document.createElement(tag || 'div'); 3288 if(classes) { 3289 el.className = classes; 3290 } 3291 return el; 3292 }, 3293 getScrollY: function() { 3294 var yOffset = window.pageYOffset; 3295 return yOffset !== undefined ? yOffset : document.documentElement.scrollTop; 3296 }, 3297 unbind: function(target, type, listener) { 3298 framework.bind(target,type,listener,true); 3299 }, 3300 removeClass: function(el, className) { 3301 var reg = new RegExp('(\\s|^)' + className + '(\\s|$)'); 3302 el.className = el.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 3303 }, 3304 addClass: function(el, className) { 3305 if( !framework.hasClass(el,className) ) { 3306 el.className += (el.className ? ' ' : '') + className; 3307 } 3308 }, 3309 hasClass: function(el, className) { 3310 return el.className && new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className); 3311 }, 3312 getChildByClass: function(parentEl, childClassName) { 3313 var node = parentEl.firstChild; 3314 while(node) { 3315 if( framework.hasClass(node, childClassName) ) { 3316 return node; 3317 } 3318 node = node.nextSibling; 3319 } 3320 }, 3321 arraySearch: function(array, value, key) { 3322 var i = array.length; 3323 while(i--) { 3324 if(array[i][key] === value) { 3325 return i; 3326 } 3327 } 3328 return -1; 3329 }, 3330 extend: function(o1, o2, preventOverwrite) { 3331 for (var prop in o2) { 3332 if (o2.hasOwnProperty(prop)) { 3333 if(preventOverwrite && o1.hasOwnProperty(prop)) { 3334 continue; 3335 } 3336 o1[prop] = o2[prop]; 3337 } 3338 } 3339 }, 3340 easing: { 3341 sine: { 3342 out: function(k) { 3343 return Math.sin(k * (Math.PI / 2)); 3344 }, 3345 inOut: function(k) { 3346 return - (Math.cos(Math.PI * k) - 1) / 2; 3347 } 3348 }, 3349 cubic: { 3350 out: function(k) { 3351 return --k * k * k + 1; 3352 } 3353 } 3354 /* 3355 elastic: { 3356 out: function ( k ) { 3357 3358 var s, a = 0.1, p = 0.4; 3359 if ( k === 0 ) return 0; 3360 if ( k === 1 ) return 1; 3361 if ( !a || a < 1 ) { a = 1; s = p / 4; } 3362 else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); 3363 return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 ); 3364 3365 }, 3366 }, 3367 back: { 3368 out: function ( k ) { 3369 var s = 1.70158; 3370 return --k * k * ( ( s + 1 ) * k + s ) + 1; 3371 } 3372 } 3373 */ 3374 }, 3375 3376 /** 3377 * 3378 * @return {object} 3379 * 3380 * { 3381 * raf : request animation frame function 3382 * caf : cancel animation frame function 3383 * transfrom : transform property key (with vendor), or null if not supported 3384 * oldIE : IE8 or below 3385 * } 3386 * 3387 */ 3388 detectFeatures: function() { 3389 if(framework.features) { 3390 return framework.features; 3391 } 3392 var helperEl = framework.createEl(), 3393 helperStyle = helperEl.style, 3394 vendor = '', 3395 features = {}; 3396 3397 // IE8 and below 3398 features.oldIE = document.all && !document.addEventListener; 3399 3400 features.touch = 'ontouchstart' in window; 3401 3402 if(window.requestAnimationFrame) { 3403 features.raf = window.requestAnimationFrame; 3404 features.caf = window.cancelAnimationFrame; 3405 } 3406 3407 features.pointerEvent = navigator.pointerEnabled || navigator.msPointerEnabled; 3408
3409 // fix false-positive detection of old Android in new IE 3410 // (IE11 ua string contains "Android 4.0") 3411 3412 if(!features.pointerEvent) { 3413 3414 var ua = navigator.userAgent; 3415 3416 // Detect if device is iPhone or iPod and if it's older than iOS 8 3417 // http://stackoverflow.com/a/14223920 3418 // 3419 // This detection is made because of buggy top/bottom toolbars 3420 // that don't trigger window.resize event. 3421 // For more info refer to _isFixedPosition variable in core.js 3422 3423 if (/iP(hone|od)/.test(navigator.platform)) { 3424 var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/); 3425 if(v && v.length > 0) { 3426 v = parseInt(v[1], 10); 3427 if(v >= 1 && v < 8 ) { 3428 features.isOldIOSPhone = true; 3429 } 3430 } 3431 } 3432 3433 // Detect old Android (before KitKat) 3434 // due to bugs related to position:fixed 3435 // http://stackoverflow.com/questions/7184573/pick-up-the-android-version-in-the-browser-by-javascript 3436 3437 var match = ua.match(/Android\s([0-9\.]*)/); 3438 var androidversion = match ? match[1] : 0; 3439 androidversion = parseFloat(androidversion); 3440 if(androidversion >= 1 ) { 3441 if(androidversion < 4.4) { 3442 features.isOldAndroid = true; // for fixed position bug & performance 3443 } 3444 features.androidVersion = androidversion; // for touchend bug 3445 }
3446 features.isMobileOpera = /opera mini|opera mobi/i.test(ua); 3447 3448 // p.s. yes, yes, UA sniffing is bad, propose your solution for above bugs. 3449 } 3450 3451 var styleChecks = ['transform', 'perspective', 'animationName'], 3452 vendors = ['', 'webkit','Moz','ms','O'], 3453 styleCheckItem, 3454 styleName; 3455 3456 for(var i = 0; i < 4; i++) { 3457 vendor = vendors[i]; 3458 3459 for(var a = 0; a < 3; a++) { 3460 styleCheckItem = styleChecks[a]; 3461 3462 // uppercase first letter of property name, if vendor is present 3463 styleName = vendor + (vendor ? 3464 styleCheckItem.charAt(0).toUpperCase() + styleCheckItem.slice(1) : 3465 styleCheckItem); 3466 3467 if(!features[styleCheckItem] && styleName in helperStyle ) { 3468 features[styleCheckItem] = styleName; 3469 } 3470 } 3471 3472 if(vendor && !features.raf) { 3473 vendor = vendor.toLowerCase(); 3474 features.raf = window[vendor+'RequestAnimationFrame']; 3475 if(features.raf) { 3476 features.caf = window[vendor+'CancelAnimationFrame'] || 3477 window[vendor+'CancelRequestAnimationFrame']; 3478 } 3479 } 3480 } 3481 3482 if(!features.raf) { 3483 var lastTime = 0; 3484 features.raf = function(fn) { 3485 var currTime = new Date().getTime(); 3486 var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 3487 var id = window.setTimeout(function() { fn(currTime + timeToCall); }, timeToCall); 3488 lastTime = currTime + timeToCall; 3489 return id; 3490 }; 3491 features.caf = function(id) { clearTimeout(id); }; 3492 } 3493 3494 // Detect SVG support 3495 features.svg = !!document.createElementNS && 3496 !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect; 3497 3498 framework.features = features; 3499 3500 return features; 3501 } 3502}; 3503 3504framework.detectFeatures(); 3505 3506// Override addEventListener for old versions of IE 3507if(framework.features.oldIE) { 3508 3509 framework.bind = function(target, type, listener, unbind) { 3510 3511 type = type.split(' '); 3512 3513 var methodName = (unbind ? 'detach' : 'attach') + 'Event', 3514 evName, 3515 _handleEv = function() { 3516 listener.handleEvent.call(listener); 3517 }; 3518 3519 for(var i = 0; i < type.length; i++) { 3520 evName = type[i]; 3521 if(evName) { 3522 3523 if(typeof listener === 'object' && listener.handleEvent) { 3524 if(!unbind) { 3525 listener['oldIE' + evName] = _handleEv; 3526 } else { 3527 if(!listener['oldIE' + evName]) { 3528 return false; 3529 } 3530 } 3531 3532 target[methodName]( 'on' + evName, listener['oldIE' + evName]); 3533 } else { 3534 target[methodName]( 'on' + evName, listener); 3535 } 3536 3537 } 3538 } 3539 }; 3540 3541} 3542 3543/*>>framework-bridge*/ 3544 3545/*>>core*/ 3546//function(template, UiClass, items, options) 3547 3548var self = this; 3549 3550/** 3551 * Static vars, don't change unless you know what you're doing. 3552 */ 3553var DOUBLE_TAP_RADIUS = 25, 3554 NUM_HOLDERS = 3; 3555 3556/** 3557 * Options 3558 */ 3559var _options = { 3560 allowPanToNext:true, 3561 spacing: 0.12, 3562 bgOpacity: 1, 3563 mouseUsed: false, 3564 loop: true, 3565 pinchToClose: true, 3566 closeOnScroll: true, 3567 closeOnVerticalDrag: true, 3568 verticalDragRange: 0.75, 3569 hideAnimationDuration: 333, 3570 showAnimationDuration: 333, 3571 showHideOpacity: false, 3572 focus: true, 3573 escKey: true, 3574 arrowKeys: true, 3575 mainScrollEndFriction: 0.35, 3576 panEndFriction: 0.35, 3577 isClickableElement: function(el) { 3578 return el.tagName === 'A'; 3579 }, 3580 getDoubleTapZoom: function(isMouseClick, item) { 3581 if(isMouseClick) { 3582 return 1; 3583 } else { 3584 return item.initialZoomLevel < 0.7 ? 1 : 1.33; 3585 } 3586 }, 3587 maxSpreadZoom: 1.33, 3588 modal: true, 3589 3590 // not fully implemented yet 3591 scaleMode: 'fit' // TODO 3592}; 3593framework.extend(_options, options); 3594 3595 3596/** 3597 * Private helper variables & functions 3598 */ 3599 3600var _getEmptyPoint = function() { 3601 return {x:0,y:0}; 3602 }; 3603 3604var _isOpen, 3605 _isDestroying, 3606 _closedByScroll, 3607 _currentItemIndex, 3608 _containerStyle, 3609 _containerShiftIndex, 3610 _currPanDist = _getEmptyPoint(), 3611 _startPanOffset = _getEmptyPoint(), 3612 _panOffset = _getEmptyPoint(), 3613 _upMoveEvents, // drag move, drag end & drag cancel events array 3614 _downEvents, // drag start events array 3615 _globalEventHandlers, 3616 _viewportSize = {}, 3617 _currZoomLevel, 3618 _startZoomLevel, 3619 _translatePrefix, 3620 _translateSufix, 3621 _updateSizeInterval, 3622 _itemsNeedUpdate,
3623 _currPositionIndex = 0, 3624 _offset = {}, 3625 _slideSize = _getEmptyPoint(), // size of slide area, including spacing 3626 _itemHolders, 3627 _prevItemIndex, 3628 _indexDiff = 0, // difference of indexes since last content update 3629 _dragStartEvent, 3630 _dragMoveEvent, 3631 _dragEndEvent, 3632 _dragCancelEvent, 3633 _transformKey, 3634 _pointerEventEnabled, 3635 _isFixedPosition = true, 3636 _likelyTouchDevice, 3637 _modules = [], 3638 _requestAF, 3639 _cancelAF, 3640 _initalClassName, 3641 _initalWindowScrollY, 3642 _oldIE, 3643 _currentWindowScrollY, 3644 _features, 3645 _windowVisibleSize = {}, 3646 _renderMaxResolution = false, 3647 3648 // Registers PhotoSWipe module (History, Controller ...) 3649 _registerModule = function(name, module) { 3650 framework.extend(self, module.publicMethods); 3651 _modules.push(name); 3652 }, 3653 3654 _getLoopedId = function(index) { 3655 var numSlides = _getNumItems(); 3656 if(index > numSlides - 1) { 3657 return index - numSlides; 3658 } else if(index < 0) { 3659 return numSlides + index; 3660 } 3661 return index; 3662 }, 3663 3664 // Micro bind/trigger 3665 _listeners = {}, 3666 _listen = function(name, fn) { 3667 if(!_listeners[name]) { 3668 _listeners[name] = []; 3669 } 3670 return _listeners[name].push(fn); 3671 }, 3672 _shout = function(name) { 3673 var listeners = _listeners[name]; 3674 3675 if(listeners) { 3676 var args = Array.prototype.slice.call(arguments); 3677 args.shift(); 3678 3679 for(var i = 0; i < listeners.length; i++) { 3680 listeners[i].apply(self, args); 3681 } 3682 } 3683 }, 3684 3685 _getCurrentTime = function() { 3686 return new Date().getTime(); 3687 }, 3688 _applyBgOpacity = function(opacity) { 3689 _bgOpacity = opacity; 3690 self.bg.style.opacity = opacity * _options.bgOpacity; 3691 }, 3692 3693 _applyZoomTransform = function(styleObj,x,y,zoom,item) { 3694 if(!_renderMaxResolution || (item && item !== self.currItem) ) { 3695 zoom = zoom / (item ? item.fitRatio : self.currItem.fitRatio); 3696 } 3697 3698 styleObj[_transformKey] = _translatePrefix + x + 'px, ' + y + 'px' + _translateSufix + ' scale(' + zoom + ')'; 3699 }, 3700 _applyCurrentZoomPan = function( allowRenderResolution ) { 3701 if(_currZoomElementStyle) { 3702 3703 if(allowRenderResolution) { 3704 if(_currZoomLevel > self.currItem.fitRatio) { 3705 if(!_renderMaxResolution) { 3706 _setImageSize(self.currItem, false, true); 3707 _renderMaxResolution = true; 3708 } 3709 } else { 3710 if(_renderMaxResolution) { 3711 _setImageSize(self.currItem); 3712 _renderMaxResolution = false; 3713 } 3714 } 3715 } 3716 3717 3718 _applyZoomTransform(_currZoomElementStyle, _panOffset.x, _panOffset.y, _currZoomLevel); 3719 } 3720 }, 3721 _applyZoomPanToItem = function(item) { 3722 if(item.container) { 3723 3724 _applyZoomTransform(item.container.style, 3725 item.initialPosition.x, 3726 item.initialPosition.y, 3727 item.initialZoomLevel, 3728 item); 3729 } 3730 }, 3731 _setTranslateX = function(x, elStyle) { 3732 elStyle[_transformKey] = _translatePrefix + x + 'px, 0px' + _translateSufix; 3733 }, 3734 _moveMainScroll = function(x, dragging) { 3735 3736 if(!_options.loop && dragging) { 3737 var newSlideIndexOffset = _currentItemIndex + (_slideSize.x * _currPositionIndex - x) / _slideSize.x, 3738 delta = Math.round(x - _mainScrollPos.x); 3739 3740 if( (newSlideIndexOffset < 0 && delta > 0) || 3741 (newSlideIndexOffset >= _getNumItems() - 1 && delta < 0) ) { 3742 x = _mainScrollPos.x + delta * _options.mainScrollEndFriction; 3743 } 3744 } 3745 3746 _mainScrollPos.x = x; 3747 _setTranslateX(x, _containerStyle); 3748 }, 3749 _calculatePanOffset = function(axis, zoomLevel) { 3750 var m = _midZoomPoint[axis] - _offset[axis]; 3751 return _startPanOffset[axis] + _currPanDist[axis] + m - m * ( zoomLevel / _startZoomLevel ); 3752 }, 3753 3754 _equalizePoints = function(p1, p2) { 3755 p1.x = p2.x; 3756 p1.y = p2.y; 3757 if(p2.id) { 3758 p1.id = p2.id; 3759 } 3760 }, 3761 _roundPoint = function(p) { 3762 p.x = Math.round(p.x); 3763 p.y = Math.round(p.y); 3764 }, 3765 3766 _mouseMoveTimeout = null, 3767 _onFirstMouseMove = function() { 3768 // Wait until mouse move event is fired at least twice during 100ms 3769 // We do this, because some mobile browsers trigger it on touchstart 3770 if(_mouseMoveTimeout ) { 3771 framework.unbind(document, 'mousemove', _onFirstMouseMove);
3772 framework.addClass(template, 'pswp--has_mouse'); 3773 _options.mouseUsed = true; 3774 _shout('mouseUsed'); 3775 } 3776 _mouseMoveTimeout = setTimeout(function() { 3777 _mouseMoveTimeout = null; 3778 }, 100); 3779 }, 3780 3781 _bindEvents = function() { 3782 framework.bind(document, 'keydown', self); 3783 3784 if(_features.transform) { 3785 // don't bind click event in browsers that don't support transform (mostly IE8) 3786 framework.bind(self.scrollWrap, 'click', self); 3787 } 3788 3789 3790 if(!_options.mouseUsed) { 3791 framework.bind(document, 'mousemove', _onFirstMouseMove); 3792 } 3793 3794 framework.bind(window, 'resize scroll', self); 3795 3796 _shout('bindEvents'); 3797 }, 3798 3799 _unbindEvents = function() { 3800 framework.unbind(window, 'resize', self); 3801 framework.unbind(window, 'scroll', _globalEventHandlers.scroll); 3802 framework.unbind(document, 'keydown', self); 3803 framework.unbind(document, 'mousemove', _onFirstMouseMove); 3804 3805 if(_features.transform) { 3806 framework.unbind(self.scrollWrap, 'click', self); 3807 } 3808 3809 if(_isDragging) { 3810 framework.unbind(window, _upMoveEvents, self); 3811 } 3812 3813 _shout('unbindEvents'); 3814 }, 3815 3816 _calculatePanBounds = function(zoomLevel, update) { 3817 var bounds = _calculateItemSize( self.currItem, _viewportSize, zoomLevel ); 3818 if(update) { 3819 _currPanBounds = bounds; 3820 } 3821 return bounds; 3822 }, 3823 3824 _getMinZoomLevel = function(item) { 3825 if(!item) { 3826 item = self.currItem; 3827 } 3828 return item.initialZoomLevel; 3829 }, 3830 _getMaxZoomLevel = function(item) { 3831 if(!item) { 3832 item = self.currItem; 3833 } 3834 return item.w > 0 ? _options.maxSpreadZoom : 1; 3835 }, 3836 3837 // Return true if offset is out of the bounds 3838 _modifyDestPanOffset = function(axis, destPanBounds, destPanOffset, destZoomLevel) { 3839 if(destZoomLevel === self.currItem.initialZoomLevel) { 3840 destPanOffset[axis] = self.currItem.initialPosition[axis]; 3841 return true; 3842 } else { 3843 destPanOffset[axis] = _calculatePanOffset(axis, destZoomLevel); 3844 3845 if(destPanOffset[axis] > destPanBounds.min[axis]) { 3846 destPanOffset[axis] = destPanBounds.min[axis]; 3847 return true; 3848 } else if(destPanOffset[axis] < destPanBounds.max[axis] ) { 3849 destPanOffset[axis] = destPanBounds.max[axis]; 3850 return true; 3851 } 3852 } 3853 return false; 3854 }, 3855 3856 _setupTransforms = function() { 3857 3858 if(_transformKey) { 3859 // setup 3d transforms 3860 var allow3dTransform = _features.perspective && !_likelyTouchDevice; 3861 _translatePrefix = 'translate' + (allow3dTransform ? '3d(' : '('); 3862 _translateSufix = _features.perspective ? ', 0px)' : ')'; 3863 return; 3864 } 3865 3866 // Override zoom/pan/move functions in case old browser is used (most likely IE) 3867 // (so they use left/top/width/height, instead of CSS transform) 3868 3869 _transformKey = 'left'; 3870 framework.addClass(template, 'pswp--ie'); 3871 3872 _setTranslateX = function(x, elStyle) { 3873 elStyle.left = x + 'px'; 3874 }; 3875 _applyZoomPanToItem = function(item) { 3876 3877 var zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio, 3878 s = item.container.style, 3879 w = zoomRatio * item.w, 3880 h = zoomRatio * item.h; 3881 3882 s.width = w + 'px'; 3883 s.height = h + 'px'; 3884 s.left = item.initialPosition.x + 'px'; 3885 s.top = item.initialPosition.y + 'px'; 3886 3887 }; 3888 _applyCurrentZoomPan = function() { 3889 if(_currZoomElementStyle) { 3890 3891 var s = _currZoomElementStyle, 3892 item = self.currItem, 3893 zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio, 3894 w = zoomRatio * item.w, 3895 h = zoomRatio * item.h; 3896 3897 s.width = w + 'px'; 3898 s.height = h + 'px'; 3899 3900 3901 s.left = _panOffset.x + 'px'; 3902 s.top = _panOffset.y + 'px'; 3903 } 3904 3905 }; 3906 }, 3907 3908 _onKeyDown = function(e) { 3909 var keydownAction = ''; 3910 if(_options.escKey && e.keyCode === 27) { 3911 keydownAction = 'close'; 3912 } else if(_options.arrowKeys) { 3913 if(e.keyCode === 37) { 3914 keydownAction = 'prev'; 3915 } else if(e.keyCode === 39) { 3916 keydownAction = 'next'; 3917 } 3918 } 3919 3920 if(keydownAction) { 3921 // don't do anything if special key pressed to prevent from overriding default browser actions 3922 // e.g. in Chrome on Mac cmd+arrow-left returns to previous page 3923 if( !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey ) { 3924 if(e.preventDefault) { 3925 e.preventDefault(); 3926 } else { 3927 e.returnValue = false; 3928 } 3929 self[keydownAction](); 3930 } 3931 } 3932 }, 3933 3934 _onGlobalClick = function(e) { 3935 if(!e) { 3936 return; 3937 } 3938 3939 // don't allow click event to pass through when triggering after drag or some other gesture 3940 if(_moved || _zoomStarted || _mainScrollAnimating || _verticalDragInitiated) { 3941 e.preventDefault(); 3942 e.stopPropagation(); 3943 } 3944 }, 3945 3946 _updatePageScrollOffset = function() { 3947 self.setScrollOffset(0, framework.getScrollY()); 3948 }; 3949 3950 3951 3952 3953 3954 3955 3956// Micro animation engine 3957var _animations = {}, 3958 _numAnimations = 0, 3959 _stopAnimation = function(name) { 3960 if(_animations[name]) { 3961 if(_animations[name].raf) { 3962 _cancelAF( _animations[name].raf ); 3963 } 3964 _numAnimations--; 3965 delete _animations[name]; 3966 } 3967 }, 3968 _registerStartAnimation = function(name) { 3969 if(_animations[name]) { 3970 _stopAnimation(name); 3971 } 3972 if(!_animations[name]) { 3973 _numAnimations++; 3974 _animations[name] = {}; 3975 } 3976 }, 3977 _stopAllAnimations = function() { 3978 for (var prop in _animations) { 3979 3980 if( _animations.hasOwnProperty( prop ) ) { 3981 _stopAnimation(prop); 3982 } 3983 3984 } 3985 },
3986 _animateProp = function(name, b, endProp, d, easingFn, onUpdate, onComplete) { 3987 var startAnimTime = _getCurrentTime(), t; 3988 _registerStartAnimation(name); 3989 3990 var animloop = function(){ 3991 if ( _animations[name] ) { 3992 3993 t = _getCurrentTime() - startAnimTime; // time diff 3994 //b - beginning (start prop) 3995 //d - anim duration 3996 3997 if ( t >= d ) { 3998 _stopAnimation(name); 3999 onUpdate(endProp); 4000 if(onComplete) { 4001 onComplete(); 4002 } 4003 return; 4004 } 4005 onUpdate( (endProp - b) * easingFn(t/d) + b ); 4006 4007 _animations[name].raf = _requestAF(animloop); 4008 } 4009 }; 4010 animloop(); 4011 }; 4012 4013 4014 4015var publicMethods = { 4016 4017 // make a few local variables and functions public 4018 shout: _shout, 4019 listen: _listen, 4020 viewportSize: _viewportSize, 4021 options: _options, 4022 4023 isMainScrollAnimating: function() { 4024 return _mainScrollAnimating; 4025 }, 4026 getZoomLevel: function() { 4027 return _currZoomLevel; 4028 }, 4029 getCurrentIndex: function() { 4030 return _currentItemIndex; 4031 }, 4032 isDragging: function() { 4033 return _isDragging; 4034 }, 4035 isZooming: function() { 4036 return _isZooming; 4037 }, 4038 setScrollOffset: function(x,y) { 4039 _offset.x = x; 4040 _currentWindowScrollY = _offset.y = y; 4041 _shout('updateScrollOffset', _offset); 4042 }, 4043 applyZoomPan: function(zoomLevel,panX,panY,allowRenderResolution) { 4044 _panOffset.x = panX; 4045 _panOffset.y = panY; 4046 _currZoomLevel = zoomLevel; 4047 _applyCurrentZoomPan( allowRenderResolution ); 4048 }, 4049 4050 init: function() { 4051 4052 if(_isOpen || _isDestroying) { 4053 return; 4054 } 4055 4056 var i; 4057 4058 self.framework = framework; // basic functionality 4059 self.template = template; // root DOM element of PhotoSwipe 4060 self.bg = framework.getChildByClass(template, 'pswp__bg'); 4061 4062 _initalClassName = template.className; 4063 _isOpen = true; 4064 4065 _features = framework.detectFeatures(); 4066 _requestAF = _features.raf; 4067 _cancelAF = _features.caf; 4068 _transformKey = _features.transform; 4069 _oldIE = _features.oldIE; 4070 4071 self.scrollWrap = framework.getChildByClass(template, 'pswp__scroll-wrap'); 4072 self.container = framework.getChildByClass(self.scrollWrap, 'pswp__container'); 4073 4074 _containerStyle = self.container.style; // for fast access 4075 4076 // Objects that hold slides (there are only 3 in DOM) 4077 self.itemHolders = _itemHolders = [ 4078 {el:self.container.children[0] , wrap:0, index: -1}, 4079 {el:self.container.children[1] , wrap:0, index: -1}, 4080 {el:self.container.children[2] , wrap:0, index: -1} 4081 ]; 4082 4083 // hide nearby item holders until initial zoom animation finishes (to avoid extra Paints) 4084 _itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'none'; 4085 4086 _setupTransforms(); 4087 4088 // Setup global events 4089 _globalEventHandlers = { 4090 resize: self.updateSize, 4091 scroll: _updatePageScrollOffset, 4092 keydown: _onKeyDown, 4093 click: _onGlobalClick 4094 }; 4095 4096 // disable show/hide effects on old browsers that don't support CSS animations or transforms, 4097 // old IOS, Android and Opera mobile. Blackberry seems to work fine, even older models. 4098 var oldPhone = _features.isOldIOSPhone || _features.isOldAndroid || _features.isMobileOpera; 4099 if(!_features.animationName || !_features.transform || oldPhone) { 4100 _options.showAnimationDuration = _options.hideAnimationDuration = 0; 4101 } 4102 4103 // init modules 4104 for(i = 0; i < _modules.length; i++) { 4105 self['init' + _modules[i]](); 4106 } 4107 4108 // init 4109 if(UiClass) { 4110 var ui = self.ui = new UiClass(self, framework); 4111 ui.init(); 4112 } 4113 4114 _shout('firstUpdate'); 4115 _currentItemIndex = _currentItemIndex || _options.index || 0; 4116 // validate index 4117 if( isNaN(_currentItemIndex) || _currentItemIndex < 0 || _currentItemIndex >= _getNumItems() ) { 4118 _currentItemIndex = 0; 4119 } 4120 self.currItem = _getItemAt( _currentItemIndex ); 4121 4122 4123 if(_features.isOldIOSPhone || _features.isOldAndroid) { 4124 _isFixedPosition = false; 4125 } 4126 4127 template.setAttribute('aria-hidden', 'false'); 4128 if(_options.modal) { 4129 if(!_isFixedPosition) { 4130 template.style.position = 'absolute'; 4131 template.style.top = framework.getScrollY() + 'px'; 4132 } else { 4133 template.style.position = 'fixed'; 4134 } 4135 } 4136 4137 if(_currentWindowScrollY === undefined) { 4138 _shout('initialLayout'); 4139 _currentWindowScrollY = _initalWindowScrollY = framework.getScrollY(); 4140 } 4141 4142 // add classes to root element of PhotoSwipe 4143 var rootClasses = 'pswp--open '; 4144 if(_options.mainClass) { 4145 rootClasses += _options.mainClass + ' '; 4146 } 4147 if(_options.showHideOpacity) { 4148 rootClasses += 'pswp--animate_opacity '; 4149 } 4150 rootClasses += _likelyTouchDevice ? 'pswp--touch' : 'pswp--notouch'; 4151 rootClasses += _features.animationName ? ' pswp--css_animation' : ''; 4152 rootClasses += _features.svg ? ' pswp--svg' : ''; 4153 framework.addClass(template, rootClasses); 4154 4155 self.updateSize(); 4156 4157 // initial update 4158 _containerShiftIndex = -1; 4159 _indexDiff = null; 4160 for(i = 0; i < NUM_HOLDERS; i++) { 4161 _setTranslateX( (i+_containerShiftIndex) * _slideSize.x, _itemHolders[i].el.style); 4162 } 4163 4164 if(!_oldIE) { 4165 framework.bind(self.scrollWrap, _downEvents, self); // no dragging for old IE 4166 } 4167 4168 _listen('initialZoomInEnd', function() { 4169 self.setContent(_itemHolders[0], _currentItemIndex-1); 4170 self.setContent(_itemHolders[2], _currentItemIndex+1); 4171 4172 _itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'block'; 4173 4174 if(_options.focus) { 4175 // focus causes layout, 4176 // which causes lag during the animation, 4177 // that's why we delay it untill the initial zoom transition ends 4178 template.focus(); 4179 } 4180 4181 4182 _bindEvents(); 4183 }); 4184 4185 // set content for center slide (first time) 4186 self.setContent(_itemHolders[1], _currentItemIndex); 4187 4188 self.updateCurrItem(); 4189 4190 _shout('afterInit'); 4191 4192 if(!_isFixedPosition) { 4193 4194 // On all versions of iOS lower than 8.0, we check size of viewport every second. 4195 // 4196 // This is done to detect when Safari top & bottom bars appear, 4197 // as this action doesn't trigger any events (like resize). 4198 // 4199 // On iOS8 they fixed this. 4200 // 4201 // 10 Nov 2014: iOS 7 usage ~40%. iOS 8 usage 56%. 4202 4203 _updateSizeInterval = setInterval(function() { 4204 if(!_numAnimations && !_isDragging && !_isZooming && (_currZoomLevel === self.currItem.initialZoomLevel) ) { 4205 self.updateSize(); 4206 } 4207 }, 1000); 4208 } 4209
4210 framework.addClass(template, 'pswp--visible'); 4211 }, 4212 4213 // Close the gallery, then destroy it 4214 close: function() { 4215 if(!_isOpen) { 4216 return; 4217 } 4218 4219 _isOpen = false; 4220 _isDestroying = true; 4221 _shout('close'); 4222 _unbindEvents(); 4223 4224 _showOrHide(self.currItem, null, true, self.destroy); 4225 }, 4226 4227 // destroys the gallery (unbinds events, cleans up intervals and timeouts to avoid memory leaks) 4228 destroy: function() { 4229 _shout('destroy'); 4230 4231 if(_showOrHideTimeout) { 4232 clearTimeout(_showOrHideTimeout); 4233 } 4234 4235 template.setAttribute('aria-hidden', 'true'); 4236 template.className = _initalClassName; 4237 4238 if(_updateSizeInterval) { 4239 clearInterval(_updateSizeInterval); 4240 } 4241 4242 framework.unbind(self.scrollWrap, _downEvents, self); 4243 4244 // we unbind scroll event at the end, as closing animation may depend on it 4245 framework.unbind(window, 'scroll', self); 4246 4247 _stopDragUpdateLoop(); 4248 4249 _stopAllAnimations(); 4250 4251 _listeners = null; 4252 }, 4253 4254 /** 4255 * Pan image to position 4256 * @param {Number} x 4257 * @param {Number} y 4258 * @param {Boolean} force Will ignore bounds if set to true. 4259 */ 4260 panTo: function(x,y,force) { 4261 if(!force) { 4262 if(x > _currPanBounds.min.x) { 4263 x = _currPanBounds.min.x; 4264 } else if(x < _currPanBounds.max.x) { 4265 x = _currPanBounds.max.x; 4266 } 4267 4268 if(y > _currPanBounds.min.y) { 4269 y = _currPanBounds.min.y; 4270 } else if(y < _currPanBounds.max.y) { 4271 y = _currPanBounds.max.y; 4272 } 4273 } 4274 4275 _panOffset.x = x; 4276 _panOffset.y = y; 4277 _applyCurrentZoomPan(); 4278 }, 4279 4280 handleEvent: function (e) { 4281 e = e || window.event; 4282 if(_globalEventHandlers[e.type]) { 4283 _globalEventHandlers[e.type](e); 4284 } 4285 }, 4286 4287 4288 goTo: function(index) { 4289 4290 index = _getLoopedId(index); 4291 4292 var diff = index - _currentItemIndex; 4293 _indexDiff = diff; 4294 4295 _currentItemIndex = index; 4296 self.currItem = _getItemAt( _currentItemIndex ); 4297 _currPositionIndex -= diff; 4298 4299 _moveMainScroll(_slideSize.x * _currPositionIndex); 4300 4301 4302 _stopAllAnimations(); 4303 _mainScrollAnimating = false; 4304 4305 self.updateCurrItem(); 4306 }, 4307 next: function() { 4308 self.goTo( _currentItemIndex + 1); 4309 }, 4310 prev: function() { 4311 self.goTo( _currentItemIndex - 1); 4312 }, 4313 4314 // update current zoom/pan objects 4315 updateCurrZoomItem: function(emulateSetContent) { 4316 if(emulateSetContent) { 4317 _shout('beforeChange', 0); 4318 } 4319 4320 // itemHolder[1] is middle (current) item 4321 if(_itemHolders[1].el.children.length) { 4322 var zoomElement = _itemHolders[1].el.children[0]; 4323 if( framework.hasClass(zoomElement, 'pswp__zoom-wrap') ) { 4324 _currZoomElementStyle = zoomElement.style; 4325 } else { 4326 _currZoomElementStyle = null; 4327 } 4328 } else { 4329 _currZoomElementStyle = null; 4330 } 4331 4332 _currPanBounds = self.currItem.bounds; 4333 _startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel; 4334 4335 _panOffset.x = _currPanBounds.center.x; 4336 _panOffset.y = _currPanBounds.center.y; 4337 4338 if(emulateSetContent) { 4339 _shout('afterChange'); 4340 } 4341 }, 4342 4343 4344 invalidateCurrItems: function() { 4345 _itemsNeedUpdate = true; 4346 for(var i = 0; i < NUM_HOLDERS; i++) { 4347 if( _itemHolders[i].item ) { 4348 _itemHolders[i].item.needsUpdate = true; 4349 } 4350 } 4351 }, 4352 4353 updateCurrItem: function(beforeAnimation) { 4354 4355 if(_indexDiff === 0) { 4356 return; 4357 } 4358 4359 var diffAbs = Math.abs(_indexDiff), 4360 tempHolder; 4361 4362 if(beforeAnimation && diffAbs < 2) { 4363 return; 4364 } 4365 4366 4367 self.currItem = _getItemAt( _currentItemIndex ); 4368 _renderMaxResolution = false; 4369 4370 _shout('beforeChange', _indexDiff); 4371 4372 if(diffAbs >= NUM_HOLDERS) { 4373 _containerShiftIndex += _indexDiff + (_indexDiff > 0 ? -NUM_HOLDERS : NUM_HOLDERS); 4374 diffAbs = NUM_HOLDERS; 4375 } 4376 for(var i = 0; i < diffAbs; i++) { 4377 if(_indexDiff > 0) { 4378 tempHolder = _itemHolders.shift(); 4379 _itemHolders[NUM_HOLDERS-1] = tempHolder; // move first to last 4380 4381 _containerShiftIndex++; 4382 _setTranslateX( (_containerShiftIndex+2) * _slideSize.x, tempHolder.el.style); 4383 self.setContent(tempHolder, _currentItemIndex - diffAbs + i + 1 + 1); 4384 } else { 4385 tempHolder = _itemHolders.pop(); 4386 _itemHolders.unshift( tempHolder ); // move last to first 4387 4388 _containerShiftIndex--; 4389 _setTranslateX( _containerShiftIndex * _slideSize.x, tempHolder.el.style); 4390 self.setContent(tempHolder, _currentItemIndex + diffAbs - i - 1 - 1); 4391 } 4392 4393 } 4394 4395 // reset zoom/pan on previous item 4396 if(_currZoomElementStyle && Math.abs(_indexDiff) === 1) { 4397 4398 var prevItem = _getItemAt(_prevItemIndex); 4399 if(prevItem.initialZoomLevel !== _currZoomLevel) { 4400 _calculateItemSize(prevItem , _viewportSize ); 4401 _setImageSize(prevItem); 4402 _applyZoomPanToItem( prevItem ); 4403 } 4404 4405 } 4406 4407 // reset diff after update 4408 _indexDiff = 0; 4409 4410 self.updateCurrZoomItem(); 4411 4412 _prevItemIndex = _currentItemIndex; 4413 4414 _shout('afterChange'); 4415 4416 }, 4417 4418 4419 4420 updateSize: function(force) { 4421 4422 if(!_isFixedPosition && _options.modal) { 4423 var windowScrollY = framework.getScrollY(); 4424 if(_currentWindowScrollY !== windowScrollY) { 4425 template.style.top = windowScrollY + 'px'; 4426 _currentWindowScrollY = windowScrollY; 4427 } 4428 if(!force && _windowVisibleSize.x === window.innerWidth && _windowVisibleSize.y === window.innerHeight) { 4429 return; 4430 } 4431 _windowVisibleSize.x = window.innerWidth; 4432 _windowVisibleSize.y = window.innerHeight; 4433 4434 //template.style.width = _windowVisibleSize.x + 'px'; 4435 template.style.height = _windowVisibleSize.y + 'px'; 4436 } 4437 4438 4439 4440 _viewportSize.x = self.scrollWrap.clientWidth; 4441 _viewportSize.y = self.scrollWrap.clientHeight; 4442 4443 _updatePageScrollOffset(); 4444 4445 _slideSize.x = _viewportSize.x + Math.round(_viewportSize.x * _options.spacing); 4446 _slideSize.y = _viewportSize.y; 4447 4448 _moveMainScroll(_slideSize.x * _currPositionIndex); 4449 4450 _shout('beforeResize'); // even may be used for example to switch image sources 4451 4452 4453 // don't re-calculate size on inital size update 4454 if(_containerShiftIndex !== undefined) { 4455 4456 var holder, 4457 item, 4458 hIndex; 4459 4460 for(var i = 0; i < NUM_HOLDERS; i++) { 4461 holder = _itemHolders[i];
4462 _setTranslateX( (i+_containerShiftIndex) * _slideSize.x, holder.el.style); 4463 4464 hIndex = _currentItemIndex+i-1; 4465 4466 if(_options.loop && _getNumItems() > 2) { 4467 hIndex = _getLoopedId(hIndex); 4468 } 4469 4470 // update zoom level on items and refresh source (if needsUpdate) 4471 item = _getItemAt( hIndex ); 4472 4473 // re-render gallery item if `needsUpdate`, 4474 // or doesn't have `bounds` (entirely new slide object) 4475 if( item && (_itemsNeedUpdate || item.needsUpdate || !item.bounds) ) { 4476 4477 self.cleanSlide( item ); 4478 4479 self.setContent( holder, hIndex ); 4480 4481 // if "center" slide 4482 if(i === 1) { 4483 self.currItem = item; 4484 self.updateCurrZoomItem(true); 4485 } 4486 4487 item.needsUpdate = false; 4488 4489 } else if(holder.index === -1 && hIndex >= 0) { 4490 // add content first time 4491 self.setContent( holder, hIndex ); 4492 } 4493 if(item && item.container) { 4494 _calculateItemSize(item, _viewportSize); 4495 _setImageSize(item); 4496 _applyZoomPanToItem( item ); 4497 } 4498 4499 } 4500 _itemsNeedUpdate = false; 4501 } 4502 4503 _startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel; 4504 _currPanBounds = self.currItem.bounds; 4505 4506 if(_currPanBounds) { 4507 _panOffset.x = _currPanBounds.center.x; 4508 _panOffset.y = _currPanBounds.center.y; 4509 _applyCurrentZoomPan( true ); 4510 } 4511 4512 _shout('resize'); 4513 }, 4514 4515 // Zoom current item to 4516 zoomTo: function(destZoomLevel, centerPoint, speed, easingFn, updateFn) { 4517 /* 4518 if(destZoomLevel === 'fit') { 4519 destZoomLevel = self.currItem.fitRatio; 4520 } else if(destZoomLevel === 'fill') { 4521 destZoomLevel = self.currItem.fillRatio; 4522 } 4523 */ 4524 4525 if(centerPoint) { 4526 _startZoomLevel = _currZoomLevel; 4527 _midZoomPoint.x = Math.abs(centerPoint.x) - _panOffset.x ; 4528 _midZoomPoint.y = Math.abs(centerPoint.y) - _panOffset.y ; 4529 _equalizePoints(_startPanOffset, _panOffset); 4530 } 4531 4532 var destPanBounds = _calculatePanBounds(destZoomLevel, false), 4533 destPanOffset = {}; 4534 4535 _modifyDestPanOffset('x', destPanBounds, destPanOffset, destZoomLevel); 4536 _modifyDestPanOffset('y', destPanBounds, destPanOffset, destZoomLevel); 4537 4538 var initialZoomLevel = _currZoomLevel; 4539 var initialPanOffset = { 4540 x: _panOffset.x, 4541 y: _panOffset.y 4542 }; 4543 4544 _roundPoint(destPanOffset); 4545 4546 var onUpdate = function(now) { 4547 if(now === 1) { 4548 _currZoomLevel = destZoomLevel; 4549 _panOffset.x = destPanOffset.x; 4550 _panOffset.y = destPanOffset.y; 4551 } else { 4552 _currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel; 4553 _panOffset.x = (destPanOffset.x - initialPanOffset.x) * now + initialPanOffset.x;
4554 _panOffset.y = (destPanOffset.y - initialPanOffset.y) * now + initialPanOffset.y; 4555 } 4556 4557 if(updateFn) { 4558 updateFn(now); 4559 } 4560 4561 _applyCurrentZoomPan( now === 1 ); 4562 }; 4563 4564 if(speed) { 4565 _animateProp('customZoomTo', 0, 1, speed, easingFn || framework.easing.sine.inOut, onUpdate); 4566 } else { 4567 onUpdate(1); 4568 } 4569 } 4570 4571 4572}; 4573 4574 4575/*>>core*/ 4576 4577/*>>gestures*/ 4578/** 4579 * Mouse/touch/pointer event handlers. 4580 * 4581 * separated from @core.js for readability 4582 */ 4583 4584var MIN_SWIPE_DISTANCE = 30, 4585 DIRECTION_CHECK_OFFSET = 10; // amount of pixels to drag to determine direction of swipe 4586 4587var _gestureStartTime, 4588 _gestureCheckSpeedTime, 4589 4590 // pool of objects that are used during dragging of zooming 4591 p = {}, // first point 4592 p2 = {}, // second point (for zoom gesture) 4593 delta = {}, 4594 _currPoint = {}, 4595 _startPoint = {}, 4596 _currPointers = [], 4597 _startMainScrollPos = {}, 4598 _releaseAnimData, 4599 _posPoints = [], // array of points during dragging, used to determine type of gesture 4600 _tempPoint = {}, 4601 4602 _isZoomingIn, 4603 _verticalDragInitiated, 4604 _oldAndroidTouchEndTimeout, 4605 _currZoomedItemIndex = 0, 4606 _centerPoint = _getEmptyPoint(), 4607 _lastReleaseTime = 0, 4608 _isDragging, // at least one pointer is down 4609 _isMultitouch, // at least two _pointers are down 4610 _zoomStarted, // zoom level changed during zoom gesture 4611 _moved, 4612 _dragAnimFrame, 4613 _mainScrollShifted, 4614 _currentPoints, // array of current touch points 4615 _isZooming, 4616 _currPointsDistance, 4617 _startPointsDistance, 4618 _currPanBounds, 4619 _mainScrollPos = _getEmptyPoint(), 4620 _currZoomElementStyle, 4621 _mainScrollAnimating, // true, if animation after swipe gesture is running 4622 _midZoomPoint = _getEmptyPoint(), 4623 _currCenterPoint = _getEmptyPoint(), 4624 _direction, 4625 _isFirstMove, 4626 _opacityChanged, 4627 _bgOpacity, 4628 _wasOverInitialZoom, 4629 4630 _isEqualPoints = function(p1, p2) { 4631 return p1.x === p2.x && p1.y === p2.y; 4632 }, 4633 _isNearbyPoints = function(touch0, touch1) { 4634 return Math.abs(touch0.x - touch1.x) < DOUBLE_TAP_RADIUS && Math.abs(touch0.y - touch1.y) < DOUBLE_TAP_RADIUS; 4635 }, 4636 _calculatePointsDistance = function(p1, p2) { 4637 _tempPoint.x = Math.abs( p1.x - p2.x ); 4638 _tempPoint.y = Math.abs( p1.y - p2.y ); 4639 return Math.sqrt(_tempPoint.x * _tempPoint.x + _tempPoint.y * _tempPoint.y); 4640 }, 4641 _stopDragUpdateLoop = function() { 4642 if(_dragAnimFrame) { 4643 _cancelAF(_dragAnimFrame); 4644 _dragAnimFrame = null; 4645 } 4646 }, 4647 _dragUpdateLoop = function() { 4648 if(_isDragging) { 4649 _dragAnimFrame = _requestAF(_dragUpdateLoop); 4650 _renderMovement(); 4651 } 4652 }, 4653 _canPan = function() { 4654 return !(_options.scaleMode === 'fit' && _currZoomLevel === self.currItem.initialZoomLevel); 4655 }, 4656 4657 // find the closest parent DOM element 4658 _closestElement = function(el, fn) { 4659 if(!el) { 4660 return false; 4661 } 4662 4663 // don't search elements above pswp__scroll-wrap 4664 if(el.className && el.className.indexOf('pswp__scroll-wrap') > -1 ) { 4665 return false; 4666 } 4667 4668 if( fn(el) ) { 4669 return el; 4670 } 4671 4672 return _closestElement(el.parentNode, fn); 4673 }, 4674 4675 _preventObj = {}, 4676 _preventDefaultEventBehaviour = function(e, isDown) { 4677 _preventObj.prevent = !_closestElement(e.target, _options.isClickableElement); 4678 4679 _shout('preventDragEvent', e, isDown, _preventObj); 4680 return _preventObj.prevent; 4681 4682 }, 4683 _convertTouchToPoint = function(touch, p) { 4684 p.x = touch.pageX; 4685 p.y = touch.pageY; 4686 p.id = touch.identifier; 4687 return p; 4688 }, 4689 _findCenterOfPoints = function(p1, p2, pCenter) { 4690 pCenter.x = (p1.x + p2.x) * 0.5; 4691 pCenter.y = (p1.y + p2.y) * 0.5; 4692 }, 4693 _pushPosPoint = function(time, x, y) { 4694 if(time - _gestureCheckSpeedTime > 50) { 4695 var o = _posPoints.length > 2 ? _posPoints.shift() : {}; 4696 o.x = x; 4697 o.y = y; 4698 _posPoints.push(o); 4699 _gestureCheckSpeedTime = time; 4700 } 4701 }, 4702 4703 _calculateVerticalDragOpacityRatio = function() { 4704 var yOffset = _panOffset.y - self.currItem.initialPosition.y; // difference between initial and current position 4705 return 1 - Math.abs( yOffset / (_viewportSize.y / 2) ); 4706 }, 4707 4708 4709 // points pool, reused during touch events 4710 _ePoint1 = {}, 4711 _ePoint2 = {}, 4712 _tempPointsArr = [], 4713 _tempCounter, 4714 _getTouchPoints = function(e) { 4715 // clean up previous points, without recreating array 4716 while(_tempPointsArr.length > 0) { 4717 _tempPointsArr.pop();
4718 } 4719 4720 if(!_pointerEventEnabled) { 4721 if(e.type.indexOf('touch') > -1) { 4722 4723 if(e.touches && e.touches.length > 0) { 4724 _tempPointsArr[0] = _convertTouchToPoint(e.touches[0], _ePoint1); 4725 if(e.touches.length > 1) { 4726 _tempPointsArr[1] = _convertTouchToPoint(e.touches[1], _ePoint2); 4727 } 4728 } 4729 4730 } else { 4731 _ePoint1.x = e.pageX; 4732 _ePoint1.y = e.pageY; 4733 _ePoint1.id = ''; 4734 _tempPointsArr[0] = _ePoint1;//_ePoint1; 4735 } 4736 } else { 4737 _tempCounter = 0; 4738 // we can use forEach, as pointer events are supported only in modern browsers 4739 _currPointers.forEach(function(p) { 4740 if(_tempCounter === 0) { 4741 _tempPointsArr[0] = p; 4742 } else if(_tempCounter === 1) { 4743 _tempPointsArr[1] = p; 4744 } 4745 _tempCounter++; 4746 4747 }); 4748 } 4749 return _tempPointsArr; 4750 }, 4751 4752 _panOrMoveMainScroll = function(axis, delta) { 4753 4754 var panFriction, 4755 overDiff = 0, 4756 newOffset = _panOffset[axis] + delta[axis], 4757 startOverDiff, 4758 dir = delta[axis] > 0, 4759 newMainScrollPosition = _mainScrollPos.x + delta.x, 4760 mainScrollDiff = _mainScrollPos.x - _startMainScrollPos.x, 4761 newPanPos, 4762 newMainScrollPos; 4763 4764 // calculate fdistance over the bounds and friction 4765 if(newOffset > _currPanBounds.min[axis] || newOffset < _currPanBounds.max[axis]) { 4766 panFriction = _options.panEndFriction; 4767 // Linear increasing of friction, so at 1/4 of viewport it's at max value. 4768 // Looks not as nice as was expected. Left for history. 4769 // panFriction = (1 - (_panOffset[axis] + delta[axis] + panBounds.min[axis]) / (_viewportSize[axis] / 4) ); 4770 } else { 4771 panFriction = 1; 4772 } 4773 4774 newOffset = _panOffset[axis] + delta[axis] * panFriction; 4775 4776 // move main scroll or start panning 4777 if(_options.allowPanToNext || _currZoomLevel === self.currItem.initialZoomLevel) { 4778 4779 4780 if(!_currZoomElementStyle) { 4781 4782 newMainScrollPos = newMainScrollPosition; 4783 4784 } else if(_direction === 'h' && axis === 'x' && !_zoomStarted ) { 4785 4786 if(dir) { 4787 if(newOffset > _currPanBounds.min[axis]) { 4788 panFriction = _options.panEndFriction; 4789 overDiff = _currPanBounds.min[axis] - newOffset; 4790 startOverDiff = _currPanBounds.min[axis] - _startPanOffset[axis]; 4791 } 4792 4793 // drag right 4794 if( (startOverDiff <= 0 || mainScrollDiff < 0) && _getNumItems() > 1 ) { 4795 newMainScrollPos = newMainScrollPosition; 4796 if(mainScrollDiff < 0 && newMainScrollPosition > _startMainScrollPos.x) { 4797 newMainScrollPos = _startMainScrollPos.x; 4798 } 4799 } else { 4800 if(_currPanBounds.min.x !== _currPanBounds.max.x) { 4801 newPanPos = newOffset; 4802 } 4803 4804 } 4805 4806 } else { 4807 4808 if(newOffset < _currPanBounds.max[axis] ) { 4809 panFriction =_options.panEndFriction; 4810 overDiff = newOffset - _currPanBounds.max[axis]; 4811 startOverDiff = _startPanOffset[axis] - _currPanBounds.max[axis]; 4812 } 4813 4814 if( (startOverDiff <= 0 || mainScrollDiff > 0) && _getNumItems() > 1 ) { 4815 newMainScrollPos = newMainScrollPosition; 4816 4817 if(mainScrollDiff > 0 && newMainScrollPosition < _startMainScrollPos.x) { 4818 newMainScrollPos = _startMainScrollPos.x; 4819 } 4820 4821 } else { 4822 if(_currPanBounds.min.x !== _currPanBounds.max.x) { 4823 newPanPos = newOffset; 4824 } 4825 } 4826 4827 } 4828 4829 4830 // 4831 } 4832 4833 if(axis === 'x') { 4834 4835 if(newMainScrollPos !== undefined) { 4836 _moveMainScroll(newMainScrollPos, true); 4837 if(newMainScrollPos === _startMainScrollPos.x) { 4838 _mainScrollShifted = false; 4839 } else { 4840 _mainScrollShifted = true; 4841 } 4842 } 4843 4844 if(_currPanBounds.min.x !== _currPanBounds.max.x) { 4845 if(newPanPos !== undefined) { 4846 _panOffset.x = newPanPos; 4847 } else if(!_mainScrollShifted) { 4848 _panOffset.x += delta.x * panFriction; 4849 } 4850 } 4851 4852 return newMainScrollPos !== undefined; 4853 } 4854 4855 } 4856 4857 if(!_mainScrollAnimating) { 4858 4859 if(!_mainScrollShifted) { 4860 if(_currZoomLevel > self.currItem.fitRatio) { 4861 _panOffset[axis] += delta[axis] * panFriction; 4862 4863 } 4864 } 4865 4866 4867 } 4868 4869 }, 4870 4871 // Pointerdown/touchstart/mousedown handler 4872 _onDragStart = function(e) { 4873 4874 // Allow dragging only via left mouse button. 4875 // As this handler is not added in IE8 - we ignore e.which 4876 // 4877 // http://www.quirksmode.org/js/events_properties.html 4878 // https://developer.mozilla.org/en-US/docs/Web/API/event.button 4879 if(e.type === 'mousedown' && e.button > 0 ) { 4880 return; 4881 } 4882 4883 if(_initialZoomRunning) { 4884 e.preventDefault(); 4885 return; 4886 } 4887 4888 if(_oldAndroidTouchEndTimeout && e.type === 'mousedown') { 4889 return; 4890 } 4891 4892 if(_preventDefaultEventBehaviour(e, true)) { 4893 e.preventDefault(); 4894 } 4895 4896 4897 4898 _shout('pointerDown'); 4899 4900 if(_pointerEventEnabled) { 4901 var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id'); 4902 if(pointerIndex < 0) { 4903 pointerIndex = _currPointers.length; 4904 } 4905 _currPointers[pointerIndex] = {x:e.pageX, y:e.pageY, id: e.pointerId}; 4906 } 4907 4908 4909 4910 var startPointsList = _getTouchPoints(e), 4911 numPoints = startPointsList.length; 4912 4913 _currentPoints = null; 4914 4915 _stopAllAnimations(); 4916 4917 // init drag 4918 if(!_isDragging || numPoints === 1) { 4919 4920 4921 4922 _isDragging = _isFirstMove = true; 4923 framework.bind(window, _upMoveEvents, self); 4924 4925 _isZoomingIn = 4926 _wasOverInitialZoom = 4927 _opacityChanged = 4928 _verticalDragInitiated = 4929 _mainScrollShifted = 4930 _moved = 4931 _isMultitouch = 4932 _zoomStarted = false; 4933 4934 _direction = null; 4935 4936 _shout('firstTouchStart', startPointsList); 4937 4938 _equalizePoints(_startPanOffset, _panOffset); 4939 4940 _currPanDist.x = _currPanDist.y = 0; 4941 _equalizePoints(_currPoint, startPointsList[0]); 4942 _equalizePoints(_startPoint, _currPoint); 4943
4944 //_equalizePoints(_startMainScrollPos, _mainScrollPos); 4945 _startMainScrollPos.x = _slideSize.x * _currPositionIndex; 4946 4947 _posPoints = [{ 4948 x: _currPoint.x, 4949 y: _currPoint.y 4950 }]; 4951 4952 _gestureCheckSpeedTime = _gestureStartTime = _getCurrentTime(); 4953 4954 //_mainScrollAnimationEnd(true); 4955 _calculatePanBounds( _currZoomLevel, true ); 4956 4957 // Start rendering 4958 _stopDragUpdateLoop(); 4959 _dragUpdateLoop(); 4960 4961 } 4962 4963 // init zoom 4964 if(!_isZooming && numPoints > 1 && !_mainScrollAnimating && !_mainScrollShifted) { 4965 _startZoomLevel = _currZoomLevel; 4966 _zoomStarted = false; // true if zoom changed at least once 4967 4968 _isZooming = _isMultitouch = true; 4969 _currPanDist.y = _currPanDist.x = 0; 4970 4971 _equalizePoints(_startPanOffset, _panOffset); 4972 4973 _equalizePoints(p, startPointsList[0]); 4974 _equalizePoints(p2, startPointsList[1]); 4975 4976 _findCenterOfPoints(p, p2, _currCenterPoint); 4977 4978 _midZoomPoint.x = Math.abs(_currCenterPoint.x) - _panOffset.x; 4979 _midZoomPoint.y = Math.abs(_currCenterPoint.y) - _panOffset.y; 4980 _currPointsDistance = _startPointsDistance = _calculatePointsDistance(p, p2); 4981 } 4982 4983 4984 }, 4985 4986 // Pointermove/touchmove/mousemove handler 4987 _onDragMove = function(e) { 4988 4989 e.preventDefault(); 4990 4991 if(_pointerEventEnabled) { 4992 var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id'); 4993 if(pointerIndex > -1) { 4994 var p = _currPointers[pointerIndex]; 4995 p.x = e.pageX; 4996 p.y = e.pageY; 4997 } 4998 } 4999 5000 if(_isDragging) { 5001 var touchesList = _getTouchPoints(e); 5002 if(!_direction && !_moved && !_isZooming) { 5003 5004 if(_mainScrollPos.x !== _slideSize.x * _currPositionIndex) { 5005 // if main scroll position is shifted â direction is always horizontal 5006 _direction = 'h'; 5007 } else { 5008 var diff = Math.abs(touchesList[0].x - _currPoint.x) - Math.abs(touchesList[0].y - _currPoint.y); 5009 // check the direction of movement 5010 if(Math.abs(diff) >= DIRECTION_CHECK_OFFSET) { 5011 _direction = diff > 0 ? 'h' : 'v'; 5012 _currentPoints = touchesList; 5013 } 5014 } 5015 5016 } else { 5017 _currentPoints = touchesList; 5018 } 5019 } 5020 }, 5021 // 5022 _renderMovement = function() { 5023 5024 if(!_currentPoints) { 5025 return; 5026 } 5027 5028 var numPoints = _currentPoints.length; 5029 5030 if(numPoints === 0) { 5031 return; 5032 } 5033 5034 _equalizePoints(p, _currentPoints[0]); 5035 5036 delta.x = p.x - _currPoint.x; 5037 delta.y = p.y - _currPoint.y; 5038 5039 if(_isZooming && numPoints > 1) { 5040 // Handle behaviour for more than 1 point 5041 5042 _currPoint.x = p.x; 5043 _currPoint.y = p.y; 5044 5045 // check if one of two points changed 5046 if( !delta.x && !delta.y && _isEqualPoints(_currentPoints[1], p2) ) { 5047 return; 5048 } 5049 5050 _equalizePoints(p2, _currentPoints[1]); 5051 5052 5053 if(!_zoomStarted) { 5054 _zoomStarted = true; 5055 _shout('zoomGestureStarted'); 5056 } 5057 5058 // Distance between two points 5059 var pointsDistance = _calculatePointsDistance(p,p2); 5060 5061 var zoomLevel = _calculateZoomLevel(pointsDistance); 5062 5063 // slightly over the of initial zoom level 5064 if(zoomLevel > self.currItem.initialZoomLevel + self.currItem.initialZoomLevel / 15) { 5065 _wasOverInitialZoom = true; 5066 } 5067 5068 // Apply the friction if zoom level is out of the bounds 5069 var zoomFriction = 1, 5070 minZoomLevel = _getMinZoomLevel(), 5071 maxZoomLevel = _getMaxZoomLevel(); 5072 5073 if ( zoomLevel < minZoomLevel ) { 5074 5075 if(_options.pinchToClose && !_wasOverInitialZoom && _startZoomLevel <= self.currItem.initialZoomLevel) { 5076 // fade out background if zooming out 5077 var minusDiff = minZoomLevel - zoomLevel; 5078 var percent = 1 - minusDiff / (minZoomLevel / 1.2); 5079 5080 _applyBgOpacity(percent); 5081 _shout('onPinchClose', percent); 5082 _opacityChanged = true; 5083 } else { 5084 zoomFriction = (minZoomLevel - zoomLevel) / minZoomLevel; 5085 if(zoomFriction > 1) { 5086 zoomFriction = 1; 5087 } 5088 zoomLevel = minZoomLevel - zoomFriction * (minZoomLevel / 3); 5089 } 5090 5091 } else if ( zoomLevel > maxZoomLevel ) { 5092 // 1.5 - extra zoom level above the max. E.g. if max is x6, real max 6 + 1.5 = 7.5 5093 zoomFriction = (zoomLevel - maxZoomLevel) / ( minZoomLevel * 6 ); 5094 if(zoomFriction > 1) { 5095 zoomFriction = 1; 5096 } 5097 zoomLevel = maxZoomLevel + zoomFriction * minZoomLevel; 5098 } 5099 5100 if(zoomFriction < 0) { 5101 zoomFriction = 0; 5102 } 5103 5104 // distance between touch points after friction is applied 5105 _currPointsDistance = pointsDistance; 5106 5107 // _centerPoint - The point in the middle of two pointers 5108 _findCenterOfPoints(p, p2, _centerPoint); 5109 5110 // paning with two pointers pressed 5111 _currPanDist.x += _centerPoint.x - _currCenterPoint.x; 5112 _currPanDist.y += _centerPoint.y - _currCenterPoint.y; 5113 _equalizePoints(_currCenterPoint, _centerPoint); 5114 5115 _panOffset.x = _calculatePanOffset('x', zoomLevel);
5116 _panOffset.y = _calculatePanOffset('y', zoomLevel); 5117 5118 _isZoomingIn = zoomLevel > _currZoomLevel; 5119 _currZoomLevel = zoomLevel; 5120 _applyCurrentZoomPan(); 5121 5122 } else { 5123 5124 // handle behaviour for one point (dragging or panning) 5125 5126 if(!_direction) { 5127 return; 5128 } 5129 5130 if(_isFirstMove) { 5131 _isFirstMove = false; 5132 5133 // subtract drag distance that was used during the detection direction 5134 5135 if( Math.abs(delta.x) >= DIRECTION_CHECK_OFFSET) { 5136 delta.x -= _currentPoints[0].x - _startPoint.x; 5137 } 5138 5139 if( Math.abs(delta.y) >= DIRECTION_CHECK_OFFSET) { 5140 delta.y -= _currentPoints[0].y - _startPoint.y; 5141 } 5142 } 5143 5144 _currPoint.x = p.x; 5145 _currPoint.y = p.y; 5146 5147 // do nothing if pointers position hasn't changed 5148 if(delta.x === 0 && delta.y === 0) { 5149 return; 5150 } 5151 5152 if(_direction === 'v' && _options.closeOnVerticalDrag) { 5153 if(!_canPan()) { 5154 _currPanDist.y += delta.y; 5155 _panOffset.y += delta.y; 5156 5157 var opacityRatio = _calculateVerticalDragOpacityRatio(); 5158 5159 _verticalDragInitiated = true; 5160 _shout('onVerticalDrag', opacityRatio); 5161 5162 _applyBgOpacity(opacityRatio); 5163 _applyCurrentZoomPan(); 5164 return ; 5165 } 5166 } 5167 5168 _pushPosPoint(_getCurrentTime(), p.x, p.y); 5169 5170 _moved = true; 5171 _currPanBounds = self.currItem.bounds; 5172 5173 var mainScrollChanged = _panOrMoveMainScroll('x', delta); 5174 if(!mainScrollChanged) { 5175 _panOrMoveMainScroll('y', delta); 5176 5177 _roundPoint(_panOffset); 5178 _applyCurrentZoomPan(); 5179 } 5180 5181 } 5182 5183 }, 5184 5185 // Pointerup/pointercancel/touchend/touchcancel/mouseup event handler 5186 _onDragRelease = function(e) { 5187 5188 if(_features.isOldAndroid ) { 5189 5190 if(_oldAndroidTouchEndTimeout && e.type === 'mouseup') { 5191 return; 5192 } 5193 5194 // on Android (v4.1, 4.2, 4.3 & possibly older) 5195 // ghost mousedown/up event isn't preventable via e.preventDefault, 5196 // which causes fake mousedown event 5197 // so we block mousedown/up for 600ms 5198 if( e.type.indexOf('touch') > -1 ) { 5199 clearTimeout(_oldAndroidTouchEndTimeout); 5200 _oldAndroidTouchEndTimeout = setTimeout(function() { 5201 _oldAndroidTouchEndTimeout = 0; 5202 }, 600); 5203 } 5204 5205 } 5206 5207 _shout('pointerUp'); 5208 5209 if(_preventDefaultEventBehaviour(e, false)) { 5210 e.preventDefault(); 5211 } 5212 5213 var releasePoint; 5214 5215 if(_pointerEventEnabled) { 5216 var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id'); 5217 5218 if(pointerIndex > -1) { 5219 releasePoint = _currPointers.splice(pointerIndex, 1)[0]; 5220 5221 if(navigator.pointerEnabled) { 5222 releasePoint.type = e.pointerType || 'mouse'; 5223 } else { 5224 var MSPOINTER_TYPES = { 5225 4: 'mouse', // event.MSPOINTER_TYPE_MOUSE 5226 2: 'touch', // event.MSPOINTER_TYPE_TOUCH 5227 3: 'pen' // event.MSPOINTER_TYPE_PEN 5228 }; 5229 releasePoint.type = MSPOINTER_TYPES[e.pointerType]; 5230 5231 if(!releasePoint.type) { 5232 releasePoint.type = e.pointerType || 'mouse'; 5233 } 5234 } 5235 5236 } 5237 } 5238 5239 var touchList = _getTouchPoints(e), 5240 gestureType, 5241 numPoints = touchList.length; 5242 5243 if(e.type === 'mouseup') { 5244 numPoints = 0; 5245 } 5246 5247 // Do nothing if there were 3 touch points or more 5248 if(numPoints === 2) { 5249 _currentPoints = null; 5250 return true; 5251 } 5252 5253 // if second pointer released 5254 if(numPoints === 1) { 5255 _equalizePoints(_startPoint, touchList[0]); 5256 } 5257 5258 5259 // pointer hasn't moved, send "tap release" point 5260 if(numPoints === 0 && !_direction && !_mainScrollAnimating) { 5261 if(!releasePoint) { 5262 if(e.type === 'mouseup') { 5263 releasePoint = {x: e.pageX, y: e.pageY, type:'mouse'}; 5264 } else if(e.changedTouches && e.changedTouches[0]) { 5265 releasePoint = {x: e.changedTouches[0].pageX, y: e.changedTouches[0].pageY, type:'touch'}; 5266 } 5267 } 5268 5269 _shout('touchRelease', e, releasePoint); 5270 } 5271 5272 // Difference in time between releasing of two last touch points (zoom gesture) 5273 var releaseTimeDiff = -1; 5274 5275 // Gesture completed, no pointers left 5276 if(numPoints === 0) { 5277 _isDragging = false; 5278 framework.unbind(window, _upMoveEvents, self); 5279 5280 _stopDragUpdateLoop(); 5281 5282 if(_isZooming) { 5283 // Two points released at the same time 5284 releaseTimeDiff = 0; 5285 } else if(_lastReleaseTime !== -1) { 5286 releaseTimeDiff = _getCurrentTime() - _lastReleaseTime; 5287 } 5288 } 5289 _lastReleaseTime = numPoints === 1 ? _getCurrentTime() : -1; 5290 5291 if(releaseTimeDiff !== -1 && releaseTimeDiff < 150) { 5292 gestureType = 'zoom'; 5293 } else { 5294 gestureType = 'swipe'; 5295 } 5296 5297 if(_isZooming && numPoints < 2) { 5298 _isZooming = false; 5299 5300 // Only second point released 5301 if(numPoints === 1) { 5302 gestureType = 'zoomPointerUp'; 5303 } 5304 _shout('zoomGestureEnded'); 5305 } 5306 5307 _currentPoints = null; 5308 if(!_moved && !_zoomStarted && !_mainScrollAnimating && !_verticalDragInitiated) { 5309 // nothing to animate 5310 return; 5311 } 5312 5313 _stopAllAnimations(); 5314 5315 5316 if(!_releaseAnimData) { 5317 _releaseAnimData = _initDragReleaseAnimationData(); 5318 } 5319 5320 _releaseAnimData.calculateSwipeSpeed('x'); 5321 5322 5323 if(_verticalDragInitiated) { 5324
5325 var opacityRatio = _calculateVerticalDragOpacityRatio(); 5326 5327 if(opacityRatio < _options.verticalDragRange) { 5328 self.close(); 5329 } else { 5330 var initalPanY = _panOffset.y, 5331 initialBgOpacity = _bgOpacity; 5332 5333 _animateProp('verticalDrag', 0, 1, 300, framework.easing.cubic.out, function(now) { 5334 5335 _panOffset.y = (self.currItem.initialPosition.y - initalPanY) * now + initalPanY; 5336 5337 _applyBgOpacity( (1 - initialBgOpacity) * now + initialBgOpacity ); 5338 _applyCurrentZoomPan(); 5339 }); 5340 5341 _shout('onVerticalDrag', 1); 5342 } 5343 5344 return; 5345 } 5346 5347 5348 // main scroll 5349 if( (_mainScrollShifted || _mainScrollAnimating) && numPoints === 0) { 5350 var itemChanged = _finishSwipeMainScrollGesture(gestureType, _releaseAnimData); 5351 if(itemChanged) { 5352 return; 5353 } 5354 gestureType = 'zoomPointerUp'; 5355 } 5356 5357 // prevent zoom/pan animation when main scroll animation runs 5358 if(_mainScrollAnimating) { 5359 return; 5360 } 5361 5362 // Complete simple zoom gesture (reset zoom level if it's out of the bounds) 5363 if(gestureType !== 'swipe') { 5364 _completeZoomGesture(); 5365 return; 5366 } 5367 5368 // Complete pan gesture if main scroll is not shifted, and it's possible to pan current image 5369 if(!_mainScrollShifted && _currZoomLevel > self.currItem.fitRatio) { 5370 _completePanGesture(_releaseAnimData); 5371 } 5372 }, 5373 5374 5375 // Returns object with data about gesture 5376 // It's created only once and then reused 5377 _initDragReleaseAnimationData = function() { 5378 // temp local vars 5379 var lastFlickDuration, 5380 tempReleasePos; 5381 5382 // s = this 5383 var s = { 5384 lastFlickOffset: {}, 5385 lastFlickDist: {}, 5386 lastFlickSpeed: {}, 5387 slowDownRatio: {}, 5388 slowDownRatioReverse: {}, 5389 speedDecelerationRatio: {}, 5390 speedDecelerationRatioAbs: {}, 5391 distanceOffset: {}, 5392 backAnimDestination: {}, 5393 backAnimStarted: {}, 5394 calculateSwipeSpeed: function(axis) { 5395 5396 5397 if( _posPoints.length > 1) { 5398 lastFlickDuration = _getCurrentTime() - _gestureCheckSpeedTime + 50; 5399 tempReleasePos = _posPoints[_posPoints.length-2][axis]; 5400 } else { 5401 lastFlickDuration = _getCurrentTime() - _gestureStartTime; // total gesture duration 5402 tempReleasePos = _startPoint[axis]; 5403 } 5404 s.lastFlickOffset[axis] = _currPoint[axis] - tempReleasePos; 5405 s.lastFlickDist[axis] = Math.abs(s.lastFlickOffset[axis]); 5406 if(s.lastFlickDist[axis] > 20) { 5407 s.lastFlickSpeed[axis] = s.lastFlickOffset[axis] / lastFlickDuration; 5408 } else { 5409 s.lastFlickSpeed[axis] = 0; 5410 } 5411 if( Math.abs(s.lastFlickSpeed[axis]) < 0.1 ) { 5412 s.lastFlickSpeed[axis] = 0; 5413 } 5414 5415 s.slowDownRatio[axis] = 0.95; 5416 s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis]; 5417 s.speedDecelerationRatio[axis] = 1; 5418 }, 5419 5420 calculateOverBoundsAnimOffset: function(axis, speed) { 5421 if(!s.backAnimStarted[axis]) { 5422 5423 if(_panOffset[axis] > _currPanBounds.min[axis]) { 5424 s.backAnimDestination[axis] = _currPanBounds.min[axis]; 5425 5426 } else if(_panOffset[axis] < _currPanBounds.max[axis]) { 5427 s.backAnimDestination[axis] = _currPanBounds.max[axis]; 5428 } 5429 5430 if(s.backAnimDestination[axis] !== undefined) { 5431 s.slowDownRatio[axis] = 0.7; 5432 s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis]; 5433 if(s.speedDecelerationRatioAbs[axis] < 0.05) { 5434 5435 s.lastFlickSpeed[axis] = 0; 5436 s.backAnimStarted[axis] = true; 5437 5438 _animateProp('bounceZoomPan'+axis,_panOffset[axis], 5439 s.backAnimDestination[axis], 5440 speed || 300, 5441 framework.easing.sine.out, 5442 function(pos) { 5443 _panOffset[axis] = pos; 5444 _applyCurrentZoomPan(); 5445 } 5446 ); 5447 5448 } 5449 } 5450 } 5451 }, 5452 5453 // Reduces the speed by slowDownRatio (per 10ms) 5454 calculateAnimOffset: function(axis) { 5455 if(!s.backAnimStarted[axis]) { 5456 s.speedDecelerationRatio[axis] = s.speedDecelerationRatio[axis] * (s.slowDownRatio[axis] + 5457 s.slowDownRatioReverse[axis] - 5458 s.slowDownRatioReverse[axis] * s.timeDiff / 10); 5459 5460 s.speedDecelerationRatioAbs[axis] = Math.abs(s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis]); 5461 s.distanceOffset[axis] = s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis] * s.timeDiff; 5462 _panOffset[axis] += s.distanceOffset[axis]; 5463 5464 } 5465 }, 5466 5467 panAnimLoop: function() { 5468 if ( _animations.zoomPan ) { 5469 _animations.zoomPan.raf = _requestAF(s.panAnimLoop); 5470 5471 s.now = _getCurrentTime(); 5472 s.timeDiff = s.now - s.lastNow; 5473 s.lastNow = s.now; 5474 5475 s.calculateAnimOffset('x'); 5476 s.calculateAnimOffset('y'); 5477 5478 _applyCurrentZoomPan(); 5479 5480 s.calculateOverBoundsAnimOffset('x'); 5481 s.calculateOverBoundsAnimOffset('y'); 5482 5483 5484 if (s.speedDecelerationRatioAbs.x < 0.05 && s.speedDecelerationRatioAbs.y < 0.05) { 5485 5486 // round pan position 5487 _panOffset.x = Math.round(_panOffset.x);
5488 _panOffset.y = Math.round(_panOffset.y); 5489 _applyCurrentZoomPan(); 5490 5491 _stopAnimation('zoomPan'); 5492 return; 5493 } 5494 } 5495 5496 } 5497 }; 5498 return s; 5499 }, 5500 5501 _completePanGesture = function(animData) { 5502 // calculate swipe speed for Y axis (paanning) 5503 animData.calculateSwipeSpeed('y'); 5504 5505 _currPanBounds = self.currItem.bounds; 5506 5507 animData.backAnimDestination = {}; 5508 animData.backAnimStarted = {}; 5509 5510 // Avoid acceleration animation if speed is too low 5511 if(Math.abs(animData.lastFlickSpeed.x) <= 0.05 && Math.abs(animData.lastFlickSpeed.y) <= 0.05 ) { 5512 animData.speedDecelerationRatioAbs.x = animData.speedDecelerationRatioAbs.y = 0; 5513 5514 // Run pan drag release animation. E.g. if you drag image and release finger without momentum. 5515 animData.calculateOverBoundsAnimOffset('x'); 5516 animData.calculateOverBoundsAnimOffset('y'); 5517 return true; 5518 } 5519 5520 // Animation loop that controls the acceleration after pan gesture ends 5521 _registerStartAnimation('zoomPan'); 5522 animData.lastNow = _getCurrentTime(); 5523 animData.panAnimLoop(); 5524 }, 5525 5526 5527 _finishSwipeMainScrollGesture = function(gestureType, _releaseAnimData) { 5528 var itemChanged; 5529 if(!_mainScrollAnimating) { 5530 _currZoomedItemIndex = _currentItemIndex; 5531 } 5532 5533 5534 5535 var itemsDiff; 5536 5537 if(gestureType === 'swipe') { 5538 var totalShiftDist = _currPoint.x - _startPoint.x, 5539 isFastLastFlick = _releaseAnimData.lastFlickDist.x < 10; 5540 5541 // if container is shifted for more than MIN_SWIPE_DISTANCE, 5542 // and last flick gesture was in right direction 5543 if(totalShiftDist > MIN_SWIPE_DISTANCE && 5544 (isFastLastFlick || _releaseAnimData.lastFlickOffset.x > 20) ) { 5545 // go to prev item 5546 itemsDiff = -1; 5547 } else if(totalShiftDist < -MIN_SWIPE_DISTANCE && 5548 (isFastLastFlick || _releaseAnimData.lastFlickOffset.x < -20) ) { 5549 // go to next item 5550 itemsDiff = 1; 5551 } 5552 } 5553 5554 var nextCircle; 5555 5556 if(itemsDiff) { 5557 5558 _currentItemIndex += itemsDiff; 5559 5560 if(_currentItemIndex < 0) { 5561 _currentItemIndex = _options.loop ? _getNumItems()-1 : 0; 5562 nextCircle = true; 5563 } else if(_currentItemIndex >= _getNumItems()) { 5564 _currentItemIndex = _options.loop ? 0 : _getNumItems()-1; 5565 nextCircle = true; 5566 } 5567 5568 if(!nextCircle || _options.loop) { 5569 _indexDiff += itemsDiff; 5570 _currPositionIndex -= itemsDiff; 5571 itemChanged = true; 5572 } 5573 5574 5575 5576 } 5577 5578 var animateToX = _slideSize.x * _currPositionIndex; 5579 var animateToDist = Math.abs( animateToX - _mainScrollPos.x ); 5580 var finishAnimDuration; 5581 5582 5583 if(!itemChanged && animateToX > _mainScrollPos.x !== _releaseAnimData.lastFlickSpeed.x > 0) { 5584 // "return to current" duration, e.g. when dragging from slide 0 to -1 5585 finishAnimDuration = 333; 5586 } else { 5587 finishAnimDuration = Math.abs(_releaseAnimData.lastFlickSpeed.x) > 0 ? 5588 animateToDist / Math.abs(_releaseAnimData.lastFlickSpeed.x) : 5589 333; 5590 5591 finishAnimDuration = Math.min(finishAnimDuration, 400); 5592 finishAnimDuration = Math.max(finishAnimDuration, 250); 5593 } 5594 5595 if(_currZoomedItemIndex === _currentItemIndex) { 5596 itemChanged = false; 5597 } 5598 5599 _mainScrollAnimating = true; 5600 5601 _shout('mainScrollAnimStart'); 5602 5603 _animateProp('mainScroll', _mainScrollPos.x, animateToX, finishAnimDuration, framework.easing.cubic.out, 5604 _moveMainScroll, 5605 function() { 5606 _stopAllAnimations(); 5607 _mainScrollAnimating = false; 5608 _currZoomedItemIndex = -1; 5609 5610 if(itemChanged || _currZoomedItemIndex !== _currentItemIndex) { 5611 self.updateCurrItem(); 5612 } 5613 5614 _shout('mainScrollAnimComplete'); 5615 } 5616 ); 5617 5618 if(itemChanged) { 5619 self.updateCurrItem(true); 5620 } 5621 5622 return itemChanged; 5623 }, 5624 5625 _calculateZoomLevel = function(touchesDistance) { 5626 return 1 / _startPointsDistance * touchesDistance * _startZoomLevel; 5627 }, 5628 5629 // Resets zoom if it's out of bounds 5630 _completeZoomGesture = function() { 5631 var destZoomLevel = _currZoomLevel, 5632 minZoomLevel = _getMinZoomLevel(), 5633 maxZoomLevel = _getMaxZoomLevel(); 5634 5635 if ( _currZoomLevel < minZoomLevel ) { 5636 destZoomLevel = minZoomLevel; 5637 } else if ( _currZoomLevel > maxZoomLevel ) { 5638 destZoomLevel = maxZoomLevel; 5639 } 5640 5641 var destOpacity = 1,
5642 onUpdate, 5643 initialOpacity = _bgOpacity; 5644 5645 if(_opacityChanged && !_isZoomingIn && !_wasOverInitialZoom && _currZoomLevel < minZoomLevel) { 5646 //_closedByScroll = true; 5647 self.close(); 5648 return true; 5649 } 5650 5651 if(_opacityChanged) { 5652 onUpdate = function(now) { 5653 _applyBgOpacity( (destOpacity - initialOpacity) * now + initialOpacity ); 5654 }; 5655 } 5656 5657 self.zoomTo(destZoomLevel, 0, 200, framework.easing.cubic.out, onUpdate); 5658 return true; 5659 }; 5660 5661 5662_registerModule('Gestures', { 5663 publicMethods: { 5664 5665 initGestures: function() { 5666 5667 // helper function that builds touch/pointer/mouse events 5668 var addEventNames = function(pref, down, move, up, cancel) { 5669 _dragStartEvent = pref + down; 5670 _dragMoveEvent = pref + move; 5671 _dragEndEvent = pref + up; 5672 if(cancel) { 5673 _dragCancelEvent = pref + cancel; 5674 } else { 5675 _dragCancelEvent = ''; 5676 } 5677 }; 5678 5679 _pointerEventEnabled = _features.pointerEvent; 5680 if(_pointerEventEnabled && _features.touch) { 5681 // we don't need touch events, if browser supports pointer events 5682 _features.touch = false; 5683 } 5684 5685 if(_pointerEventEnabled) { 5686 if(navigator.pointerEnabled) { 5687 addEventNames('pointer', 'down', 'move', 'up', 'cancel'); 5688 } else { 5689 // IE10 pointer events are case-sensitive 5690 addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel'); 5691 } 5692 } else if(_features.touch) { 5693 addEventNames('touch', 'start', 'move', 'end', 'cancel'); 5694 _likelyTouchDevice = true; 5695 } else { 5696 addEventNames('mouse', 'down', 'move', 'up'); 5697 } 5698 5699 _upMoveEvents = _dragMoveEvent + ' ' + _dragEndEvent + ' ' + _dragCancelEvent; 5700 _downEvents = _dragStartEvent; 5701 5702 if(_pointerEventEnabled && !_likelyTouchDevice) { 5703 _likelyTouchDevice = (navigator.maxTouchPoints > 1) || (navigator.msMaxTouchPoints > 1); 5704 } 5705 // make variable public 5706 self.likelyTouchDevice = _likelyTouchDevice; 5707 5708 _globalEventHandlers[_dragStartEvent] = _onDragStart; 5709 _globalEventHandlers[_dragMoveEvent] = _onDragMove; 5710 _globalEventHandlers[_dragEndEvent] = _onDragRelease; // the Kraken 5711 5712 if(_dragCancelEvent) { 5713 _globalEventHandlers[_dragCancelEvent] = _globalEventHandlers[_dragEndEvent]; 5714 } 5715 5716 // Bind mouse events on device with detected hardware touch support, in case it supports multiple types of input. 5717 if(_features.touch) { 5718 _downEvents += ' mousedown'; 5719 _upMoveEvents += ' mousemove mouseup'; 5720 _globalEventHandlers.mousedown = _globalEventHandlers[_dragStartEvent]; 5721 _globalEventHandlers.mousemove = _globalEventHandlers[_dragMoveEvent]; 5722 _globalEventHandlers.mouseup = _globalEventHandlers[_dragEndEvent]; 5723 } 5724 5725 if(!_likelyTouchDevice) { 5726 // don't allow pan to next slide from zoomed state on Desktop 5727 _options.allowPanToNext = false; 5728 } 5729 } 5730 5731 } 5732}); 5733 5734 5735/*>>gestures*/ 5736 5737/*>>show-hide-transition*/ 5738/** 5739 * show-hide-transition.js: 5740 * 5741 * Manages initial opening or closing transition. 5742 * 5743 * If you're not planning to use transition for gallery at all, 5744 * you may set options hideAnimationDuration and showAnimationDuration to 0, 5745 * and just delete startAnimation function. 5746 * 5747 */ 5748 5749 5750var _showOrHideTimeout, 5751 _showOrHide = function(item, img, out, completeFn) { 5752 5753 if(_showOrHideTimeout) { 5754 clearTimeout(_showOrHideTimeout); 5755 } 5756 5757 _initialZoomRunning = true; 5758 _initialContentSet = true; 5759 5760 // dimensions of small thumbnail {x:,y:,w:}. 5761 // Height is optional, as calculated based on large image. 5762 var thumbBounds; 5763 if(item.initialLayout) { 5764 thumbBounds = item.initialLayout; 5765 item.initialLayout = null; 5766 } else { 5767 thumbBounds = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex); 5768 } 5769 5770 var duration = out ? _options.hideAnimationDuration : _options.showAnimationDuration; 5771 5772 var onComplete = function() { 5773 _stopAnimation('initialZoom'); 5774 if(!out) { 5775 _applyBgOpacity(1); 5776 if(img) { 5777 img.style.display = 'block'; 5778 } 5779 framework.addClass(template, 'pswp--animated-in'); 5780 _shout('initialZoom' + (out ? 'OutEnd' : 'InEnd')); 5781 } else { 5782 self.template.removeAttribute('style'); 5783 self.bg.removeAttribute('style'); 5784 } 5785 5786 if(completeFn) { 5787 completeFn(); 5788 } 5789 _initialZoomRunning = false; 5790 }; 5791 5792 // if bounds aren't provided, just open gallery without animation 5793 if(!duration || !thumbBounds || thumbBounds.x === undefined) { 5794 5795 _shout('initialZoom' + (out ? 'Out' : 'In') ); 5796 5797 _currZoomLevel = item.initialZoomLevel; 5798 _equalizePoints(_panOffset, item.initialPosition ); 5799 _applyCurrentZoomPan(); 5800 5801 template.style.opacity = out ? 0 : 1; 5802 _applyBgOpacity(1); 5803 5804 if(duration) { 5805 setTimeout(function() { 5806 onComplete(); 5807 }, duration); 5808 } else { 5809 onComplete(); 5810 } 5811 5812 return; 5813 } 5814 5815 var startAnimation = function() { 5816 var closeWithRaf = _closedByScroll, 5817 fadeEverything = !self.currItem.src || self.currItem.loadError || _options.showHideOpacity; 5818 5819 // apply hw-acceleration to image 5820 if(item.miniImg) { 5821 item.miniImg.style.webkitBackfaceVisibility = 'hidden'; 5822 } 5823 5824 if(!out) { 5825 _currZoomLevel = thumbBounds.w / item.w; 5826 _panOffset.x = thumbBounds.x;
5827 _panOffset.y = thumbBounds.y - _initalWindowScrollY; 5828 5829 self[fadeEverything ? 'template' : 'bg'].style.opacity = 0.001; 5830 _applyCurrentZoomPan(); 5831 } 5832 5833 _registerStartAnimation('initialZoom'); 5834 5835 if(out && !closeWithRaf) { 5836 framework.removeClass(template, 'pswp--animated-in'); 5837 } 5838 5839 if(fadeEverything) { 5840 if(out) { 5841 framework[ (closeWithRaf ? 'remove' : 'add') + 'Class' ](template, 'pswp--animate_opacity'); 5842 } else { 5843 setTimeout(function() { 5844 framework.addClass(template, 'pswp--animate_opacity'); 5845 }, 30); 5846 } 5847 } 5848 5849 _showOrHideTimeout = setTimeout(function() { 5850 5851 _shout('initialZoom' + (out ? 'Out' : 'In') ); 5852 5853 5854 if(!out) { 5855 5856 // "in" animation always uses CSS transitions (instead of rAF). 5857 // CSS transition work faster here, 5858 // as developer may also want to animate other things, 5859 // like ui on top of sliding area, which can be animated just via CSS 5860 5861 _currZoomLevel = item.initialZoomLevel; 5862 _equalizePoints(_panOffset, item.initialPosition ); 5863 _applyCurrentZoomPan(); 5864 _applyBgOpacity(1); 5865 5866 if(fadeEverything) { 5867 template.style.opacity = 1; 5868 } else { 5869 _applyBgOpacity(1); 5870 } 5871 5872 _showOrHideTimeout = setTimeout(onComplete, duration + 20); 5873 } else { 5874 5875 // "out" animation uses rAF only when PhotoSwipe is closed by browser scroll, to recalculate position 5876 var destZoomLevel = thumbBounds.w / item.w, 5877 initialPanOffset = { 5878 x: _panOffset.x, 5879 y: _panOffset.y 5880 }, 5881 initialZoomLevel = _currZoomLevel, 5882 initalBgOpacity = _bgOpacity, 5883 onUpdate = function(now) { 5884 5885 if(now === 1) { 5886 _currZoomLevel = destZoomLevel; 5887 _panOffset.x = thumbBounds.x; 5888 _panOffset.y = thumbBounds.y - _currentWindowScrollY; 5889 } else { 5890 _currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel; 5891 _panOffset.x = (thumbBounds.x - initialPanOffset.x) * now + initialPanOffset.x; 5892 _panOffset.y = (thumbBounds.y - _currentWindowScrollY - initialPanOffset.
5892y) * now + initialPanOffset.y; 5893 } 5894 5895 _applyCurrentZoomPan(); 5896 if(fadeEverything) { 5897 template.style.opacity = 1 - now; 5898 } else { 5899 _applyBgOpacity( initalBgOpacity - now * initalBgOpacity ); 5900 } 5901 }; 5902 5903 if(closeWithRaf) { 5904 _animateProp('initialZoom', 0, 1, duration, framework.easing.cubic.out, onUpdate, onComplete); 5905 } else { 5906 onUpdate(1); 5907 _showOrHideTimeout = setTimeout(onComplete, duration + 20); 5908 } 5909 } 5910 5911 }, out ? 25 : 90); // Main purpose of this delay is to give browser time to paint and 5912 // create composite layers of PhotoSwipe UI parts (background, controls, caption, arrows). 5913 // Which avoids lag at the beginning of scale transition. 5914 }; 5915 startAnimation(); 5916 5917 5918 }; 5919 5920/*>>show-hide-transition*/ 5921 5922/*>>items-controller*/ 5923/** 5924* 5925* Controller manages gallery items, their dimensions, and their content. 5926* 5927*/ 5928 5929var _items, 5930 _tempPanAreaSize = {}, 5931 _imagesToAppendPool = [], 5932 _initialContentSet, 5933 _initialZoomRunning, 5934 _controllerDefaultOptions = { 5935 index: 0, 5936 errorMsg: '<div class="pswp__error-msg"><a href="%url%" target="_blank">The image</a> could not be loaded.</div>', 5937 forceProgressiveLoading: false, // TODO 5938 preload: [1,1], 5939 getNumItemsFn: function() { 5940 return _items.length; 5941 } 5942 }; 5943 5944 5945var _getItemAt, 5946 _getNumItems, 5947 _initialIsLoop, 5948 _getZeroBounds = function() { 5949 return { 5950 center:{x:0,y:0}, 5951 max:{x:0,y:0}, 5952 min:{x:0,y:0} 5953 }; 5954 }, 5955 _calculateSingleItemPanBounds = function(item, realPanElementW, realPanElementH ) { 5956 var bounds = item.bounds; 5957 5958 // position of element when it's centered 5959 bounds.center.x = Math.round((_tempPanAreaSize.x - realPanElementW) / 2); 5960 bounds.center.y = Math.round((_tempPanAreaSize.y - realPanElementH) / 2) + item.vGap.top; 5961 5962 // maximum pan position 5963 bounds.max.x = (realPanElementW > _tempPanAreaSize.x) ? 5964 Math.round(_tempPanAreaSize.x - realPanElementW) : 5965 bounds.center.x; 5966 5967 bounds.max.y = (realPanElementH > _tempPanAreaSize.y) ? 5968 Math.round(_tempPanAreaSize.y - realPanElementH) + item.vGap.top : 5969 bounds.center.y; 5970 5971 // minimum pan position 5972 bounds.min.x = (realPanElementW > _tempPanAreaSize.x) ? 0 : bounds.center.x; 5973 bounds.min.y = (realPanElementH > _tempPanAreaSize.y) ? item.vGap.top : bounds.center.y; 5974 }, 5975 _calculateItemSize = function(item, viewportSize, zoomLevel) { 5976 5977 if (item.src && !item.loadError) { 5978 var isInitial = !zoomLevel; 5979 5980 if(isInitial) { 5981 if(!item.vGap) { 5982 item.vGap = {top:0,bottom:0}; 5983 } 5984 // allows overriding vertical margin for individual items 5985 _shout('parseVerticalMargin', item); 5986 } 5987 5988 5989 _tempPanAreaSize.x = viewportSize.x; 5990 _tempPanAreaSize.y = viewportSize.y - item.vGap.top - item.vGap.bottom; 5991 5992 if (isInitial) { 5993 var hRatio = _tempPanAreaSize.x / item.w; 5994 var vRatio = _tempPanAreaSize.y / item.h; 5995 5996 item.fitRatio = hRatio < vRatio ? hRatio : vRatio; 5997 //item.fillRatio = hRatio > vRatio ? hRatio : vRatio; 5998 5999 var scaleMode = _options.scaleMode; 6000 6001 if (scaleMode === 'orig') { 6002 zoomLevel = 1; 6003 } else if (scaleMode === 'fit') { 6004 zoomLevel = item.fitRatio; 6005 } 6006 6007 if (zoomLevel > 1) { 6008 zoomLevel = 1; 6009 } 6010 6011 item.initialZoomLevel = zoomLevel; 6012 6013 if(!item.bounds) { 6014 // reuse bounds object 6015 item.bounds = _getZeroBounds(); 6016 } 6017 } 6018 6019 if(!zoomLevel) { 6020 return; 6021 } 6022 6023 _calculateSingleItemPanBounds(item, item.w * zoomLevel, item.h * zoomLevel); 6024 6025 if (isInitial && zoomLevel === item.initialZoomLevel) { 6026 item.initialPosition = item.bounds.center; 6027 } 6028 6029 return item.bounds; 6030 } else { 6031 item.w = item.h = 0; 6032 item.initialZoomLevel = item.fitRatio = 1; 6033 item.bounds = _getZeroBounds(); 6034 item.initialPosition = item.bounds.center; 6035 6036 // if it's not image, we return zero bounds (content is not zoomable) 6037 return item.bounds; 6038 } 6039 return false; 6040 }, 6041 6042 6043 6044 6045 _appendImage = function(index, item, baseDiv, img, preventAnimation, keepPlaceholder) { 6046 6047 6048 if(item.loadError) { 6049 return; 6050 } 6051 6052 if(img) { 6053 6054 item.imageAppended = true; 6055 _setImageSize(item, img); 6056 6057 baseDiv.appendChild(img); 6058 6059 if(keepPlaceholder) { 6060 setTimeout(function() { 6061 if(item && item.loaded && item.placeholder) { 6062 item.placeholder.style.display = 'none'; 6063 item.placeholder = null; 6064 } 6065 }, 500); 6066 } 6067 } 6068 }, 6069 6070 6071 6072 _preloadImage = function(item) { 6073 item.loading = true; 6074 item.loaded = false; 6075 var img = item.img = framework.createEl('pswp__img', 'img'); 6076 var onComplete = function() { 6077 item.loading = false; 6078 item.loaded = true; 6079 6080 if(item.loadComplete) { 6081 item.loadComplete(item); 6082 } else { 6083 item.img = null; // no need to store image object 6084 } 6085 img.onload = img.onerror = null; 6086 img = null; 6087 }; 6088 img.onload = onComplete; 6089 img.onerror = function() { 6090 item.loadError = true; 6091 onComplete(); 6092 }; 6093 6094 img.src = item.src;// + '?a=' + Math.random(); 6095 6096 return img; 6097 }, 6098 _checkForError = function(item, cleanUp) { 6099 if(item.src && item.loadError && item.container) { 6100 6101 if(cleanUp) { 6102 item.container.innerHTML = ''; 6103 } 6104 6105 item.container.innerHTML = _options.errorMsg.replace('%url%', item.src ); 6106 return true; 6107 6108 } 6109 }, 6110 _setImageSize = function(item, img, maxRes) { 6111 if(!item.src) { 6112 return; 6113 } 6114 6115 if(!img) { 6116 img = item.container.lastChild; 6117 } 6118 6119 var w = maxRes ? item.w : Math.round(item.w * item.fitRatio), 6120 h = maxRes ? item.h : Math.round(item.h * item.fitRatio); 6121 6122 if(item.placeholder && !item.loaded) { 6123 item.placeholder.style.width = w + 'px'; 6124 item.placeholder.style.height = h + 'px'; 6125 } 6126 6127 img.style.width = w + 'px'; 6128 img.style.height = h + 'px'; 6129 }, 6130 _appendImagesPool = function() { 6131 6132 if(_imagesToAppendPool.length) { 6133 var poolItem; 6134 6135 for(var i = 0; i < _imagesToAppendPool.length; i++) { 6136 poolItem = _imagesToAppendPool[i]; 6137 if( poolItem.holder.index === poolItem.index ) { 6138 _appendImage(poolItem.index, poolItem.item, poolItem.baseDiv, poolItem.img, false, poolItem.clearPlaceholder); 6139 } 6140 } 6141 _imagesToAppendPool = []; 6142 } 6143 }; 6144 6145 6146 6147_registerModule('Controller', { 6148 6149 publicMethods: { 6150 6151 lazyLoadItem: function(index) { 6152 index = _getLoopedId(index); 6153 var item = _getItemAt(index); 6154 6155 if(!item || ((item.loaded || item.loading) && !_itemsNeedUpdate)) { 6156 return; 6157 } 6158 6159 _shout('gettingData', index, item); 6160 6161 if (!item.src) { 6162 return; 6163 } 6164 6165 _preloadImage(item); 6166 }, 6167 initController: function() { 6168 framework.extend(_options, _controllerDefaultOptions, true); 6169 self.items = _items = items; 6170 _getItemAt = self.getItemAt; 6171 _getNumItems = _options.getNumItemsFn; //self.getNumItems; 6172 6173 6174 6175 _initialIsLoop = _options.loop; 6176 if(_getNumItems() < 3) { 6177 _options.loop = false; // disable loop if less then 3 items 6178 } 6179 6180 _listen('beforeChange', function(diff) { 6181 6182 var p = _options.preload, 6183 isNext = diff === null ? true : (diff >= 0), 6184 preloadBefore = Math.min(p[0], _getNumItems() ),
6185 preloadAfter = Math.min(p[1], _getNumItems() ), 6186 i; 6187 6188 6189 for(i = 1; i <= (isNext ? preloadAfter : preloadBefore); i++) { 6190 self.lazyLoadItem(_currentItemIndex+i); 6191 } 6192 for(i = 1; i <= (isNext ? preloadBefore : preloadAfter); i++) { 6193 self.lazyLoadItem(_currentItemIndex-i); 6194 } 6195 }); 6196 6197 _listen('initialLayout', function() { 6198 self.currItem.initialLayout = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex); 6199 }); 6200 6201 _listen('mainScrollAnimComplete', _appendImagesPool); 6202 _listen('initialZoomInEnd', _appendImagesPool); 6203 6204 6205 6206 _listen('destroy', function() { 6207 var item; 6208 for(var i = 0; i < _items.length; i++) { 6209 item = _items[i]; 6210 // remove reference to DOM elements, for GC 6211 if(item.container) { 6212 item.container = null; 6213 } 6214 if(item.placeholder) { 6215 item.placeholder = null; 6216 } 6217 if(item.img) { 6218 item.img = null; 6219 } 6220 if(item.preloader) { 6221 item.preloader = null; 6222 } 6223 if(item.loadError) { 6224 item.loaded = item.loadError = false; 6225 } 6226 } 6227 _imagesToAppendPool = null; 6228 }); 6229 }, 6230 6231 6232 getItemAt: function(index) { 6233 if (index >= 0) { 6234 return _items[index] !== undefined ? _items[index] : false; 6235 } 6236 return false; 6237 }, 6238 6239 allowProgressiveImg: function() { 6240 // 1. Progressive image loading isn't working on webkit/blink 6241 // when hw-acceleration (e.g. translateZ) is applied to IMG element. 6242 // That's why in PhotoSwipe parent element gets zoom transform, not image itself. 6243 // 6244 // 2. Progressive image loading sometimes blinks in webkit/blink when applying animation to parent element. 6245 // That's why it's disabled on touch devices (mainly because of swipe transition) 6246 // 6247 // 3. Progressive image loading sometimes doesn't work in IE (up to 11). 6248 6249 // Don't allow progressive loading on non-large touch devices 6250 return _options.forceProgressiveLoading || !_likelyTouchDevice || _options.mouseUsed || screen.width > 1200; 6251 // 1200 - to eliminate touch devices with large screen (like Chromebook Pixel) 6252 }, 6253 6254 setContent: function(holder, index) { 6255 6256 if(_options.loop) { 6257 index = _getLoopedId(index); 6258 } 6259 6260 var prevItem = self.getItemAt(holder.index); 6261 if(prevItem) { 6262 prevItem.container = null; 6263 } 6264 6265 var item = self.getItemAt(index), 6266 img; 6267 6268 if(!item) { 6269 holder.el.innerHTML = ''; 6270 return; 6271 } 6272 6273 // allow to override data 6274 _shout('gettingData', index, item); 6275 6276 holder.index = index; 6277 holder.item = item; 6278 6279 // base container DIV is created only once for each of 3 holders 6280 var baseDiv = item.container = framework.createEl('pswp__zoom-wrap'); 6281 6282 6283 6284 if(!item.src && item.html) { 6285 if(item.html.tagName) { 6286 baseDiv.appendChild(item.html); 6287 } else { 6288 baseDiv.innerHTML = item.html; 6289 } 6290 } 6291 6292 _checkForError(item); 6293 6294 _calculateItemSize(item, _viewportSize); 6295 6296 if(item.src && !item.loadError && !item.loaded) { 6297 6298 item.loadComplete = function(item) { 6299 6300 // gallery closed before image finished loading 6301 if(!_isOpen) { 6302 return; 6303 } 6304 6305 // check if holder hasn't changed while image was loading 6306 if(holder && holder.index === index ) { 6307 if( _checkForError(item, true) ) { 6308 item.loadComplete = item.img = null; 6309 _calculateItemSize(item, _viewportSize); 6310 _applyZoomPanToItem(item); 6311 6312 if(holder.index === _currentItemIndex) { 6313 // recalculate dimensions 6314 self.updateCurrZoomItem(); 6315 } 6316 return; 6317 } 6318 if( !item.imageAppended ) { 6319 if(_features.transform && (_mainScrollAnimating || _initialZoomRunning) ) { 6320 _imagesToAppendPool.push({ 6321 item:item, 6322 baseDiv:baseDiv, 6323 img:item.img, 6324 index:index, 6325 holder:holder, 6326 clearPlaceholder:true 6327 });
6328 } else { 6329 _appendImage(index, item, baseDiv, item.img, _mainScrollAnimating || _initialZoomRunning, true); 6330 } 6331 } else { 6332 // remove preloader & mini-img 6333 if(!_initialZoomRunning && item.placeholder) { 6334 item.placeholder.style.display = 'none'; 6335 item.placeholder = null; 6336 } 6337 } 6338 } 6339 6340 item.loadComplete = null; 6341 item.img = null; // no need to store image element after it's added 6342 6343 _shout('imageLoadComplete', index, item); 6344 }; 6345 6346 if(framework.features.transform) { 6347 6348 var placeholderClassName = 'pswp__img pswp__img--placeholder'; 6349 placeholderClassName += (item.msrc ? '' : ' pswp__img--placeholder--blank'); 6350 6351 var placeholder = framework.createEl(placeholderClassName, item.msrc ? 'img' : ''); 6352 if(item.msrc) { 6353 placeholder.src = item.msrc; 6354 } 6355 6356 _setImageSize(item, placeholder); 6357 6358 baseDiv.appendChild(placeholder); 6359 item.placeholder = placeholder; 6360 6361 } 6362 6363 6364 6365 6366 if(!item.loading) { 6367 _preloadImage(item); 6368 } 6369 6370 6371 if( self.allowProgressiveImg() ) { 6372 // just append image 6373 if(!_initialContentSet && _features.transform) { 6374 _imagesToAppendPool.push({ 6375 item:item, 6376 baseDiv:baseDiv, 6377 img:item.img, 6378 index:index, 6379 holder:holder 6380 }); 6381 } else { 6382 _appendImage(index, item, baseDiv, item.img, true, true); 6383 } 6384 } 6385 6386 } else if(item.src && !item.loadError) { 6387 // image object is created every time, due to bugs of image loading & delay when switching images 6388 img = framework.createEl('pswp__img', 'img'); 6389 img.style.opacity = 1; 6390 img.src = item.src; 6391 _setImageSize(item, img); 6392 _appendImage(index, item, baseDiv, img, true); 6393 } 6394 6395 6396 if(!_initialContentSet && index === _currentItemIndex) { 6397 _currZoomElementStyle = baseDiv.style; 6398 _showOrHide(item, (img ||item.img) ); 6399 } else { 6400 _applyZoomPanToItem(item); 6401 } 6402 6403 holder.el.innerHTML = ''; 6404 holder.el.appendChild(baseDiv); 6405 }, 6406 6407 cleanSlide: function( item ) { 6408 if(item.img ) { 6409 item.img.onload = item.img.onerror = null; 6410 } 6411 item.loaded = item.loading = item.img = item.imageAppended = false; 6412 } 6413 6414 } 6415}); 6416 6417/*>>items-controller*/ 6418 6419/*>>tap*/ 6420/** 6421 * tap.js: 6422 * 6423 * Displatches tap and double-tap events. 6424 * 6425 */ 6426 6427var tapTimer, 6428 tapReleasePoint = {}, 6429 _dispatchTapEvent = function(origEvent, releasePoint, pointerType) { 6430 var e = document.createEvent( 'CustomEvent' ), 6431 eDetail = { 6432 origEvent:origEvent, 6433 target:origEvent.target, 6434 releasePoint: releasePoint, 6435 pointerType:pointerType || 'touch' 6436 }; 6437 6438 e.initCustomEvent( 'pswpTap', true, true, eDetail ); 6439 origEvent.target.dispatchEvent(e); 6440 }; 6441 6442_registerModule('Tap', { 6443 publicMethods: { 6444 initTap: function() { 6445 _listen('firstTouchStart', self.onTapStart); 6446 _listen('touchRelease', self.onTapRelease); 6447 _listen('destroy', function() { 6448 tapReleasePoint = {}; 6449 tapTimer = null; 6450 }); 6451 }, 6452 onTapStart: function(touchList) { 6453 if(touchList.length > 1) { 6454 clearTimeout(tapTimer); 6455 tapTimer = null; 6456 } 6457 }, 6458 onTapRelease: function(e, releasePoint) { 6459 if(!releasePoint) { 6460 return; 6461 } 6462 6463 if(!_moved && !_isMultitouch && !_numAnimations) { 6464 var p0 = releasePoint; 6465 if(tapTimer) { 6466 clearTimeout(tapTimer); 6467 tapTimer = null; 6468 6469 // Check if taped on the same place 6470 if ( _isNearbyPoints(p0, tapReleasePoint) ) { 6471 _shout('doubleTap', p0); 6472 return; 6473 } 6474 } 6475 6476 if(releasePoint.type === 'mouse') { 6477 _dispatchTapEvent(e, releasePoint, 'mouse'); 6478 return; 6479 } 6480 6481 var clickedTagName = e.target.tagName.toUpperCase(); 6482 // avoid double tap delay on buttons and elements that have class pswp__single-tap 6483 if(clickedTagName === 'BUTTON' || framework.hasClass(e.target, 'pswp__single-tap') ) { 6484 _dispatchTapEvent(e, releasePoint); 6485 return; 6486 } 6487 6488 _equalizePoints(tapReleasePoint, p0); 6489 6490 tapTimer = setTimeout(function() { 6491 _dispatchTapEvent(e, releasePoint); 6492 tapTimer = null; 6493 }, 300); 6494 } 6495 } 6496 } 6497}); 6498 6499/*>>tap*/ 6500 6501/*>>desktop-zoom*/ 6502/** 6503 * 6504 * desktop-zoom.js: 6505 * 6506 * - Binds mousewheel event for paning zoomed image. 6507 * - Manages "dragging", "zoomed-in", "zoom-out" classes. 6508 * (which are used for cursors and zoom icon) 6509 * - Adds toggleDesktopZoom function. 6510 * 6511 */ 6512 6513var _wheelDelta; 6514 6515_registerModule('DesktopZoom', { 6516 6517 publicMethods: { 6518 6519 initDesktopZoom: function() { 6520 6521 if(_oldIE) { 6522 // no zoom for old IE (<=8) 6523 return; 6524 } 6525 6526 if(_likelyTouchDevice) { 6527 // if detected hardware touch support, we wait until mouse is used, 6528 // and only then apply desktop-zoom features 6529 _listen('mouseUsed', function() { 6530 self.setupDesktopZoom(); 6531 }); 6532 } else { 6533 self.setupDesktopZoom(true); 6534 } 6535 6536 }, 6537 6538 setupDesktopZoom: function(onInit) { 6539 6540 _wheelDelta = {}; 6541 6542 var events = 'wheel mousewheel DOMMouseScroll'; 6543 6544 _listen('bindEvents', function() { 6545 framework.bind(template, events, self.handleMouseWheel); 6546 }); 6547 6548 _listen('unbindEvents', function() { 6549 if(_wheelDelta) { 6550 framework.unbind(template, events, self.handleMouseWheel); 6551 } 6552 }); 6553 6554 self.mouseZoomedIn = false; 6555 6556 var hasDraggingClass, 6557 updateZoomable = function() { 6558 if(self.mouseZoomedIn) {
6559 framework.removeClass(template, 'pswp--zoomed-in'); 6560 self.mouseZoomedIn = false; 6561 } 6562 if(_currZoomLevel < 1) { 6563 framework.addClass(template, 'pswp--zoom-allowed'); 6564 } else { 6565 framework.removeClass(template, 'pswp--zoom-allowed'); 6566 } 6567 removeDraggingClass(); 6568 }, 6569 removeDraggingClass = function() { 6570 if(hasDraggingClass) { 6571 framework.removeClass(template, 'pswp--dragging'); 6572 hasDraggingClass = false; 6573 } 6574 }; 6575 6576 _listen('resize' , updateZoomable); 6577 _listen('afterChange' , updateZoomable); 6578 _listen('pointerDown', function() { 6579 if(self.mouseZoomedIn) { 6580 hasDraggingClass = true; 6581 framework.addClass(template, 'pswp--dragging'); 6582 } 6583 }); 6584 _listen('pointerUp', removeDraggingClass); 6585 6586 if(!onInit) { 6587 updateZoomable(); 6588 } 6589 6590 }, 6591 6592 handleMouseWheel: function(e) { 6593 6594 if(_currZoomLevel <= self.currItem.fitRatio) { 6595 if( _options.modal ) { 6596 6597 if (!_options.closeOnScroll || _numAnimations || _isDragging) { 6598 e.preventDefault(); 6599 } else if(_transformKey && Math.abs(e.deltaY) > 2) { 6600 // close PhotoSwipe 6601 // if browser supports transforms & scroll changed enough 6602 _closedByScroll = true; 6603 self.close(); 6604 } 6605 6606 } 6607 return true; 6608 } 6609 6610 // allow just one event to fire 6611 e.stopPropagation(); 6612 6613 // https://developer.mozilla.org/en-US/docs/Web/Events/wheel 6614 _wheelDelta.x = 0; 6615 6616 if('deltaX' in e) { 6617 if(e.deltaMode === 1 /* DOM_DELTA_LINE */) { 6618 // 18 - average line height 6619 _wheelDelta.x = e.deltaX * 18; 6620 _wheelDelta.y = e.deltaY * 18; 6621 } else { 6622 _wheelDelta.x = e.deltaX; 6623 _wheelDelta.y = e.deltaY; 6624 } 6625 } else if('wheelDelta' in e) { 6626 if(e.wheelDeltaX) { 6627 _wheelDelta.x = -0.16 * e.wheelDeltaX; 6628 } 6629 if(e.wheelDeltaY) { 6630 _wheelDelta.y = -0.16 * e.wheelDeltaY; 6631 } else { 6632 _wheelDelta.y = -0.16 * e.wheelDelta; 6633 } 6634 } else if('detail' in e) { 6635 _wheelDelta.y = e.detail; 6636 } else { 6637 return; 6638 } 6639 6640 _calculatePanBounds(_currZoomLevel, true); 6641 6642 var newPanX = _panOffset.x - _wheelDelta.x, 6643 newPanY = _panOffset.y - _wheelDelta.y; 6644 6645 // only prevent scrolling in nonmodal mode when not at edges 6646 if (_options.modal || 6647 ( 6648 newPanX <= _currPanBounds.min.x && newPanX >= _currPanBounds.max.x && 6649 newPanY <= _currPanBounds.min.y && newPanY >= _currPanBounds.max.y 6650 ) ) { 6651 e.preventDefault(); 6652 } 6653 6654 // TODO: use rAF instead of mousewheel? 6655 self.panTo(newPanX, newPanY); 6656 }, 6657 6658 toggleDesktopZoom: function(centerPoint) { 6659 centerPoint = centerPoint || {x:_viewportSize.x/2 + _offset.x, y:_viewportSize.y/2 + _offset.y }; 6660 6661 var doubleTapZoomLevel = _options.getDoubleTapZoom(true, self.currItem); 6662 var zoomOut = _currZoomLevel === doubleTapZoomLevel; 6663 6664 self.mouseZoomedIn = !zoomOut; 6665 6666 self.zoomTo(zoomOut ? self.currItem.initialZoomLevel : doubleTapZoomLevel, centerPoint, 333); 6667 framework[ (!zoomOut ? 'add' : 'remove') + 'Class'](template, 'pswp--zoomed-in'); 6668 } 6669 6670 } 6671}); 6672 6673 6674/*>>desktop-zoom*/ 6675 6676/*>>history*/ 6677/** 6678 * 6679 * history.js: 6680 * 6681 * - Back button to close gallery. 6682 * 6683 * - Unique URL for each slide: example.com/&pid=1&gid=3 6684 * (where PID is picture index, and GID and gallery index) 6685 * 6686 * - Switch URL when slides change. 6687 * 6688 */ 6689 6690 6691var _historyDefaultOptions = { 6692 history: true, 6693 galleryUID: 1 6694}; 6695 6696var _historyUpdateTimeout, 6697 _hashChangeTimeout, 6698 _hashAnimCheckTimeout, 6699 _hashChangedByScript, 6700 _hashChangedByHistory, 6701 _hashReseted, 6702 _initialHash, 6703 _historyChanged, 6704 _closedFromURL, 6705 _urlChangedOnce, 6706 _windowLoc, 6707 6708 _supportsPushState, 6709 6710 _getHash = function() { 6711 return _windowLoc.hash.substring(1); 6712 }, 6713 _cleanHistoryTimeouts = function() { 6714 6715 if(_historyUpdateTimeout) { 6716 clearTimeout(_historyUpdateTimeout); 6717 } 6718 6719 if(_hashAnimCheckTimeout) { 6720 clearTimeout(_hashAnimCheckTimeout); 6721 } 6722 }, 6723 6724 // pid - Picture index 6725 // gid - Gallery index 6726 _parseItemIndexFromURL = function() { 6727 var hash = _getHash(), 6728 params = {}; 6729 6730 if(hash.length < 5) { // pid=1 6731 return params; 6732 } 6733 6734 var i, vars = hash.split('&'); 6735 for (i = 0; i < vars.length; i++) { 6736 if(!vars[i]) { 6737 continue; 6738 } 6739 var pair = vars[i].split('='); 6740 if(pair.length < 2) { 6741 continue; 6742 } 6743 params[pair[0]] = pair[1]; 6744 } 6745 if(_options.galleryPIDs) { 6746 // detect custom pid in hash and search for it among the items collection 6747 var searchfor = params.pid; 6748 params.pid = 0; // if custom pid cannot be found, fallback to the first item 6749 for(i = 0; i < _items.length; i++) { 6750 if(_items[i].pid === searchfor) { 6751 params.pid = i; 6752 break; 6753 } 6754 } 6755 } else { 6756 params.pid = parseInt(params.pid,10)-1; 6757 } 6758 if( params.pid < 0 ) { 6759 params.pid = 0; 6760 } 6761 return params; 6762 }, 6763 _updateHash = function() { 6764 6765 if(_hashAnimCheckTimeout) { 6766 clearTimeout(_hashAnimCheckTimeout); 6767 } 6768 6769 6770 if(_numAnimations || _isDragging) { 6771 // changing browser URL forces layout/paint in some browsers, which causes noticable lag during animation 6772 // that's why we update hash only when no animations running 6773 _hashAnimCheckTimeout = setTimeout(_updateHash, 500); 6774 return; 6775 } 6776 6777 if(_hashChangedByScript) { 6778 clearTimeout(_hashChangeTimeout); 6779 } else { 6780 _hashChangedByScript = true; 6781 } 6782 6783 6784 var pid = (_currentItemIndex + 1); 6785 var item = _getItemAt( _currentItemIndex ); 6786 if(item.hasOwnProperty('pid')) { 6787 // carry forward any custom pid assigned to the item 6788 pid = item.pid; 6789 } 6790 var newHash = _initialHash + '&' + 'gid=' + _options.galleryUID + '&' + 'pid=' + pid; 6791 6792 if(!_historyChanged) { 6793 if(_windowLoc.hash.indexOf(newHash) === -1) { 6794 _urlChangedOnce = true; 6795 } 6796 // first time - add new hisory record, then just replace 6797 } 6798 6799 var newURL = _windowLoc.href.split('#')[0] + '#' + newHash; 6800 6801 if( _supportsPushState ) { 6802 6803 if('#' + newHash !== window.location.hash) { 6804 history[_historyChanged ? 'replaceState' : 'pushState']('', document.title, newURL); 6805 } 6806 6807 } else { 6808 if(_historyChanged) { 6809 _windowLoc.replace( newURL ); 6810 } else { 6811 _windowLoc.hash = newHash; 6812 } 6813 } 6814 6815 6816 6817 _historyChanged = true; 6818 _hashChangeTimeout = setTimeout(function() { 6819 _hashChangedByScript = false; 6820 }, 60); 6821 }; 6822 6823 6824 6825 6826 6827_registerModule('History', { 6828 6829 6830 6831 publicMethods: { 6832 initHistory: function() { 6833 6834 framework.extend(_options, _historyDefaultOptions, true); 6835 6836 if( !_options.history ) { 6837 return; 6838 } 6839 6840 6841 _windowLoc = window.location; 6842 _urlChangedOnce = false; 6843 _closedFromURL = false; 6844 _historyChanged = false; 6845 _initialHash = _getHash(); 6846 _supportsPushState = ('pushState' in history); 6847 6848 6849 if(_initialHash.indexOf('gid=') > -1) { 6850 _initialHash = _initialHash.split('&gid=')[0]; 6851 _initialHash = _initialHash.split('?gid=')[0]; 6852 } 6853 6854 6855 _listen('afterChange', self.updateURL); 6856 _listen('unbindEvents', function() { 6857 framework.unbind(window, 'hashchange', self.onHashChange); 6858 }); 6859 6860 6861 var returnToOriginal = function() { 6862 _hashReseted = true; 6863 if(!_closedFromURL) { 6864 6865 if(_urlChangedOnce) { 6866 history.back(); 6867 } else { 6868 6869 if(_initialHash) { 6870 _windowLoc.hash = _initialHash; 6871 } else { 6872 if (_supportsPushState) { 6873 6874 // remove hash from url without refreshing it or scrolling to top 6875 history.pushState('', document.title, _windowLoc.pathname + _windowLoc.search ); 6876 } else { 6877 _windowLoc.hash = ''; 6878 } 6879 } 6880 } 6881 6882 } 6883 6884 _cleanHistoryTimeouts(); 6885 }; 6886 6887 6888 _listen('unbindEvents', function() { 6889 if(_closedByScroll) { 6890 // if PhotoSwipe is closed by scroll, we go "back" before the closing animation starts 6891 // this is done to keep the scroll position 6892 returnToOriginal(); 6893 } 6894 }); 6895 _listen('destroy', function() { 6896 if(!_hashReseted) { 6897 returnToOriginal(); 6898 } 6899 }); 6900 _listen('firstUpdate', function() { 6901 _currentItemIndex = _parseItemIndexFromURL().pid; 6902 }); 6903 6904 6905 6906 6907 var index = _initialHash.indexOf('pid='); 6908 if(index > -1) { 6909 _initialHash = _initialHash.substring(0, index); 6910 if(_initialHash.slice(-1) === '&') { 6911 _initialHash = _initialHash.slice(0, -1); 6912 } 6913 } 6914 6915 6916 setTimeout(function() { 6917 if(_isOpen) { // hasn't destroyed yet 6918 framework.bind(window, 'hashchange', self.onHashChange); 6919 } 6920 }, 40); 6921 6922 }, 6923 onHashChange: function() { 6924 6925 if(_getHash() === _initialHash) { 6926 6927 _closedFromURL = true; 6928 self.close(); 6929 return; 6930 } 6931 if(!_hashChangedByScript) { 6932 6933 _hashChangedByHistory = true; 6934 self.goTo( _parseItemIndexFromURL().pid ); 6935 _hashChangedByHistory = false; 6936 } 6937 6938 }, 6939 updateURL: function() { 6940 6941 // Delay the update of URL, to avoid lag during transition, 6942 // and to not to trigger actions like "refresh page sound" or "blinking favicon" to often 6943 6944 _cleanHistoryTimeouts(); 6945 6946 6947 if(_hashChangedByHistory) { 6948 return; 6949 } 6950 6951 if(!_historyChanged) { 6952 _updateHash(); // first time 6953 } else { 6954 _historyUpdateTimeout = setTimeout(_updateHash, 800); 6955 } 6956 } 6957 6958 } 6959}); 6960 6961 6962/*>>history*/ 6963 framework.extend(self, publicMethods); }; 6964 return PhotoSwipe; 6965}); 6966/*! PhotoSwipe Default UI - 4.1.0 - 2015-07-11 6967* http://photoswipe.com 6968* Copyright (c) 2015 Dmitry Semenov; */ 6969/** 6970* 6971* UI on top of main sliding area (caption, arrows, close button, etc.). 6972* Built just using public methods/properties of PhotoSwipe. 6973* 6974*/ 6975(function (root, factory) { 6976 if (typeof define === 'function' && define.amd) { 6977 define(factory); 6978 } else if (typeof exports === 'object') { 6979 module.exports = factory(); 6980 } else { 6981 root.PhotoSwipeUI_Default = factory(); 6982 } 6983})(this, function () { 6984 6985 'use strict'; 6986 6987 6988 6989var PhotoSwipeUI_Default = 6990 function(pswp, framework) { 6991 6992 var ui = this; 6993 var _overlayUIUpdated = false, 6994 _controlsVisible = true, 6995 _fullscrenAPI, 6996 _controls, 6997 _captionContainer, 6998 _fakeCaptionContainer, 6999 _indexIndicator, 7000 _shareButton, 7001 _shareModal, 7002 _shareModalHidden = true, 7003 _initalCloseOnScrollValue, 7004 _isIdle, 7005 _listen, 7006 7007 _loadingIndicator, 7008 _loadingIndicatorHidden, 7009 _loadingIndicatorTimeout, 7010 7011 _galleryHasOneSlide, 7012 7013 _options, 7014 _defaultUIOptions = { 7015 barsSize: {top:44, bottom:'auto'}, 7016 closeElClasses: ['item', 'caption', 'zoom-wrap', 'ui', 'top-bar'], 7017 timeToIdle: 4000, 7018 timeToIdleOutside: 1000, 7019 loadingIndicatorDelay: 1000, // 2s 7020 7021 addCaptionHTMLFn: function(item, captionEl /*, isFake */) { 7022 if(!item.title) { 7023 captionEl.children[0].innerHTML = ''; 7024 return false; 7025 } 7026 captionEl.children[0].innerHTML = item.title; 7027 return true; 7028 }, 7029 7030 closeEl:true, 7031 captionEl: true, 7032 fullscreenEl: true, 7033 zoomEl: true, 7034 shareEl: true,
7035 counterEl: true, 7036 arrowEl: true, 7037 preloaderEl: true, 7038 7039 tapToClose: false, 7040 tapToToggleControls: true, 7041 7042 clickToCloseNonZoomable: true, 7043 7044 shareButtons: [ 7045 {id:'facebook', label:'Share on Facebook', url:'https://www.facebook.com/sharer/sharer.php?u={{url}}'}, 7046 {id:'twitter', label:'Tweet', url:'https://twitter.com/intent/tweet?text={{text}}&url={{url}}'}, 7047 {id:'pinterest', label:'Pin it', url:'http://www.pinterest.com/pin/create/button/'+ 7048 '?url={{url}}&media={{image_url}}&description={{text}}'}, 7049 {id:'download', label:'Download image', url:'{{raw_image_url}}', download:true} 7050 ], 7051 getImageURLForShare: function( /* shareButtonData */ ) { 7052 return pswp.currItem.src || ''; 7053 }, 7054 getPageURLForShare: function( /* shareButtonData */ ) { 7055 return window.location.href; 7056 }, 7057 getTextForShare: function( /* shareButtonData */ ) { 7058 return pswp.currItem.title || ''; 7059 }, 7060 7061 indexIndicatorSep: ' / ' 7062 7063 }, 7064 _blockControlsTap, 7065 _blockControlsTapTimeout; 7066 7067 7068 7069 var _onControlsTap = function(e) { 7070 if(_blockControlsTap) { 7071 return true; 7072 } 7073 7074 7075 e = e || window.event; 7076 7077 if(_options.timeToIdle && _options.mouseUsed && !_isIdle) { 7078 // reset idle timer 7079 _onIdleMouseMove(); 7080 } 7081 7082 7083 var target = e.target || e.srcElement, 7084 uiElement, 7085 clickedClass = target.className, 7086 found; 7087 7088 for(var i = 0; i < _uiElements.length; i++) { 7089 uiElement = _uiElements[i]; 7090 if(uiElement.onTap && clickedClass.indexOf('pswp__' + uiElement.name ) > -1 ) { 7091 uiElement.onTap(); 7092 found = true; 7093 7094 } 7095 } 7096 7097 if(found) { 7098 if(e.stopPropagation) { 7099 e.stopPropagation(); 7100 } 7101 _blockControlsTap = true; 7102 7103 // Some versions of Android don't prevent ghost click event 7104 // when preventDefault() was called on touchstart and/or touchend. 7105 // 7106 // This happens on v4.3, 4.2, 4.1, 7107 // older versions strangely work correctly, 7108 // but just in case we add delay on all of them) 7109 var tapDelay = framework.features.isOldAndroid ? 600 : 30; 7110 _blockControlsTapTimeout = setTimeout(function() { 7111 _blockControlsTap = false; 7112 }, tapDelay); 7113 } 7114 7115 }, 7116 _fitControlsInViewport = function() { 7117 return !pswp.likelyTouchDevice || _options.mouseUsed || screen.width > 1200; 7118 }, 7119 _togglePswpClass = function(el, cName, add) { 7120 framework[ (add ? 'add' : 'remove') + 'Class' ](el, 'pswp__' + cName); 7121 }, 7122 7123 // add class when there is just one item in the gallery 7124 // (by default it hides left/right arrows and 1ofX counter) 7125 _countNumItems = function() { 7126 var hasOneSlide = (_options.getNumItemsFn() === 1); 7127 7128 if(hasOneSlide !== _galleryHasOneSlide) { 7129 _togglePswpClass(_controls, 'ui--one-slide', hasOneSlide); 7130 _galleryHasOneSlide = hasOneSlide; 7131 } 7132 }, 7133 _toggleShareModalClass = function() { 7134 _togglePswpClass(_shareModal, 'share-modal--hidden', _shareModalHidden); 7135 }, 7136 _toggleShareModal = function() { 7137 7138 _shareModalHidden = !_shareModalHidden; 7139 7140 7141 if(!_shareModalHidden) { 7142 _toggleShareModalClass(); 7143 setTimeout(function() { 7144 if(!_shareModalHidden) { 7145 framework.addClass(_shareModal, 'pswp__share-modal--fade-in'); 7146 } 7147 }, 30); 7148 } else { 7149 framework.removeClass(_shareModal, 'pswp__share-modal--fade-in'); 7150 setTimeout(function() { 7151 if(_shareModalHidden) { 7152 _toggleShareModalClass(); 7153 } 7154 }, 300); 7155 } 7156 7157 if(!_shareModalHidden) { 7158 _updateShareURLs(); 7159 } 7160 return false; 7161 }, 7162 7163 _openWindowPopup = function(e) { 7164 e = e || window.event; 7165 var target = e.target || e.srcElement; 7166 7167 pswp.shout('shareLinkClick', e, target); 7168 7169 if(!target.href) { 7170 return false; 7171 } 7172 7173 if( target.hasAttribute('download') ) { 7174 return true; 7175 } 7176 7177 window.open(target.href, 'pswp_share', 'scrollbars=yes,resizable=yes,toolbar=no,'+ 7178 'location=yes,width=550,height=420,top=100,left=' + 7179 (window.screen ? Math.round(screen.width / 2 - 275) : 100) ); 7180 7181 if(!_shareModalHidden) { 7182 _toggleShareModal(); 7183 } 7184 7185 return false; 7186 }, 7187 _updateShareURLs = function() { 7188 var shareButtonOut = '', 7189 shareButtonData, 7190 shareURL, 7191 image_url, 7192 page_url, 7193 share_text; 7194 7195 for(var i = 0; i < _options.shareButtons.length; i++) { 7196 shareButtonData = _options.shareButtons[i]; 7197 7198 image_url = _options.getImageURLForShare(shareButtonData); 7199 page_url = _options.getPageURLForShare(shareButtonData); 7200 share_text = _options.getTextForShare(shareButtonData); 7201 7202 shareURL = shareButtonData.url.replace('{{url}}', encodeURIComponent(page_url) ) 7203 .replace('{{image_url}}', encodeURIComponent(image_url) ) 7204 .replace('{{raw_image_url}}', image_url ) 7205 .replace('{{text}}', encodeURIComponent(share_text) ); 7206 7207 shareButtonOut += '<a href="' + shareURL + '" target="_blank" '+ 7208 'class="pswp__share--' + shareButtonData.id + '"' + 7209 (shareButtonData.download ? 'download' : '') + '>
7209' + 7210 shareButtonData.label + '</a>'; 7211 7212 if(_options.parseShareButtonOut) { 7213 shareButtonOut = _options.parseShareButtonOut(shareButtonData, shareButtonOut); 7214 } 7215 } 7216 _shareModal.children[0].innerHTML = shareButtonOut; 7217 _shareModal.children[0].onclick = _openWindowPopup; 7218 7219 }, 7220 _hasCloseClass = function(target) { 7221 for(var i = 0; i < _options.closeElClasses.length; i++) { 7222 if( framework.hasClass(target, 'pswp__' + _options.closeElClasses[i]) ) { 7223 return true; 7224 } 7225 } 7226 }, 7227 _idleInterval, 7228 _idleTimer, 7229 _idleIncrement = 0, 7230 _onIdleMouseMove = function() { 7231 clearTimeout(_idleTimer); 7232 _idleIncrement = 0; 7233 if(_isIdle) { 7234 ui.setIdle(false); 7235 } 7236 }, 7237 _onMouseLeaveWindow = function(e) { 7238 e = e ? e : window.event; 7239 var from = e.relatedTarget || e.toElement; 7240 if (!from || from.nodeName === 'HTML') { 7241 clearTimeout(_idleTimer); 7242 _idleTimer = setTimeout(function() { 7243 ui.setIdle(true); 7244 }, _options.timeToIdleOutside); 7245 } 7246 }, 7247 _setupFullscreenAPI = function() { 7248 if(_options.fullscreenEl) { 7249 if(!_fullscrenAPI) { 7250 _fullscrenAPI = ui.getFullscreenAPI(); 7251 } 7252 if(_fullscrenAPI) { 7253 framework.bind(document, _fullscrenAPI.eventK, ui.updateFullscreen); 7254 ui.updateFullscreen(); 7255 framework.addClass(pswp.template, 'pswp--supports-fs'); 7256 } else { 7257 framework.removeClass(pswp.template, 'pswp--supports-fs'); 7258 } 7259 } 7260 }, 7261 _setupLoadingIndicator = function() { 7262 // Setup loading indicator 7263 if(_options.preloaderEl) { 7264 7265 _toggleLoadingIndicator(true); 7266 7267 _listen('beforeChange', function() { 7268 7269 clearTimeout(_loadingIndicatorTimeout); 7270 7271 // display loading indicator with delay 7272 _loadingIndicatorTimeout = setTimeout(function() { 7273 7274 if(pswp.currItem && pswp.currItem.loading) { 7275 7276 if( !pswp.allowProgressiveImg() || (pswp.currItem.img && !pswp.currItem.img.naturalWidth) ) { 7277 // show preloader if progressive loading is not enabled, 7278 // or image width is not defined yet (because of slow connection) 7279 _toggleLoadingIndicator(false); 7280 // items-controller.js function allowProgressiveImg 7281 } 7282 7283 } else { 7284 _toggleLoadingIndicator(true); // hide preloader 7285 } 7286 7287 }, _options.loadingIndicatorDelay); 7288 7289 }); 7290 _listen('imageLoadComplete', function(index, item) { 7291 if(pswp.currItem === item) { 7292 _toggleLoadingIndicator(true); 7293 } 7294 }); 7295 7296 } 7297 }, 7298 _toggleLoadingIndicator = function(hide) { 7299 if( _loadingIndicatorHidden !== hide ) { 7300 _togglePswpClass(_loadingIndicator, 'preloader--active', !hide); 7301 _loadingIndicatorHidden = hide; 7302 } 7303 }, 7304 _applyNavBarGaps = function(item) { 7305 var gap = item.vGap; 7306 7307 if( _fitControlsInViewport() ) { 7308 7309 var bars = _options.barsSize; 7310 if(_options.captionEl && bars.bottom === 'auto') { 7311 if(!_fakeCaptionContainer) { 7312 _fakeCaptionContainer = framework.createEl('pswp__caption pswp__caption--fake'); 7313 _fakeCaptionContainer.appendChild( framework.createEl('pswp__caption__center') ); 7314 _controls.insertBefore(_fakeCaptionContainer, _captionContainer); 7315 framework.addClass(_controls, 'pswp__ui--fit'); 7316 } 7317 if( _options.addCaptionHTMLFn(item, _fakeCaptionContainer, true) ) { 7318 7319 var captionSize = _fakeCaptionContainer.clientHeight; 7320 gap.bottom = parseInt(captionSize,10) || 44; 7321 } else { 7322 gap.bottom = bars.top; // if no caption, set size of bottom gap to size of top 7323 } 7324 } else { 7325 gap.bottom = bars.bottom === 'auto' ? 0 : bars.bottom; 7326 } 7327 7328 // height of top bar is static, no need to calculate it 7329 gap.top = bars.top; 7330 } else { 7331 gap.top = gap.bottom = 0; 7332 } 7333 }, 7334 _setupIdle = function() { 7335 // Hide controls when mouse is used 7336 if(_options.timeToIdle) { 7337 _listen('mouseUsed', function() { 7338 7339 framework.bind(document, 'mousemove', _onIdleMouseMove); 7340 framework.bind(document, 'mouseout', _onMouseLeaveWindow); 7341 7342 _idleInterval = setInterval(function() { 7343 _idleIncrement++; 7344 if(_idleIncrement === 2) { 7345 ui.setIdle(true); 7346 } 7347 }, _options.timeToIdle / 2); 7348 }); 7349 } 7350 }, 7351 _setupHidingControlsDuringGestures = function() { 7352 7353 // Hide controls on vertical drag 7354 _listen('onVerticalDrag', function(now) { 7355 if(_controlsVisible && now < 0.95) { 7356 ui.hideControls(); 7357 } else if(!_controlsVisible && now >= 0.95) { 7358 ui.showControls(); 7359 } 7360 }); 7361 7362 // Hide controls when pinching to close
7363 var pinchControlsHidden; 7364 _listen('onPinchClose' , function(now) { 7365 if(_controlsVisible && now < 0.9) { 7366 ui.hideControls(); 7367 pinchControlsHidden = true; 7368 } else if(pinchControlsHidden && !_controlsVisible && now > 0.9) { 7369 ui.showControls(); 7370 } 7371 }); 7372 7373 _listen('zoomGestureEnded', function() { 7374 pinchControlsHidden = false; 7375 if(pinchControlsHidden && !_controlsVisible) { 7376 ui.showControls(); 7377 } 7378 }); 7379 7380 }; 7381 7382 7383 7384 var _uiElements = [ 7385 { 7386 name: 'caption', 7387 option: 'captionEl', 7388 onInit: function(el) { 7389 _captionContainer = el; 7390 } 7391 }, 7392 { 7393 name: 'share-modal', 7394 option: 'shareEl', 7395 onInit: function(el) { 7396 _shareModal = el; 7397 }, 7398 onTap: function() { 7399 _toggleShareModal(); 7400 } 7401 }, 7402 { 7403 name: 'button--share', 7404 option: 'shareEl', 7405 onInit: function(el) { 7406 _shareButton = el; 7407 }, 7408 onTap: function() { 7409 _toggleShareModal(); 7410 } 7411 }, 7412 { 7413 name: 'button--zoom', 7414 option: 'zoomEl', 7415 onTap: pswp.toggleDesktopZoom 7416 }, 7417 { 7418 name: 'counter', 7419 option: 'counterEl', 7420 onInit: function(el) { 7421 _indexIndicator = el; 7422 } 7423 }, 7424 { 7425 name: 'button--close', 7426 option: 'closeEl', 7427 onTap: pswp.close 7428 }, 7429 { 7430 name: 'button--arrow--left', 7431 option: 'arrowEl', 7432 onTap: pswp.prev 7433 }, 7434 { 7435 name: 'button--arrow--right', 7436 option: 'arrowEl', 7437 onTap: pswp.next 7438 }, 7439 { 7440 name: 'button--fs', 7441 option: 'fullscreenEl', 7442 onTap: function() { 7443 if(_fullscrenAPI.isFullscreen()) { 7444 _fullscrenAPI.exit(); 7445 } else { 7446 _fullscrenAPI.enter(); 7447 } 7448 } 7449 }, 7450 { 7451 name: 'preloader', 7452 option: 'preloaderEl', 7453 onInit: function(el) { 7454 _loadingIndicator = el; 7455 } 7456 } 7457 7458 ]; 7459 7460 var _setupUIElements = function() { 7461 var item, 7462 classAttr, 7463 uiElement; 7464 7465 var loopThroughChildElements = function(sChildren) { 7466 if(!sChildren) { 7467 return; 7468 } 7469 7470 var l = sChildren.length; 7471 for(var i = 0; i < l; i++) { 7472 item = sChildren[i]; 7473 classAttr = item.className; 7474 7475 for(var a = 0; a < _uiElements.length; a++) { 7476 uiElement = _uiElements[a]; 7477 7478 if(classAttr.indexOf('pswp__' + uiElement.name) > -1 ) { 7479 7480 if( _options[uiElement.option] ) { // if element is not disabled from options 7481 7482 framework.removeClass(item, 'pswp__element--disabled'); 7483 if(uiElement.onInit) { 7484 uiElement.onInit(item); 7485 } 7486 7487 //item.style.display = 'block'; 7488 } else { 7489 framework.addClass(item, 'pswp__element--disabled'); 7490 //item.style.display = 'none'; 7491 } 7492 } 7493 } 7494 } 7495 }; 7496 loopThroughChildElements(_controls.children); 7497 7498 var topBar = framework.getChildByClass(_controls, 'pswp__top-bar'); 7499 if(topBar) { 7500 loopThroughChildElements( topBar.children ); 7501 } 7502 }; 7503 7504 7505 7506 7507 ui.init = function() { 7508 7509 // extend options 7510 framework.extend(pswp.options, _defaultUIOptions, true); 7511 7512 // create local link for fast access 7513 _options = pswp.options; 7514
7515 // find pswp__ui element 7516 _controls = framework.getChildByClass(pswp.scrollWrap, 'pswp__ui'); 7517 7518 // create local link 7519 _listen = pswp.listen; 7520 7521 7522 _setupHidingControlsDuringGestures(); 7523 7524 // update controls when slides change 7525 _listen('beforeChange', ui.update); 7526 7527 // toggle zoom on double-tap 7528 _listen('doubleTap', function(point) { 7529 var initialZoomLevel = pswp.currItem.initialZoomLevel; 7530 if(pswp.getZoomLevel() !== initialZoomLevel) { 7531 pswp.zoomTo(initialZoomLevel, point, 333); 7532 } else { 7533 pswp.zoomTo(_options.getDoubleTapZoom(false, pswp.currItem), point, 333); 7534 } 7535 }); 7536 7537 // Allow text selection in caption 7538 _listen('preventDragEvent', function(e, isDown, preventObj) { 7539 var t = e.target || e.srcElement; 7540 if( 7541 t && 7542 t.className && e.type.indexOf('mouse') > -1 && 7543 ( t.className.indexOf('__caption') > 0 || (/(SMALL|STRONG|EM)/i).test(t.tagName) ) 7544 ) { 7545 preventObj.prevent = false; 7546 } 7547 }); 7548 7549 // bind events for UI 7550 _listen('bindEvents', function() { 7551 framework.bind(_controls, 'pswpTap click', _onControlsTap); 7552 framework.bind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap); 7553 7554 if(!pswp.likelyTouchDevice) { 7555 framework.bind(pswp.scrollWrap, 'mouseover', ui.onMouseOver); 7556 } 7557 }); 7558 7559 // unbind events for UI 7560 _listen('unbindEvents', function() { 7561 if(!_shareModalHidden) { 7562 _toggleShareModal(); 7563 } 7564 7565 if(_idleInterval) { 7566 clearInterval(_idleInterval); 7567 } 7568 framework.unbind(document, 'mouseout', _onMouseLeaveWindow); 7569 framework.unbind(document, 'mousemove', _onIdleMouseMove); 7570 framework.unbind(_controls, 'pswpTap click', _onControlsTap); 7571 framework.unbind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap); 7572 framework.unbind(pswp.scrollWrap, 'mouseover', ui.onMouseOver); 7573 7574 if(_fullscrenAPI) { 7575 framework.unbind(document, _fullscrenAPI.eventK, ui.updateFullscreen); 7576 if(_fullscrenAPI.isFullscreen()) { 7577 _options.hideAnimationDuration = 0; 7578 _fullscrenAPI.exit(); 7579 } 7580 _fullscrenAPI = null; 7581 } 7582 }); 7583 7584 7585 // clean up things when gallery is destroyed 7586 _listen('destroy', function() { 7587 if(_options.captionEl) { 7588 if(_fakeCaptionContainer) { 7589 _controls.removeChild(_fakeCaptionContainer); 7590 } 7591 framework.removeClass(_captionContainer, 'pswp__caption--empty'); 7592 } 7593 7594 if(_shareModal) { 7595 _shareModal.children[0].onclick = null; 7596 } 7597 framework.removeClass(_controls, 'pswp__ui--over-close'); 7598 framework.addClass( _controls, 'pswp__ui--hidden'); 7599 ui.setIdle(false); 7600 }); 7601 7602 7603 if(!_options.showAnimationDuration) { 7604 framework.removeClass( _controls, 'pswp__ui--hidden'); 7605 } 7606 _listen('initialZoomIn', function() { 7607 if(_options.showAnimationDuration) { 7608 framework.removeClass( _controls, 'pswp__ui--hidden'); 7609 } 7610 }); 7611 _listen('initialZoomOut', function() { 7612 framework.addClass( _controls, 'pswp__ui--hidden'); 7613 }); 7614 7615 _listen('parseVerticalMargin', _applyNavBarGaps); 7616 7617 _setupUIElements(); 7618 7619 if(_options.shareEl && _shareButton && _shareModal) { 7620 _shareModalHidden = true; 7621 } 7622 7623 _countNumItems(); 7624 7625 _setupIdle(); 7626 7627 _setupFullscreenAPI(); 7628 7629 _setupLoadingIndicator(); 7630 }; 7631 7632 ui.setIdle = function(isIdle) { 7633 _isIdle = isIdle; 7634 _togglePswpClass(_controls, 'ui--idle', isIdle); 7635 }; 7636 7637 ui.update = function() { 7638 // Don't update UI if it's hidden 7639 if(_controlsVisible && pswp.currItem) { 7640 7641 ui.updateIndexIndicator(); 7642 7643 if(_options.captionEl) { 7644 _options.addCaptionHTMLFn(pswp.currItem, _captionContainer); 7645 7646 _togglePswpClass(_captionContainer, 'caption--empty', !pswp.currItem.title); 7647 } 7648 7649 _overlayUIUpdated = true; 7650 7651 } else { 7652 _overlayUIUpdated = false; 7653 } 7654 7655 if(!_shareModalHidden) { 7656 _toggleShareModal(); 7657 } 7658 7659 _countNumItems(); 7660 }; 7661 7662 ui.updateFullscreen = function(e) { 7663 7664 if(e) { 7665 // some browsers change window scroll position during the fullscreen 7666 // so PhotoSwipe updates it just in case 7667 setTimeout(function() { 7668 pswp.setScrollOffset( 0, framework.getScrollY() ); 7669 }, 50); 7670 } 7671 7672 // toogle pswp--fs class on root element 7673 framework[ (_fullscrenAPI.isFullscreen() ? 'add' : 'remove') + 'Class' ](pswp.template, 'psw
7673p--fs'); 7674 }; 7675 7676 ui.updateIndexIndicator = function() { 7677 if(_options.counterEl) { 7678 _indexIndicator.innerHTML = (pswp.getCurrentIndex()+1) + 7679 _options.indexIndicatorSep + 7680 _options.getNumItemsFn(); 7681 } 7682 }; 7683 7684 ui.onGlobalTap = function(e) { 7685 e = e || window.event; 7686 var target = e.target || e.srcElement; 7687 7688 if(_blockControlsTap) { 7689 return; 7690 } 7691 7692 if(e.detail && e.detail.pointerType === 'mouse') { 7693 7694 // close gallery if clicked outside of the image 7695 if(_hasCloseClass(target)) { 7696 pswp.close(); 7697 return; 7698 } 7699 7700 if(framework.hasClass(target, 'pswp__img')) { 7701 if(pswp.getZoomLevel() === 1 && pswp.getZoomLevel() <= pswp.currItem.fitRatio) { 7702 if(_options.clickToCloseNonZoomable) { 7703 pswp.close(); 7704 } 7705 } else { 7706 pswp.toggleDesktopZoom(e.detail.releasePoint); 7707 } 7708 } 7709 7710 } else { 7711 7712 // tap anywhere (except buttons) to toggle visibility of controls 7713 if(_options.tapToToggleControls) { 7714 if(_controlsVisible) { 7715 ui.hideControls(); 7716 } else { 7717 ui.showControls(); 7718 } 7719 } 7720 7721 // tap to close gallery 7722 if(_options.tapToClose && (framework.hasClass(target, 'pswp__img') || _hasCloseClass(target)) ) { 7723 pswp.close(); 7724 return; 7725 } 7726 7727 } 7728 }; 7729 ui.onMouseOver = function(e) { 7730 e = e || window.event; 7731 var target = e.target || e.srcElement; 7732 7733 // add class when mouse is over an element that should close the gallery 7734 _togglePswpClass(_controls, 'ui--over-close', _hasCloseClass(target)); 7735 }; 7736 7737 ui.hideControls = function() { 7738 framework.addClass(_controls,'pswp__ui--hidden'); 7739 _controlsVisible = false; 7740 }; 7741 7742 ui.showControls = function() { 7743 _controlsVisible = true; 7744 if(!_overlayUIUpdated) { 7745 ui.update(); 7746 } 7747 framework.removeClass(_controls,'pswp__ui--hidden'); 7748 }; 7749 7750 ui.supportsFullscreen = function() { 7751 var d = document; 7752 return !!(d.exitFullscreen || d.mozCancelFullScreen || d.webkitExitFullscreen || d.msExitFullscreen); 7753 }; 7754 7755 ui.getFullscreenAPI = function() { 7756 var dE = document.documentElement, 7757 api, 7758 tF = 'fullscreenchange'; 7759 7760 if (dE.requestFullscreen) { 7761 api = { 7762 enterK: 'requestFullscreen', 7763 exitK: 'exitFullscreen', 7764 elementK: 'fullscreenElement', 7765 eventK: tF 7766 }; 7767 7768 } else if(dE.mozRequestFullScreen ) { 7769 api = { 7770 enterK: 'mozRequestFullScreen', 7771 exitK: 'mozCancelFullScreen', 7772 elementK: 'mozFullScreenElement', 7773 eventK: 'moz' + tF 7774 }; 7775 7776 7777 7778 } else if(dE.webkitRequestFullscreen) { 7779 api = { 7780 enterK: 'webkitRequestFullscreen', 7781 exitK: 'webkitExitFullscreen', 7782 elementK: 'webkitFullscreenElement', 7783 eventK: 'webkit' + tF 7784 }; 7785 7786 } else if(dE.msRequestFullscreen) { 7787 api = { 7788 enterK: 'msRequestFullscreen', 7789 exitK: 'msExitFullscreen', 7790 elementK: 'msFullscreenElement', 7791 eventK: 'MSFullscreenChange' 7792 }; 7793 } 7794 7795 if(api) { 7796 api.enter = function() { 7797 // disable close-on-scroll in fullscreen 7798 _initalCloseOnScrollValue = _options.closeOnScroll; 7799 _options.closeOnScroll = false; 7800 7801 if(this.enterK === 'webkitRequestFullscreen') { 7802 pswp.template[this.enterK]( Element.ALLOW_KEYBOARD_INPUT ); 7803 } else { 7804 return pswp.template[this.enterK](); 7805 } 7806 }; 7807 api.exit = function() { 7808 _options.closeOnScroll = _initalCloseOnScrollValue; 7809 7810 return document[this.exitK](); 7811 7812 }; 7813 api.isFullscreen = function() { return document[this.elementK]; }; 7814 } 7815 7816 return api; 7817 }; 7818 7819 7820 7821}; 7822return PhotoSwipeUI_Default; 7823 7824 7825}); 7826/*! Picturefill - v2.2.0-beta - 2014-10-30 7827* http://scottjehl.github.io/picturefill 7828* Copyright (c) 2014 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT */ 7829/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license */ 7830 7831window.matchMedia || (window.matchMedia = function() { 7832 "use strict"; 7833 7834 // For browsers that support matchMedium api such as IE 9 and webkit 7835 var styleMedia = (window.styleMedia || window.media); 7836 7837 // For those that don't support matchMedium 7838 if (!styleMedia) { 7839 var style = document.createElement('style'), 7840 script = document.getElementsByTagName('script')[0], 7841 info = null; 7842 7843 style.type = 'text/css'; 7844 style.id = 'matchmediajs-test'; 7845 7846 script.parentNode.insertBefore(style, script); 7847 7848 // 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers 7849 info = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle; 7850 7851 styleMedia = { 7852 matchMedium: function(media) { 7853 var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }'; 7854 7855 // 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers 7856 if (style.styleSheet) { 7857 style.styleSheet.cssText = text; 7858 } else { 7859 style.textContent = text; 7860 } 7861 7862 // Test if media query is true or false 7863 return info.width === '1px'; 7864 } 7865 }; 7866 } 7867 7868 return function(media) { 7869 return { 7870 matches: styleMedia.matchMedium(media || 'all'), 7871 media: media || 'all'
7872 }; 7873 }; 7874}()); 7875/*! Picturefill - Responsive Images that work today. 7876* Author: Scott Jehl, Filament Group, 2012 ( new proposal implemented by Shawn Jansepar ) 7877* License: MIT/GPLv2 7878* Spec: http://picture.responsiveimages.org/ 7879*/ 7880(function( w, doc, image ) { 7881 // Enable strict mode 7882 "use strict"; 7883 7884 // If picture is supported, well, that's awesome. Let's get outta here... 7885 if ( w.HTMLPictureElement ) { 7886 w.picturefill = function() { }; 7887 return; 7888 } 7889 7890 // HTML shim|v it for old IE (IE9 will still need the HTML video tag workaround) 7891 doc.createElement( "picture" ); 7892 7893 // local object for method references and testing exposure 7894 var pf = {}; 7895 7896 // namespace 7897 pf.ns = "picturefill"; 7898 7899 // srcset support test 7900 (function() { 7901 pf.srcsetSupported = "srcset" in image; 7902 pf.sizesSupported = "sizes" in image; 7903 })(); 7904 7905 // just a string trim workaround 7906 pf.trim = function( str ) { 7907 return str.trim ? str.trim() : str.replace( /^\s+|\s+$/g, "" ); 7908 }; 7909 7910 // just a string endsWith workaround 7911 pf.endsWith = function( str, suffix ) { 7912 return str.endsWith ? str.endsWith( suffix ) : str.indexOf( suffix, str.length - suffix.length ) !== -1; 7913 }; 7914 7915 /** 7916 * Shortcut method for https://w3c.github.io/webappsec/specs/mixedcontent/#restricts-mixed-content ( for easy overriding in tests ) 7917 */ 7918 pf.restrictsMixedContent = function() { 7919 return w.location.protocol === "https:"; 7920 }; 7921 /** 7922 * Shortcut method for matchMedia ( for easy overriding in tests ) 7923 */ 7924 7925 pf.matchesMedia = function( media ) { 7926 return w.matchMedia && w.matchMedia( media ).matches; 7927 }; 7928 7929 // Shortcut method for `devicePixelRatio` ( for easy overriding in tests ) 7930 pf.getDpr = function() { 7931 return ( w.devicePixelRatio || 1 ); 7932 }; 7933 7934 /** 7935 * Get width in css pixel value from a "length" value 7936 * http://dev.w3.org/csswg/css-values-3/#length-value 7937 */ 7938 pf.getWidthFromLength = function( length ) { 7939 // If a length is specified and doesnât contain a percentage, and it is greater than 0 or using `calc`, use it. Else, use the `100vw` default. 7940 length = length && length.indexOf( "%" ) > -1 === false && ( parseFloat( length ) > 0 || length.indexOf( "calc(" ) > -1 ) ? length : "100vw"; 7941 7942 /** 7943 * If length is specified in `vw` units, use `%` instead since the div weâre measuring 7944 * is injected at the top of the document. 7945 * 7946 * TODO: maybe we should put this behind a feature test for `vw`? 7947 */ 7948 length = length.replace( "vw", "%" ); 7949 7950 // Create a cached element for getting length value widths 7951 if ( !pf.lengthEl ) { 7952 pf.lengthEl = doc.createElement( "div" ); 7953 7954 // Positioning styles help prevent padding/margin/width on `html` or `body` from throwing calculations off. 7955 pf.lengthEl.style.cssText = "border:0;display:block;font-size:1em;
7955left:0;margin:0;padding:0;position:absolute;visibility:hidden"; 7956 } 7957 7958 pf.lengthEl.style.width = length; 7959 7960 doc.body.appendChild(pf.lengthEl); 7961 7962 // Add a class, so that everyone knows where this element comes from 7963 pf.lengthEl.className = "helper-from-picturefill-js"; 7964 7965 if ( pf.lengthEl.offsetWidth <= 0 ) { 7966 // Something has gone wrong. `calc()` is in use and unsupported, most likely. Default to `100vw` (`100%`, for broader support.): 7967 pf.lengthEl.style.width = doc.documentElement.offsetWidth + "px"; 7968 } 7969 7970 var offsetWidth = pf.lengthEl.offsetWidth; 7971 7972 doc.body.removeChild( pf.lengthEl ); 7973 7974 return offsetWidth; 7975 }; 7976 7977 // container of supported mime types that one might need to qualify before using 7978 pf.types = {}; 7979 7980 // Add support for standard mime types 7981 pf.types[ "image/jpeg" ] = true; 7982 pf.types[ "image/gif" ] = true; 7983 pf.types[ "image/png" ] = true; 7984 7985 // test svg support 7986 pf.types[ "image/svg+xml" ] = doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1"); 7987 7988 // test webp support, only when the markup calls for it 7989 pf.types[ "image/webp" ] = function() { 7990 // based on Modernizr's lossless img-webp test 7991 // note: asynchronous 7992 var type = "image/webp"; 7993 7994 image.onerror = function() { 7995 pf.types[ type ] = false; 7996 picturefill(); 7997 }; 7998 image.onload = function() { 7999 pf.types[ type ] = image.width === 1; 8000 picturefill(); 8001 }; 8002 image.src = "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA="; 8003 }; 8004 8005 /** 8006 * Takes a source element and checks if its type attribute is present and if so, supported 8007 * Note: for type tests that require a async logic, 8008 * you can define them as a function that'll run only if that type needs to be tested. Just make the test function call picturefill again when it is complete. 8009 * see the async webp test above for example 8010 */ 8011 pf.verifyTypeSupport = function( source ) { 8012 var type = source.getAttribute( "type" ); 8013 // if type attribute exists, return test result, otherwise return true 8014 if ( type === null || type === "" ) { 8015 return true; 8016 } else { 8017 // if the type test is a function, run it and return "pending" status. The function will rerun picturefill on pending elements once finished. 8018 if ( typeof( pf.types[ type ] ) === "function" ) { 8019 pf.types[ type ](); 8020 return "pending"; 8021 } else { 8022 return pf.types[ type ]; 8023 } 8024 } 8025 }; 8026 8027 // Parses an individual `size` and returns the length, and optional media query 8028 pf.parseSize = function( sourceSizeStr ) { 8029 var match = /(\([^)]+\))?\s*(.+)/g.exec( sourceSizeStr ); 8030 return { 8031 media: match && match[1], 8032 length: match && match[2] 8033 }; 8034 }; 8035 8036 // Takes a string of sizes and returns the width in pixels as a number 8037 pf.findWidthFromSourceSize = function( sourceSizeListStr ) { 8038 // Split up source size list, ie ( max-width: 30em ) 100%, ( max-width: 50em ) 50%, 33% 8039 // or (min-width:30em) calc(30% - 15px) 8040 var sourceSizeList = pf.trim( sourceSizeListStr ).split( /\s*,\s*/ ), 8041 winningLength; 8042 8043 for ( var i = 0, len = sourceSizeList.length; i < len; i++ ) { 8044 // Match <media-condition>? length, ie ( min-width: 50em ) 100% 8045 var sourceSize = sourceSizeList[ i ], 8046 // Split "( min-width: 50em ) 100%" into separate strings 8047 parsedSize = pf.parseSize( sourceSize ), 8048 length = parsedSize.length, 8049 media = parsedSize.media; 8050 8051 if ( !length ) { 8052 continue; 8053 } 8054 if ( !media || pf.matchesMedia( media ) ) { 8055 // if there is no media query or it matches, choose this as our winning length 8056 // and end algorithm 8057 winningLength = length; 8058 break; 8059 } 8060 } 8061 8062 // pass the length to a method that can properly determine length 8063 // in pixels based on these formats: http://dev.w3.org/csswg/css-values-3/#length-value 8064 return pf.getWidthFromLength( winningLength ); 8065 }; 8066 8067 pf.parseSrcset = function( srcset ) { 8068 /** 8069 * A lot of this was pulled from Boris Smusâ parser for the now-defunct WHATWG `srcset` 8070 * https://github.com/borismus/srcset-polyfill/blob/master/js/sr
8070cset-info.js 8071 * 8072 * 1. Let input (`srcset`) be the value passed to this algorithm. 8073 * 2. Let position be a pointer into input, initially pointing at the start of the string. 8074 * 3. Let raw candidates be an initially empty ordered list of URLs with associated 8075 * unparsed descriptors. The order of entries in the list is the order in which entries 8076 * are added to the list. 8077 */ 8078 var candidates = []; 8079 8080 while ( srcset !== "" ) { 8081 srcset = srcset.replace( /^\s+/g, "" ); 8082 8083 // 5. Collect a sequence of characters that are not space characters, and let that be url. 8084 var pos = srcset.search(/\s/g), 8085 url, descriptor = null; 8086 8087 if ( pos !== -1 ) { 8088 url = srcset.slice( 0, pos ); 8089 8090 var last = url.slice(-1); 8091 8092 // 6. If url ends with a U+002C COMMA character (,), remove that character from url 8093 // and let descriptors be the empty string. Otherwise, follow these substeps 8094 // 6.1. If url is empty, then jump to the step labeled descriptor parser. 8095 8096 if ( last === "," || url === "" ) { 8097 url = url.replace( /,+$/, "" ); 8098 descriptor = ""; 8099 } 8100 srcset = srcset.slice( pos + 1 ); 8101 8102 // 6.2. Collect a sequence of characters that are not U+002C COMMA characters (,), and 8103 // let that be descriptors. 8104 if ( descriptor === null ) { 8105 var descpos = srcset.indexOf( "," ); 8106 if ( descpos !== -1 ) { 8107 descriptor = srcset.slice( 0, descpos ); 8108 srcset = srcset.slice( descpos + 1 ); 8109 } else { 8110 descriptor = srcset; 8111 srcset = ""; 8112 } 8113 } 8114 } else { 8115 url = srcset; 8116 srcset = ""; 8117 } 8118 8119 // 7. Add url to raw candidates, associated with descriptors. 8120 if ( url || descriptor ) { 8121 candidates.push({ 8122 url: url, 8123 descriptor: descriptor 8124 }); 8125 } 8126 } 8127 return candidates; 8128 }; 8129 8130 pf.parseDescriptor = function( descriptor, sizesattr ) { 8131 // 11. Descriptor parser: Let candidates be an initially empty source set. The order of entries in the list 8132 // is the order in which entries are added to the list. 8133 var sizes = sizesattr || "100vw", 8134 sizeDescriptor = descriptor && descriptor.replace( /(^\s+|\s+$)/g, "" ), 8135 widthInCssPixels = pf.findWidthFromSourceSize( sizes ), 8136 resCandidate; 8137 8138 if ( sizeDescriptor ) { 8139 var splitDescriptor = sizeDescriptor.split(" "); 8140 8141 for (var i = splitDescriptor.length - 1; i >= 0; i--) { 8142 var curr = splitDescriptor[ i ], 8143 lastchar = curr && curr.slice( curr.length - 1 ); 8144 8145 if ( ( lastchar === "h" || lastchar === "w" ) && !pf.sizesSupported ) { 8146 resCandidate = parseFloat( ( parseInt( curr, 10 ) / widthInCssPixels ) ); 8147 } else if ( lastchar === "x" ) { 8148 var res = curr && parseFloat( curr, 10 ); 8149 resCandidate = res && !isNaN( res ) ? res : 1; 8150 } 8151 } 8152 } 8153 return resCandidate || 1; 8154 }; 8155 8156 /** 8157 * Takes a srcset in the form of url/ 8158 * ex. "images/pic-medium.png 1x, images/pic-medium-2x.png 2x" or 8159 * "images/pic-medium.png 400w, images/pic-medium-2x.png 800w" or 8160 * "images/pic-small.png" 8161 * Get an array of image candidates in the form of 8162 * {url: "/foo/bar.png", resolution: 1} 8163 * where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value 8164 * If sizes is specified, resolution is calculated 8165 */ 8166 pf.getCandidatesFromSourceSet = function( srcset, sizes ) { 8167 var candidates = pf.parseSrcset( srcset ), 8168 formattedCandidates = []; 8169 8170 for ( var i = 0, len = candidates.length; i < len; i++ ) { 8171 var candidate = candidates[ i ]; 8172 8173 formattedCandidates.push({ 8174 url: candidate.url, 8175 resolution: pf.parseDescriptor( candidate.descriptor, sizes ) 8176 }); 8177 } 8178 return formattedCandidates; 8179 }; 8180 8181 /** 8182 * if it's an img element and it has a srcset property, 8183 * we need to remove the attribute so we can manipulate src 8184 * (the property's existence infers native srcset support, and a srcset-supporting browser will prioritize srcset's value over our winning picture candidate) 8185 * this moves srcset's value to memory for later use and removes the attr 8186 */ 8187 pf.dodgeSrcset = function( img ) { 8188 if ( img.srcset ) { 8189 img[ pf.ns ].srcset = img.srcset; 8190 img.removeAttribute( "srcset" ); 8191 } 8192 }; 8193 8194 // Accept a source or img element and process its srcset and sizes attrs 8195 pf.processSourceSet = function( el ) { 8196 var srcset = el.getAttribute( "srcset" ), 8197 sizes = el.getAttribute( "sizes" ), 8198 candidates = []; 8199 8200 // if it's an img element, use the cached srcset property (defined or not) 8201 if ( el.nodeName.toUpperCase() === "IMG" && el[ pf.ns ] && el[ pf.ns ].srcset ) { 8202 srcset = el[ pf.ns ].srcset; 8203 } 8204 8205 if ( srcset ) { 8206 candidates = pf.getCandidatesFromSourceSet( srcset, sizes ); 8207 } 8208 return candidates; 8209 }; 8210 8211 pf.applyBestCandidate = function( candidates, picImg ) { 8212 var candidate, 8213 length, 8214 bestCandidate; 8215 8216 candidates.sort( pf.ascendingSort ); 8217 8218 length = candidates.length; 8219 bestCandidate = candidates[ length - 1 ]; 8220 8221 for ( var i = 0; i < length; i++ ) { 8222 candidate = candidates[ i ]; 8223 if ( candidate.resolution >= pf.getDpr() ) { 8224 bestCandidate = candidate; 8225 break; 8226 } 8227 } 8228 8229 if ( bestCandidate && !pf.endsWith( picImg.src, bestCandidate.url ) ) { 8230 if ( pf.restrictsMixedContent() && bestCandidate.url.substr(0, "http:".length).toLowerCase() === "http:" ) { 8231 if ( typeof console !== undefined ) { 8232 console.warn( "Blocked mixed content image " + bestCandidate.url ); 8233 } 8234 } else { 8235 picImg.src = bestCandidate.url; 8236 // currentSrc attribute and property to match 8237 // http://picture.responsiveimages.org/#the-img-element 8238 picImg.currentSrc = picImg.src; 8239 8240 var style = picImg.style || {}, 8241 WebkitBackfaceVisibility = "webkitBackfaceVisibility" in style, 8242 currentZoom = style.zoom; 8243 8244 if (WebkitBackfaceVisibility) { // See: https://github.com/scottjehl/picturefill/issues/332 8245 style.zoom = ".999"; 8246 8247 WebkitBackfaceVisibility = picImg.offsetWidth; 8248 8249 style.zoom = currentZoom; 8250 } 8251 } 8252 } 8253 }; 8254 8255 pf.ascendingSort = function( a, b ) { 8256 return a.resolution - b.resolution; 8257 }; 8258 8259 /** 8260 * In IE9, <source> elements get removed if they aren't children of 8261 * video elements. Thus, we conditionally wrap source elements 8262 * using <!--[if IE 9]><video style="display: none;"><![endif]--> 8263 * and must account for that here by moving those source elements 8264 * back into the picture element. 8265 */ 8266 pf.removeVideoShim = function( picture ) { 8267 var videos = picture.getElementsByTagName( "video" ); 8268 if ( videos.length ) { 8269 var video = videos[ 0 ], 8270 vsources = video.getElementsByTagName( "source" ); 8271 while ( vsources.length ) { 8272 picture.insertBefore( vsources[ 0 ], video ); 8273 } 8274 // Remove the video element once we're finished removing its children 8275 video.parentNode.removeChild( video ); 8276 } 8277 }; 8278 8279 /** 8280 * Find all `img` elements, and add them to the candidate list if they have 8281 * a `picture` parent, a `sizes` attribute in basic `srcset` supporting browsers, 8282 * a `srcset` attribute at all, and they havenât been evaluated already. 8283 */ 8284 pf.getAllElements = function() { 8285 var elems = [], 8286 imgs = doc.getElementsByTagName( "img" ); 8287 8288 for ( var h = 0, len = imgs.length; h < len; h++ ) { 8289 var currImg = imgs[ h ]; 8290 8291 if ( currImg.parentNode.nodeName.toUpperCase() === "PICTURE" || 8292 ( currImg.getAttribute( "srcset" ) !== null ) || currImg[ pf.ns ] && currImg[ pf.ns ].srcset !== null ) { 8293 elems.push( currImg ); 8294 } 8295 } 8296 return elems; 8297 }; 8298 8299 pf.getMatch = function( img, picture ) { 8300 var sources = picture.childNodes, 8301 match; 8302 8303 // Go through each child, and if they have media queries, evaluate them 8304 for ( var j = 0, slen = sources.length; j < slen; j++ ) { 8305 var source = sources[ j ]; 8306 8307 // ignore non-element nodes 8308 if ( source.nodeType !== 1 ) { 8309 continue; 8310 } 8311 8312 // Hitting the `img` element that started everything stops the search for `sources`. 8313 // If no previous `source` matches, the `img` itself is evaluated later. 8314 if ( source === img ) { 8315 return match; 8316 } 8317 8318 // ignore non-`source` nodes 8319 if ( source.nodeName.toUpperCase() !== "SOURCE" ) { 8320 continue; 8321 } 8322 // if it's a source element that has the `src` property set, throw a warning in the console 8323 if ( source.getAttribute( "src" ) !== null && typeof console !== undefined ) {
8324 console.warn("The `src` attribute is invalid on `picture` `source` element; instead, use `srcset`."); 8325 } 8326 8327 var media = source.getAttribute( "media" ); 8328 8329 // if source does not have a srcset attribute, skip 8330 if ( !source.getAttribute( "srcset" ) ) { 8331 continue; 8332 } 8333 8334 // if there's no media specified, OR w.matchMedia is supported 8335 if ( ( !media || pf.matchesMedia( media ) ) ) { 8336 var typeSupported = pf.verifyTypeSupport( source ); 8337 8338 if ( typeSupported === true ) { 8339 match = source; 8340 break; 8341 } else if ( typeSupported === "pending" ) { 8342 return false; 8343 } 8344 } 8345 } 8346 8347 return match; 8348 }; 8349 8350 function picturefill( opt ) { 8351 var elements, 8352 element, 8353 parent, 8354 firstMatch, 8355 candidates, 8356 options = opt || {}; 8357 8358 elements = options.elements || pf.getAllElements(); 8359 8360 // Loop through all elements 8361 for ( var i = 0, plen = elements.length; i < plen; i++ ) { 8362 element = elements[ i ]; 8363 parent = element.parentNode; 8364 firstMatch = undefined; 8365 candidates = undefined; 8366 8367 // immediately skip non-`img` nodes 8368 if ( element.nodeName.toUpperCase() !== "IMG" ) { 8369 continue; 8370 } 8371 8372 // expando for caching data on the img 8373 if ( !element[ pf.ns ] ) { 8374 element[ pf.ns ] = {}; 8375 } 8376 8377 // if the element has already been evaluated, skip it unless 8378 // `options.reevaluate` is set to true ( this, for example, 8379 // is set to true when running `picturefill` on `resize` ). 8380 if ( !options.reevaluate && element[ pf.ns ].evaluated ) { 8381 continue; 8382 } 8383 8384 // if `img` is in a `picture` element 8385 if ( parent.nodeName.toUpperCase() === "PICTURE" ) { 8386 8387 // IE9 video workaround 8388 pf.removeVideoShim( parent ); 8389 8390 // return the first match which might undefined 8391 // returns false if there is a pending source 8392 // TODO the return type here is brutal, cleanup 8393 firstMatch = pf.getMatch( element, parent ); 8394 8395 // if any sources are pending in this picture due to async type test(s) 8396 // remove the evaluated attr and skip for now ( the pending test will 8397 // rerun picturefill on this element when complete) 8398 if ( firstMatch === false ) { 8399 continue; 8400 } 8401 } else { 8402 firstMatch = undefined; 8403 } 8404 8405 // Cache and remove `srcset` if present and weâre going to be doing `picture`/`srcset`/`sizes` polyfilling to it. 8406 if ( parent.nodeName.toUpperCase() === "PICTURE" || 8407 ( element.srcset && !pf.srcsetSupported ) || 8408 ( !pf.sizesSupported && ( element.srcset && element.srcset.indexOf("w") > -1 ) ) ) { 8409 pf.dodgeSrcset( element ); 8410 } 8411 8412 if ( firstMatch ) { 8413 candidates = pf.processSourceSet( firstMatch ); 8414 pf.applyBestCandidate( candidates, element ); 8415 } else { 8416 // No sources matched, so weâre down to processing the inner `img` as a source. 8417 candidates = pf.processSourceSet( element ); 8418 8419 if ( element.srcset === undefined || element[ pf.ns ].srcset ) { 8420 // Either `srcset` is completely unsupported, or we need to polyfill `sizes` functionality. 8421 pf.applyBestCandidate( candidates, element ); 8422 } // Else, resolution-only `srcset` is supported natively. 8423 } 8424 8425 // set evaluated to true to avoid unnecessary reparsing 8426 element[ pf.ns ].evaluated = true; 8427 } 8428 } 8429 8430 /** 8431 * Sets up picture polyfill by polling the document and running 8432 * the polyfill every 250ms until the document is ready. 8433 * Also attaches picturefill on resize 8434 */ 8435 function runPicturefill() { 8436 picturefill(); 8437 var intervalId = setInterval( function() { 8438 // When the document has finished loading, stop checking for new images 8439 // https://github.com/ded/domready/blob/master/ready.js#L15 8440 picturefill(); 8441 if ( /^loaded|^i|^c/.test( doc.readyState ) ) { 8442 clearInterval( intervalId ); 8443 return; 8444 } 8445 }, 250 ); 8446 8447 function checkResize() { 8448 var resizeThrottle; 8449 8450 if ( !w._picturefillWorking ) { 8451 w._picturefillWorking = true; 8452 w.clearTimeout( resizeThrottle ); 8453 resizeThrottle = w.setTimeout( function() { 8454 picturefill({ reevaluate: true });
8455 w._picturefillWorking = false; 8456 }, 60 ); 8457 } 8458 } 8459 8460 if ( w.addEventListener ) { 8461 w.addEventListener( "resize", checkResize, false ); 8462 } else if ( w.attachEvent ) { 8463 w.attachEvent( "onresize", checkResize ); 8464 } 8465 } 8466 8467 runPicturefill(); 8468 8469 /* expose methods for testing */ 8470 picturefill._ = pf; 8471 8472 /* expose picturefill */ 8473 if ( typeof module === "object" && typeof module.exports === "object" ) { 8474 // CommonJS, just export 8475 module.exports = picturefill; 8476 } else if ( typeof define === "function" && define.amd ) { 8477 // AMD support 8478 define( function() { return picturefill; } ); 8479 } else if ( typeof w === "object" ) { 8480 // If no AMD and we are in the browser, attach to window 8481 w.picturefill = picturefill; 8482 } 8483 8484} )( this, this.document, new this.Image() ); 8485/*! Pushy - v0.9.2 - 2014-9-13 8486* Pushy is a responsive off-canvas navigation menu using CSS transforms & transitions. 8487* https://github.com/christophery/pushy/ 8488* by Christopher Yee */ 8489 8490 8491$(function() { 8492 var pushy = $('.mobile-nav'), //menu css class 8493 body = $('body'), 8494 container = $('.content'), //container css class 8495 push = $('.push, .main-header, .main-nav, .main-footer'), //css class to add pushy capability 8496 siteOverlay = $('.site-overlay, .btn--close-menu'), //site overlay 8497 pushyClass = "pushy-left pushy-open", //menu position & menu open class 8498 pushyActiveClass = "pushy-active", //css class to toggle site overlay 8499 containerClass = "container-push", //container open class 8500 pushClass = "push-push", //css class to add pushy capability 8501 menuBtn = $('.btn--toggle-menu'), //css classes to toggle the menu 8502 menuSpeed = 200, //jQuery fallback menu speed 8503 menuWidth = pushy.width() + "px"; //jQuery fallback menu width 8504 8505 function togglePushy(){ 8506 body.toggleClass(pushyActiveClass); //toggle site overlay 8507 pushy.toggleClass(pushyClass); 8508 container.toggleClass(containerClass); 8509 push.toggleClass(pushClass); //css class to add pushy capability 8510 } 8511 8512 function openPushyFallback(){ 8513 body.addClass(pushyActiveClass); 8514 8515 if ($('html[dir="rtl"]').length) { 8516 pushy.animate({right: "0px"}, menuSpeed); 8517 container.animate({right: menuWidth}, menuSpeed); 8518 push.animate({right: menuWidth}, menuSpeed); //css class to add pushy capability 8519 } else { 8520 pushy.animate({left: "0px"}, menuSpeed); 8521 container.animate({left: menuWidth}, menuSpeed); 8522 push.animate({left: menuWidth}, menuSpeed); //css class to add pushy capability 8523 } 8524 } 8525 8526 function closePushyFallback(){ 8527 body.removeClass(pushyActiveClass); 8528 8529 if ($('html[dir="rtl"]').length) { 8530 pushy.animate({right: "-" + menuWidth}, menuSpeed); 8531 container.animate({right: "0px"}, menuSpeed); 8532 push.animate({right: "0px"}, menuSpeed); //css class to add pushy capability 8533 } else { 8534 pushy.animate({left: "-" + menuWidth}, menuSpeed); 8535 container.animate({left: "0px"}, menuSpeed); 8536 push.animate({left: "0px"}, menuSpeed); //css class to add pushy capability 8537 } 8538 } 8539 8540 //checks if 3d transforms are supported removing the modernizr dependency 8541 cssTransforms3d = (function csstransforms3d(){ 8542 var el = document.createElement('p'), 8543 supported = false, 8544 transforms = { 8545 'webkitTransform':'-webkit-transform',
8546 'OTransform':'-o-transform', 8547 'msTransform':'-ms-transform', 8548 'MozTransform':'-moz-transform', 8549 'transform':'transform' 8550 }; 8551 8552 // Add it to the body to get the computed style 8553 document.body.insertBefore(el, null); 8554 8555 for(var t in transforms){ 8556 if( el.style[t] !== undefined ){ 8557 el.style[t] = 'translate3d(1px,1px,1px)'; 8558 supported = window.getComputedStyle(el).getPropertyValue(transforms[t]); 8559 } 8560 } 8561 8562 document.body.removeChild(el); 8563 8564 return (supported !== undefined && supported.length > 0 && supported !== "none"); 8565 })(); 8566 8567 if(cssTransforms3d){ 8568 //toggle menu 8569 menuBtn.click(function() { 8570 togglePushy(); 8571 }); 8572 //close menu when clicking site overlay 8573 siteOverlay.click(function(){ 8574 togglePushy(); 8575 }); 8576 }else{ 8577 //jQuery fallback 8578 if ($('html[dir="rtl"]').length) { 8579 pushy.css({right: "-" + menuWidth}); //hide menu by default 8580 } else { 8581 pushy.css({left: "-" + menuWidth}); //hide menu by default 8582 } 8583 container.css({"overflow-x": "hidden"}); //fixes IE scrollbar issue 8584 8585 //keep track of menu state (open/close) 8586 var state = true; 8587 8588 //toggle menu 8589 menuBtn.click(function() { 8590 if (state) { 8591 openPushyFallback(); 8592 state = false; 8593 } else { 8594 closePushyFallback(); 8595 state = true; 8596 } 8597 }); 8598 8599 //close menu when clicking site overlay 8600 siteOverlay.click(function(){ 8601 if (state) { 8602 openPushyFallback(); 8603 state = false; 8604 } else { 8605 closePushyFallback(); 8606 state = true; 8607 } 8608 }); 8609 } 8610}); 8611var Accordion = { 8612 init: function() { 8613 Accordion.toggle(); 8614 }, 8615 toggle: function() { 8616 var $toggleButton = $('.accordion__button'); 8617 8618 $toggleButton.on('click', function() { 8619 $(this).parents('.accordion__item').toggleClass('is--open'); 8620 $(this).parents('.accordion__item').find('.accordion__content-more').slideToggle(200); 8621 }); 8622 } 8623}; 8624 8625$(document).ready(Accordion.init); 8626 8627/*jshint multistr: true */ 8628var audioplayer = { 8629 instances: null, 8630 init: function() { 8631 8632 if (!$('.audioplayer').length) { 8633 return; 8634 } 8635 8636 // Setup the player to autoplay the next track 8637 audioplayer.instances = audiojs.createAll({ 8638 swfLocation: 'typo3conf/ext/ueberdosisprovider/Resources/Public/JavaScript/audiojs.swf', 8639 createPlayer: { 8640 markup: '\ 8641 <div class="audioplayer__play-pause"> \ 8642 <p class="audioplayer__play"></p> \ 8643 <p class="audioplayer__pause"></p> \ 8644 <p class="is-loading"></p> \ 8645 <p class="audioplayer__error"></p> \ 8646 </div> \ 8647 <div class="audioplayer__scrubber-outer"> \ 8648 <div class="audioplayer__scrubber"> \ 8649 <div class="audioplayer__progress"></div> \ 8650 <div class="audioplayer__loaded"></div> \ 8651 </div> \ 8652 </div> \ 8653 <div class="audioplayer__time"> \ 8654 <span class="audioplayer__played">00:00</span> / <span class="audioplayer__duration">00:00</span> \ 8655 </div> \ 8656 <div class="audioplayer__error-message"></div>', 8657 playPauseClass: 'audioplayer__play-pause', 8658 scrubberClass: 'audioplayer__scrubber', 8659 progressClass: 'audioplayer__progress', 8660 loaderClass: 'audioplayer__loaded', 8661 timeClass: 'audioplayer__time', 8662 durationClass: 'audioplayer__duration', 8663 playedClass: 'audioplayer__played', 8664 errorMessageClass: 'audioplayer__error-message', 8665 playingClass: 'is-playing', 8666 loadingClass: 'is-loading', 8667 errorClass: 'audioplayer__error' 8668 }, 8669 css: null, 8670 trackEnded: function() { 8671 var instance = audioplayer.instances[0], 8672 next = $('.audioplayer__playlist__item.is-playing').next(); 8673 8674 if (!next.length) { 8675 next = $('.audioplayer__playlist__item').first(); 8676 } 8677 next.addClass('is-playing') 8678 .siblings() 8679 .removeClass('is-playing'); 8680 8681 instance.load( $('a', next).attr('href') ); 8682 instance.play(); 8683 } 8684 }); 8685 8686 audioplayer.onLoad(); 8687 8688 $('.audioplayer__playlist__item').on('click', audioplayer.onClick); 8689 }, 8690 onLoad: function() { 8691 var instance = audioplayer.instances[0], 8692 first = $('.audioplayer__playlist a').attr('href'); 8693 8694 instance.load(first); 8695 8696 $('.audioplayer__playlist__item') 8697 .first() 8698 .addClass('is-playing'); 8699 }, 8700 onClick: function(e) { 8701 e.preventDefault(); 8702 8703 var instance = audioplayer.instances[0]; 8704 8705 instance.load( $('a', this).attr('href') ); 8706 instance.play(); 8707 8708 $(this) 8709 .addClass('is-playing') 8710 .siblings() 8711 .removeClass('is-playing'); 8712 } 8713}; 8714 8715$(document).ready(audioplayer.init); 8716 8717/** 8718 * Object sets equal height for col-wrapper next to each other 8719 */ 8720var ColWrapper = { 8721 init: function() { 8722 ColWrapper.setHeight(); 8723 $(window).resize(ColWrapper.setHeight); 8724 }, 8725 setHeight: function() { 8726 var $colWrappers = $('[class*="col-wrapper"] > .col-inner'); 8727 8728 // iterate through "col-inner" = first level 8729 if ($colWrappers.length > 0) { 8730 $colWrappers.each(function() { 8731 var $cols = $(this).children('.col'); 8732 if ($cols.length > 1) { 8733 var colWrappers = [], 8734 colWrapperCount = 0; 8735 8736 // iterate through "col" = second level 8737 $cols.each(function(index) { 8738 var $colWrapper = $(this).children('[class*="col-wrapper"]'); 8739 if ($colWrapper.length > 0) { 8740 // reset height of boxes 8741 $colWrapper.removeAttr('style'); 8742 8743 // reset no-padding class 8744 $(this).removeClass('no-padding'); 8745 8746 // set colWrapper properties 8747 colWrappers.push({ 8748 index: index, 8749 height: $colWrapper.height(), 8750 position: $colWrapper.position() 8751 });
8752 colWrapperCount++; 8753 } 8754 }); 8755 8756 // the col has at least 2 colWrapper so we need the same height for the elements 8757 if (colWrapperCount > 1) { 8758 var colWrapperOnSameTopPosition = []; 8759 for (var i = 0; i < colWrappers.length; i++) { 8760 var colWrapperTopPosition = colWrappers[i].position.top; 8761 if (!Array.isArray(colWrapperOnSameTopPosition[colWrapperTopPosition])) { 8762 colWrapperOnSameTopPosition[colWrapperTopPosition] = []; 8763 } 8764 colWrapperOnSameTopPosition[colWrapperTopPosition].push(colWrappers[i]); 8765 } 8766 8767 for (var index in colWrapperOnSameTopPosition) { 8768 if (colWrapperOnSameTopPosition[index].length > 1) { 8769 // get the highest Wrapper on the same position 8770 var maxColwrapperHeight = 0; 8771 for (var j = 0; j < colWrapperOnSameTopPosition[index].length; j++) { 8772 if (typeof colWrapperOnSameTopPosition[index][j] !== 'undefined') { 8773 if (colWrapperOnSameTopPosition[index][j].height > maxColwrapperHeight) { 8774 maxColwrapperHeight = colWrapperOnSameTopPosition[index][j].height; 8775 } 8776 } 8777 } 8778 8779 // Set height for the wrapper on the same top position 8780 for (var k = 0; k < colWrapperOnSameTopPosition[index].length; k++) { 8781 $cols.children('[class*="col-wrapper"]').eq(k).height(maxColwrapperHeight); 8782 } 8783 } else { 8784 // Set no-padding class to prevent padding top for wrapped cols 8785 var elementIndex = colWrapperOnSameTopPosition[index][0].index; 8786 $cols.children('[class*="col-wrapper"]').eq(elementIndex).parents('.col').addClass('no-padding'); 8787 } 8788 } 8789 } 8790 } 8791 }); 8792 } 8793 } 8794}; 8795 8796$(document).ready(ColWrapper.init); 8797 8798var MainHeaderActionBar = { 8799 initialized: false, 8800 onInit: function() { 8801 MainHeaderActionBar.smallDeviceDetection(); 8802 $(window).resize(MainHeaderActionBar.smallDeviceDetection); 8803 MainHeaderActionBar.initialized = true; 8804 }, 8805 smallDeviceDetection: function() { 8806 var $container = $('.main-header-inner'), 8807 $logo = $('.main-header__logo'), 8808 $actionBar = $('.main-header__action'), 8809 smallClass = 'main-header-inner--small', 8810 mediumClass = 'main-header-inner--medium', 8811 largeClass = 'main-header-inner--large'; 8812 } 8813}; 8814$(document).ready(MainHeaderActionBar.onInit); 8815 8816var map = { 8817 $element: $('.map'), 8818 frame: '.map__frame', 8819 icon_svg: 'typo3conf/ext/ueberdosisprovider/Resources/Public/Img/pin.svg', 8820 icon_png: 'typo3conf/ext/ueberdosisprovider/Resources/Public/Img/pin.png', 8821 options: { 8822 zoom: 17, 8823 disableDefaultUI: true 8824 }, 8825 init: function() { 8826 if (!map.$element.length) { 8827 return; 8828 } 8829 8830 map.$element.each(function(index) { 8831 map.initMaps( $(this) ); 8832 }); 8833 }, 8834 initMaps: function($element) { 8835 var lat = $element.data('lat'), 8836 lng = $element.data('lng'); 8837 8838 map.options.center = new google.maps.LatLng(lat, lng); 8839 map.options.zoom = $element.data('zoom'); 8840 8841 var googlemaps = new google.maps.Map( $element.find(map.frame)[0], map.options); 8842 8843 var isIE11 = !!navigator.userAgent.match(/Trident.*rv[ :]*11\./); 8844 8845 var icon = map.icon_png; 8846 if (Modernizr.svg && !isIE11) { 8847 icon = map.icon_svg; 8848 } 8849 8850 var image = { 8851 url: icon, 8852 }; 8853 8854 var marker = new google.maps.Marker({ 8855 position: map.options.center, 8856 map: googlemaps, 8857 icon: image 8858 }); 8859 } 8860}; 8861 8862$(document).ready(map.init); 8863 8864/** 8865 * @author: ideawu 8866 * @link: http://www.ideawu.net/prj/jquery-lazy-bind/ 8867 * Modified by Patrick Baber to use $(this). 8868 */ 8869 8870(function($){ 8871 // lazybind for menu 8872 $.fn.lazybind = function(types, fn, timeout, abort){ 8873 var timer = null; 8874 if (abort !== undefined){ 8875 $(this).bind(abort, function(){ 8876 clearTimeout(timer); 8877 }); 8878 } 8879 return $(this).bind(types, function(event){ 8880 var data = { 8881 event: event, 8882 element: this 8883 }; 8884 timer = setTimeout(function(){ 8885 fn.call(data.element, data.event); 8886 }, timeout); 8887 }); 8888 }; 8889})(jQuery); 8890 8891/** 8892 * This file is enhancing the navigation for mobile and desktop usage 8893 */ 8894var Navigation = { 8895 initialized: false, 8896 init: function() { 8897 // Subnavigation delay 8898 Navigation.hoverDelay(); 8899 8900 // sticky or not sticky 8901 Navigation.toggleSticky(); 8902 $(window).on('scroll', Navigation.toggleSticky); 8903 8904 // size detection 8905 Navigation.detectSize(); 8906 $(window).resize(Navigation.detectSize); 8907 Navigation.initialized = true; 8908 8909 // init language and contact
8910 Navigation.Language.init(); 8911 Navigation.Contact.init(); 8912 }, 8913 /** 8914 * Delay subnavigation opening by hover. 8915 */ 8916 hoverDelay: function() { 8917 var $navigationWithChild = $('.main-nav__lvl-1 > li.has-child'); 8918 8919 $navigationWithChild.each(function() { 8920 var $this = $(this); 8921 8922 $this.lazybind( 8923 'mouseenter', 8924 function(){ 8925 $this.addClass('hover'); 8926 }, 8927 200, 8928 'mouseleave' 8929 ); 8930 8931 $this.lazybind( 8932 'mouseleave', 8933 function(){ 8934 $this.removeClass('hover'); 8935 }, 8936 200, 8937 'mouseenter' 8938 ); 8939 }); 8940 }, 8941 /** 8942 * Toggle the navigation to the sticky mode if the user scrolls bellow the header. 8943 */ 8944 toggleSticky: function() { 8945 var $navigation = $('.main-nav'), 8946 $header = $('.main-header'), 8947 headerHeight = $header.height(), 8948 windowScrollOffset = $(document).scrollTop(), 8949 stickyClass = 'main-nav--sticky'; 8950 8951 if (windowScrollOffset > headerHeight) { 8952 if (!$navigation.hasClass(stickyClass) || Navigation.initialized === false) { 8953 $navigation.addClass(stickyClass); 8954 $navigation.trigger('sticky.navigation'); 8955 } 8956 } else { 8957 if ($navigation.hasClass(stickyClass) || Navigation.initialized === false) { 8958 $navigation.removeClass(stickyClass); 8959 $navigation.trigger('unsticky.navigation'); 8960 } 8961 } 8962 }, 8963 /** 8964 * Detection, which size is needed for the best user experience. Set classes "medium" or "large" for the navigation. 8965 */ 8966 detectSize: function() { 8967 //we are using the shadow nav because of wrong dimensions of collapsing elements in non sticky navigation. 8968 var $nav = $('.main-nav'), 8969 $shadowNav = Navigation.createShadow(), 8970 $items = $shadowNav.find('.main-nav__lvl-1'), 8971 $actionBar = $shadowNav.find('.main-nav__meta-action'), 8972 $container = $shadowNav.find('.main-nav-inner'); 8973 8974 if ($items.outerWidth() + $actionBar.outerWidth() <= $container.width()) { 8975 $nav.removeClass('medium'); 8976 $nav.addClass('large'); 8977 } else { 8978 $nav.addClass('medium'); 8979 $nav.removeClass('large'); 8980 } 8981 }, 8982 /** 8983 * For the size detection of the navigation we need a "shadow navigation" for calculating the right dimensions. 8984 */ 8985 createShadow: function() { 8986 var $shadow = $('.main-nav--clone'); 8987 if (!$shadow.length) { 8988 var $mainNav = $('.main-nav'); 8989 $shadow = $mainNav.clone(); 8990 8991 //change class of clone 8992 $shadow 8993 .removeClass('main-nav') 8994 .addClass('main-nav--clone'); 8995 8996 //insert clone nav into DOM 8997 $mainNav.after($shadow); 8998 } 8999 9000 return $shadow; 9001 }, 9002 Language: { 9003 init: function() { 9004 $('.main-nav__language').click(Navigation.Language.toggle); 9005 $('.main-nav__meta-action').on('open.search open.contact', Navigation.Language.hide); 9006 // init language hover delay 9007 Navigation.Language.hoverDelay(); 9008 }, 9009 /** 9010 * Delay subnavigation opening by hover. 9011 */ 9012 hoverDelay: function() { 9013 var $languageNavigation = $('.main-header__language'); 9014 9015 $languageNavigation.each(function() { 9016 var $this = $(this); 9017 9018 $this.lazybind( 9019 'mouseenter', 9020 function(){ 9021 $this.addClass('hover'); 9022 }, 9023 200, 9024 'mouseleave' 9025 ); 9026 9027 $this.lazybind( 9028 'mouseleave', 9029 function(){ 9030 $this.removeClass('hover'); 9031 }, 9032 200, 9033 'mouseenter' 9034 ); 9035 }); 9036 }, 9037 toggle: function() { 9038 if ($('.main-nav__language').hasClass('is-open')) { 9039 Navigation.Language.hide(); 9040 } else { 9041 Navigation.Language.show(); 9042 } 9043 }, 9044 show: function() { 9045 $('.main-nav__language').addClass('is-open').trigger('open.language'); 9046 }, 9047 hide: function() { 9048 $('.main-nav__language').removeClass('is-open'); 9049 } 9050 }, 9051 Contact: { 9052 init: function() { 9053 $('.main-nav__contact').click(Navigation.Contact.toggle); 9054 $('.main-nav__meta-action').on('open.search open.language', Navigation.Contact.hide); 9055 }, 9056 toggle: function() { 9057 if ($('.main-nav__contact').hasClass('is-open')) { 9058 Navigation.Contact.hide(); 9059 } else { 9060 Navigation.Contact.show(); 9061 } 9062 }, 9063 show: function() { 9064 $('.main-nav__contact').addClass('is-open').trigger('open.contact'); 9065 }, 9066 hide: function() { 9067 $('.main-nav__contact').removeClass('is-open'); 9068 } 9069 } 9070}; 9071 9072$(document).ready(Navigation.init); 9073 9074//off-canvas navigation 9075 9076var OffCanvasNav = { 9077 onReady: function() { 9078 var $mobileNavigation = $('.mobile-nav'), 9079 $btnToggleMenu = $('.btn--toggle-menu'), 9080 $btnCloseMenu = $('.btn--close-menu'), 9081 $lvl1ItemWithSubNav = $('.mobile-nav .has-child > a'); 9082 9083 // Open subnavigation, if available. 9084 $lvl1ItemWithSubNav.on('click', OffCanvasNav.toggleMenu); 9085 9086 // Open subnavigation, if current page is in subnavigation 9087 $('.mobile-nav__lvl-1 > .has-child.is-current > a').addClass('open').next('ul').show(); 9088 }, 9089 toggleMenu: function(e) { 9090 var $this = $(this), 9091 $lvl1ItemWithSubNav = $('.mobile-nav .has-child > a'), 9092 $lvl2 = $('.mobile-nav .mobile-nav__lvl-2'); 9093 9094 e.preventDefault(); 9095 $lvl1ItemWithSubNav.removeClass('open'); 9096 $lvl2.stop().slideUp(); 9097 9098 if ($this.next().is(':visible')) { 9099 $this. 9100 removeClass('open') 9101 .next().stop().slideUp(); 9102 } else { 9103 $this. 9104 addClass('open') 9105 .next().stop().slideDown(); 9106 } 9107 } 9108}; 9109 9110$(document).ready(OffCanvasNav.onReady); 9111 9112//add jquery event 'outside' 9113$.fn.outside = function(ename, cb){ 9114 return this.each(function(){ 9115 var $this = $(this), 9116 self = this; 9117 $(document.body).on(ename, function tempo(e){ 9118 if(e.target !== self && !$.contains(self, e.target)){ 9119 cb.apply(self, [e]); 9120 if(!self.parentNode) $(document.body).off(ename, tempo); 9121 } 9122 }); 9123 }); 9124}; 9125 9126/* ========================================================================== 9127 Photoswipe 9128 ========================================================================== */ 9129 9130var photoswipe = { 9131 init: function() { 9132 9133 var $pic = $('a.photoswipe'); 9134 9135 var items = function() { 9136 9137 var items = []; 9138 $pic.each(function(index) { 9139 var $this = $(this), 9140 $href = $this.attr('href'), 9141 $size = $this.data('size').split('x'), 9142 $width = $size[0], 9143 $height = $size[1], 9144 $title = $this.attr('title'), 9145 $alt = $this.find('img').attr('alt'), 9146 $caption = $(this).next('.news-img-caption, .csc-textpic-caption').text(), 9147 slide_caption = null; 9148 9149 if (typeof $caption === 'string' && $caption.length > 0) { 9150 slide_caption = $caption; 9151 } else if (typeof $title === 'string' && $title.length > 0) { 9152 slide_caption = $title; 9153 } else if (typeof $alt === 'string' && $alt.length > 0) { 9154 slide_caption = $alt; 9155 } 9156 9157 var item = { 9158 src : $href, 9159 w : parseInt($width), 9160 h : parseInt($height), 9161 title : slide_caption, 9162 pid : index 9163 }; 9164 9165 items.push(item); 9166 9167 $this.attr('data-index', index); 9168 9169 }); 9170 9171 return items; 9172 9173 }(); 9174 9175 var $pswp = $('.pswp')[0]; 9176 9177 $pic.on('click', function(event) { 9178 event.preventDefault(); 9179 9180 var options = { 9181 index: parseInt( $(this).attr('data-index') ), 9182 bgOpacity: 0.9, 9183 shareEl: false, 9184 history: false 9185 }; 9186 9187 // Initialize PhotoSwipe 9188 var lightBox = new PhotoSwipe($pswp, PhotoSwipeUI_Default, items, options); 9189 lightBox.init(); 9190 }); 9191 } 9192}; 9193 9194$(document).ready(photoswipe.init); 9195 9196var ResponsiveTable = (function() { 9197 'use strict'; 9198 9199 var $responsiveTable = $('.contenttable').has('thead'); 9200 9201 var init = function() { 9202 9203 if ($responsiveTable) { 9204 setHeaders(); 9205 } 9206 9207 }; 9208 9209 var setHeaders = function() { 9210 9211 $responsiveTable.find('td').each(function() { 9212 var $this = $(this), 9213 tableCellIndex = $this.index(), 9214 headerContent = $responsiveTable.find('th').eq(tableCellIndex).text(); 9215 9216 $this 9217 .attr('headers', headerContent) 9218 .wrapInner('<span />'); 9219 }); 9220 9221 }; 9222 9223 return { 9224 init: init 9225 }; 9226 9227})(); 9228 9229$(document).ready( ResponsiveTable.init ); 9230 9231/** 9232 * Object controls the appearance of the search form in the header and the navigation. 9233 */ 9234var Search = { 9235 init: function() { 9236 Search.Header.init(); 9237 Search.Navigation.init(); 9238 }, 9239 /** 9240 * Search form in header. 9241 */ 9242 Header: { 9243 init: function() {
9244 Search.Header.toggle(); 9245 $(window).resize(Search.Header.toggle); 9246 }, 9247 /** 9248 * Bind toggle handler to the search field, if the search has size "small". 9249 */ 9250 toggle: function() { 9251 var $queryField = $('.main-header__search input[type="text"]'), 9252 $submitButton = $('.main-header__search .btn--search'); 9253 9254 // detect search field size 9255 if (!$queryField.is(':visible')) { 9256 // small size 9257 // add click handler once 9258 $submitButton 9259 .off('click', Search.Header.smallSearchClick) 9260 .on('click', Search.Header.smallSearchClick); 9261 } else { 9262 // normal size 9263 $submitButton.off('click', Search.Header.smallSearchClick); 9264 } 9265 }, 9266 /** 9267 * Open search field on first click. Search on second click, if the field is not empty. 9268 */ 9269 smallSearchClick: function(e) { 9270 var $queryField = $('.main-header__search input[type="text"]'), 9271 queryValue = $queryField.val(); 9272 9273 if (!$queryField.hasClass('is-open')) { 9274 Search.Header.show(); 9275 } else { 9276 if (queryValue !== '') { 9277 Search.Header.submit(); 9278 } else { 9279 Search.Header.hide(); 9280 } 9281 } 9282 9283 e.preventDefault(); 9284 }, 9285 /** 9286 * Open search field and set focus. 9287 */ 9288 show: function() { 9289 $('.main-header__search input[type="text"]') 9290 .addClass('is-open') 9291 .focus(); 9292 }, 9293 /** 9294 * Hide search field. 9295 */ 9296 hide: function() { 9297 $('.main-header__search input[type="text"]').removeClass('is-open'); 9298 }, 9299 /** 9300 * Submit form. 9301 */ 9302 submit: function() { 9303 $('.main-header__search form').submit(); 9304 } 9305 }, 9306 /** 9307 * Search form in navigation. 9308 */ 9309 Navigation: { 9310 init: function() { 9311 //collapse and expand 9312 $('.main-nav__search .btn--search').click(Search.Navigation.toggle); 9313 9314 //set focus in field after reset button click 9315 $('.main-nav__search__btn--reset').click(Search.Navigation.focus); 9316 9317 //listen on other navigation action bar events 9318 $('.main-nav__meta-action').on('open.language open.contact', Search.Navigation.hide); 9319 }, 9320 /**
9321 * Open or close search field. 9322 */ 9323 toggle: function(e) { 9324 if ($('.main-nav__search-form').is(':visible')) { 9325 Search.Navigation.hide(); 9326 } else { 9327 Search.Navigation.show(); 9328 } 9329 9330 e.preventDefault(); 9331 }, 9332 /** 9333 * Open search field and set focus. 9334 */ 9335 show: function() { 9336 $('.main-nav__search-form').addClass('is-open'); 9337 Search.Navigation.focus(); 9338 $('.main-nav__search').trigger('open.search'); 9339 }, 9340 /** 9341 * Hide search field. 9342 */ 9343 hide: function() { 9344 $('.main-nav__search-form').removeClass('is-open'); 9345 }, 9346 /** 9347 * Set focus in input 9348 */ 9349 focus: function() { 9350 $('.main-nav__search__input').focus(); 9351 } 9352 } 9353}; 9354$(document).ready(Search.init); 9355 9356var SectionIndexMenu = { 9357 initialized: false, 9358 $sectionIndex: $('.section-index'), 9359 stickyClass: 'section-index--sticky', 9360 openClass: 'section-index--open', 9361 sectionMarginTop: null, 9362 init: function() { 9363 if (SectionIndexMenu.$sectionIndex.length) { 9364 SectionIndexMenu.sectionMarginTop = parseInt($('.section-index__inner').css('marginTop').replace('px', '')); 9365 9366 SectionIndexMenu.toggleSticky(); 9367 SectionIndexMenu.toggleMenu(); 9368 $(window).on('scroll', SectionIndexMenu.toggleSticky); 9369 $(window).on('resize', SectionIndexMenu.toggleSticky); 9370 9371 SectionIndexMenu.$sectionIndex.find('a').on('click', SectionIndexMenu.closeMenu); 9372 } 9373 }, 9374 toggleSticky: function(forceToggle) { 9375 var windowScrollOffset = $(document).scrollTop(), 9376 $navigation = $('.main-nav'), 9377 navigationHeight = $navigation.outerHeight(), 9378 parentColWrapper = $('[data-toggle-section-index]').parents('[class*="col-wrapper"]'), 9379 buttonOffset = parentColWrapper.offset().top + parentColWrapper.outerHeight() + SectionIndexMenu.sectionMarginTop; 9380 9381 if (windowScrollOffset > (buttonOffset - navigationHeight)) { 9382 if (!SectionIndexMenu.$sectionIndex.hasClass(SectionIndexMenu.stickyClass) || SectionIndexMenu.initialized === false) { 9383 SectionIndexMenu.$sectionIndex.addClass(SectionIndexMenu.stickyClass); 9384 SectionIndexMenu.$sectionIndex.trigger('sticky.navigation'); 9385 } 9386 } else { 9387 if (SectionIndexMenu.$sectionIndex.hasClass(SectionIndexMenu.stickyClass) || SectionIndexMenu.initialized === false) { 9388 SectionIndexMenu.$sectionIndex.removeClass(SectionIndexMenu.stickyClass); 9389 SectionIndexMenu.$sectionIndex.trigger('unsticky.navigation'); 9390 } 9391 } 9392 }, 9393 toggleMenu: function() { 9394 $('[data-toggle-section-index]').on('click', function() { 9395 SectionIndexMenu.$sectionIndex.toggleClass(SectionIndexMenu.openClass); 9396 }); 9397 }, 9398 closeMenu: function() { 9399 SectionIndexMenu.$sectionIndex.removeClass(SectionIndexMenu.openClass); 9400 } 9401}; 9402 9403$(document).ready(SectionIndexMenu.init); 9404 9405/* ========================================================================== 9406 Slider 9407 ========================================================================== */ 9408 9409var slider = { 9410 init: function() { 9411 var $slider = $('.slider'), 9412 $sliderSmall = $('.slider--small'), 9413 $sliderLarge = $('.slider--large'); 9414 9415 // V2: Slider Items zufällig ausgeben 9416 // if ($slider.length) { 9417 // //sort random function 9418 // $slider.each(function() { 9419 // var $slider = $(this); 9420 9421 // $slider.children().sort(function() { 9422 // return Math.round(Math.random()) - 0.5; 9423 // }).each(function(){ 9424 // $(this).appendTo($slider); 9425 // }); 9426 // }); 9427 // } 9428 9429 var rtl = false; 9430 if ($('html[dir="rtl"]').length) { 9431 rtl = true; 9432 } 9433 9434 if ($sliderSmall.length) { 9435 $sliderSmall.owlCarousel({ 9436 navText: [], 9437 nav: true, 9438 navRewind: false, 9439 dots: false, 9440 rtl: rtl, 9441 responsiveClass: true, 9442 responsive: { 9443 0: { 9444 items: 1 9445 }, 9446 700: { 9447 items: 2 9448 }, 9449 940: { 9450 items: 3 9451 }, 9452 1420: { 9453 items: 4 9454 } 9455 } 9456 }); 9457 } 9458 9459 if ($sliderLarge.length) { 9460 $sliderLarge.owlCarousel({ 9461 navText: [], 9462 nav: true, 9463 navRewind: false, 9464 dots: false, 9465 rtl: rtl, 9466 responsiveClass: true, 9467 responsive: { 9468 0: { 9469 items: 1 9470 }, 9471 500: { 9472 items: 2 9473 }, 9474 700: { 9475 items: 3 9476 }, 9477 940: { 9478 items: 4 9479 }, 9480 1420: { 9481 items: 5 9482 } 9483 } 9484 }); 9485 } 9486 } 9487}; 9488 9489$(document).ready(slider.init); 9490 9491/* ========================================================================== 9492 Slideshow 9493 ========================================================================== */ 9494 9495var slideshow = { 9496 $element: $('.slideshow'), 9497 init: function() { 9498 var rtl = false; 9499 if ($('html[dir="rtl"]').length) { 9500 rtl = true; 9501 } 9502 9503 if (slideshow.$element.length) { 9504 slideshow.$element.owlCarousel({ 9505 slideBy: 'page', 9506 navText: [], 9507 loop: true, 9508 nav: true, 9509 rtl: rtl, 9510 items: 1, 9511 autoplayTimeout: 4000, 9512 autoplayHoverPause: true, 9513 animateIn: 'fadeIn', 9514 animateOut: 'fadeOut', 9515 mouseDrag: false, 9516 touchDrag: false, 9517 autoHeight: true, 9518 responsive: { 9519 940: { 9520 autoplay: true 9521 } 9522 }, 9523 onInitialized: function() { 9524 slideshow.controls(this.$element); 9525 }, 9526 onResized: function() { 9527 slideshow.controls(this.$element); 9528 }, 9529 onRefreshed: function() { 9530 slideshow.controls(this.$element); 9531 }, 9532 onChanged: function() { 9533 slideshow.controls(this.$element); 9534 } 9535 }); 9536 9537 // FIX: Autoheight on window resize 9538 slideshow.$element.on('resized.owl.carousel', slideshow.refresh); 9539 $(window).load(slideshow.refresh); 9540 } 9541 }, 9542 controls: function($element) { 9543 var $content = $element.find('.active .slideshow__content'), 9544 $controls = $element.find('.owl-nav > div'), 9545 $image = $element.find('.active .slideshow__image'), 9546 height = $image.outerHeight(), 9547 top = height/2; 9548 9549 if (top > 0 && $content.css('position') != 'absolute') { 9550 $controls.css('top', top); 9551 } else { 9552 $controls.css('top', '50%'); 9553 } 9554 }, 9555 refresh: function () { 9556 slideshow.$element.find('.owl-height').css('height', slideshow.$element.find('.owl-item.active').height()); 9557 } 9558}; 9559 9560$(document).ready(slideshow.init); 9561 9562//replace svg files with png files on old browsers 9563jQuery(function($) { 9564 if (!Modernizr.svg) { 9565 $('img[src*="svg"]').attr('src', function() {
9566 return $(this).attr('src').replace('.svg', '.png'); 9567 }); 9568 } 9569}); 9570 9571var ToggleFeedbackText = { 9572 init: function() { 9573 ToggleFeedbackText.toggle(); 9574 }, 9575 toggle: function() { 9576 var $toggleButton = $('.feedback__more'); 9577 9578 $toggleButton.on('click', function() { 9579 $(this).toggleClass('is--open'); 9580 $(this).parent().find('.feedback__content-more').slideToggle(); 9581 }); 9582 } 9583}; 9584 9585$(document).ready(ToggleFeedbackText.init);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.