jquery.isotope.js 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356
  1. /**
  2. * Isotope v1.5.03
  3. * An exquisite jQuery plugin for magical layouts
  4. * http://isotope.metafizzy.co
  5. *
  6. * Commercial use requires one-time license fee
  7. * http://metafizzy.co/#licenses
  8. *
  9. * Copyright 2011 David DeSandro / Metafizzy
  10. */
  11. /*jshint curly: true, eqeqeq: true, forin: false, immed: false, newcap: true, noempty: true, undef: true */
  12. /*global Modernizr: true, jQuery: true */
  13. (function( window, $, undefined ){
  14. 'use strict';
  15. // helper function
  16. var capitalize = function( str ) {
  17. return str.charAt(0).toUpperCase() + str.slice(1);
  18. };
  19. // ========================= getStyleProperty by kangax ===============================
  20. // http://perfectionkills.com/feature-testing-css-properties/
  21. var prefixes = 'Moz Webkit Khtml O Ms'.split(' ');
  22. var getStyleProperty = function( propName ) {
  23. var style = document.documentElement.style,
  24. prefixed;
  25. // test standard property first
  26. if ( typeof style[propName] === 'string' ) {
  27. return propName;
  28. }
  29. // capitalize
  30. propName = capitalize( propName );
  31. // test vendor specific properties
  32. for ( var i=0, len = prefixes.length; i < len; i++ ) {
  33. prefixed = prefixes[i] + propName;
  34. if ( typeof style[ prefixed ] === 'string' ) {
  35. return prefixed;
  36. }
  37. }
  38. };
  39. var transformProp = getStyleProperty('transform'),
  40. transitionProp = getStyleProperty('transitionProperty');
  41. // ========================= miniModernizr ===============================
  42. // <3<3<3 and thanks to Faruk and Paul for doing the heavy lifting
  43. /*!
  44. * Modernizr v1.6ish: miniModernizr for Isotope
  45. * http://www.modernizr.com
  46. *
  47. * Developed by:
  48. * - Faruk Ates http://farukat.es/
  49. * - Paul Irish http://paulirish.com/
  50. *
  51. * Copyright (c) 2009-2010
  52. * Dual-licensed under the BSD or MIT licenses.
  53. * http://www.modernizr.com/license/
  54. */
  55. /*
  56. * This version whittles down the script just to check support for
  57. * CSS transitions, transforms, and 3D transforms.
  58. */
  59. var tests = {
  60. csstransforms: function() {
  61. return !!transformProp;
  62. },
  63. csstransforms3d: function() {
  64. var test = !!getStyleProperty('perspective');
  65. // double check for Chrome's false positive
  66. if ( test ) {
  67. var vendorCSSPrefixes = ' -o- -moz- -ms- -webkit- -khtml- '.split(' '),
  68. mediaQuery = '@media (' + vendorCSSPrefixes.join('transform-3d),(') + 'modernizr)',
  69. $style = $('<style>' + mediaQuery + '{#modernizr{height:3px}}' + '</style>')
  70. .appendTo('head'),
  71. $div = $('<div id="modernizr" />').appendTo('html');
  72. test = $div.height() === 3;
  73. $div.remove();
  74. $style.remove();
  75. }
  76. return test;
  77. },
  78. csstransitions: function() {
  79. return !!transitionProp;
  80. }
  81. };
  82. if ( window.Modernizr ) {
  83. // if there's a previous Modernzir, check if there are necessary tests
  84. for ( var testName in tests) {
  85. if ( !Modernizr.hasOwnProperty( testName ) ) {
  86. // if test hasn't been run, use addTest to run it
  87. Modernizr.addTest( testName, tests[ testName ] );
  88. }
  89. }
  90. } else {
  91. // or create new mini Modernizr that just has the 3 tests
  92. window.Modernizr = (function(){
  93. var miniModernizr = {
  94. _version : '1.6ish: miniModernizr for Isotope'
  95. },
  96. classes = ' ',
  97. result, testName;
  98. // Run through tests
  99. for ( testName in tests) {
  100. result = tests[ testName ]();
  101. miniModernizr[ testName ] = result;
  102. classes += ' ' + ( result ? '' : 'no-' ) + testName;
  103. }
  104. // Add the new classes to the <html> element.
  105. $('html').addClass( classes );
  106. return miniModernizr;
  107. })();
  108. }
  109. // ========================= isoTransform ===============================
  110. /**
  111. * provides hooks for .css({ scale: value, translate: [x, y] })
  112. * Progressively enhanced CSS transforms
  113. * Uses hardware accelerated 3D transforms for Safari
  114. * or falls back to 2D transforms.
  115. */
  116. if ( Modernizr.csstransforms ) {
  117. // i.e. transformFnNotations.scale(0.5) >> 'scale3d( 0.5, 0.5, 1)'
  118. var transformFnNotations = Modernizr.csstransforms3d ?
  119. { // 3D transform functions
  120. translate : function ( position ) {
  121. return 'translate3d(' + position[0] + 'px, ' + position[1] + 'px, 0) ';
  122. },
  123. scale : function ( scale ) {
  124. return 'scale3d(' + scale + ', ' + scale + ', 1) ';
  125. }
  126. } :
  127. { // 2D transform functions
  128. translate : function ( position ) {
  129. return 'translate(' + position[0] + 'px, ' + position[1] + 'px) ';
  130. },
  131. scale : function ( scale ) {
  132. return 'scale(' + scale + ') ';
  133. }
  134. }
  135. ;
  136. var setIsoTransform = function ( elem, name, value ) {
  137. // unpack current transform data
  138. var data = $.data( elem, 'isoTransform' ) || {},
  139. newData = {},
  140. fnName,
  141. transformObj = {},
  142. transformValue;
  143. // i.e. newData.scale = 0.5
  144. newData[ name ] = value;
  145. // extend new value over current data
  146. $.extend( data, newData );
  147. for ( fnName in data ) {
  148. transformValue = data[ fnName ];
  149. transformObj[ fnName ] = transformFnNotations[ fnName ]( transformValue );
  150. }
  151. // get proper order
  152. // ideally, we could loop through this give an array, but since we only have
  153. // a couple transforms we're keeping track of, we'll do it like so
  154. var translateFn = transformObj.translate || '',
  155. scaleFn = transformObj.scale || '',
  156. // sorting so translate always comes first
  157. valueFns = translateFn + scaleFn;
  158. // set data back in elem
  159. $.data( elem, 'isoTransform', data );
  160. // set name to vendor specific property
  161. elem.style[ transformProp ] = valueFns;
  162. };
  163. // ==================== scale ===================
  164. $.cssNumber.scale = true;
  165. $.cssHooks.scale = {
  166. set: function( elem, value ) {
  167. // uncomment this bit if you want to properly parse strings
  168. // if ( typeof value === 'string' ) {
  169. // value = parseFloat( value );
  170. // }
  171. setIsoTransform( elem, 'scale', value );
  172. },
  173. get: function( elem, computed ) {
  174. var transform = $.data( elem, 'isoTransform' );
  175. return transform && transform.scale ? transform.scale : 1;
  176. }
  177. };
  178. $.fx.step.scale = function( fx ) {
  179. $.cssHooks.scale.set( fx.elem, fx.now+fx.unit );
  180. };
  181. // ==================== translate ===================
  182. $.cssNumber.translate = true;
  183. $.cssHooks.translate = {
  184. set: function( elem, value ) {
  185. // uncomment this bit if you want to properly parse strings
  186. // if ( typeof value === 'string' ) {
  187. // value = value.split(' ');
  188. // }
  189. //
  190. // var i, val;
  191. // for ( i = 0; i < 2; i++ ) {
  192. // val = value[i];
  193. // if ( typeof val === 'string' ) {
  194. // val = parseInt( val );
  195. // }
  196. // }
  197. setIsoTransform( elem, 'translate', value );
  198. },
  199. get: function( elem, computed ) {
  200. var transform = $.data( elem, 'isoTransform' );
  201. return transform && transform.translate ? transform.translate : [ 0, 0 ];
  202. }
  203. };
  204. }
  205. // ========================= get transition-end event ===============================
  206. var transitionEndEvent, transitionDurProp;
  207. if ( Modernizr.csstransitions ) {
  208. transitionEndEvent = {
  209. WebkitTransitionProperty: 'webkitTransitionEnd', // webkit
  210. MozTransitionProperty: 'transitionend',
  211. OTransitionProperty: 'oTransitionEnd',
  212. transitionProperty: 'transitionEnd'
  213. }[ transitionProp ];
  214. transitionDurProp = getStyleProperty('transitionDuration');
  215. }
  216. // ========================= smartresize ===============================
  217. /*
  218. * smartresize: debounced resize event for jQuery
  219. *
  220. * latest version and complete README available on Github:
  221. * https://github.com/louisremi/jquery.smartresize.js
  222. *
  223. * Copyright 2011 @louis_remi
  224. * Licensed under the MIT license.
  225. */
  226. var $event = $.event,
  227. resizeTimeout;
  228. $event.special.smartresize = {
  229. setup: function() {
  230. $(this).bind( "resize", $event.special.smartresize.handler );
  231. },
  232. teardown: function() {
  233. $(this).unbind( "resize", $event.special.smartresize.handler );
  234. },
  235. handler: function( event, execAsap ) {
  236. // Save the context
  237. var context = this,
  238. args = arguments;
  239. // set correct event type
  240. event.type = "smartresize";
  241. if ( resizeTimeout ) { clearTimeout( resizeTimeout ); }
  242. resizeTimeout = setTimeout(function() {
  243. jQuery.event.handle.apply( context, args );
  244. }, execAsap === "execAsap"? 0 : 100 );
  245. }
  246. };
  247. $.fn.smartresize = function( fn ) {
  248. return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] );
  249. };
  250. // ========================= Isotope ===============================
  251. // our "Widget" object constructor
  252. $.Isotope = function( options, element, callback ){
  253. this.element = $( element );
  254. this._create( options );
  255. this._init( callback );
  256. };
  257. // styles of container element we want to keep track of
  258. var isoContainerStyles = [ 'overflow', 'position', 'width', 'height' ];
  259. $.Isotope.settings = {
  260. resizable: true,
  261. layoutMode : 'masonry',
  262. containerClass : 'isotope',
  263. itemClass : 'isotope-item',
  264. hiddenClass : 'isotope-hidden',
  265. hiddenStyle : Modernizr.csstransforms && !$.browser.opera ?
  266. { opacity : 0, scale : 0.001 } : // browsers support CSS transforms, not Opera
  267. { opacity : 0 }, // other browsers, including Opera
  268. visibleStyle : Modernizr.csstransforms && !$.browser.opera ?
  269. { opacity : 1, scale : 1 } : // browsers support CSS transforms, not Opera
  270. { opacity : 1 }, // other browsers, including Opera
  271. animationEngine : $.browser.opera ? 'jquery' : 'best-available',
  272. animationOptions: {
  273. queue: false,
  274. duration: 800
  275. },
  276. sortBy : 'original-order',
  277. sortAscending : true,
  278. resizesContainer : true,
  279. transformsEnabled : true,
  280. itemPositionDataEnabled: false
  281. };
  282. $.Isotope.prototype = {
  283. // sets up widget
  284. _create : function( options ) {
  285. this.options = $.extend( {}, $.Isotope.settings, options );
  286. this.styleQueue = [];
  287. this.elemCount = 0;
  288. // get original styles in case we re-apply them in .destroy()
  289. var elemStyle = this.element[0].style;
  290. this.originalStyle = {};
  291. for ( var i=0, len = isoContainerStyles.length; i < len; i++ ) {
  292. var prop = isoContainerStyles[i];
  293. this.originalStyle[ prop ] = elemStyle[ prop ] || '';
  294. }
  295. this.element.css({
  296. overflow : 'hidden',
  297. position : 'relative'
  298. });
  299. this._updateAnimationEngine();
  300. this._updateUsingTransforms();
  301. // sorting
  302. var originalOrderSorter = {
  303. 'original-order' : function( $elem, instance ) {
  304. instance.elemCount ++;
  305. return instance.elemCount;
  306. },
  307. random : function() {
  308. return Math.random();
  309. }
  310. };
  311. this.options.getSortData = $.extend( this.options.getSortData, originalOrderSorter );
  312. // need to get atoms
  313. this.reloadItems();
  314. // get top left position of where the bricks should be
  315. var $cursor = $( document.createElement('div') ).prependTo( this.element );
  316. this.offset = $cursor.position();
  317. $cursor.remove();
  318. // add isotope class first time around
  319. var instance = this;
  320. setTimeout( function() {
  321. instance.element.addClass( instance.options.containerClass );
  322. }, 0 );
  323. // bind resize method
  324. if ( this.options.resizable ) {
  325. $(window).bind( 'smartresize.isotope', function() {
  326. instance.resize();
  327. });
  328. }
  329. // dismiss all click events from hidden events
  330. this.element.delegate( '.' + this.options.hiddenClass, 'click', function(){
  331. return false;
  332. });
  333. },
  334. _getAtoms : function( $elems ) {
  335. var selector = this.options.itemSelector,
  336. // filter & find
  337. $atoms = selector ? $elems.filter( selector ).add( $elems.find( selector ) ) : $elems,
  338. // base style for atoms
  339. atomStyle = { position: 'absolute' };
  340. if ( this.usingTransforms ) {
  341. atomStyle.left = 0;
  342. atomStyle.top = 0;
  343. }
  344. $atoms.css( atomStyle ).addClass( this.options.itemClass );
  345. this.updateSortData( $atoms, true );
  346. return $atoms;
  347. },
  348. // _init fires when your instance is first created
  349. // (from the constructor above), and when you
  350. // attempt to initialize the widget again (by the bridge)
  351. // after it has already been initialized.
  352. _init : function( callback ) {
  353. this.$filteredAtoms = this._filter( this.$allAtoms );
  354. this._sort();
  355. this.reLayout( callback );
  356. },
  357. option : function( opts ){
  358. // change options AFTER initialization:
  359. // signature: $('#foo').bar({ cool:false });
  360. if ( $.isPlainObject( opts ) ){
  361. this.options = $.extend( true, this.options, opts );
  362. // trigger _updateOptionName if it exists
  363. var updateOptionFn;
  364. for ( var optionName in opts ) {
  365. updateOptionFn = '_update' + capitalize( optionName );
  366. if ( this[ updateOptionFn ] ) {
  367. this[ updateOptionFn ]();
  368. }
  369. }
  370. }
  371. },
  372. // ====================== updaters ====================== //
  373. // kind of like setters
  374. _updateAnimationEngine : function() {
  375. var animationEngine = this.options.animationEngine.toLowerCase().replace( /[ _\-]/g, '');
  376. // set applyStyleFnName
  377. switch ( animationEngine ) {
  378. case 'css' :
  379. case 'none' :
  380. this.isUsingJQueryAnimation = false;
  381. break;
  382. case 'jquery' :
  383. this.isUsingJQueryAnimation = true;
  384. break;
  385. default : // best available
  386. this.isUsingJQueryAnimation = !Modernizr.csstransitions;
  387. }
  388. this._updateUsingTransforms();
  389. },
  390. _updateTransformsEnabled : function() {
  391. this._updateUsingTransforms();
  392. },
  393. _updateUsingTransforms : function() {
  394. this.usingTransforms = this.options.transformsEnabled && Modernizr.csstransforms && Modernizr.csstransitions && !this.isUsingJQueryAnimation;
  395. this.getPositionStyles = this.usingTransforms ? this._translate : this._positionAbs;
  396. },
  397. // ====================== Filtering ======================
  398. _filter : function( $atoms ) {
  399. var filter = this.options.filter === '' ? '*' : this.options.filter;
  400. if ( !filter ) {
  401. return $atoms;
  402. }
  403. var hiddenClass = this.options.hiddenClass,
  404. hiddenSelector = '.' + hiddenClass,
  405. $hiddenAtoms = $atoms.filter( hiddenSelector ),
  406. $atomsToShow = $hiddenAtoms;
  407. if ( filter !== '*' ) {
  408. $atomsToShow = $hiddenAtoms.filter( filter );
  409. var $atomsToHide = $atoms.not( hiddenSelector ).not( filter ).addClass( hiddenClass );
  410. this.styleQueue.push({ $el: $atomsToHide, style: this.options.hiddenStyle });
  411. }
  412. this.styleQueue.push({ $el: $atomsToShow, style: this.options.visibleStyle });
  413. $atomsToShow.removeClass( hiddenClass );
  414. return $atoms.filter( filter );
  415. },
  416. // ====================== Sorting ======================
  417. updateSortData : function( $atoms, isIncrementingElemCount ) {
  418. var instance = this,
  419. getSortData = this.options.getSortData,
  420. $this, sortData;
  421. $atoms.each(function(){
  422. $this = $(this);
  423. sortData = {};
  424. // get value for sort data based on fn( $elem ) passed in
  425. for ( var key in getSortData ) {
  426. if ( !isIncrementingElemCount && key === 'original-order' ) {
  427. // keep original order original
  428. sortData[ key ] = $.data( this, 'isotope-sort-data' )[ key ];
  429. } else {
  430. sortData[ key ] = getSortData[ key ]( $this, instance );
  431. }
  432. }
  433. // apply sort data to element
  434. $.data( this, 'isotope-sort-data', sortData );
  435. });
  436. },
  437. // used on all the filtered atoms
  438. _sort : function() {
  439. var sortBy = this.options.sortBy,
  440. getSorter = this._getSorter,
  441. sortDir = this.options.sortAscending ? 1 : -1,
  442. sortFn = function( alpha, beta ) {
  443. var a = getSorter( alpha, sortBy ),
  444. b = getSorter( beta, sortBy );
  445. // fall back to original order if data matches
  446. if ( a === b && sortBy !== 'original-order') {
  447. a = getSorter( alpha, 'original-order' );
  448. b = getSorter( beta, 'original-order' );
  449. }
  450. return ( ( a > b ) ? 1 : ( a < b ) ? -1 : 0 ) * sortDir;
  451. };
  452. this.$filteredAtoms.sort( sortFn );
  453. },
  454. _getSorter : function( elem, sortBy ) {
  455. return $.data( elem, 'isotope-sort-data' )[ sortBy ];
  456. },
  457. // ====================== Layout Helpers ======================
  458. _translate : function( x, y ) {
  459. return { translate : [ x, y ] };
  460. },
  461. _positionAbs : function( x, y ) {
  462. return { left: x, top: y };
  463. },
  464. _pushPosition : function( $elem, x, y ) {
  465. x += this.offset.left;
  466. y += this.offset.top;
  467. var position = this.getPositionStyles( x, y );
  468. this.styleQueue.push({ $el: $elem, style: position });
  469. if ( this.options.itemPositionDataEnabled ) {
  470. $elem.data('isotope-item-position', {x: x, y: y} );
  471. }
  472. },
  473. // ====================== General Layout ======================
  474. // used on collection of atoms (should be filtered, and sorted before )
  475. // accepts atoms-to-be-laid-out to start with
  476. layout : function( $elems, callback ) {
  477. var layoutMode = this.options.layoutMode;
  478. // layout logic
  479. this[ '_' + layoutMode + 'Layout' ]( $elems );
  480. // set the size of the container
  481. if ( this.options.resizesContainer ) {
  482. var containerStyle = this[ '_' + layoutMode + 'GetContainerSize' ]();
  483. this.styleQueue.push({ $el: this.element, style: containerStyle });
  484. }
  485. this._processStyleQueue( $elems, callback );
  486. this.isLaidOut = true;
  487. },
  488. _processStyleQueue : function( $elems, callback ) {
  489. // are we animating the layout arrangement?
  490. // use plugin-ish syntax for css or animate
  491. var styleFn = !this.isLaidOut ? 'css' : (
  492. this.isUsingJQueryAnimation ? 'animate' : 'css'
  493. ),
  494. animOpts = this.options.animationOptions,
  495. objStyleFn, processor,
  496. triggerCallbackNow, callbackFn;
  497. // default styleQueue processor, may be overwritten down below
  498. processor = function( i, obj ) {
  499. obj.$el[ styleFn ]( obj.style, animOpts );
  500. };
  501. if ( this._isInserting && this.isUsingJQueryAnimation ) {
  502. // if using styleQueue to insert items
  503. processor = function( i, obj ) {
  504. // only animate if it not being inserted
  505. objStyleFn = obj.$el.hasClass('no-transition') ? 'css' : styleFn;
  506. obj.$el[ objStyleFn ]( obj.style, animOpts );
  507. };
  508. } else if ( callback ) {
  509. // has callback
  510. var isCallbackTriggered = false,
  511. instance = this;
  512. triggerCallbackNow = true;
  513. // trigger callback only once
  514. callbackFn = function() {
  515. if ( isCallbackTriggered ) {
  516. return;
  517. }
  518. callback.call( instance.element, $elems );
  519. isCallbackTriggered = true;
  520. };
  521. if ( this.isUsingJQueryAnimation && styleFn === 'animate' ) {
  522. // add callback to animation options
  523. animOpts.complete = callbackFn;
  524. triggerCallbackNow = false;
  525. } else if ( Modernizr.csstransitions ) {
  526. // detect if first item has transition
  527. var i = 0,
  528. testElem = this.styleQueue[0].$el,
  529. styleObj;
  530. // get first non-empty jQ object
  531. while ( !testElem.length ) {
  532. styleObj = this.styleQueue[ i++ ];
  533. // HACK: sometimes styleQueue[i] is undefined
  534. if ( !styleObj ) {
  535. return;
  536. }
  537. testElem = styleObj.$el;
  538. }
  539. // get transition duration of the first element in that object
  540. // yeah, this is inexact
  541. var duration = parseFloat( getComputedStyle( testElem[0] )[ transitionDurProp ] );
  542. if ( duration > 0 ) {
  543. processor = function( i, obj ) {
  544. obj.$el[ styleFn ]( obj.style, animOpts )
  545. // trigger callback at transition end
  546. .one( transitionEndEvent, callbackFn );
  547. };
  548. triggerCallbackNow = false;
  549. }
  550. }
  551. }
  552. // process styleQueue
  553. $.each( this.styleQueue, processor );
  554. if ( triggerCallbackNow ) {
  555. callbackFn();
  556. }
  557. // clear out queue for next time
  558. this.styleQueue = [];
  559. },
  560. resize : function() {
  561. if ( this[ '_' + this.options.layoutMode + 'ResizeChanged' ]() ) {
  562. this.reLayout();
  563. }
  564. },
  565. reLayout : function( callback ) {
  566. this[ '_' + this.options.layoutMode + 'Reset' ]();
  567. this.layout( this.$filteredAtoms, callback );
  568. },
  569. // ====================== Convenience methods ======================
  570. // ====================== Adding items ======================
  571. // adds a jQuery object of items to a isotope container
  572. addItems : function( $content, callback ) {
  573. var $newAtoms = this._getAtoms( $content );
  574. // add new atoms to atoms pools
  575. this.$allAtoms = this.$allAtoms.add( $newAtoms );
  576. if ( callback ) {
  577. callback( $newAtoms );
  578. }
  579. },
  580. // convienence method for adding elements properly to any layout
  581. // positions items, hides them, then animates them back in <--- very sezzy
  582. insert : function( $content, callback ) {
  583. // position items
  584. this.element.append( $content );
  585. var instance = this;
  586. this.addItems( $content, function( $newAtoms ) {
  587. var $newFilteredAtoms = instance._filter( $newAtoms, true );
  588. instance._addHideAppended( $newFilteredAtoms );
  589. instance._sort();
  590. instance.reLayout();
  591. instance._revealAppended( $newFilteredAtoms, callback );
  592. });
  593. },
  594. // convienence method for working with Infinite Scroll
  595. appended : function( $content, callback ) {
  596. var instance = this;
  597. this.addItems( $content, function( $newAtoms ) {
  598. instance._addHideAppended( $newAtoms );
  599. instance.layout( $newAtoms );
  600. instance._revealAppended( $newAtoms, callback );
  601. });
  602. },
  603. // adds new atoms, then hides them before positioning
  604. _addHideAppended : function( $newAtoms ) {
  605. this.$filteredAtoms = this.$filteredAtoms.add( $newAtoms );
  606. $newAtoms.addClass('no-transition');
  607. this._isInserting = true;
  608. // apply hidden styles
  609. this.styleQueue.push({ $el: $newAtoms, style: this.options.hiddenStyle });
  610. },
  611. // sets visible style on new atoms
  612. _revealAppended : function( $newAtoms, callback ) {
  613. var instance = this;
  614. // apply visible style after a sec
  615. setTimeout( function() {
  616. // enable animation
  617. $newAtoms.removeClass('no-transition');
  618. // reveal newly inserted filtered elements
  619. instance.styleQueue.push({ $el: $newAtoms, style: instance.options.visibleStyle });
  620. instance._isInserting = false;
  621. instance._processStyleQueue( $newAtoms, callback );
  622. }, 10 );
  623. },
  624. // gathers all atoms
  625. reloadItems : function() {
  626. this.$allAtoms = this._getAtoms( this.element.children() );
  627. },
  628. // removes elements from Isotope widget
  629. remove : function( $content ) {
  630. this.$allAtoms = this.$allAtoms.not( $content );
  631. this.$filteredAtoms = this.$filteredAtoms.not( $content );
  632. $content.remove();
  633. },
  634. shuffle : function( callback ) {
  635. this.updateSortData( this.$allAtoms );
  636. this.options.sortBy = 'random';
  637. this._sort();
  638. this.reLayout( callback );
  639. },
  640. // destroys widget, returns elements and container back (close) to original style
  641. destroy : function() {
  642. var usingTransforms = this.usingTransforms;
  643. this.$allAtoms
  644. .removeClass( this.options.hiddenClass + ' ' + this.options.itemClass )
  645. .each(function(){
  646. this.style.position = '';
  647. this.style.top = '';
  648. this.style.left = '';
  649. this.style.opacity = '';
  650. if ( usingTransforms ) {
  651. this.style[ transformProp ] = '';
  652. }
  653. });
  654. // re-apply saved container styles
  655. var elemStyle = this.element[0].style;
  656. for ( var i=0, len = isoContainerStyles.length; i < len; i++ ) {
  657. var prop = isoContainerStyles[i];
  658. elemStyle[ prop ] = this.originalStyle[ prop ];
  659. }
  660. this.element
  661. .unbind('.isotope')
  662. .undelegate( '.' + this.options.hiddenClass, 'click' )
  663. .removeClass( this.options.containerClass )
  664. .removeData('isotope');
  665. $(window).unbind('.isotope');
  666. },
  667. // ====================== LAYOUTS ======================
  668. // calculates number of rows or columns
  669. // requires columnWidth or rowHeight to be set on namespaced object
  670. // i.e. this.masonry.columnWidth = 200
  671. _getSegments : function( isRows ) {
  672. var namespace = this.options.layoutMode,
  673. measure = isRows ? 'rowHeight' : 'columnWidth',
  674. size = isRows ? 'height' : 'width',
  675. segmentsName = isRows ? 'rows' : 'cols',
  676. containerSize = this.element[ size ](),
  677. segments,
  678. // i.e. options.masonry && options.masonry.columnWidth
  679. segmentSize = this.options[ namespace ] && this.options[ namespace ][ measure ] ||
  680. // or use the size of the first item, i.e. outerWidth
  681. this.$filteredAtoms[ 'outer' + capitalize(size) ](true) ||
  682. // if there's no items, use size of container
  683. containerSize;
  684. segments = Math.floor( containerSize / segmentSize );
  685. segments = Math.max( segments, 1 );
  686. // i.e. this.masonry.cols = ....
  687. this[ namespace ][ segmentsName ] = segments;
  688. // i.e. this.masonry.columnWidth = ...
  689. this[ namespace ][ measure ] = segmentSize;
  690. },
  691. _checkIfSegmentsChanged : function( isRows ) {
  692. var namespace = this.options.layoutMode,
  693. segmentsName = isRows ? 'rows' : 'cols',
  694. prevSegments = this[ namespace ][ segmentsName ];
  695. // update cols/rows
  696. this._getSegments( isRows );
  697. // return if updated cols/rows is not equal to previous
  698. return ( this[ namespace ][ segmentsName ] !== prevSegments );
  699. },
  700. // ====================== Masonry ======================
  701. _masonryReset : function() {
  702. // layout-specific props
  703. this.masonry = {};
  704. // FIXME shouldn't have to call this again
  705. this._getSegments();
  706. var i = this.masonry.cols;
  707. this.masonry.colYs = [];
  708. while (i--) {
  709. this.masonry.colYs.push( 0 );
  710. }
  711. },
  712. _masonryLayout : function( $elems ) {
  713. var instance = this,
  714. props = instance.masonry;
  715. $elems.each(function(){
  716. var $this = $(this),
  717. //how many columns does this brick span
  718. colSpan = Math.ceil( $this.outerWidth(true) / props.columnWidth );
  719. colSpan = Math.min( colSpan, props.cols );
  720. if ( colSpan === 1 ) {
  721. // if brick spans only one column, just like singleMode
  722. instance._masonryPlaceBrick( $this, props.colYs );
  723. } else {
  724. // brick spans more than one column
  725. // how many different places could this brick fit horizontally
  726. var groupCount = props.cols + 1 - colSpan,
  727. groupY = [],
  728. groupColY,
  729. i;
  730. // for each group potential horizontal position
  731. for ( i=0; i < groupCount; i++ ) {
  732. // make an array of colY values for that one group
  733. groupColY = props.colYs.slice( i, i+colSpan );
  734. // and get the max value of the array
  735. groupY[i] = Math.max.apply( Math, groupColY );
  736. }
  737. instance._masonryPlaceBrick( $this, groupY );
  738. }
  739. });
  740. },
  741. // worker method that places brick in the columnSet
  742. // with the the minY
  743. _masonryPlaceBrick : function( $brick, setY ) {
  744. // get the minimum Y value from the columns
  745. var minimumY = Math.min.apply( Math, setY ),
  746. shortCol = 0;
  747. // Find index of short column, the first from the left
  748. for (var i=0, len = setY.length; i < len; i++) {
  749. if ( setY[i] === minimumY ) {
  750. shortCol = i;
  751. break;
  752. }
  753. }
  754. // position the brick
  755. var x = this.masonry.columnWidth * shortCol,
  756. y = minimumY;
  757. this._pushPosition( $brick, x, y );
  758. // apply setHeight to necessary columns
  759. var setHeight = minimumY + $brick.outerHeight(true),
  760. setSpan = this.masonry.cols + 1 - len;
  761. for ( i=0; i < setSpan; i++ ) {
  762. this.masonry.colYs[ shortCol + i ] = setHeight;
  763. }
  764. },
  765. _masonryGetContainerSize : function() {
  766. var containerHeight = Math.max.apply( Math, this.masonry.colYs );
  767. return { height: containerHeight };
  768. },
  769. _masonryResizeChanged : function() {
  770. return this._checkIfSegmentsChanged();
  771. },
  772. // ====================== fitRows ======================
  773. _fitRowsReset : function() {
  774. this.fitRows = {
  775. x : 0,
  776. y : 0,
  777. height : 0
  778. };
  779. },
  780. _fitRowsLayout : function( $elems ) {
  781. var instance = this,
  782. containerWidth = this.element.width(),
  783. props = this.fitRows;
  784. $elems.each( function() {
  785. var $this = $(this),
  786. atomW = $this.outerWidth(true),
  787. atomH = $this.outerHeight(true);
  788. if ( props.x !== 0 && atomW + props.x > containerWidth ) {
  789. // if this element cannot fit in the current row
  790. props.x = 0;
  791. props.y = props.height;
  792. }
  793. // position the atom
  794. instance._pushPosition( $this, props.x, props.y );
  795. props.height = Math.max( props.y + atomH, props.height );
  796. props.x += atomW;
  797. });
  798. },
  799. _fitRowsGetContainerSize : function () {
  800. return { height : this.fitRows.height };
  801. },
  802. _fitRowsResizeChanged : function() {
  803. return true;
  804. },
  805. // ====================== cellsByRow ======================
  806. _cellsByRowReset : function() {
  807. this.cellsByRow = {
  808. index : 0
  809. };
  810. // get this.cellsByRow.columnWidth
  811. this._getSegments();
  812. // get this.cellsByRow.rowHeight
  813. this._getSegments(true);
  814. },
  815. _cellsByRowLayout : function( $elems ) {
  816. var instance = this,
  817. props = this.cellsByRow;
  818. $elems.each( function(){
  819. var $this = $(this),
  820. col = props.index % props.cols,
  821. row = Math.floor( props.index / props.cols ),
  822. x = Math.round( ( col + 0.5 ) * props.columnWidth - $this.outerWidth(true) / 2 ),
  823. y = Math.round( ( row + 0.5 ) * props.rowHeight - $this.outerHeight(true) / 2 );
  824. instance._pushPosition( $this, x, y );
  825. props.index ++;
  826. });
  827. },
  828. _cellsByRowGetContainerSize : function() {
  829. return { height : Math.ceil( this.$filteredAtoms.length / this.cellsByRow.cols ) * this.cellsByRow.rowHeight + this.offset.top };
  830. },
  831. _cellsByRowResizeChanged : function() {
  832. return this._checkIfSegmentsChanged();
  833. },
  834. // ====================== straightDown ======================
  835. _straightDownReset : function() {
  836. this.straightDown = {
  837. y : 0
  838. };
  839. },
  840. _straightDownLayout : function( $elems ) {
  841. var instance = this;
  842. $elems.each( function( i ){
  843. var $this = $(this);
  844. instance._pushPosition( $this, 0, instance.straightDown.y );
  845. instance.straightDown.y += $this.outerHeight(true);
  846. });
  847. },
  848. _straightDownGetContainerSize : function() {
  849. return { height : this.straightDown.y };
  850. },
  851. _straightDownResizeChanged : function() {
  852. return true;
  853. },
  854. // ====================== masonryHorizontal ======================
  855. _masonryHorizontalReset : function() {
  856. // layout-specific props
  857. this.masonryHorizontal = {};
  858. // FIXME shouldn't have to call this again
  859. this._getSegments( true );
  860. var i = this.masonryHorizontal.rows;
  861. this.masonryHorizontal.rowXs = [];
  862. while (i--) {
  863. this.masonryHorizontal.rowXs.push( 0 );
  864. }
  865. },
  866. _masonryHorizontalLayout : function( $elems ) {
  867. var instance = this,
  868. props = instance.masonryHorizontal;
  869. $elems.each(function(){
  870. var $this = $(this),
  871. //how many rows does this brick span
  872. rowSpan = Math.ceil( $this.outerHeight(true) / props.rowHeight );
  873. rowSpan = Math.min( rowSpan, props.rows );
  874. if ( rowSpan === 1 ) {
  875. // if brick spans only one column, just like singleMode
  876. instance._masonryHorizontalPlaceBrick( $this, props.rowXs );
  877. } else {
  878. // brick spans more than one row
  879. // how many different places could this brick fit horizontally
  880. var groupCount = props.rows + 1 - rowSpan,
  881. groupX = [],
  882. groupRowX, i;
  883. // for each group potential horizontal position
  884. for ( i=0; i < groupCount; i++ ) {
  885. // make an array of colY values for that one group
  886. groupRowX = props.rowXs.slice( i, i+rowSpan );
  887. // and get the max value of the array
  888. groupX[i] = Math.max.apply( Math, groupRowX );
  889. }
  890. instance._masonryHorizontalPlaceBrick( $this, groupX );
  891. }
  892. });
  893. },
  894. _masonryHorizontalPlaceBrick : function( $brick, setX ) {
  895. // get the minimum Y value from the columns
  896. var minimumX = Math.min.apply( Math, setX ),
  897. smallRow = 0;
  898. // Find index of smallest row, the first from the top
  899. for (var i=0, len = setX.length; i < len; i++) {
  900. if ( setX[i] === minimumX ) {
  901. smallRow = i;
  902. break;
  903. }
  904. }
  905. // position the brick
  906. var x = minimumX,
  907. y = this.masonryHorizontal.rowHeight * smallRow;
  908. this._pushPosition( $brick, x, y );
  909. // apply setHeight to necessary columns
  910. var setWidth = minimumX + $brick.outerWidth(true),
  911. setSpan = this.masonryHorizontal.rows + 1 - len;
  912. for ( i=0; i < setSpan; i++ ) {
  913. this.masonryHorizontal.rowXs[ smallRow + i ] = setWidth;
  914. }
  915. },
  916. _masonryHorizontalGetContainerSize : function() {
  917. var containerWidth = Math.max.apply( Math, this.masonryHorizontal.rowXs );
  918. return { width: containerWidth };
  919. },
  920. _masonryHorizontalResizeChanged : function() {
  921. return this._checkIfSegmentsChanged(true);
  922. },
  923. // ====================== fitColumns ======================
  924. _fitColumnsReset : function() {
  925. this.fitColumns = {
  926. x : 0,
  927. y : 0,
  928. width : 0
  929. };
  930. },
  931. _fitColumnsLayout : function( $elems ) {
  932. var instance = this,
  933. containerHeight = this.element.height(),
  934. props = this.fitColumns;
  935. $elems.each( function() {
  936. var $this = $(this),
  937. atomW = $this.outerWidth(true),
  938. atomH = $this.outerHeight(true);
  939. if ( props.y !== 0 && atomH + props.y > containerHeight ) {
  940. // if this element cannot fit in the current column
  941. props.x = props.width;
  942. props.y = 0;
  943. }
  944. // position the atom
  945. instance._pushPosition( $this, props.x, props.y );
  946. props.width = Math.max( props.x + atomW, props.width );
  947. props.y += atomH;
  948. });
  949. },
  950. _fitColumnsGetContainerSize : function () {
  951. return { width : this.fitColumns.width };
  952. },
  953. _fitColumnsResizeChanged : function() {
  954. return true;
  955. },
  956. // ====================== cellsByColumn ======================
  957. _cellsByColumnReset : function() {
  958. this.cellsByColumn = {
  959. index : 0
  960. };
  961. // get this.cellsByColumn.columnWidth
  962. this._getSegments();
  963. // get this.cellsByColumn.rowHeight
  964. this._getSegments(true);
  965. },
  966. _cellsByColumnLayout : function( $elems ) {
  967. var instance = this,
  968. props = this.cellsByColumn;
  969. $elems.each( function(){
  970. var $this = $(this),
  971. col = Math.floor( props.index / props.rows ),
  972. row = props.index % props.rows,
  973. x = Math.round( ( col + 0.5 ) * props.columnWidth - $this.outerWidth(true) / 2 ),
  974. y = Math.round( ( row + 0.5 ) * props.rowHeight - $this.outerHeight(true) / 2 );
  975. instance._pushPosition( $this, x, y );
  976. props.index ++;
  977. });
  978. },
  979. _cellsByColumnGetContainerSize : function() {
  980. return { width : Math.ceil( this.$filteredAtoms.length / this.cellsByColumn.rows ) * this.cellsByColumn.columnWidth };
  981. },
  982. _cellsByColumnResizeChanged : function() {
  983. return this._checkIfSegmentsChanged(true);
  984. },
  985. // ====================== straightAcross ======================
  986. _straightAcrossReset : function() {
  987. this.straightAcross = {
  988. x : 0
  989. };
  990. },
  991. _straightAcrossLayout : function( $elems ) {
  992. var instance = this;
  993. $elems.each( function( i ){
  994. var $this = $(this);
  995. instance._pushPosition( $this, instance.straightAcross.x, 0 );
  996. instance.straightAcross.x += $this.outerWidth(true);
  997. });
  998. },
  999. _straightAcrossGetContainerSize : function() {
  1000. return { width : this.straightAcross.x };
  1001. },
  1002. _straightAcrossResizeChanged : function() {
  1003. return true;
  1004. }
  1005. };
  1006. // ======================= imagesLoaded Plugin ===============================
  1007. /*!
  1008. * jQuery imagesLoaded plugin v1.0.3
  1009. * http://github.com/desandro/imagesloaded
  1010. *
  1011. * MIT License. by Paul Irish et al.
  1012. */
  1013. // $('#my-container').imagesLoaded(myFunction)
  1014. // or
  1015. // $('img').imagesLoaded(myFunction)
  1016. // execute a callback when all images have loaded.
  1017. // needed because .load() doesn't work on cached images
  1018. // callback function gets image collection as argument
  1019. // `this` is the container
  1020. $.fn.imagesLoaded = function( callback ) {
  1021. var $this = this,
  1022. $images = $this.find('img').add( $this.filter('img') ),
  1023. len = $images.length,
  1024. blank = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
  1025. function triggerCallback() {
  1026. callback.call( $this, $images );
  1027. }
  1028. function imgLoaded( event ) {
  1029. if ( --len <= 0 && event.target.src !== blank ){
  1030. setTimeout( triggerCallback );
  1031. $images.unbind( 'load error', imgLoaded );
  1032. }
  1033. }
  1034. if ( !len ) {
  1035. triggerCallback();
  1036. }
  1037. $images.bind( 'load error', imgLoaded ).each( function() {
  1038. // cached images don't fire load sometimes, so we reset src.
  1039. if (this.complete || this.complete === undefined){
  1040. var src = this.src;
  1041. // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
  1042. // data uri bypasses webkit log warning (thx doug jones)
  1043. this.src = blank;
  1044. this.src = src;
  1045. }
  1046. });
  1047. return $this;
  1048. };
  1049. // helper function for logging errors
  1050. // $.error breaks jQuery chaining
  1051. var logError = function( message ) {
  1052. if ( window.console ) {
  1053. window.console.error( message );
  1054. }
  1055. };
  1056. // ======================= Plugin bridge ===============================
  1057. // leverages data method to either create or return $.Isotope constructor
  1058. // A bit from jQuery UI
  1059. // https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js
  1060. // A bit from jcarousel
  1061. // https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js
  1062. $.fn.isotope = function( options, callback ) {
  1063. if ( typeof options === 'string' ) {
  1064. // call method
  1065. var args = Array.prototype.slice.call( arguments, 1 );
  1066. this.each(function(){
  1067. var instance = $.data( this, 'isotope' );
  1068. if ( !instance ) {
  1069. logError( "cannot call methods on isotope prior to initialization; " +
  1070. "attempted to call method '" + options + "'" );
  1071. return;
  1072. }
  1073. if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
  1074. logError( "no such method '" + options + "' for isotope instance" );
  1075. return;
  1076. }
  1077. // apply method
  1078. instance[ options ].apply( instance, args );
  1079. });
  1080. } else {
  1081. this.each(function() {
  1082. var instance = $.data( this, 'isotope' );
  1083. if ( instance ) {
  1084. // apply options & init
  1085. instance.option( options );
  1086. instance._init( callback );
  1087. } else {
  1088. // initialize new instance
  1089. $.data( this, 'isotope', new $.Isotope( options, this, callback ) );
  1090. }
  1091. });
  1092. }
  1093. // return jQuery object
  1094. // so plugin methods do not have to
  1095. return this;
  1096. };
  1097. })( window, jQuery );