PageSourceSearch

https://www.lecorpsutopique.com/wp-content/themes/fluxus/js/jquery.fluxus-lightbox.js?ver=1.9.4

js lecorpsutopique.com collected 2026-10-07 16:13:31 UTC 33,830 bytes, 1,113 lines download raw bytes

1/**
2 * Fluxus Lightbox 2.0
3 *
4 * Dependencies:
5 *     jQuery
6 *     underscore debounce
7 */
8(function ($, debounce) {
9  var TOUCH_DEVICE = false;
10  var FADE_OUT_DURATION = 300;
11  var DEFAULT_OPTIONS = {
12    error: 'Unable to load',
13    mode: 'fit', // fit or full,
14    margin: 15, // Margin around content when in fit mode.
15    loadingIndicatorDelay: 800,
16    idleTimeout: 5000,
17    onHide: function () {},
18    // Called on link click. Can return false to cancel
19    // opening of the Lightbox.
20    onClick: undefined
21  };
22  var $window = $(window);
23  var $html = $('html');
24
25  $window.on('touchstart.fluxus-lightbox', function () {
26    TOUCH_DEVICE = true;
27    $window.trigger('is-touch-device').off('touchstart.fluxus-lightbox');
28  });
29
30  $.FluxusLightbox = function ($links, options) {
31    this.$links = $links;
32    if (0 === this.$links.length) {
33      return false;
34    }
35
36    this.options = $.extend({}, DEFAULT_OPTIONS, options);
37    this._isModeFullWidth = this.options.mode == 'full';
38    this.hdImage = new Image();
39
40    this.render();
41    this.bindOnClickListener();
42
43    // Detects touch device and enables gestures.
44    if (TOUCH_DEVICE) {
45      this.setupGestures();
46    } else {
47      $window.on(
48        'is-touch-device.fluxus-lightbox',
49        this.setupGestures.bind(this)
50      );
51    }
52  };
53
54  $.FluxusLightbox.prototype = {
55    open: function (event, callback) {
56      // Render Lightbox offscreen, prevent overflow.
57      $html.addClass('fluxus-lightbox-visible');
58
59      // Store current window size.
60      $window.on(
61        'resize.fluxus-lightbox',
62        debounce(this.storeWindowSize.bind(this))
63      );
64      this.storeWindowSize();
65
66      // Calculate scroll position and start tracking mouse.
67      this._scrollPosition = {
68        left: $window.scrollLeft(),
69        top: $window.scrollTop()
70      };
71      event && this.onMouseMove(event);
72      $window.on('mousemove.fluxus-lightbox', this.onMouseMove.bind(this));
73
74      if (this._isModeFullWidth) {
75        this.toggleFullWidthMode(true);
76      }
77
78      // Transition into view.
79      onTransitionEnd(
80        this.$lightbox,
81        function () {
82          this.$lightbox.addClass('fluxus-lightbox--transitioned');
83        }.bind(this),
84        { propertyName: 'transform' }
85      );
86      this.$lightbox.addClass('fluxus-lightbox--slide-in');
87
88      $window.on(
89        'keydown.fluxus-lightbox',
90        this.handleKeyboardEvent.bind(this)
91      );
92
93      callback && callback.call(this);
94    },
95
96    close: function (options) {
97      $window.off('.fluxus-lightbox');
98      this.$content.css({ transition: '' });
99
100      options = $.extend({}, { animation: true }, options);
101
102      if (!options.animation) {
103        this.$lightbox
104          .css({ display: 'none', transition: 'none' })
105          .removeClass('fluxus-lightbox--transitioned')
106          .removeClass('fluxus-lightbox--slide-in');
107        this.$lightbox.width();
108        $html.removeClass('fluxus-lightbox-visible');
109        this.$lightbox.css({ display: '', transition: '' });
110        return;
111      }
112
113      onTransitionEnd(
114        this.$lightbox,
115        function () {
116          $html.removeClass('fluxus-lightbox-visible');
117          var $iframe = this.$media.find('iframe');
118          if ($iframe.length) {
119            this.$media.html('');
120          }
121          this.options.onHide && this.options.onHide.call(this);
122        }.bind(this),
123        { propertyName: 'transform' }
124      );
125
126      this.$lightbox
127        .removeClass('fluxus-lightbox--transitioned')
128        .removeClass('fluxus-lightbox--slide-in');
129    },
130
131    _startCloseAnimation: function (callback) {
132      onTransitionEnd(this.$lightbox, callback.bind(this), {
133        propertyName: 'transform'
134      });
135
136      this.$lightbox
137        .removeClass('fluxus-lightbox--transitioned')
138        .removeClass('fluxus-lightbox--slide-in');
139    },
140
141    idleOn: function () {
142      this.isIdle = true;
143      this.$lightbox.addClass('fluxus-lightbox--idle');
144    },
145
146    idleOff: function () {
147      this.isIdle = false;
148      this.$lightbox.removeClass('fluxus-lightbox--idle');
149    },
150
151    onMouseMove: function (event) {
152      this.stickTipToCursorOnMouseMove(event);
153      this.triggerActivityEvent();
154    },
155
156    triggerActivityEvent: function () {
157      this.idleOff();
158      clearTimeout(this._idleTimeout);
159      this._idleTimeout = setTimeout(
160        this.idleOn.bind(this),
161        this.options.idleTimeout
162      );
163    },
164
165    stickTipToCursorOnMouseMove: function (event) {
166      this._mousePosition = {
167        x: event.pageX,
168        y: event.pageY
169      };
170      this.stickTipToCursor();
171    },
172
173    stickTipToCursor: function () {
174      var x = this._mousePosition.x - this._scrollPosition.left,
175        y = this._mousePosition.y - this._scrollPosition.top;
176
177      var middlePoint = this._windowSize.width / 2;
178      var rotate;
179      if (x > middlePoint) {
180        rotate = ' rotate(0)';
181      } else {
182        rotate = ' rotate(180deg)';
183      }
184
185      this.$tip.css('transform', 'translate3d(' + x + 'px, ' + y + 'px, 0)');
186      this.$tipIcon.css('transform', rotate);
187    },
188
189    handleKeyboardEvent: function (event) {
190      if (39 == event.which) {
191        event.preventDefault();
192        this.next();
193      }
194
195      if (37 == event.which) {
196        event.preventDefault();
197        this.previous();
198      }
199
200      if (27 == event.which) {
201        event.preventDefault();
202
203        if (this._isModeFullWidth) {
204          this.toggleFullWidthMode(false);
205        } else {
206          this.close();
207        }
208      }
209    },
210
211    loadingStarted: function () {
212      this._loadingMessageTimeout = setTimeout(
213        function () {
214          this.$lightbox.addClass('fluxus-lightbox--long-loading');
215        }.bind(this),
216        this.options.loadingIndicatorDelay
217      );
218    },
219
220    loadingFinished: function () {
221      clearTimeout(this._loadingMessageTimeout);
222      this.$lightbox.removeClass('fluxus-lightbox--long-loading');
223    },
224
225    showIframe: function ($iframe, options) {
226      this.$lightbox.addClass('fluxus-lightbox--iframe');
227      this.$lightbox.removeClass('fluxus-lightbox--error');
228
229      // Show loading.
230      this.$media.removeClass('fluxus-lightbox__media--loaded');
231      this.loadingStarted();
232
233      // Mark active element.
234      this.$links.removeClass('lightbox-active');
235      $iframe.addClass('lightbox-active');
236
237      this.$iframe = $iframe.clone().attr('style', '');
238
239      this.$iframe
240        .on(
241          'error',
242          function () {
243            this.loadingFinished();
244            this.$lightbox.addClass('fluxus-lightbox--error');
245          }.bind(this)
246        )
247        .on(
248          'load',
249          function () {
250            // put iframe in position
251            this.resizeIframe();
252            this.$media.addClass('fluxus-lightbox__media--loaded');
253
254            $window
255              .off('resize.media.fluxus-lightbox')
256              .on(
257                'resize.media.fluxus-lightbox',
258                debounce(this.resizeIframe.bind(this))
259              );
260
261            this.loadingFinished();
262          }.bind(this)
263        );
264
265      // Append contents with iFrame
266      this.$iframeWrap = $('<div class="fluxus-lightbox__iframe-wrap" />')
267        .on('mousemove.fluxus-lightbox', this.onMouseMoveOverIframe.bind(this))
268        .prepend(this.$iframe);
269
270      this.$media.html(this.$iframeWrap);
271    },
272
273    /**
274     * iFrame captures mouseover events. As a workaround we create an overlay,
275     * it captures mouse move and hides itself and navigation tip. When mouse
276     * event is captured again we show the overlay.
277     *
278     * Timeout is needed because window.mousemove event will fire straight away.
279     */
280    onMouseMoveOverIframe: function () {
281      this.$lightbox.addClass('fluxus-lightbox--mouse-over-iframe');
282      setTimeout(
283        function () {
284          $window.one(
285            'mousemove.iframe.fluxus-lightbox',
286            this.onMouseOutOfIframe.bind(this)
287          );
288        }.bind(this),
289        50
290      );
291    },
292
293    onMouseOutOfIframe: function () {
294      this.$lightbox.removeClass('fluxus-lightbox--mouse-over-iframe');
295    },
296
297    resizeIframe: function () {
298      var CONTROLS_HEIGHT = 160,
299        DEFAULT_RATIO = 16 / 9,
300        parentWidth = this._windowSize.width - this.options.margin * 2,
301        parentHeight =
302          this._windowSize.height - CONTROLS_HEIGHT - this.options.margin * 2,
303        height,
304        width;
305
306      var parentRatio =
307          parentHeight > 0 ? parentWidth / parentHeight : DEFAULT_RATIO,
308        iframeRatio;
309
310      if (this.$iframe.attr('width') && this.$iframe.attr('height')) {
311        iframeRatio = this.$iframe.attr('width') / this.$iframe.attr('height');
312      } else {
313        iframeRatio = DEFAULT_RATIO;
314      }
315
316      if (iframeRatio > parentRatio) {
317        // Fill axis: x
318        this.$iframeWrap.css({
319          width: parentWidth,
320          height: parentWidth / iframeRatio
321        });
322      } else {
323        this.$iframeWrap.css({
324          width: parentHeight * iframeRatio,
325          height: parentHeight
326        });
327      }
328    },
329
330    showImage: function ($image, options) {
331      options = $.extend(
332        {
333          delay: FADE_OUT_DURATION
334        },
335        options
336      );
337
338      this.$lightbox.removeClass(
339        'fluxus-lightbox--iframe fluxus-lightbox--error'
340      );
341      this.$media.removeClass('fluxus-lightbox__media--loaded');
342      this.$links.removeClass('lightbox-active');
343      $image.addClass('lightbox-active');
344
345      this._userTransform = { x: 0, y: 0, scale: 1 };
346      this.loadingStarted();
347
348      var srcset = $image.attr('srcset');
349      if (srcset) {
350        srcset = srcset.split(',');
351        srcset = srcset[srcset.length - 1]; // For now simply assume last src to have highest resolution.
352        srcset = srcset.split(' ');
353        this.hdSource = srcset[0];
354      } else {
355        this.hdSource = null;
356      }
357
358      var source =
359        $image.attr('href') || $image.attr('src') || $image.data('src');
360      var fadeOutAnimation = createTimeoutPromise(options.delay);
361
362      return $.when(this.loadImage(source), fadeOutAnimation)
363        .then(
364          function (image) {
365            this.loadingFinished();
366            $window
367              .off('resize.media.fluxus-lightbox')
368              .on(
369                'resize.media.fluxus-lightbox',
370                debounce(this.resizeImage.bind(this))
371              );
372
373            this.$image = $('<img />').attr({
374              src: image.src,
375              width: image.width,
376              height: image.height
377            });
378            this.resizeImage();
379            this.$media.html(this.$image);
380            this.$image[0].offsetHeight;
381            this.$media.addClass('fluxus-lightbox__media--loaded');
382
383            // Load HD
384            var disableHd = TOUCH_DEVICE;
385            if (!disableHd && this.hdSource) {
386              // Cancel previous HD load.
387              this.hdImage.src =
388                'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAI=;';
389              this.hdImage.onload = function () {
390                this.hdImageLoaded(new String(this.hdSource));
391              }.bind(this);
392              this.hdImage.src = this.hdSource;
393            }
394          }.bind(this)
395        )
396        .fail(
397          function () {
398            this.loadingFinished();
399            this.$lightbox.addClass('fluxus-lightbox--error');
400          }.bind(this)
401        );
402    },
403
404    hdImageLoaded: function (originalSource) {
405      // Only update image if it has not changed.
406      if (this.hdSource == originalSource) {
407        this.$image.attr('src', this.hdImage.src);
408      }
409    },
410
411    /**
412     * Calculates images transform and scale values.
413     * We only operate on translate values for position.
414     * Combining other properties appeared problematic when trying to transition them.
415     */
416    resizeImage: function () {
417      var width = this.$image.attr('width'),
418        height = this.$image.attr('height'),
419        ratio = width / height,
420        parentHeight = this.$media.height(),
421        parentWidth = this.$media.width(),
422        parentRatio = parentWidth / parentHeight,
423        data = {
424          height: parseInt(height, 10),
425          width: parseInt(width, 10),
426          // Which axis fills whole area
427          fillAxis: ratio > parentRatio ? 'x' : 'y'
428        },
429        scaledWidth,
430        scaledHeight,
431        invertedScale;
432
433      if (!this._isModeFullWidth) {
434        // Fit mode has margins. Remove margins from available height.
435        parentWidth = parentWidth - this.options.margin * 2;
436        parentHeight = parentHeight - this.options.margin * 2;
437      }
438
439      if (data.fillAxis == 'x') {
440        // Full width, center vertically.
441        if (this._isModeFullWidth) {
442          data.scale = parentHeight / height;
443          data.x = (width * data.scale - parentWidth) / -2;
444          data.y = 0;
445        } else {
446          data.scale = parentWidth / width;
447        }
448      } else {
449        // Full height, center horizontally.
450        if (this._isModeFullWidth) {
451          data.scale = parentWidth / width;
452          data.x = 0;
453          data.y = (height * data.scale - parentHeight) / -2;
454        } else {
455          data.scale = parentHeight / height;
456        }
457      }
458
459      /**
460       * "(parentWidth - scaledWidth) / 2" = move by half the difference between image and container.
461       * "+ this.options.margin" = add margin.
462       */
463      if (!this._isModeFullWidth) {
464        scaledWidth = width * data.scale;
465        scaledHeight = height * data.scale;
466        data.x = (parentWidth - scaledWidth) / 2 + this.options.margin;
467        data.y = (parentHeight - scaledHeight) / 2 + this.options.margin;
468      }
469
470      this.setImageStyle(data);
471    },
472
473    setImageStyle: function (sugarStyle) {
474      this._imageStyle = $.extend({}, sugarStyle);
475      this.$image.scss(sugarStyle);
476    },
477
478    loadImage: function (src) {
479      var dfd = new $.Deferred(),
480        image = new Image();
481
482      image.onload = function () {
483        dfd.resolve(image);
484      }.bind(this);
485
486      image.onerror = dfd.reject;
487      image.src = src;
488
489      return dfd;
490    },
491
492    next: function (options) {
493      var activeIndex = this.$links.index(
494          this.$links.filter('.lightbox-active')
495        ),
496        newIndex = 0;
497
498      if (activeIndex != -1 && activeIndex + 1 != this.$links.length) {
499        newIndex = activeIndex + 1;
500      }
501
502      return this.show(this.$links.eq(newIndex), options);
503    },
504
505    previous: function (options) {
506      var activeIndex = this.$links.index(
507          this.$links.filter('.lightbox-active')
508        ),
509        newIndex = this.$links.length - 1;
510
511      if (activeIndex !== -1 && activeIndex !== 0) {
512        newIndex = activeIndex - 1;
513      }
514
515      return this.show(this.$links.eq(newIndex), options);
516    },
517
518    show: function ($item, options) {
519      var method = $item.is('iframe') ? 'showIframe' : 'showImage';
520      return this[method]($item, options);
521    },
522
523    bindOnClickListener: function () {
524      this.$links.filter(':not(iframe)').on(
525        'click.fluxus-lightbox',
526        function (event) {
527          event.preventDefault();
528          if (this.options.onClick && this.options.onClick(event) === false) {
529            return;
530          }
531          this.showImage($(event.currentTarget));
532          this.open(event, this.options.onShow);
533        }.bind(this)
534      );
535    },
536
537    render: function () {
538      var that = this,
539        template =
540          '' +
541          '<div class="fluxus-lightbox">' +
542          '<div class="fluxus-lightbox__content">' +
543          '<div class="fluxus-lightbox__media"></div>' +
544          '</div>' +
545          '<div class="fluxus-lightbox__tip"><div class="fluxus-lightbox__tip-icon"></div></div>' +
546          '<div class="fluxus-lightbox__top-right">' +
547          '<span class="fluxus-lightbox__btn fluxus-lightbox__btn--close icon-close-slim"></span>' +
548          '</div>' +
549          '<div class="fluxus-lightbox__bottom-right">' +
550          '<span class="fluxus-lightbox__btn fluxus-lightbox__btn--resize">' +
551          '<span class="icon-zoom-in-slim"></span>' +
552          '<span class="icon-zoom-out-slim"></span>' +
553          '</span>' +
554          '<span class="fluxus-lightbox__btn fluxus-lightbox__btn--previous icon-previous-slim-arrow"></span>' +
555          '<span class="fluxus-lightbox__btn fluxus-lightbox__btn--next icon-next-slim-arrow"></span>' +
556          '</div>' +
557          '<div class="fluxus-lightbox__status"></div>' +
558          '<div class="fluxus-lightbox__error">' +
559          this.options.error +
560          '</div>' +
561          '</div>';
562
563      this.$lightbox = $(template);
564      if (this.$links.length == 1) {
565        this.$lightbox.addClass('fluxus-lightbox--single');
566      }
567      this.$content = this.$lightbox.find('.fluxus-lightbox__content');
568      this.$media = this.$lightbox.find('.fluxus-lightbox__media');
569      this.$tip = this.$lightbox.find('.fluxus-lightbox__tip');
570      this.$tipIcon = this.$lightbox.find('.fluxus-lightbox__tip-icon');
571
572      this.$lightbox
573        .find('.fluxus-lightbox__top-right, .fluxus-lightbox__bottom-right')
574        .on('mouseenter mouseleave', this.onMouseHoverControls.bind(this));
575
576      /**
577       * Assign DOM events
578       */
579      this.$next = this.$lightbox
580        .on('click', '.fluxus-lightbox__btn--next', function (e) {
581          that.next.call(that);
582          e && e.preventDefault();
583        })
584        .find('.fluxus-lightbox__btn--next');
585
586      this.$previous = this.$lightbox
587        .on('click', '.fluxus-lightbox__btn--previous', function (e) {
588          that.previous.call(that);
589          e && e.preventDefault();
590        })
591        .find('.fluxus-lightbox__btn--previous');
592
593      this.$close = this.$lightbox
594        .on('click', '.fluxus-lightbox__btn--close', function (e) {
595          that.close.call(that);
596          e && e.preventDefault();
597        })
598        .find('.fluxus-lightbox__btn--close');
599
600      this.$resize = this.$lightbox
601        .on('click', '.fluxus-lightbox__btn--resize', function (event) {
602          event && event.preventDefault();
603          that.toggleFullWidthMode(!that._isModeFullWidth);
604        })
605        .find('.fluxus-lightbox__btn--resize');
606      this.$lightbox.toggleClass(
607        'fluxus-lightbox--full-width',
608        this.options.mode === 'fill'
609      );
610
611      if (this.$links.length > 1) {
612        this.$content.on('click', this.onAreaClick.bind(this));
613      }
614
615      $('body').append(this.$lightbox);
616    },
617
618    toggleFullWidthMode: function (on) {
619      this._isModeFullWidth = on;
620      // resizeImage calculates width that depends on .fluxus-lightbox--full-width
621      // therefore order of execution is important.
622      this.$lightbox.toggleClass('fluxus-lightbox--full-width', on);
623      this.resizeImage();
624      if (on) {
625        $window
626          .off('mousemove.pan.fluxus-lightbox')
627          .on(
628            'mousemove.pan.fluxus-lightbox',
629            this.panImageOnMouseMove.bind(this)
630          );
631      } else {
632        $window.off('mousemove.pan.fluxus-lightbox');
633      }
634    },
635
636    onAreaClick: function (event) {
637      if (!TOUCH_DEVICE) {
638        var x = event.pageX - this._scrollPosition.left,
639          y = event.pageY - this._scrollPosition.top,
640          middlePoint = this._windowSize.width / 2;
641
642        if (x > middlePoint) {
643          this.next();
644        } else {
645          this.previous();
646        }
647      }
648    },
649
650    panImageOnMouseMove: function (event) {
651      var previousImageStyle = this._imageStyle;
652
653      if (previousImageStyle.fillAxis == 'x') {
654        var offscreenWidth =
655            previousImageStyle.width * previousImageStyle.scale -
656            this._windowSize.width,
657          panRatio = offscreenWidth / this._windowSize.width;
658
659        previousImageStyle.x =
660          -(event.pageX - this.$media.offset().left) * panRatio;
661      } else {
662        var offscreenHeight =
663            previousImageStyle.height * previousImageStyle.scale -
664            this._windowSize.height,
665          panRatio = offscreenHeight / this._windowSize.height;
666
667        previousImageStyle.y =
668          -(event.pageY - this.$media.offset().top) * panRatio;
669      }
670
671      this.setImageStyle(previousImageStyle);
672    },
673
674    onMouseHoverControls: function (event) {
675      this.$lightbox.toggleClass(
676        'fluxus-lightbox--over-controls',
677        event.type == 'mouseenter'
678      );
679    },
680
681    storeWindowSize: function () {
682      this._windowSize = {
683        width: $window.width(),
684        height: $window.height()
685      };
686    },
687
688    setupGestures: function () {
689      this.$lightbox.addClass('fluxus-lightbox--touch');
690      var enableIfNotPinchingAndNotZoomedIn = function () {
691        return (
692          !this._isPinching &&
693          (!this._userTransform || this._userTransform.scale == 1)
694        );
695      }.bind(this);
696
697      var horizontalPan = new Hammer.Pan({
698        event: 'horizontalPan',
699        direction: Hammer.DIRECTION_HORIZONTAL,
700        enable: enableIfNotPinchingAndNotZoomedIn
701      });
702      var verticalPan = new Hammer.Pan({
703        event: 'verticalPan',
704        direction: Hammer.DIRECTION_VERTICAL,
705        enable: enableIfNotPinchingAndNotZoomedIn
706      });
707      var pan = new Hammer.Pan({
708        enable: function () {
709          return (
710            !this._isPinching &&
711            this._userTransform &&
712            this._userTransform.scale > 1
713          );
714        }.bind(this)
715      });
716      var pinch = new Hammer.Pinch();
717      var singleTap = new Hammer.Tap({ event: 'singleTap' });
718      var doubleTap = new Hammer.Tap({ event: 'doubleTap', taps: 2 });
719
720      var manager = new Hammer.Manager(this.$lightbox[0]);
721      manager.add([
722        horizontalPan,
723        verticalPan,
724        pan,
725        pinch,
726        doubleTap,
727        singleTap
728      ]);
729
730      // See http://hammerjs.github.io/require-failure/
731      doubleTap.recognizeWith(singleTap);
732      singleTap.requireFailure(doubleTap);
733
734      manager.on('horizontalPanstart', this.onHorizontalPanStart.bind(this));
735      manager.on('horizontalPanend', this.onHorizontalPanEnd.bind(this));
736      manager.on('horizontalPan', this.onHorizontalPan.bind(this));
737
738      manager.on('verticalPanstart', this.onVerticalPanStart.bind(this));
739      manager.on('verticalPanend', this.onVerticalPanEnd.bind(this));
740      manager.on('verticalPan', this.onVerticalPan.bind(this));
741
742      manager.on('pan', this.onPan.bind(this));
743      manager.on('panend', this.onPanEnd.bind(this));
744
745      manager.on('pinchstart', this.onPinchStart.bind(this));
746      manager.on('pinch pinchend pinchstart', this.onPinching.bind(this));
747      manager.on('pinchend', this.onPinchEnd.bind(this));
748
749      manager.on('singleTap', this.onSingleTap.bind(this));
750      manager.on('doubleTap', this.onDoubleTap.bind(this));
751    },
752
753    onVerticalPanStart: function () {
754      this.$content.css('transition', 'none');
755    },
756
757    onVerticalPanEnd: function (event) {
758      this.$lightbox.addClass('fluxus-lightbox--bounce-back-transition');
759      this.$lightbox.css({ transition: '' });
760      this.$lightbox.width();
761
762      var MIN_DISTANCE = 200;
763
764      // Come back to place.
765      if (event.distance < MIN_DISTANCE) {
766        onTransitionEnd(
767          this.$lightbox,
768          function () {
769            this.$lightbox.removeClass(
770              'fluxus-lightbox--bounce-back-transition'
771            );
772          }.bind(this)
773        );
774        this.$lightbox.scss({ y: 0, opacity: 1 });
775        return;
776      }
777
778      var direction = event.deltaY > 0 ? 1 : -1;
779      var endY = screen.height * direction;
780      onTransitionEnd(
781        this.$lightbox,
782        function () {
783          this.close({ animation: false });
784          this.$lightbox
785            .scss({ x: '', y: '' })
786            .removeClass('fluxus-lightbox--bounce-back-transition');
787        }.bind(this),
788        { propertyName: 'transform' }
789      );
790      this.$lightbox.scss({ y: endY });
791    },
792
793    onVerticalPan: function (event) {
794      this.$lightbox.scss({
795        y: event.deltaY,
796        transition: 'none'
797      });
798    },
799
800    animateUserTransform: function (destination) {
801      var delta = {
802        x: destination.x - this._userTransform.x,
803        y: destination.y - this._userTransform.y,
804        scale: destination.scale - this._userTransform.scale
805      };
806
807      if (!delta.x && !delta.y && !delta.scale) {
808        return;
809      }
810
811      var initialTransform = $.extend({}, this._userTransform);
812      var time = {
813        start: performance.now(),
814        total: 300
815      };
816      var animate = function (now) {
817        time.elapsed = now - time.start;
818        var progress = time.elapsed / time.total;
819        progress = progress > 1 ? 1 : progress;
820
821        this._userTransform.scale =
822          initialTransform.scale + progress * delta.scale;
823        this._userTransform.x = initialTransform.x + progress * delta.x;
824        this._userTransform.y = initialTransform.y + progress * delta.y;
825        this.$media.scss(this._userTransform);
826
827        progress < 1 && requestAnimationFrame(animate);
828      }.bind(this);
829
830      requestAnimationFrame(animate);
831    },
832
833    startTransformConstraintAnimation: function () {
834      // Scale
835      var MAX_SCALE = 3;
836      var MIN_SCALE = 1;
837      var normalizedTransform = $.extend({}, this._userTransform);
838
839      if (normalizedTransform.scale > MAX_SCALE) {
840        normalizedTransform.scale = MAX_SCALE;
841      } else if (normalizedTransform.scale < MIN_SCALE) {
842        normalizedTransform.scale = MIN_SCALE;
843      }
844
845      // X
846      var mediaWidth = this._imageStyle.width * this._imageStyle.scale;
847      // When scale is 1 we use fit mode with margin, thus mediaWidth
848      // when scale is zoomed in we use whole viewport what is this.$media.width()
849      var viewportWidth =
850        normalizedTransform.scale == 1 ? mediaWidth : this.$media.width();
851      var scaledMediaWidth = mediaWidth * normalizedTransform.scale;
852      var boundingX = Math.abs(scaledMediaWidth - viewportWidth) / 2;
853
854      if (normalizedTransform.x < boundingX * -1) {
855        normalizedTransform.x = boundingX * -1;
856      } else if (normalizedTransform.x > boundingX) {
857        normalizedTransform.x = boundingX;
858      }
859
860      // Y
861      var mediaHeight = this._imageStyle.height * this._imageStyle.scale;
862      // When scale is 1 we use fit mode with margin, thus mediaHeight
863      // when scale is zoomed in we use whole viewport what is this.$media.height()
864      var viewportHeight =
865        normalizedTransform.scale == 1 ? mediaHeight : this.$media.height();
866      var scaledMediaHeight = mediaHeight * normalizedTransform.scale;
867      var boundingY = Math.abs(scaledMediaHeight - viewportHeight) / 2;
868
869      if (normalizedTransform.y < boundingY * -1) {
870        normalizedTransform.y = boundingY * -1;
871      } else if (normalizedTransform.y > boundingY) {
872        normalizedTransform.y = boundingY;
873      }
874
875      this.animateUserTransform(normalizedTransform);
876    },
877
878    onHorizontalPanStart: function (event) {
879      this.$content.css('transition', 'none');
880    },
881
882    onHorizontalPanEnd: function (event) {
883      var $content = this.$content;
884      var MIN_PAN_DISTANCE = 100;
885      var limitAnimationDuration = function (duration) {
886        if (duration < 100) {
887          return 100;
888        }
889
890        if (duration > 600) {
891          return 600;
892        }
893
894        return duration;
895      };
896      var pxPerMillisecond = Math.abs(event.overallVelocityX);
897      var LINEAR_TRANSITION = 'transform %dms linear';
898      var EASE_OUT_TRANSITION =
899        'transform %dms cubic-bezier(.165, .84, .44, 1)';
900
901      // Come back to place.
902      if (event.distance < MIN_PAN_DISTANCE) {
903        var comebackDuration = Math.abs(event.distance) / pxPerMillisecond;
904        comebackDuration = limitAnimationDuration(comebackDuration);
905        $content.css({
906          transition: EASE_OUT_TRANSITION.replace('%d', comebackDuration)
907        });
908        $content.scss({ x: 0 });
909        return;
910      }
911
912      var distanceToGo = $content.width() - Math.abs(event.distance);
913      var swipeoutDuration = distanceToGo / pxPerMillisecond;
914      swipeoutDuration = limitAnimationDuration(swipeoutDuration);
915      $content.css(
916        'transition',
917        LINEAR_TRANSITION.replace('%d', swipeoutDuration)
918      );
919
920      var panDirection = event.deltaX > 0 ? 'previous' : 'next';
921      var panDirectionModifier = panDirection == 'previous' ? 1 : -1;
922
923      $content.scss({ x: toCssPercent(100 * panDirectionModifier) });
924
925      setTimeout(
926        function () {
927          this[panDirection]({ delay: 0 }).then(function () {
928            $content.css('transition', 'none');
929            $content
930              .scss({ x: toCssPercent(-100 * panDirectionModifier) })
931              .width();
932            var duration = $content.width() / pxPerMillisecond;
933            duration = limitAnimationDuration(duration);
934            $content.css({
935              transition: EASE_OUT_TRANSITION.replace('%d', duration)
936            });
937            $content.scss({ x: 0 });
938          });
939        }.bind(this),
940        swipeoutDuration
941      );
942    },
943
944    onHorizontalPan: function (event) {
945      this.$content.scss({ x: event.deltaX });
946    },
947
948    onPan: function (event) {
949      var currentTransform = $.extend({}, this._userTransform, {
950        x: this._userTransform.x + event.deltaX,
951        y: this._userTransform.y + event.deltaY
952      });
953      this.$media.scss(currentTransform);
954    },
955
956    onPanEnd: function (event) {
957      this._userTransform.x = this._userTransform.x + event.deltaX;
958      this._userTransform.y = this._userTransform.y + event.deltaY;
959      this.startTransformConstraintAnimation();
960    },
961
962    onSingleTap: function (event) {
963      this.$lightbox.addClass('fluxus-lightbox--after-tap');
964      if (this.isIdle) {
965        this.triggerActivityEvent();
966      } else {
967        this.idleOn();
968      }
969      setTimeout(
970        function () {
971          this.$lightbox.removeClass('fluxus-lightbox--after-tap');
972        }.bind(this),
973        1000
974      );
975    },
976
977    onDoubleTap: function () {
978      var zoomIn = this._userTransform.scale == 1;
979      var transform = $.extend({}, this._userTransform, {
980        scale: this._userTransform.scale == 1 ? 2 : 1
981      });
982      if (!zoomIn) {
983        transform.x = 0;
984        transform.y = 0;
985      }
986      this.animateUserTransform(transform);
987    },
988
989    onPinchStart: function (event) {
990      this._isPinching = true;
991    },
992
993    onPinchEnd: function (event) {
994      this._userTransform.scale = this._userTransform.scale * event.scale;
995      this.startTransformConstraintAnimation();
996      setTimeout(
997        function () {
998          this._isPinching = false;
999        }.bind(this),
1000        300
1001      );
1002    },
1003
1004    onPinching: function (event) {
1005      event.preventDefault();
1006      var currentTransform = $.extend({}, this._userTransform, {
1007        scale: this._userTransform.scale * event.scale
1008      });
1009      this.$media.scss(currentTransform);
1010    }
1011  };
1012
1013  $.fn.fluxusLightbox = function (options) {
1014    return this.data('fluxus-lightbox', new $.FluxusLightbox(this, options));
1015  };
1016
1017  /**
1018   * Utils
1019   */
1020  var cssFromSugar = function (sugar) {
1021    sugar = $.extend({}, sugar);
1022
1023    var transform = [],
1024      numberWithotSuffix = /^\-?\d+(\.\d+)?$/;
1025
1026    if (sugar.x == '' && sugar.y == '') {
1027      // Explicitly remove transform.
1028    } else if (sugar.x !== void 0 || sugar.y !== void 0) {
1029      sugar.x = sugar.x ? sugar.x : 0;
1030      sugar.x = String(sugar.x).match(numberWithotSuffix)
1031        ? sugar.x + 'px'
1032        : sugar.x;
1033      sugar.y = sugar.y ? sugar.y : 0;
1034      sugar.y = String(sugar.y).match(numberWithotSuffix)
1035        ? sugar.y + 'px'
1036        : sugar.y;
1037
1038      transform.push('translate3d(' + sugar.x + ', ' + sugar.y + ', 0)');
1039    } else {
1040      transform.push('translate3d(0, 0, 0)');
1041    }
1042
1043    if (sugar.scale !== void 0) {
1044      transform.push('scale(' + sugar.scale + ')');
1045    }
1046
1047    sugar['transform'] = transform.join(' ');
1048    sugar['-webkit-transform'] = sugar['transform'];
1049
1050    // Remove blacklisted props.
1051    delete sugar.x;
1052    delete sugar.y;
1053    delete sugar.scale;
1054    delete sugar.fillAxis;
1055    return sugar;
1056  };
1057
1058  var createTimeoutPromise = function (delay) {
1059    var dfd = new $.Deferred();
1060    setTimeout(dfd.resolve.bind(this), delay);
1061    return dfd;
1062  };
1063
1064  var toCssPercent = function (numer) {
1065    return String(numer) + '%';
1066  };
1067
1068  /**
1069   * Utility for tracking transitionend event.
1070   *   once: Boolean - will unbind after first event.
1071   *   propertyName: String - will trigger only on specific properties.
1072   */
1073  var onTransitionEnd = (function () {
1074    var index = 0;
1075    var uuid = function () {
1076      return 'lightbox-event-' + index++;
1077    };
1078
1079    return function ($el, callback, options) {
1080      options = $.extend(
1081        {},
1082        {
1083          once: true,
1084          propertyName: undefined,
1085          captureBubbledEvents: false
1086        },
1087        options
1088      );
1089
1090      var namespace = uuid();
1091      var events =
1092        'transitionend.' + namespace + ' webkitTransitionEnd.' + namespace;
1093
1094      $el.on(events, function (event) {
1095        var matchesPropertyName =
1096          options.propertyName &&
1097          event.originalEvent.propertyName == options.propertyName;
1098
1099        var matchesTarget =
1100          !options.captureBubbledEvents && event.originalEvent.target == $el[0];
1101
1102        if (matchesPropertyName && matchesTarget) {
1103          callback.call(this, event.originalEvent.srcElement);
1104          options && options.once && $el.off(events);
1105        }
1106      });
1107    };
1108  })();
1109
1110  $.fn.scss = function (sugar) {
1111    return this.css(cssFromSugar(sugar));
1112  };
1113})(jQuery, _.debounce);

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.