1/*! lg-zoom - v1.0.4 - 2016-12-20
2* http://sachinchoolur.github.io/lightGallery
3* Copyright (c) 2016 Sachin N; Licensed GPLv3 */
4
5(function (root, factory) {
6  if (typeof define === 'function' && define.amd) {
7    // AMD. Register as an anonymous module unless amdModuleId is set
8    define(['jquery'], function (a0) {
9      return (factory(a0));
10    });
11  } else if (typeof exports === 'object') {
12    // Node. Does not work with strict CommonJS, but
13    // only CommonJS-like environments that support module.exports,
14    // like Node.
15    module.exports = factory(require('jquery'));
16  } else {
17    factory(jQuery);
18  }
19}(this, function ($) {
20
21(function() {
22
23    'use strict';
24
25    var getUseLeft = function() {
26        var useLeft = false;
27        var isChrome = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
28        if (isChrome && parseInt(isChrome[2], 10) < 54) {
29            useLeft = true;
30        }
31
32        return useLeft;
33    };
34
35    var defaults = {
36        scale: 1,
37        zoom: true,
38        actualSize: true,
39        enableZoomAfter: 300,
40        useLeftForZoom: getUseLeft()
41    };
42
43    var Zoom = function(element) {
44
45        this.core = $(element).data('lightGallery');
46
47        this.core.s = $.extend({}, defaults, this.core.s);
48
49        if (this.core.s.zoom && this.core.doCss()) {
50            this.init();
51
52            // Store the zoomable timeout value just to clear it while closing
53            this.zoomabletimeout = false;
54
55            // Set the initial value center
56            this.pageX = $(window).width() / 2;
57            this.pageY = ($(window).height() / 2) + $(window).scrollTop();
58        }
59
60        return this;
61    };
62
63    Zoom.prototype.init = function() {
64
65        var _this = this;
66        var zoomIcons = '<span id="lg-zoom-in" class="lg-icon"></span><span id="lg-zoom-out" class="lg-icon"></span>';
67
68        if (_this.core.s.actualSize) {
69            zoomIcons += '<span id="lg-actual-size" class="lg-icon"></span>';
70        }
71
72        if (_this.core.s.useLeftForZoom) {
73            _this.core.$outer.addClass('lg-use-left-for-zoom');
74        } else {
75            _this.core.$outer.addClass('lg-use-transition-for-zoom');
76        }
77
78        this.core.$outer.find('.lg-toolbar').append(zoomIcons);
79
80        // Add zoomable class
81        _this.core.$el.on('onSlideItemLoad.lg.tm.zoom', function(event, index, delay) {
82
83            // delay will be 0 except first time
84            var _speed = _this.core.s.enableZoomAfter + delay;
85
86            // set _speed value 0 if gallery opened from direct url and if it is first slide
87            if ($('body').hasClass('lg-from-hash') && delay) {
88
89                // will execute only once
90                _speed = 0;
91            } else {
92
93                // Remove lg-from-hash to enable starting animation.
94                $('body').removeClass('lg-from-hash');
95            }
96
97            _this.zoomabletimeout = setTimeout(function() {
98                _this.core.$slide.eq(index).addClass('lg-zoomable');
99            }, _speed + 30);
100        });
101
102        var scale = 1;
103        /**
104         * @desc Image zoom
105         * Translate the wrap and scale the image to get better user experience
106         *
107         * @param {String} scaleVal - Zoom decrement/increment value
108         */
109        var zoom = function(scaleVal) {
110
111            var $image = _this.core.$outer.find('.lg-current .lg-image');
112            var _x;
113            var _y;
114
115            // Find offset manually to avoid issue after zoom
116            var offsetX = ($(window).width() - $image.prop('offsetWidth')) / 2;
117            var offsetY = (($(window).height() - $image.prop('offsetHeight')) / 2) + $(window).scrollTop();
118
119            _x = _this.pageX - offsetX;
120            _y = _this.pageY - offsetY;
121
122            var x = (scaleVal - 1) * (_x);
123            var y = (scaleVal - 1) * (_y);
124
125            $image.css('transform', 'scale3d(' + scaleVal + ', ' + scaleVal + ', 1)').attr('data-scale', scaleVal);
126
127            if (_this.core.s.useLeftForZoom) {
128                $image.parent().css({
129                    left: -x + 'px',
130                    top: -y + 'px'
131                }).attr('data-x', x).attr('data-y', y);
132            } else {
133                $image.parent().css('transform', 'translate3d(-' + x + 'px, -' + y + 'px, 0)').attr('data-x', x).attr('data-y', y);
134            }
135        };
136
137        var callScale = function() {
138            if (scale > 1) {
139                _this.core.$outer.addClass('lg-zoomed');
140            } else {
141                _this.resetZoom();
142            }
143
144            if (scale < 1) {
145                scale = 1;
146            }
147
148            zoom(scale);
149        };
150
151        var actualSize = function(event, $image, index, fromIcon) {
152            var w = $image.prop('offsetWidth');
153            var nw;
154            if (_this.core.s.dynamic) {
155                nw = _this.core.s.dynamicEl[index].width || $image[0].naturalWidth || w;
156            } else {
157                nw = _this.core.$items.eq(index).attr('data-width') || $image[0].naturalWidth || w;
158            }
159
160            var _scale;
161
162            if (_this.core.$outer.hasClass('lg-zoomed')) {
163                scale = 1;
164            } else {
165                if (nw > w) {
166                    _scale = nw / w;
167                    scale = _scale || 2;
168                }
169            }
170
171            if (fromIcon) {
172                _this.pageX = $(window).width() / 2;
173                _this.pageY = ($(window).height() / 2) + $(window).scrollTop();
174            } else {
175                _this.pageX = event.pageX || event.originalEvent.targetTouches[0].pageX;
176                _this.pageY = event.pageY || event.originalEvent.targetTouches[0].pageY;
177            }
178
179            callScale();
180            setTimeout(function() {
181                _this.core.$outer.removeClass('lg-grabbing').addClass('lg-grab');
182            }, 10);
183        };
184
185        var tapped = false;
186
187        // event triggered after appending slide content
188        _this.core.$el.on('onAferAppendSlide.lg.tm.zoom', function(event, index) {
189
190            // Get the current element
191            var $image = _this.core.$slide.eq(index).find('.lg-image');
192
193            $image.on('dblclick', function(event) {
194                actualSize(event, $image, index);
195            });
196
197            $image.on('touchstart', function(event) {
198                if (!tapped) {
199                    tapped = setTimeout(function() {
200                        tapped = null;
201                    }, 300);
202                } else {
203                    clearTimeout(tapped);
204                    tapped = null;
205                    actualSize(event, $image, index);
206                }
207
208                event.preventDefault();
209            });
210
211        });
212
213        // Update zoom on resize and orientationchange
214        $(window).on('resize.lg.zoom scroll.lg.zoom orientationchange.lg.zoom', function() {
215            _this.pageX = $(window).width() / 2;
216            _this.pageY = ($(window).height() / 2) + $(window).scrollTop();
217            zoom(scale);
218        });
219
220        $('#lg-zoom-out').on('click.lg', function() {
221            if (_this.core.$outer.find('.lg-current .lg-image').length) {
222                scale -= _this.core.s.scale;
223                callScale();
224            }
225        });
226
227        $('#lg-zoom-in').on('click.lg', function() {
228            if (_this.core.$outer.find('.lg-current .lg-image').length) {
229                scale += _this.core.s.scale;
230                callScale();
231            }
232        });
233
234        $('#lg-actual-size').on('click.lg', function(event) {
235            actualSize(event, _this.core.$slide.eq(_this.core.index).find('.lg-image'), _this.core.index, true);
236        });
237
238        // Reset zoom on slide change
239        _this.core.$el.on('onBeforeSlide.lg.tm', function() {
240            scale = 1;
241            _this.resetZoom();
242        });
243
244        // Drag option after zoom
245        if (!_this.core.isTouch) {
246            _this.zoomDrag();
247        }
248
249        if (_this.core.isTouch) {
250            _this.zoomSwipe();
251        }
252
253    };
254
255    // Reset zoom effect
256    Zoom.prototype.resetZoom = function() {
257        this.core.$outer.removeClass('lg-zoomed');
258        this.core.$slide.find('.lg-img-wrap').removeAttr('style data-x data-y');
259        this.core.$slide.find('.lg-image').removeAttr('style data-scale');
260
261        // Reset pagx pagy values to center
262        this.pageX = $(window).width() / 2;
263        this.pageY = ($(window).height() / 2) + $(window).scrollTop();
264    };
265
266    Zoom.prototype.zoomSwipe = function() {
267        var _this = this;
268        var startCoords = {};
269        var endCoords = {};
270        var isMoved = false;
271
272        // Allow x direction drag
273        var allowX = false;
274
275        // Allow Y direction drag
276        var allowY = false;
277
278        _this.core.$slide.on('touchstart.lg', function(e) {
279
280            if (_this.core.$outer.hasClass('lg-zoomed')) {
281                var $image = _this.core.$slide.eq(_this.core.index).find('.lg-object');
282
283                allowY = $image.prop('offsetHeight') * $image.attr('data-scale') > _this.core.$outer.find('.lg').height();
284                allowX = $image.prop('offsetWidth') * $image.attr('data-scale') > _this.core.$outer.find('.lg').width();
285                if ((allowX || allowY)) {
286                    e.preventDefault();
287                    startCoords = {
288                        x: e.originalEvent.targetTouches[0].pageX,
289                        y: e.originalEvent.targetTouches[0].pageY
290                    };
291                }
292            }
293
294        });
295
296        _this.core.$slide.on('touchmove.lg', function(e) {
297
298            if (_this.core.$outer.hasClass('lg-zoomed')) {
299
300                var _$el = _this.core.$slide.eq(_this.core.index).find('.lg-img-wrap');
301                var distanceX;
302                var distanceY;
303
304                e.preventDefault();
305                isMoved = true;
306
307                endCoords = {
308                    x: e.originalEvent.targetTouches[0].pageX,
309                    y: e.originalEvent.targetTouches[0].pageY
310                };
311
312                // reset opacity and transition duration
313                _this.core.$outer.addClass('lg-zoom-dragging');
314
315                if (allowY) {
316                    distanceY = (-Math.abs(_$el.attr('data-y'))) + (endCoords.y - startCoords.y);
317                } else {
318                    distanceY = -Math.abs(_$el.attr('data-y'));
319                }
320
321                if (allowX) {
322                    distanceX = (-Math.abs(_$el.attr('data-x'))) + (endCoords.x - startCoords.x);
323                } else {
324                    distanceX = -Math.abs(_$el.attr('data-x'));
325                }
326
327                if ((Math.abs(endCoords.x - startCoords.x) > 15) || (Math.abs(endCoords.y - startCoords.y) > 15)) {
328
329                    if (_this.core.s.useLeftForZoom) {
330                        _$el.css({
331                            left: distanceX + 'px',
332                            top: distanceY + 'px'
333                        });
334                    } else {
335                        _$el.css('transform', 'translate3d(' + distanceX + 'px, ' + distanceY + 'px, 0)');
336                    }
337                }
338
339            }
340
341        });
342
343        _this.core.$slide.on('touchend.lg', function() {
344            if (_this.core.$outer.hasClass('lg-zoomed')) {
345                if (isMoved) {
346                    isMoved = false;
347                    _this.core.$outer.removeClass('lg-zoom-dragging');
348                    _this.touchendZoom(startCoords, endCoords, allowX, allowY);
349
350                }
351            }
352        });
353
354    };
355
356    Zoom.prototype.zoomDrag = function() {
357
358        var _this = this;
359        var startCoords = {};
360        var endCoords = {};
361        var isDraging = false;
362        var isMoved = false;
363
364        // Allow x direction drag
365        var allowX = false;
366
367        // Allow Y direction drag
368        var allowY = false;
369
370        _this.core.$slide.on('mousedown.lg.zoom', function(e) {
371
372            // execute only on .lg-object
373            var $image = _this.core.$slide.eq(_this.core.index).find('.lg-object');
374
375            allowY = $image.prop('offsetHeight') * $image.attr('data-scale') > _this.core.$outer.find('.lg').height();
376            allowX = $image.prop('offsetWidth') * $image.attr('data-scale') > _this.core.$outer.find('.lg').width();
377
378            if (_this.core.$outer.hasClass('lg-zoomed')) {
379                if ($(e.target).hasClass('lg-object') && (allowX || allowY)) {
380                    e.preventDefault();
381                    startCoords = {
382                        x: e.pageX,
383                        y: e.pageY
384                    };
385
386                    isDraging = true;
387
388                    // ** Fix for webkit cursor issue https://code.google.com/p/chromium/issues/detail?id=26723
389                    _this.core.$outer.scrollLeft += 1;
390                    _this.core.$outer.scrollLeft -= 1;
391
392                    _this.core.$outer.removeClass('lg-grab').addClass('lg-grabbing');
393                }
394            }
395        });
396
397        $(window).on('mousemove.lg.zoom', function(e) {
398            if (isDraging) {
399                var _$el = _this.core.$slide.eq(_this.core.index).find('.lg-img-wrap');
400                var distanceX;
401                var distanceY;
402
403                isMoved = true;
404                endCoords = {
405                    x: e.pageX,
406                    y: e.pageY
407                };
408
409                // reset opacity and transition duration
410                _this.core.$outer.addClass('lg-zoom-dragging');
411
412                if (allowY) {
413                    distanceY = (-Math.abs(_$el.attr('data-y'))) + (endCoords.y - startCoords.y);
414                } else {
415                    distanceY = -Math.abs(_$el.attr('data-y'));
416                }
417
418                if (allowX) {
419                    distanceX = (-Math.abs(_$el.attr('data-x'))) + (endCoords.x - startCoords.x);
420                } else {
421                    distanceX = -Math.abs(_$el.attr('data-x'));
422                }
423
424                if (_this.core.s.useLeftForZoom) {
425                    _$el.css({
426                        left: distanceX + 'px',
427                        top: distanceY + 'px'
428                    });
429                } else {
430                    _$el.css('transform', 'translate3d(' + distanceX + 'px, ' + distanceY + 'px, 0)');
431                }
432            }
433        });
434
435        $(window).on('mouseup.lg.zoom', function(e) {
436
437            if (isDraging) {
438                isDraging = false;
439                _this.core.$outer.removeClass('lg-zoom-dragging');
440
441                // Fix for chrome mouse move on click
442                if (isMoved && ((startCoords.x !== endCoords.x) || (startCoords.y !== endCoords.y))) {
443                    endCoords = {
444                        x: e.pageX,
445                        y: e.pageY
446                    };
447                    _this.touchendZoom(startCoords, endCoords, allowX, allowY);
448
449                }
450
451                isMoved = false;
452            }
453
454            _this.core.$outer.removeClass('lg-grabbing').addClass('lg-grab');
455
456        });
457    };
458
459    Zoom.prototype.touchendZoom = function(startCoords, endCoords, allowX, allowY) {
460
461        var _this = this;
462        var _$el = _this.core.$slide.eq(_this.core.index).find('.lg-img-wrap');
463        var $image = _this.core.$slide.eq(_this.core.index).find('.lg-object');
464        var distanceX = (-Math.abs(_$el.attr('data-x'))) + (endCoords.x - startCoords.x);
465        var distanceY = (-Math.abs(_$el.attr('data-y'))) + (endCoords.y - startCoords.y);
466        var minY = (_this.core.$outer.find('.lg').height() - $image.prop('offsetHeight')) / 2;
467        var maxY = Math.abs(($image.prop('offsetHeight') * Math.abs($image.attr('data-scale'))) - _this.core.$outer.find('.lg').height() + minY);
468        var minX = (_this.core.$outer.find('.lg').width() - $image.prop('offsetWidth')) / 2;
469        var maxX = Math.abs(($image.prop('offsetWidth') * Math.abs($image.attr('data-scale'))) - _this.core.$outer.find('.lg').width() + minX);
470
471        if ((Math.abs(endCoords.x - startCoords.x) > 15) || (Math.abs(endCoords.y - startCoords.y) > 15)) {
472            if (allowY) {
473                if (distanceY <= -maxY) {
474                    distanceY = -maxY;
475                } else if (distanceY >= -minY) {
476                    distanceY = -minY;
477                }
478            }
479
480            if (allowX) {
481                if (distanceX <= -maxX) {
482                    distanceX = -maxX;
483                } else if (distanceX >= -minX) {
484                    distanceX = -minX;
485                }
486            }
487
488            if (allowY) {
489                _$el.attr('data-y', Math.abs(distanceY));
490            } else {
491                distanceY = -Math.abs(_$el.attr('data-y'));
492            }
493
494            if (allowX) {
495                _$el.attr('data-x', Math.abs(distanceX));
496            } else {
497                distanceX = -Math.abs(_$el.attr('data-x'));
498            }
499
500            if (_this.core.s.useLeftForZoom) {
501                _$el.css({
502                    left: distanceX + 'px',
503                    top: distanceY + 'px'
504                });
505            } else {
506                _$el.css('transform', 'translate3d(' + distanceX + 'px, ' + distanceY + 'px, 0)');
507            }
508
509        }
510    };
511
512    Zoom.prototype.destroy = function() {
513
514        var _this = this;
515
516        // Unbind all events added by lightGallery zoom plugin
517        _this.core.$el.off('.lg.zoom');
518        $(window).off('.lg.zoom');
519        _this.core.$slide.off('.lg.zoom');
520        _this.core.$el.off('.lg.tm.zoom');
521        _this.resetZoom();
522        clearTimeout(_this.zoomabletimeout);
523        _this.zoomabletimeout = false;
524    };
525
526    $.fn.lightGallery.modules.zoom = Zoom;
527
528})();
529
530
531}));
532