PageSourceSearch

https://www.horumontaichi.com/shared/js/remodal.js

js horumontaichi.com collected 2026-10-08 01:52:36 UTC 20,079 bytes, 810 lines download raw bytes

vendor: 13,462 bytes, lines 1-552
1/*
2 *  Remodal - v1.1.1
3 *  Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking.
4 *  http://vodkabears.github.io/remodal/
5 *
6 *  Made by Ilya Makarov
7 *  Under MIT License
8 */
9
10!(function(root, factory) {
11  if (typeof define === 'function' && define.amd) {
12    define(['jquery'], function($) {
13      return factory(root, $);
14    });
15  } else if (typeof exports === 'object') {
16    factory(root, require('jquery'));
17  } else {
18    factory(root, root.jQuery || root.Zepto);
19  }
20})(this, function(global, $) {
21
22  'use strict';
23
24  /**
25   * Name of the plugin
26   * @private
27   * @const
28   * @type {String}
29   */
30  var PLUGIN_NAME = 'remodal';
31
32  /**
33   * Namespace for CSS and events
34   * @private
35   * @const
36   * @type {String}
37   */
38  var NAMESPACE = global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.NAMESPACE || PLUGIN_NAME;
39
40  /**
41   * Animationstart event with vendor prefixes
42   * @private
43   * @const
44   * @type {String}
45   */
46  var ANIMATIONSTART_EVENTS = $.map(
47    ['animationstart', 'webkitAnimationStart', 'MSAnimationStart', 'oAnimationStart'],
48
49    function(eventName) {
50      return eventName + '.' + NAMESPACE;
51    }
52
53  ).join(' ');
54
55  /**
56   * Animationend event with vendor prefixes
57   * @private
58   * @const
59   * @type {String}
60   */
61  var ANIMATIONEND_EVENTS = $.map(
62    ['animationend', 'webkitAnimationEnd', 'MSAnimationEnd', 'oAnimationEnd'],
63
64    function(eventName) {
65      return eventName + '.' + NAMESPACE;
66    }
67
68  ).join(' ');
69
70  /**
71   * Default settings
72   * @private
73   * @const
74   * @type {Object}
75   */
76  var DEFAULTS = $.extend({
77    hashTracking: true,
78    closeOnConfirm: true,
79    closeOnCancel: true,
80    closeOnEscape: true,
81    closeOnOutsideClick: true,
82    modifier: '',
83    appendTo: null
84  }, global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.DEFAULTS);
85
86  /**
87   * States of the Remodal
88   * @private
89   * @const
90   * @enum {String}
91   */
92  var STATES = {
93    CLOSING: 'closing',
94    CLOSED: 'closed',
95    OPENING: 'opening',
96    OPENED: 'opened'
97  };
98
99  /**
100   * Reasons of the state change.
101   * @private
102   * @const
103   * @enum {String}
104   */
105  var STATE_CHANGE_REASONS = {
106    CONFIRMATION: 'confirmation',
107    CANCELLATION: 'cancellation'
108  };
109
110  /**
111   * Is animation supported?
112   * @private
113   * @const
114   * @type {Boolean}
115   */
116  var IS_ANIMATION = (function() {
117    var style = document.createElement('div').style;
118
119    return style.animationName !== undefined ||
120      style.WebkitAnimationName !== undefined ||
121      style.MozAnimationName !== undefined ||
122      style.msAnimationName !== undefined ||
123      style.OAnimationName !== undefined;
124  })();
125
126  /**
127   * Is iOS?
128   * @private
129   * @const
130   * @type {Boolean}
131   */
132  var IS_IOS = /iPad|iPhone|iPod/.test(navigator.platform);
133
134  /**
135   * Current modal
136   * @private
137   * @type {Remodal}
138   */
139  var current;
140
141  /**
142   * Scrollbar position
143   * @private
144   * @type {Number}
145   */
146  var scrollTop;
147
148  /**
149   * Returns an animation duration
150   * @private
151   * @param {jQuery} $elem
152   * @returns {Number}
153   */
154  function getAnimationDuration($elem) {
155    if (
156      IS_ANIMATION &&
157      $elem.css('animation-name') === 'none' &&
158      $elem.css('-webkit-animation-name') === 'none' &&
159      $elem.css('-moz-animation-name') === 'none' &&
160      $elem.css('-o-animation-name') === 'none' &&
161      $elem.css('-ms-animation-name') === 'none'
162    ) {
163      return 0;
164    }
165
166    var duration = $elem.css('animation-duration') ||
167      $elem.css('-webkit-animation-duration') ||
168      $elem.css('-moz-animation-duration') ||
169      $elem.css('-o-animation-duration') ||
170      $elem.css('-ms-animation-duration') ||
171      '0s';
172
173    var delay = $elem.css('animation-delay') ||
174      $elem.css('-webkit-animation-delay') ||
175      $elem.css('-moz-animation-delay') ||
176      $elem.css('-o-animation-delay') ||
177      $elem.css('-ms-animation-delay') ||
178      '0s';
179
180    var iterationCount = $elem.css('animation-iteration-count') ||
181      $elem.css('-webkit-animation-iteration-count') ||
182      $elem.css('-moz-animation-iteration-count') ||
183      $elem.css('-o-animation-iteration-count') ||
184      $elem.css('-ms-animation-iteration-count') ||
185      '1';
186
187    var max;
188    var len;
189    var num;
190    var i;
191
192    duration = duration.split(', ');
193    delay = delay.split(', ');
194    iterationCount = iterationCount.split(', ');
195
196    // The 'duration' size is the same as the 'delay' size
197    for (i = 0, len = duration.length, max = Number.NEGATIVE_INFINITY; i < len; i++) {
198      num = parseFloat(duration[i]) * parseInt(iterationCount[i], 10) + parseFloat(delay[i]);
199
200      if (num > max) {
201        max = num;
202      }
203    }
204
205    return max;
206  }
207
208  /**
209   * Returns a scrollbar width
210   * @private
211   * @returns {Number}
212   */
213  function getScrollbarWidth() {
214    if ($(document).height() <= $(window).height()) {
215      return 0;
216    }
217
218    var outer = document.createElement('div');
219    var inner = document.createElement('div');
220    var widthNoScroll;
221    var widthWithScroll;
222
223    outer.style.visibility = 'hidden';
224    outer.style.width = '100px';
225    document.body.appendChild(outer);
226
227    widthNoScroll = outer.offsetWidth;
228
229    // Force scrollbars
230    outer.style.overflow = 'scroll';
231
232    // Add inner div
233    inner.style.width = '100%';
234    outer.appendChild(inner);
235
236    widthWithScroll = inner.offsetWidth;
237
238    // Remove divs
239    outer.parentNode.removeChild(outer);
240
241    return widthNoScroll - widthWithScroll;
242  }
243
244  /**
245   * Locks the screen
246   * @private
247   */
248  function lockScreen() {
249    if (IS_IOS) {
250      return;
251    }
252
253    var $html = $('html');
254    var lockedClass = namespacify('is-locked');
255    var paddingRight;
256    var $body;
257
258    if (!$html.hasClass(lockedClass)) {
259      $body = $(document.body);
260
261      // Zepto does not support '-=', '+=' in the `css` method
262      paddingRight = parseInt($body.css('padding-right'), 10) + getScrollbarWidth();
263
264      $body.css('padding-right', paddingRight + 'px');
265      $html.addClass(lockedClass);
266    }
267  }
268
269  /**
270   * Unlocks the screen
271   * @private
272   */
273  function unlockScreen() {
274    if (IS_IOS) {
275      return;
276    }
277
278    var $html = $('html');
279    var lockedClass = namespacify('is-locked');
280    var paddingRight;
281    var $body;
282
283    if ($html.hasClass(lockedClass)) {
284      $body = $(document.body);
285
286      // Zepto does not support '-=', '+=' in the `css` method
287      paddingRight = parseInt($body.css('padding-right'), 10) - getScrollbarWidth();
288
289      $body.css('padding-right', paddingRight + 'px');
290      $html.removeClass(lockedClass);
291    }
292  }
293
294  /**
295   * Sets a state for an instance
296   * @private
297   * @param {Remodal} instance
298   * @param {STATES} state
299   * @param {Boolean} isSilent If true, Remodal does not trigger events
300   * @param {String} Reason of a state change.
301   */
302  function setState(instance, state, isSilent, reason) {
303
304    var newState = namespacify('is', state);
305    var allStates = [namespacify('is', STATES.CLOSING),
306                     namespacify('is', STATES.OPENING),
307                     namespacify('is', STATES.CLOSED),
308                     namespacify('is', STATES.OPENED)].join(' ');
309
310    instance.$bg
311      .removeClass(allStates)
312      .addClass(newState);
313
314    instance.$overlay
315      .removeClass(allStates)
316      .addClass(newState);
317
318    instance.$wrapper
319      .removeClass(allStates)
320      .addClass(newState);
321
322    instance.$modal
323      .removeClass(allStates)
324      .addClass(newState);
325
326    instance.state = state;
327    !isSilent && instance.$modal.trigger({
328      type: state,
329      reason: reason
330    }, [{ reason: reason }]);
331  }
332
333  /**
334   * Synchronizes with the animation
335   * @param {Function} doBeforeAnimation
336   * @param {Function} doAfterAnimation
337   * @param {Remodal} instance
338   */
339  function syncWithAnimation(doBeforeAnimation, doAfterAnimation, instance) {
340    var runningAnimationsCount = 0;
341
342    var handleAnimationStart = function(e) {
343      if (e.target !== this) {
344        return;
345      }
346
347      runningAnimationsCount++;
348    };
349
350    var handleAnimationEnd = function(e) {
351      if (e.target !== this) {
352        return;
353      }
354
355      if (--runningAnimationsCount === 0) {
356
357        // Remove event listeners
358        $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
359          instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
360        });
361
362        doAfterAnimation();
363      }
364    };
365
366    $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
367      instance[elemName]
368        .on(ANIMATIONSTART_EVENTS, handleAnimationStart)
369        .on(ANIMATIONEND_EVENTS, handleAnimationEnd);
370    });
371
372    doBeforeAnimation();
373
374    // If the animation is not supported by a browser or its duration is 0
375    if (
376      getAnimationDuration(instance.$bg) === 0 &&
377      getAnimationDuration(instance.$overlay) === 0 &&
378      getAnimationDuration(instance.$wrapper) === 0 &&
379      getAnimationDuration(instance.$modal) === 0
380    ) {
381
382      // Remove event listeners
383      $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
384        instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
385      });
386
387      doAfterAnimation();
388    }
389  }
390
391  /**
392   * Closes immediately
393   * @private
394   * @param {Remodal} instance
395   */
396  function halt(instance) {
397    if (instance.state === STATES.CLOSED) {
398      return;
399    }
400
401    $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
402      instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
403    });
404
405    instance.$bg.removeClass(instance.settings.modifier);
406    instance.$overlay.removeClass(instance.settings.modifier).hide();
407    instance.$wrapper.hide();
408    unlockScreen();
409    setState(instance, STATES.CLOSED, true);
410  }
411
412  /**
413   * Parses a string with options
414   * @private
415   * @param str
416   * @returns {Object}
417   */
418  function parseOptions(str) {
419    var obj = {};
420    var arr;
421    var len;
422    var val;
423    var i;
424
425    // Remove spaces before and after delimiters
426    str = str.replace(/\s*:\s*/g, ':').replace(/\s*,\s*/g, ',');
427
428    // Parse a string
429    arr = str.split(',');
430    for (i = 0, len = arr.length; i < len; i++) {
431      arr[i] = arr[i].split(':');
432      val = arr[i][1];
433
434      // Convert a string value if it is like a boolean
435      if (typeof val === 'string' || val instanceof String) {
436        val = val === 'true' || (val === 'false' ? false : val);
437      }
438
439      // Convert a string value if it is like a number
440      if (typeof val === 'string' || val instanceof String) {
441        val = !isNaN(val) ? +val : val;
442      }
443
444      obj[arr[i][0]] = val;
445    }
446
447    return obj;
448  }
449
450  /**
451   * Generates a string separated by dashes and prefixed with NAMESPACE
452   * @private
453   * @param {...String}
454   * @returns {String}
455   */
456  function namespacify() {
457    var result = NAMESPACE;
458
459    for (var i = 0; i < arguments.length; ++i) {
460      result += '-' + arguments[i];
461    }
462
463    return result;
464  }
465
466  /**
467   * Handles the hashchange event
468   * @private
469   * @listens hashchange
470   */
471  function handleHashChangeEvent() {
472    var id = location.hash.replace('#', '');
473    var instance;
474    var $elem;
475
476    if (!id) {
477
478      // Check if we have currently opened modal and animation was completed
479      if (current && current.state === STATES.OPENED && current.settings.hashTracking) {
480        current.close();
481      }
482    } else {
483
484      // Catch syntax error if your hash is bad
485      try {
486        $elem = $(
487          '[data-' + PLUGIN_NAME + '-id="' + id + '"]'
488        );
489      } catch (err) {}
490
491      if ($elem && $elem.length) {
492        instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
493
494        if (instance && instance.settings.hashTracking) {
495          instance.open();
496        }
497      }
498
499    }
500  }
501
502  /**
503   * Remodal constructor
504   * @constructor
505   * @param {jQuery} $modal
506   * @param {Object} options
507   */
508  function Remodal($modal, options) {
509    var $body = $(document.body);
510    var $appendTo = $body;
511    var remodal = this;
512
513    remodal.settings = $.extend({}, DEFAULTS, options);
514    remodal.index = $[PLUGIN_NAME].lookup.push(remodal) - 1;
515    remodal.state = STATES.CLOSED;
516
517    remodal.$overlay = $('.' + namespacify('overlay'));
518
519    if (remodal.settings.appendTo !== null && remodal.settings.appendTo.length) {
520      $appendTo = $(remodal.settings.appendTo);
521    }
522
523    if (!remodal.$overlay.length) {
524      remodal.$overlay = $('<div>').addClass(namespacify('overlay') + ' ' + namespacify('is', STATES.CLOSED)).hide();
525      $appendTo.append(remodal.$overlay);
526    }
527
528    remodal.$bg = $('.' + namespacify('bg')).addClass(namespacify('is', STATES.CLOSED));
529
530    remodal.$modal = $modal
531      .addClass(
532        NAMESPACE + ' ' +
533        namespacify('is-initialized') + ' ' +
534        remodal.settings.modifier + ' ' +
535        namespacify('is', STATES.CLOSED))
536      .attr('tabindex', '-1');
537
538    remodal.$wrapper = $('<div>')
539      .addClass(
540        namespacify('wrapper') + ' ' +
541        remodal.settings.modifier + ' ' +
542        namespacify('is', STATES.CLOSED))
543      .hide()
544      .append(remodal.$modal);
545    $appendTo.append(remodal.$wrapper);
546
547    // Add the event listener for the close button
548    remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="close"]', function(e) {
549      e.preventDefault();
550
551      remodal.close();
552    
vendor: 5,727 bytes, lines 552-773
552  $('[data-' + PLUGIN_NAME + '-target]').removeClass('active');
553      unlockBody();
554    });
555
556    // Add the event listener for the cancel button
557    remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="cancel"]', function(e) {
558      e.preventDefault();
559
560      remodal.$modal.trigger(STATE_CHANGE_REASONS.CANCELLATION);
561
562      if (remodal.settings.closeOnCancel) {
563        remodal.close(STATE_CHANGE_REASONS.CANCELLATION);
564        $('[data-' + PLUGIN_NAME + '-target]').removeClass('active');
565        unlockBody();
566      }
567    });
568
569    // Add the event listener for the confirm button
570    remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="confirm"]', function(e) {
571      e.preventDefault();
572
573      remodal.$modal.trigger(STATE_CHANGE_REASONS.CONFIRMATION);
574
575      if (remodal.settings.closeOnConfirm) {
576        remodal.close(STATE_CHANGE_REASONS.CONFIRMATION);
577        $('[data-' + PLUGIN_NAME + '-target]').removeClass('active');
578        unlockBody();
579      }
580    });
581
582    // Add the event listener for the overlay
583    remodal.$wrapper.on('click.' + NAMESPACE, function(e) {
584      var $target = $(e.target);
585
586      if (!$target.hasClass(namespacify('wrapper'))) {
587        return;
588      }
589
590      if (remodal.settings.closeOnOutsideClick) {
591        remodal.close();
592        $('[data-' + PLUGIN_NAME + '-target]').removeClass('active');
593        unlockBody();
594      }
595    });
596  }
597
598  /**
599   * Opens a modal window
600   * @public
601   */
602  Remodal.prototype.open = function() {
603    var remodal = this;
604    var id;
605
606    // Check if the animation was completed
607    if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING) {
608      return;
609    }
610
611    id = remodal.$modal.attr('data-' + PLUGIN_NAME + '-id');
612
613    if (id && remodal.settings.hashTracking) {
614      scrollTop = $(window).scrollTop();
615      location.hash = id;
616    }
617
618    if (current && current !== remodal) {
619      halt(current);
620    }
621
622    current = remodal;
623    lockScreen();
624    remodal.$bg.addClass(remodal.settings.modifier);
625    remodal.$overlay.addClass(remodal.settings.modifier).show();
626    remodal.$wrapper.show().scrollTop(0);
627    remodal.$modal.focus();
628
629    syncWithAnimation(
630      function() {
631        setState(remodal, STATES.OPENING);
632      },
633
634      function() {
635        setState(remodal, STATES.OPENED);
636      },
637
638      remodal);
639  };
640
641  /**
642   * Closes a modal window
643   * @public
644   * @param {String} reason
645   */
646  Remodal.prototype.close = function(reason) {
647    var remodal = this;
648
649    // Check if the animation was completed
650    if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING || remodal.state === STATES.CLOSED) {
651      return;
652    }
653
654    if (
655      remodal.settings.hashTracking &&
656      remodal.$modal.attr('data-' + PLUGIN_NAME + '-id') === location.hash.substr(1)
657    ) {
658      location.hash = '';
659      $(window).scrollTop(scrollTop);
660    }
661
662    syncWithAnimation(
663      function() {
664        setState(remodal, STATES.CLOSING, false, reason);
665      },
666
667      function() {
668        remodal.$bg.removeClass(remodal.settings.modifier);
669        remodal.$overlay.removeClass(remodal.settings.modifier).hide();
670        remodal.$wrapper.hide();
671        unlockScreen();
672
673        setState(remodal, STATES.CLOSED, false, reason);
674      },
675
676      remodal);
677  };
678
679  /**
680   * Returns a current state of a modal
681   * @public
682   * @returns {STATES}
683   */
684  Remodal.prototype.getState = function() {
685    return this.state;
686  };
687
688  /**
689   * Destroys a modal
690   * @public
691   */
692  Remodal.prototype.destroy = function() {
693    var lookup = $[PLUGIN_NAME].lookup;
694    var instanceCount;
695
696    halt(this);
697    this.$wrapper.remove();
698
699    delete lookup[this.index];
700    instanceCount = $.grep(lookup, function(instance) {
701      return !!instance;
702    }).length;
703
704    if (instanceCount === 0) {
705      this.$overlay.remove();
706      this.$bg.removeClass(
707        namespacify('is', STATES.CLOSING) + ' ' +
708        namespacify('is', STATES.OPENING) + ' ' +
709        namespacify('is', STATES.CLOSED) + ' ' +
710        namespacify('is', STATES.OPENED));
711    }
712  };
713
714  /**
715   * Special plugin object for instances
716   * @public
717   * @type {Object}
718   */
719  $[PLUGIN_NAME] = {
720    lookup: []
721  };
722
723  /**
724   * Plugin constructor
725   * @constructor
726   * @param {Object} options
727   * @returns {JQuery}
728   */
729  $.fn[PLUGIN_NAME] = function(opts) {
730    var instance;
731    var $elem;
732
733    this.each(function(index, elem) {
734      $elem = $(elem);
735
736      if ($elem.data(PLUGIN_NAME) == null) {
737        instance = new Remodal($elem, opts);
738        $elem.data(PLUGIN_NAME, instance.index);
739
740        if (
741          instance.settings.hashTracking &&
742          $elem.attr('data-' + PLUGIN_NAME + '-id') === location.hash.substr(1)
743        ) {
744          instance.open();
745        }
746      } else {
747        instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
748      }
749    });
750
751    return instance;
752  };
753
754  $(document).ready(function() {
755
756    // data-remodal-target opens a modal window with the special Id
757    $(document).on('click', '[data-' + PLUGIN_NAME + '-target]', function(e) {
758      e.preventDefault();
759
760      var elem = e.currentTarget;
761      var id = elem.getAttribute('data-' + PLUGIN_NAME + '-target');
762      var $target = $('[data-' + PLUGIN_NAME + '-id="' + id + '"]');
763      $(this).addClass('active');
764      lockBody();
765
766      $[PLUGIN_NAME].lookup[$target.data(PLUGIN_NAME)].open();
767    });
768
769    // Auto initialization of modal windows
770    // They should have the 'remodal' class attribute
771    // Also you can write the `data-remodal-options` attribute to pass params into the modal
772    $(document).find('.' + NAMESPACE).each(function(i, container) {
773      var $container = $(container);
774      var options = $container.data(PLUGIN_NAME + '-options');
775
776      if (!options) {
777        options = {};
778      } else if (typeof options === 'string' || options instanceof String) {
779        options = parseOptions(options);
780      }
781
782      $container[PLUGIN_NAME](options);
783    });
784
785    // Handles the keydown event
786    $(document).on('keydown.' + NAMESPACE, function(e) {
787      if (current && current.settings.closeOnEscape && current.state === STATES.OPENED && e.keyCode === 27) {
788        current.close();
789      }
790    });
791
792    // Handles the hashchange event
793    $(window).on('hashchange.' + NAMESPACE, handleHashChangeEvent);
794  });
795});
796
797
798function lockBody () {
799  offsetY = window.pageYOffset;
800  $('body').css({
801    position: 'fixed',
802    width: '100%',
803    'top': -offsetY + 'px'
804  });
805}
806
807function unlockBody () {
808  $('body').css('position', 'static');
809  $(window).scrollTop(offsetY);
810}

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.