PageSourceSearch

https://stanislaw.ru/sys/scripts/jquery/jquery-masonry.js

js stanislaw.ru collected 2026-10-01 12:37:11 UTC 14,498 bytes, 491 lines download raw bytes

1/**
2 * jQuery Masonry v2.0.111015
3 * A dynamic layout plugin for jQuery
4 * The flip-side of CSS Floats
5 * http://masonry.desandro.com
6 *
7 * Licensed under the MIT license.
8 * Copyright 2011 David DeSandro
9 */
10 
11(function(window, $, undefined){
12
13  /*
14   * smartresize: debounced resize event for jQuery
15   *
16   * latest version and complete README available on Github:
17   * https://github.com/louisremi/jquery.smartresize.js
18   *
19   * Copyright 2011 @louis_remi
20   * Licensed under the MIT license.
21   */
22
23  var $event = $.event,
24      resizeTimeout;
25
26  $event.special.smartresize = {
27    setup: function() {
28      $(this).bind("resize", $event.special.smartresize.handler);
29    },
30    teardown: function() {
31      $(this).unbind("resize", $event.special.smartresize.handler);
32    },
33    handler: function(event, execAsap) {
34      // Save the context
35      var context = this,
36          args = arguments;
37
38      // set correct event type
39      event.type = "smartresize";
40
41      if (resizeTimeout) { clearTimeout(resizeTimeout); }
42      resizeTimeout = setTimeout(function() {
43        jQuery.event.handle.apply(context, args);
44      }, execAsap === "execAsap"? 0 : 100);
45    }
46  };
47
48  $.fn.smartresize = function(fn) {
49    return fn ? this.bind("smartresize", fn) : this.trigger("smartresize", ["execAsap"]);
50  };
51
52
53
54// ========================= Masonry ===============================
55
56
57  // our "Widget" object constructor
58  $.Mason = function(options, element){
59    this.element = $(element);
60
61    this._create(options);
62    this._init();
63  };
64  
65  // styles of container element we want to keep track of
66  var masonryContainerStyles = [ 'position', 'height' ];
67  
68  $.Mason.settings = {
69    isResizable: true,
70    isAnimated: false,
71    animationOptions: {
72      queue: false,
73      duration: 500
74    },
75    gutterWidth: 0,
76    isRTL: false,
77    isFitWidth: false
78  };
79
80  $.Mason.prototype = {
81
82    _filterFindBricks: function($elems) {
83      var selector = this.options.itemSelector;
84      // if there is a selector
85      // filter/find appropriate item elements
86      return !selector ? $elems : $elems.filter(selector).add($elems.find(selector));
87    },
88
89    _getBricks: function($elems) {
90      var $bricks = this._filterFindBricks($elems)
91        .css({ position: 'absolute' })
92        .addClass('masonry-brick');
93      return $bricks;
94    },
95    
96    // sets up widget
97    _create : function(options) {
98      
99      this.options = $.extend(true, {}, $.Mason.settings, options);
100      
101      this.styleQueue = [];
102      // need to get bricks
103      this.reloadItems();
104
105
106      // get original styles in case we re-apply them in .destroy()
107      var elemStyle = this.element[0].style;
108      this.originalStyle = {};
109      for (var i=0, len = masonryContainerStyles.length; i < len; i++) {
110        var prop = masonryContainerStyles[i];
111        this.originalStyle[ prop ] = elemStyle[ prop ] || '';
112      }
113
114      this.element.css({
115        position : 'relative'
116      });
117      
118      this.horizontalDirection = this.options.isRTL ? 'right' : 'left';
119      this.offset = {};
120      
121      // get top left position of where the bricks should be
122      var $cursor = $(document.createElement('div'));
123      this.element.prepend($cursor);
124      this.offset.y = Math.round($cursor.position().top);
125      // get horizontal offset
126      if (!this.options.isRTL) {
127        this.offset.x = Math.round($cursor.position().left);
128      } else {
129        $cursor.css({ 'float': 'right', display: 'inline-block'});
130        this.offset.x = Math.round(this.element.outerWidth() - $cursor.position().left);
131      }
132      $cursor.remove();
133
134      // add masonry class first time around
135      var instance = this;
136      setTimeout(function() {
137        instance.element.addClass('masonry');
138      }, 0);
139      
140      // bind resize method
141      if (this.options.isResizable) {
142        $(window).bind('smartresize.masonry', function() { 
143          instance.resize();
144        });
145      }
146      
147    },
148  
149    // _init fires when instance is first created
150    // and when instance is triggered again -> $el.masonry();
151    _init : function(callback) {
152      this._getColumns();
153      this._reLayout(callback);
154    },
155
156    option: function(key, value){
157      // set options AFTER initialization:
158      // signature: $('#foo').bar({ cool:false });
159      if ($.isPlainObject(key)){
160        this.options = $.extend(true, this.options, key);
161      } 
162    },
163    
164    // ====================== General Layout ======================
165
166    // used on collection of atoms (should be filtered, and sorted before)
167    // accepts atoms-to-be-laid-out to start with
168    layout : function($bricks, callback) {
169
170      // layout logic
171      var $brick, colSpan, groupCount, groupY, groupColY, j;
172      
173      for (var i=0, len = $bricks.length; i < len; i++) {
174        $brick = $($bricks[i]);
175        //how many columns does this brick span
176        colSpan = Math.ceil($brick.outerWidth(true) / this.columnWidth);
177        colSpan = Math.min(colSpan, this.cols);
178
179        if (colSpan === 1) {
180          // if brick spans only one column, just like singleMode
181          this._placeBrick($brick, this.colYs);
182        } else {
183          // brick spans more than one column
184          // how many different places could this brick fit horizontally
185          groupCount = this.cols + 1 - colSpan;
186          groupY = [];
187
188          // for each group potential horizontal position
189          for (j=0; j < groupCount; j++) {
190            // make an array of colY values for that one group
191            groupColY = this.colYs.slice(j, j+colSpan);
192            // and get the max value of the array
193            groupY[j] = Math.max.apply(Math, groupColY);
194          }
195        
196          this._placeBrick($brick, groupY);
197        }
198      }
199      
200      // set the size of the container
201      var containerSize = {};
202      containerSize.height = Math.max.apply(Math, this.colYs) - this.offset.y;
203      if (this.options.isFitWidth) {
204        var unusedCols = 0,
205            i = this.cols;
206        // count unused columns
207        while (--i) {
208          if (this.colYs[i] !== this.offset.y) {
209            break;
210          }
211          unusedCols++;
212        }
213        // fit container to columns that have been used;
214        containerSize.width = (this.cols - unusedCols) * this.columnWidth - this.options.gutterWidth;
215      }
216      this.styleQueue.push({ $el: this.element, style: containerSize });
217
218      // are we animating the layout arrangement?
219      // use plugin-ish syntax for css or animate
220      var styleFn = !this.isLaidOut ? 'css' : (
221            this.options.isAnimated ? 'animate' : 'css'
222         ),
223          animOpts = this.options.animationOptions;
224
225      // process styleQueue
226      var obj;
227      for (i=0, len = this.styleQueue.length; i < len; i++) {
228        obj = this.styleQueue[i];
229        obj.$el[ styleFn ](obj.style, animOpts);
230      }
231
232      // clear out queue for next time
233      this.styleQueue = [];
234
235      // provide $elems as context for the callback
236      if (callback) {
237        callback.call($bricks);
238      }
239      
240      this.isLaidOut = true;
241    },
242    
243    // calculates number of columns
244    // i.e. this.columnWidth = 200
245    _getColumns : function() {
246      var container = this.options.isFitWidth ? this.element.parent() : this.element,
247          containerWidth = container.width();
248      
249      this.columnWidth = this.options.columnWidth ||
250                    // or use the size of the first item
251                    this.$bricks.outerWidth(true) ||
252                    // if there's no items, use size of container
253                    containerWidth;
254
255      this.columnWidth += this.options.gutterWidth;
256
257      this.cols = Math.floor((containerWidth + this.options.gutterWidth) / this.columnWidth);
258      this.cols = Math.max(this.cols, 1);
259
260    },
261
262    _placeBrick : function($brick, setY) {
263      // get the minimum Y value from the columns
264      var minimumY = Math.min.apply(Math, setY),
265          shortCol = 0;
266      
267      // Find index of short column, the first from the left
268      for (var i=0, len = setY.length; i < len; i++) {
269        if (setY[i] === minimumY) {
270          shortCol = i;
271          break;
272        }
273      }
274
275      // position the brick
276      var position = {
277        top : minimumY
278      };
279      // position.left or position.right
280      position[ this.horizontalDirection ] = this.columnWidth * shortCol + this.offset.x;
281      this.styleQueue.push({ $el: $brick, style: position });
282
283      // apply setHeight to necessary columns
284      var setHeight = minimumY + $brick.outerHeight(true),
285          setSpan = this.cols + 1 - len;
286      for (i=0; i < setSpan; i++) {
287        this.colYs[ shortCol + i ] = setHeight;
288      }
289
290    },
291    
292    
293    resize : function() {
294      var prevColCount = this.cols;
295      // get updated colCount
296      this._getColumns();
297      if (this.cols !== prevColCount) {
298        // if column count has changed, trigger new layout
299        this._reLayout();
300      }
301    },
302    
303    
304    _reLayout : function(callback) {
305      // reset columns
306      var i = this.cols;
307      this.colYs = [];
308      while (i--) {
309        this.colYs.push(this.offset.y);
310      }
311      // apply layout logic to all bricks
312      this.layout(this.$bricks, callback);
313    },
314    
315    // ====================== Convenience methods ======================
316    
317    // goes through all children again and gets bricks in proper order
318    reloadItems : function() {
319      this.$bricks = this._getBricks(this.element.children());
320    },
321    
322    
323    reload : function(callback) {
324      this.reloadItems();
325      this._init(callback);
326    },
327    
328
329    // convienence method for working with Infinite Scroll
330    appended : function($content, isAnimatedFromBottom, callback) {
331      if (isAnimatedFromBottom) {
332        // set new stuff to the bottom
333        this._filterFindBricks($content).css({ top: this.element.height() });
334        var instance = this;
335        setTimeout(function(){
336          instance._appended($content, callback);
337        }, 1);
338      } else {
339        this._appended($content, callback);
340      }
341    },
342    
343    _appended : function($content, callback) {
344      var $newBricks = this._getBricks($content);
345      // add new bricks to brick pool
346      this.$bricks = this.$bricks.add($newBricks);
347      this.layout($newBricks, callback);
348    },
349    
350    // removes elements from Masonry widget
351    remove : function($content) {
352      this.$bricks = this.$bricks.not($content);
353      $content.remove();
354    },
355    
356    // destroys widget, returns elements and container back (close) to original style
357    destroy : function() {
358
359      this.$bricks
360        .removeClass('masonry-brick')
361        .each(function(){
362          this.style.position = '';
363          this.style.top = '';
364          this.style.left = '';
365        });
366      
367      // re-apply saved container styles
368      var elemStyle = this.element[0].style;
369      for (var i=0, len = masonryContainerStyles.length; i < len; i++) {
370        var prop = masonryContainerStyles[i];
371        elemStyle[ prop ] = this.originalStyle[ prop ];
372      }
373      
374      this.element
375        .unbind('.masonry')
376        .removeClass('masonry')
377        .removeData('masonry');
378      
379      $(window).unbind('.masonry');
380
381    }
382    
383  };
384  
385  
386  // ======================= imagesLoaded Plugin ===============================
387  /*!
388   * jQuery imagesLoaded plugin v1.0.3
389   * http://github.com/desandro/imagesloaded
390   *
391   * MIT License. by Paul Irish et al.
392   */
393
394
395  // $('#my-container').imagesLoaded(myFunction)
396  // or
397  // $('img').imagesLoaded(myFunction)
398
399  // execute a callback when all images have loaded.
400  // needed because .load() doesn't work on cached images
401
402  // callback function gets image collection as argument
403  //  `this` is the container
404
405  $.fn.imagesLoaded = function(callback) {
406    var $this = this,
407        $images = $this.find('img').add($this.filter('img')),
408        len = $images.length,
409        blank = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
410
411    function triggerCallback() {
412      callback.call($this, $images);
413    }
414
415    function imgLoaded() {
416      if (--len <= 0 && this.src !== blank){
417        setTimeout(triggerCallback);
418        $images.unbind('load error', imgLoaded);
419      }
420    }
421
422    if (!len) {
423      triggerCallback();
424    }
425
426    $images.bind('load error',  imgLoaded).each(function() {
427      // cached images don't fire load sometimes, so we reset src.
428      if (this.complete || this.complete === undefined){
429        var src = this.src;
430        // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
431        // data uri bypasses webkit log warning (thx doug jones)
432        this.src = blank;
433        this.src = src;
434      }
435    });
436
437    return $this;
438  };
439
440
441  // helper function for logging errors
442  // $.error breaks jQuery chaining
443  var logError = function(message) {
444    if (this.console) {
445      console.error(message);
446    }
447  };
448  
449  // =======================  Plugin bridge  ===============================
450  // leverages data method to either create or return $.Mason constructor
451  // A bit from jQuery UI
452  //   https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js
453  // A bit from jcarousel 
454  //   https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarou
454sel.js
455
456  $.fn.masonry = function(options) {
457    if (typeof options === 'string') {
458      // call method
459      var args = Array.prototype.slice.call(arguments, 1);
460
461      this.each(function(){
462        var instance = $.data(this, 'masonry');
463        if (!instance) {
464          logError("cannot call methods on masonry prior to initialization; " +
465            "attempted to call method '" + options + "'");
466          return;
467        }
468        if (!$.isFunction(instance[options]) || options.charAt(0) === "_") {
469          logError("no such method '" + options + "' for masonry instance");
470          return;
471        }
472        // apply method
473        instance[ options ].apply(instance, args);
474      });
475    } else {
476      this.each(function() {
477        var instance = $.data(this, 'masonry');
478        if (instance) {
479          // apply options & init
480          instance.option(options || {});
481          instance._init();
482        } else {
483          // initialize new instance
484          $.data(this, 'masonry', new $.Mason(options, this));
485        }
486      });
487    }
488    return this;
489  };
490
491}) (window, jQuery);

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.