visibility.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311
  1. /*!
  2. * # Semantic UI 2.3.3 - Visibility
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Released under the MIT license
  7. * http://opensource.org/licenses/MIT
  8. *
  9. */
  10. ;(function ($, window, document, undefined) {
  11. 'use strict';
  12. window = (typeof window != 'undefined' && window.Math == Math)
  13. ? window
  14. : (typeof self != 'undefined' && self.Math == Math)
  15. ? self
  16. : Function('return this')()
  17. ;
  18. $.fn.visibility = function(parameters) {
  19. var
  20. $allModules = $(this),
  21. moduleSelector = $allModules.selector || '',
  22. time = new Date().getTime(),
  23. performance = [],
  24. query = arguments[0],
  25. methodInvoked = (typeof query == 'string'),
  26. queryArguments = [].slice.call(arguments, 1),
  27. returnedValue,
  28. moduleCount = $allModules.length,
  29. loadedCount = 0
  30. ;
  31. $allModules
  32. .each(function() {
  33. var
  34. settings = ( $.isPlainObject(parameters) )
  35. ? $.extend(true, {}, $.fn.visibility.settings, parameters)
  36. : $.extend({}, $.fn.visibility.settings),
  37. className = settings.className,
  38. namespace = settings.namespace,
  39. error = settings.error,
  40. metadata = settings.metadata,
  41. eventNamespace = '.' + namespace,
  42. moduleNamespace = 'module-' + namespace,
  43. $window = $(window),
  44. $module = $(this),
  45. $context = $(settings.context),
  46. $placeholder,
  47. selector = $module.selector || '',
  48. instance = $module.data(moduleNamespace),
  49. requestAnimationFrame = window.requestAnimationFrame
  50. || window.mozRequestAnimationFrame
  51. || window.webkitRequestAnimationFrame
  52. || window.msRequestAnimationFrame
  53. || function(callback) { setTimeout(callback, 0); },
  54. element = this,
  55. disabled = false,
  56. contextObserver,
  57. observer,
  58. module
  59. ;
  60. module = {
  61. initialize: function() {
  62. module.debug('Initializing', settings);
  63. module.setup.cache();
  64. if( module.should.trackChanges() ) {
  65. if(settings.type == 'image') {
  66. module.setup.image();
  67. }
  68. if(settings.type == 'fixed') {
  69. module.setup.fixed();
  70. }
  71. if(settings.observeChanges) {
  72. module.observeChanges();
  73. }
  74. module.bind.events();
  75. }
  76. module.save.position();
  77. if( !module.is.visible() ) {
  78. module.error(error.visible, $module);
  79. }
  80. if(settings.initialCheck) {
  81. module.checkVisibility();
  82. }
  83. module.instantiate();
  84. },
  85. instantiate: function() {
  86. module.debug('Storing instance', module);
  87. $module
  88. .data(moduleNamespace, module)
  89. ;
  90. instance = module;
  91. },
  92. destroy: function() {
  93. module.verbose('Destroying previous module');
  94. if(observer) {
  95. observer.disconnect();
  96. }
  97. if(contextObserver) {
  98. contextObserver.disconnect();
  99. }
  100. $window
  101. .off('load' + eventNamespace, module.event.load)
  102. .off('resize' + eventNamespace, module.event.resize)
  103. ;
  104. $context
  105. .off('scroll' + eventNamespace, module.event.scroll)
  106. .off('scrollchange' + eventNamespace, module.event.scrollchange)
  107. ;
  108. if(settings.type == 'fixed') {
  109. module.resetFixed();
  110. module.remove.placeholder();
  111. }
  112. $module
  113. .off(eventNamespace)
  114. .removeData(moduleNamespace)
  115. ;
  116. },
  117. observeChanges: function() {
  118. if('MutationObserver' in window) {
  119. contextObserver = new MutationObserver(module.event.contextChanged);
  120. observer = new MutationObserver(module.event.changed);
  121. contextObserver.observe(document, {
  122. childList : true,
  123. subtree : true
  124. });
  125. observer.observe(element, {
  126. childList : true,
  127. subtree : true
  128. });
  129. module.debug('Setting up mutation observer', observer);
  130. }
  131. },
  132. bind: {
  133. events: function() {
  134. module.verbose('Binding visibility events to scroll and resize');
  135. if(settings.refreshOnLoad) {
  136. $window
  137. .on('load' + eventNamespace, module.event.load)
  138. ;
  139. }
  140. $window
  141. .on('resize' + eventNamespace, module.event.resize)
  142. ;
  143. // pub/sub pattern
  144. $context
  145. .off('scroll' + eventNamespace)
  146. .on('scroll' + eventNamespace, module.event.scroll)
  147. .on('scrollchange' + eventNamespace, module.event.scrollchange)
  148. ;
  149. }
  150. },
  151. event: {
  152. changed: function(mutations) {
  153. module.verbose('DOM tree modified, updating visibility calculations');
  154. module.timer = setTimeout(function() {
  155. module.verbose('DOM tree modified, updating sticky menu');
  156. module.refresh();
  157. }, 100);
  158. },
  159. contextChanged: function(mutations) {
  160. [].forEach.call(mutations, function(mutation) {
  161. if(mutation.removedNodes) {
  162. [].forEach.call(mutation.removedNodes, function(node) {
  163. if(node == element || $(node).find(element).length > 0) {
  164. module.debug('Element removed from DOM, tearing down events');
  165. module.destroy();
  166. }
  167. });
  168. }
  169. });
  170. },
  171. resize: function() {
  172. module.debug('Window resized');
  173. if(settings.refreshOnResize) {
  174. requestAnimationFrame(module.refresh);
  175. }
  176. },
  177. load: function() {
  178. module.debug('Page finished loading');
  179. requestAnimationFrame(module.refresh);
  180. },
  181. // publishes scrollchange event on one scroll
  182. scroll: function() {
  183. if(settings.throttle) {
  184. clearTimeout(module.timer);
  185. module.timer = setTimeout(function() {
  186. $context.triggerHandler('scrollchange' + eventNamespace, [ $context.scrollTop() ]);
  187. }, settings.throttle);
  188. }
  189. else {
  190. requestAnimationFrame(function() {
  191. $context.triggerHandler('scrollchange' + eventNamespace, [ $context.scrollTop() ]);
  192. });
  193. }
  194. },
  195. // subscribes to scrollchange
  196. scrollchange: function(event, scrollPosition) {
  197. module.checkVisibility(scrollPosition);
  198. },
  199. },
  200. precache: function(images, callback) {
  201. if (!(images instanceof Array)) {
  202. images = [images];
  203. }
  204. var
  205. imagesLength = images.length,
  206. loadedCounter = 0,
  207. cache = [],
  208. cacheImage = document.createElement('img'),
  209. handleLoad = function() {
  210. loadedCounter++;
  211. if (loadedCounter >= images.length) {
  212. if ($.isFunction(callback)) {
  213. callback();
  214. }
  215. }
  216. }
  217. ;
  218. while (imagesLength--) {
  219. cacheImage = document.createElement('img');
  220. cacheImage.onload = handleLoad;
  221. cacheImage.onerror = handleLoad;
  222. cacheImage.src = images[imagesLength];
  223. cache.push(cacheImage);
  224. }
  225. },
  226. enableCallbacks: function() {
  227. module.debug('Allowing callbacks to occur');
  228. disabled = false;
  229. },
  230. disableCallbacks: function() {
  231. module.debug('Disabling all callbacks temporarily');
  232. disabled = true;
  233. },
  234. should: {
  235. trackChanges: function() {
  236. if(methodInvoked) {
  237. module.debug('One time query, no need to bind events');
  238. return false;
  239. }
  240. module.debug('Callbacks being attached');
  241. return true;
  242. }
  243. },
  244. setup: {
  245. cache: function() {
  246. module.cache = {
  247. occurred : {},
  248. screen : {},
  249. element : {},
  250. };
  251. },
  252. image: function() {
  253. var
  254. src = $module.data(metadata.src)
  255. ;
  256. if(src) {
  257. module.verbose('Lazy loading image', src);
  258. settings.once = true;
  259. settings.observeChanges = false;
  260. // show when top visible
  261. settings.onOnScreen = function() {
  262. module.debug('Image on screen', element);
  263. module.precache(src, function() {
  264. module.set.image(src, function() {
  265. loadedCount++;
  266. if(loadedCount == moduleCount) {
  267. settings.onAllLoaded.call(this);
  268. }
  269. settings.onLoad.call(this);
  270. });
  271. });
  272. };
  273. }
  274. },
  275. fixed: function() {
  276. module.debug('Setting up fixed');
  277. settings.once = false;
  278. settings.observeChanges = false;
  279. settings.initialCheck = true;
  280. settings.refreshOnLoad = true;
  281. if(!parameters.transition) {
  282. settings.transition = false;
  283. }
  284. module.create.placeholder();
  285. module.debug('Added placeholder', $placeholder);
  286. settings.onTopPassed = function() {
  287. module.debug('Element passed, adding fixed position', $module);
  288. module.show.placeholder();
  289. module.set.fixed();
  290. if(settings.transition) {
  291. if($.fn.transition !== undefined) {
  292. $module.transition(settings.transition, settings.duration);
  293. }
  294. }
  295. };
  296. settings.onTopPassedReverse = function() {
  297. module.debug('Element returned to position, removing fixed', $module);
  298. module.hide.placeholder();
  299. module.remove.fixed();
  300. };
  301. }
  302. },
  303. create: {
  304. placeholder: function() {
  305. module.verbose('Creating fixed position placeholder');
  306. $placeholder = $module
  307. .clone(false)
  308. .css('display', 'none')
  309. .addClass(className.placeholder)
  310. .insertAfter($module)
  311. ;
  312. }
  313. },
  314. show: {
  315. placeholder: function() {
  316. module.verbose('Showing placeholder');
  317. $placeholder
  318. .css('display', 'block')
  319. .css('visibility', 'hidden')
  320. ;
  321. }
  322. },
  323. hide: {
  324. placeholder: function() {
  325. module.verbose('Hiding placeholder');
  326. $placeholder
  327. .css('display', 'none')
  328. .css('visibility', '')
  329. ;
  330. }
  331. },
  332. set: {
  333. fixed: function() {
  334. module.verbose('Setting element to fixed position');
  335. $module
  336. .addClass(className.fixed)
  337. .css({
  338. position : 'fixed',
  339. top : settings.offset + 'px',
  340. left : 'auto',
  341. zIndex : settings.zIndex
  342. })
  343. ;
  344. settings.onFixed.call(element);
  345. },
  346. image: function(src, callback) {
  347. $module
  348. .attr('src', src)
  349. ;
  350. if(settings.transition) {
  351. if( $.fn.transition !== undefined) {
  352. if($module.hasClass(className.visible)) {
  353. module.debug('Transition already occurred on this image, skipping animation');
  354. return;
  355. }
  356. $module.transition(settings.transition, settings.duration, callback);
  357. }
  358. else {
  359. $module.fadeIn(settings.duration, callback);
  360. }
  361. }
  362. else {
  363. $module.show();
  364. }
  365. }
  366. },
  367. is: {
  368. onScreen: function() {
  369. var
  370. calculations = module.get.elementCalculations()
  371. ;
  372. return calculations.onScreen;
  373. },
  374. offScreen: function() {
  375. var
  376. calculations = module.get.elementCalculations()
  377. ;
  378. return calculations.offScreen;
  379. },
  380. visible: function() {
  381. if(module.cache && module.cache.element) {
  382. return !(module.cache.element.width === 0 && module.cache.element.offset.top === 0);
  383. }
  384. return false;
  385. },
  386. verticallyScrollableContext: function() {
  387. var
  388. overflowY = ($context.get(0) !== window)
  389. ? $context.css('overflow-y')
  390. : false
  391. ;
  392. return (overflowY == 'auto' || overflowY == 'scroll');
  393. },
  394. horizontallyScrollableContext: function() {
  395. var
  396. overflowX = ($context.get(0) !== window)
  397. ? $context.css('overflow-x')
  398. : false
  399. ;
  400. return (overflowX == 'auto' || overflowX == 'scroll');
  401. }
  402. },
  403. refresh: function() {
  404. module.debug('Refreshing constants (width/height)');
  405. if(settings.type == 'fixed') {
  406. module.resetFixed();
  407. }
  408. module.reset();
  409. module.save.position();
  410. if(settings.checkOnRefresh) {
  411. module.checkVisibility();
  412. }
  413. settings.onRefresh.call(element);
  414. },
  415. resetFixed: function () {
  416. module.remove.fixed();
  417. module.remove.occurred();
  418. },
  419. reset: function() {
  420. module.verbose('Resetting all cached values');
  421. if( $.isPlainObject(module.cache) ) {
  422. module.cache.screen = {};
  423. module.cache.element = {};
  424. }
  425. },
  426. checkVisibility: function(scroll) {
  427. module.verbose('Checking visibility of element', module.cache.element);
  428. if( !disabled && module.is.visible() ) {
  429. // save scroll position
  430. module.save.scroll(scroll);
  431. // update calculations derived from scroll
  432. module.save.calculations();
  433. // percentage
  434. module.passed();
  435. // reverse (must be first)
  436. module.passingReverse();
  437. module.topVisibleReverse();
  438. module.bottomVisibleReverse();
  439. module.topPassedReverse();
  440. module.bottomPassedReverse();
  441. // one time
  442. module.onScreen();
  443. module.offScreen();
  444. module.passing();
  445. module.topVisible();
  446. module.bottomVisible();
  447. module.topPassed();
  448. module.bottomPassed();
  449. // on update callback
  450. if(settings.onUpdate) {
  451. settings.onUpdate.call(element, module.get.elementCalculations());
  452. }
  453. }
  454. },
  455. passed: function(amount, newCallback) {
  456. var
  457. calculations = module.get.elementCalculations(),
  458. amountInPixels
  459. ;
  460. // assign callback
  461. if(amount && newCallback) {
  462. settings.onPassed[amount] = newCallback;
  463. }
  464. else if(amount !== undefined) {
  465. return (module.get.pixelsPassed(amount) > calculations.pixelsPassed);
  466. }
  467. else if(calculations.passing) {
  468. $.each(settings.onPassed, function(amount, callback) {
  469. if(calculations.bottomVisible || calculations.pixelsPassed > module.get.pixelsPassed(amount)) {
  470. module.execute(callback, amount);
  471. }
  472. else if(!settings.once) {
  473. module.remove.occurred(callback);
  474. }
  475. });
  476. }
  477. },
  478. onScreen: function(newCallback) {
  479. var
  480. calculations = module.get.elementCalculations(),
  481. callback = newCallback || settings.onOnScreen,
  482. callbackName = 'onScreen'
  483. ;
  484. if(newCallback) {
  485. module.debug('Adding callback for onScreen', newCallback);
  486. settings.onOnScreen = newCallback;
  487. }
  488. if(calculations.onScreen) {
  489. module.execute(callback, callbackName);
  490. }
  491. else if(!settings.once) {
  492. module.remove.occurred(callbackName);
  493. }
  494. if(newCallback !== undefined) {
  495. return calculations.onOnScreen;
  496. }
  497. },
  498. offScreen: function(newCallback) {
  499. var
  500. calculations = module.get.elementCalculations(),
  501. callback = newCallback || settings.onOffScreen,
  502. callbackName = 'offScreen'
  503. ;
  504. if(newCallback) {
  505. module.debug('Adding callback for offScreen', newCallback);
  506. settings.onOffScreen = newCallback;
  507. }
  508. if(calculations.offScreen) {
  509. module.execute(callback, callbackName);
  510. }
  511. else if(!settings.once) {
  512. module.remove.occurred(callbackName);
  513. }
  514. if(newCallback !== undefined) {
  515. return calculations.onOffScreen;
  516. }
  517. },
  518. passing: function(newCallback) {
  519. var
  520. calculations = module.get.elementCalculations(),
  521. callback = newCallback || settings.onPassing,
  522. callbackName = 'passing'
  523. ;
  524. if(newCallback) {
  525. module.debug('Adding callback for passing', newCallback);
  526. settings.onPassing = newCallback;
  527. }
  528. if(calculations.passing) {
  529. module.execute(callback, callbackName);
  530. }
  531. else if(!settings.once) {
  532. module.remove.occurred(callbackName);
  533. }
  534. if(newCallback !== undefined) {
  535. return calculations.passing;
  536. }
  537. },
  538. topVisible: function(newCallback) {
  539. var
  540. calculations = module.get.elementCalculations(),
  541. callback = newCallback || settings.onTopVisible,
  542. callbackName = 'topVisible'
  543. ;
  544. if(newCallback) {
  545. module.debug('Adding callback for top visible', newCallback);
  546. settings.onTopVisible = newCallback;
  547. }
  548. if(calculations.topVisible) {
  549. module.execute(callback, callbackName);
  550. }
  551. else if(!settings.once) {
  552. module.remove.occurred(callbackName);
  553. }
  554. if(newCallback === undefined) {
  555. return calculations.topVisible;
  556. }
  557. },
  558. bottomVisible: function(newCallback) {
  559. var
  560. calculations = module.get.elementCalculations(),
  561. callback = newCallback || settings.onBottomVisible,
  562. callbackName = 'bottomVisible'
  563. ;
  564. if(newCallback) {
  565. module.debug('Adding callback for bottom visible', newCallback);
  566. settings.onBottomVisible = newCallback;
  567. }
  568. if(calculations.bottomVisible) {
  569. module.execute(callback, callbackName);
  570. }
  571. else if(!settings.once) {
  572. module.remove.occurred(callbackName);
  573. }
  574. if(newCallback === undefined) {
  575. return calculations.bottomVisible;
  576. }
  577. },
  578. topPassed: function(newCallback) {
  579. var
  580. calculations = module.get.elementCalculations(),
  581. callback = newCallback || settings.onTopPassed,
  582. callbackName = 'topPassed'
  583. ;
  584. if(newCallback) {
  585. module.debug('Adding callback for top passed', newCallback);
  586. settings.onTopPassed = newCallback;
  587. }
  588. if(calculations.topPassed) {
  589. module.execute(callback, callbackName);
  590. }
  591. else if(!settings.once) {
  592. module.remove.occurred(callbackName);
  593. }
  594. if(newCallback === undefined) {
  595. return calculations.topPassed;
  596. }
  597. },
  598. bottomPassed: function(newCallback) {
  599. var
  600. calculations = module.get.elementCalculations(),
  601. callback = newCallback || settings.onBottomPassed,
  602. callbackName = 'bottomPassed'
  603. ;
  604. if(newCallback) {
  605. module.debug('Adding callback for bottom passed', newCallback);
  606. settings.onBottomPassed = newCallback;
  607. }
  608. if(calculations.bottomPassed) {
  609. module.execute(callback, callbackName);
  610. }
  611. else if(!settings.once) {
  612. module.remove.occurred(callbackName);
  613. }
  614. if(newCallback === undefined) {
  615. return calculations.bottomPassed;
  616. }
  617. },
  618. passingReverse: function(newCallback) {
  619. var
  620. calculations = module.get.elementCalculations(),
  621. callback = newCallback || settings.onPassingReverse,
  622. callbackName = 'passingReverse'
  623. ;
  624. if(newCallback) {
  625. module.debug('Adding callback for passing reverse', newCallback);
  626. settings.onPassingReverse = newCallback;
  627. }
  628. if(!calculations.passing) {
  629. if(module.get.occurred('passing')) {
  630. module.execute(callback, callbackName);
  631. }
  632. }
  633. else if(!settings.once) {
  634. module.remove.occurred(callbackName);
  635. }
  636. if(newCallback !== undefined) {
  637. return !calculations.passing;
  638. }
  639. },
  640. topVisibleReverse: function(newCallback) {
  641. var
  642. calculations = module.get.elementCalculations(),
  643. callback = newCallback || settings.onTopVisibleReverse,
  644. callbackName = 'topVisibleReverse'
  645. ;
  646. if(newCallback) {
  647. module.debug('Adding callback for top visible reverse', newCallback);
  648. settings.onTopVisibleReverse = newCallback;
  649. }
  650. if(!calculations.topVisible) {
  651. if(module.get.occurred('topVisible')) {
  652. module.execute(callback, callbackName);
  653. }
  654. }
  655. else if(!settings.once) {
  656. module.remove.occurred(callbackName);
  657. }
  658. if(newCallback === undefined) {
  659. return !calculations.topVisible;
  660. }
  661. },
  662. bottomVisibleReverse: function(newCallback) {
  663. var
  664. calculations = module.get.elementCalculations(),
  665. callback = newCallback || settings.onBottomVisibleReverse,
  666. callbackName = 'bottomVisibleReverse'
  667. ;
  668. if(newCallback) {
  669. module.debug('Adding callback for bottom visible reverse', newCallback);
  670. settings.onBottomVisibleReverse = newCallback;
  671. }
  672. if(!calculations.bottomVisible) {
  673. if(module.get.occurred('bottomVisible')) {
  674. module.execute(callback, callbackName);
  675. }
  676. }
  677. else if(!settings.once) {
  678. module.remove.occurred(callbackName);
  679. }
  680. if(newCallback === undefined) {
  681. return !calculations.bottomVisible;
  682. }
  683. },
  684. topPassedReverse: function(newCallback) {
  685. var
  686. calculations = module.get.elementCalculations(),
  687. callback = newCallback || settings.onTopPassedReverse,
  688. callbackName = 'topPassedReverse'
  689. ;
  690. if(newCallback) {
  691. module.debug('Adding callback for top passed reverse', newCallback);
  692. settings.onTopPassedReverse = newCallback;
  693. }
  694. if(!calculations.topPassed) {
  695. if(module.get.occurred('topPassed')) {
  696. module.execute(callback, callbackName);
  697. }
  698. }
  699. else if(!settings.once) {
  700. module.remove.occurred(callbackName);
  701. }
  702. if(newCallback === undefined) {
  703. return !calculations.onTopPassed;
  704. }
  705. },
  706. bottomPassedReverse: function(newCallback) {
  707. var
  708. calculations = module.get.elementCalculations(),
  709. callback = newCallback || settings.onBottomPassedReverse,
  710. callbackName = 'bottomPassedReverse'
  711. ;
  712. if(newCallback) {
  713. module.debug('Adding callback for bottom passed reverse', newCallback);
  714. settings.onBottomPassedReverse = newCallback;
  715. }
  716. if(!calculations.bottomPassed) {
  717. if(module.get.occurred('bottomPassed')) {
  718. module.execute(callback, callbackName);
  719. }
  720. }
  721. else if(!settings.once) {
  722. module.remove.occurred(callbackName);
  723. }
  724. if(newCallback === undefined) {
  725. return !calculations.bottomPassed;
  726. }
  727. },
  728. execute: function(callback, callbackName) {
  729. var
  730. calculations = module.get.elementCalculations(),
  731. screen = module.get.screenCalculations()
  732. ;
  733. callback = callback || false;
  734. if(callback) {
  735. if(settings.continuous) {
  736. module.debug('Callback being called continuously', callbackName, calculations);
  737. callback.call(element, calculations, screen);
  738. }
  739. else if(!module.get.occurred(callbackName)) {
  740. module.debug('Conditions met', callbackName, calculations);
  741. callback.call(element, calculations, screen);
  742. }
  743. }
  744. module.save.occurred(callbackName);
  745. },
  746. remove: {
  747. fixed: function() {
  748. module.debug('Removing fixed position');
  749. $module
  750. .removeClass(className.fixed)
  751. .css({
  752. position : '',
  753. top : '',
  754. left : '',
  755. zIndex : ''
  756. })
  757. ;
  758. settings.onUnfixed.call(element);
  759. },
  760. placeholder: function() {
  761. module.debug('Removing placeholder content');
  762. if($placeholder) {
  763. $placeholder.remove();
  764. }
  765. },
  766. occurred: function(callback) {
  767. if(callback) {
  768. var
  769. occurred = module.cache.occurred
  770. ;
  771. if(occurred[callback] !== undefined && occurred[callback] === true) {
  772. module.debug('Callback can now be called again', callback);
  773. module.cache.occurred[callback] = false;
  774. }
  775. }
  776. else {
  777. module.cache.occurred = {};
  778. }
  779. }
  780. },
  781. save: {
  782. calculations: function() {
  783. module.verbose('Saving all calculations necessary to determine positioning');
  784. module.save.direction();
  785. module.save.screenCalculations();
  786. module.save.elementCalculations();
  787. },
  788. occurred: function(callback) {
  789. if(callback) {
  790. if(module.cache.occurred[callback] === undefined || (module.cache.occurred[callback] !== true)) {
  791. module.verbose('Saving callback occurred', callback);
  792. module.cache.occurred[callback] = true;
  793. }
  794. }
  795. },
  796. scroll: function(scrollPosition) {
  797. scrollPosition = scrollPosition + settings.offset || $context.scrollTop() + settings.offset;
  798. module.cache.scroll = scrollPosition;
  799. },
  800. direction: function() {
  801. var
  802. scroll = module.get.scroll(),
  803. lastScroll = module.get.lastScroll(),
  804. direction
  805. ;
  806. if(scroll > lastScroll && lastScroll) {
  807. direction = 'down';
  808. }
  809. else if(scroll < lastScroll && lastScroll) {
  810. direction = 'up';
  811. }
  812. else {
  813. direction = 'static';
  814. }
  815. module.cache.direction = direction;
  816. return module.cache.direction;
  817. },
  818. elementPosition: function() {
  819. var
  820. element = module.cache.element,
  821. screen = module.get.screenSize()
  822. ;
  823. module.verbose('Saving element position');
  824. // (quicker than $.extend)
  825. element.fits = (element.height < screen.height);
  826. element.offset = $module.offset();
  827. element.width = $module.outerWidth();
  828. element.height = $module.outerHeight();
  829. // compensate for scroll in context
  830. if(module.is.verticallyScrollableContext()) {
  831. element.offset.top += $context.scrollTop() - $context.offset().top;
  832. }
  833. if(module.is.horizontallyScrollableContext()) {
  834. element.offset.left += $context.scrollLeft - $context.offset().left;
  835. }
  836. // store
  837. module.cache.element = element;
  838. return element;
  839. },
  840. elementCalculations: function() {
  841. var
  842. screen = module.get.screenCalculations(),
  843. element = module.get.elementPosition()
  844. ;
  845. // offset
  846. if(settings.includeMargin) {
  847. element.margin = {};
  848. element.margin.top = parseInt($module.css('margin-top'), 10);
  849. element.margin.bottom = parseInt($module.css('margin-bottom'), 10);
  850. element.top = element.offset.top - element.margin.top;
  851. element.bottom = element.offset.top + element.height + element.margin.bottom;
  852. }
  853. else {
  854. element.top = element.offset.top;
  855. element.bottom = element.offset.top + element.height;
  856. }
  857. // visibility
  858. element.topPassed = (screen.top >= element.top);
  859. element.bottomPassed = (screen.top >= element.bottom);
  860. element.topVisible = (screen.bottom >= element.top) && !element.topPassed;
  861. element.bottomVisible = (screen.bottom >= element.bottom) && !element.bottomPassed;
  862. element.pixelsPassed = 0;
  863. element.percentagePassed = 0;
  864. // meta calculations
  865. element.onScreen = ((element.topVisible || element.passing) && !element.bottomPassed);
  866. element.passing = (element.topPassed && !element.bottomPassed);
  867. element.offScreen = (!element.onScreen);
  868. // passing calculations
  869. if(element.passing) {
  870. element.pixelsPassed = (screen.top - element.top);
  871. element.percentagePassed = (screen.top - element.top) / element.height;
  872. }
  873. module.cache.element = element;
  874. module.verbose('Updated element calculations', element);
  875. return element;
  876. },
  877. screenCalculations: function() {
  878. var
  879. scroll = module.get.scroll()
  880. ;
  881. module.save.direction();
  882. module.cache.screen.top = scroll;
  883. module.cache.screen.bottom = scroll + module.cache.screen.height;
  884. return module.cache.screen;
  885. },
  886. screenSize: function() {
  887. module.verbose('Saving window position');
  888. module.cache.screen = {
  889. height: $context.height()
  890. };
  891. },
  892. position: function() {
  893. module.save.screenSize();
  894. module.save.elementPosition();
  895. }
  896. },
  897. get: {
  898. pixelsPassed: function(amount) {
  899. var
  900. element = module.get.elementCalculations()
  901. ;
  902. if(amount.search('%') > -1) {
  903. return ( element.height * (parseInt(amount, 10) / 100) );
  904. }
  905. return parseInt(amount, 10);
  906. },
  907. occurred: function(callback) {
  908. return (module.cache.occurred !== undefined)
  909. ? module.cache.occurred[callback] || false
  910. : false
  911. ;
  912. },
  913. direction: function() {
  914. if(module.cache.direction === undefined) {
  915. module.save.direction();
  916. }
  917. return module.cache.direction;
  918. },
  919. elementPosition: function() {
  920. if(module.cache.element === undefined) {
  921. module.save.elementPosition();
  922. }
  923. return module.cache.element;
  924. },
  925. elementCalculations: function() {
  926. if(module.cache.element === undefined) {
  927. module.save.elementCalculations();
  928. }
  929. return module.cache.element;
  930. },
  931. screenCalculations: function() {
  932. if(module.cache.screen === undefined) {
  933. module.save.screenCalculations();
  934. }
  935. return module.cache.screen;
  936. },
  937. screenSize: function() {
  938. if(module.cache.screen === undefined) {
  939. module.save.screenSize();
  940. }
  941. return module.cache.screen;
  942. },
  943. scroll: function() {
  944. if(module.cache.scroll === undefined) {
  945. module.save.scroll();
  946. }
  947. return module.cache.scroll;
  948. },
  949. lastScroll: function() {
  950. if(module.cache.screen === undefined) {
  951. module.debug('First scroll event, no last scroll could be found');
  952. return false;
  953. }
  954. return module.cache.screen.top;
  955. }
  956. },
  957. setting: function(name, value) {
  958. if( $.isPlainObject(name) ) {
  959. $.extend(true, settings, name);
  960. }
  961. else if(value !== undefined) {
  962. settings[name] = value;
  963. }
  964. else {
  965. return settings[name];
  966. }
  967. },
  968. internal: function(name, value) {
  969. if( $.isPlainObject(name) ) {
  970. $.extend(true, module, name);
  971. }
  972. else if(value !== undefined) {
  973. module[name] = value;
  974. }
  975. else {
  976. return module[name];
  977. }
  978. },
  979. debug: function() {
  980. if(!settings.silent && settings.debug) {
  981. if(settings.performance) {
  982. module.performance.log(arguments);
  983. }
  984. else {
  985. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  986. module.debug.apply(console, arguments);
  987. }
  988. }
  989. },
  990. verbose: function() {
  991. if(!settings.silent && settings.verbose && settings.debug) {
  992. if(settings.performance) {
  993. module.performance.log(arguments);
  994. }
  995. else {
  996. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  997. module.verbose.apply(console, arguments);
  998. }
  999. }
  1000. },
  1001. error: function() {
  1002. if(!settings.silent) {
  1003. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  1004. module.error.apply(console, arguments);
  1005. }
  1006. },
  1007. performance: {
  1008. log: function(message) {
  1009. var
  1010. currentTime,
  1011. executionTime,
  1012. previousTime
  1013. ;
  1014. if(settings.performance) {
  1015. currentTime = new Date().getTime();
  1016. previousTime = time || currentTime;
  1017. executionTime = currentTime - previousTime;
  1018. time = currentTime;
  1019. performance.push({
  1020. 'Name' : message[0],
  1021. 'Arguments' : [].slice.call(message, 1) || '',
  1022. 'Element' : element,
  1023. 'Execution Time' : executionTime
  1024. });
  1025. }
  1026. clearTimeout(module.performance.timer);
  1027. module.performance.timer = setTimeout(module.performance.display, 500);
  1028. },
  1029. display: function() {
  1030. var
  1031. title = settings.name + ':',
  1032. totalTime = 0
  1033. ;
  1034. time = false;
  1035. clearTimeout(module.performance.timer);
  1036. $.each(performance, function(index, data) {
  1037. totalTime += data['Execution Time'];
  1038. });
  1039. title += ' ' + totalTime + 'ms';
  1040. if(moduleSelector) {
  1041. title += ' \'' + moduleSelector + '\'';
  1042. }
  1043. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  1044. console.groupCollapsed(title);
  1045. if(console.table) {
  1046. console.table(performance);
  1047. }
  1048. else {
  1049. $.each(performance, function(index, data) {
  1050. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  1051. });
  1052. }
  1053. console.groupEnd();
  1054. }
  1055. performance = [];
  1056. }
  1057. },
  1058. invoke: function(query, passedArguments, context) {
  1059. var
  1060. object = instance,
  1061. maxDepth,
  1062. found,
  1063. response
  1064. ;
  1065. passedArguments = passedArguments || queryArguments;
  1066. context = element || context;
  1067. if(typeof query == 'string' && object !== undefined) {
  1068. query = query.split(/[\. ]/);
  1069. maxDepth = query.length - 1;
  1070. $.each(query, function(depth, value) {
  1071. var camelCaseValue = (depth != maxDepth)
  1072. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  1073. : query
  1074. ;
  1075. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  1076. object = object[camelCaseValue];
  1077. }
  1078. else if( object[camelCaseValue] !== undefined ) {
  1079. found = object[camelCaseValue];
  1080. return false;
  1081. }
  1082. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  1083. object = object[value];
  1084. }
  1085. else if( object[value] !== undefined ) {
  1086. found = object[value];
  1087. return false;
  1088. }
  1089. else {
  1090. module.error(error.method, query);
  1091. return false;
  1092. }
  1093. });
  1094. }
  1095. if ( $.isFunction( found ) ) {
  1096. response = found.apply(context, passedArguments);
  1097. }
  1098. else if(found !== undefined) {
  1099. response = found;
  1100. }
  1101. if($.isArray(returnedValue)) {
  1102. returnedValue.push(response);
  1103. }
  1104. else if(returnedValue !== undefined) {
  1105. returnedValue = [returnedValue, response];
  1106. }
  1107. else if(response !== undefined) {
  1108. returnedValue = response;
  1109. }
  1110. return found;
  1111. }
  1112. };
  1113. if(methodInvoked) {
  1114. if(instance === undefined) {
  1115. module.initialize();
  1116. }
  1117. instance.save.scroll();
  1118. instance.save.calculations();
  1119. module.invoke(query);
  1120. }
  1121. else {
  1122. if(instance !== undefined) {
  1123. instance.invoke('destroy');
  1124. }
  1125. module.initialize();
  1126. }
  1127. })
  1128. ;
  1129. return (returnedValue !== undefined)
  1130. ? returnedValue
  1131. : this
  1132. ;
  1133. };
  1134. $.fn.visibility.settings = {
  1135. name : 'Visibility',
  1136. namespace : 'visibility',
  1137. debug : false,
  1138. verbose : false,
  1139. performance : true,
  1140. // whether to use mutation observers to follow changes
  1141. observeChanges : true,
  1142. // check position immediately on init
  1143. initialCheck : true,
  1144. // whether to refresh calculations after all page images load
  1145. refreshOnLoad : true,
  1146. // whether to refresh calculations after page resize event
  1147. refreshOnResize : true,
  1148. // should call callbacks on refresh event (resize, etc)
  1149. checkOnRefresh : true,
  1150. // callback should only occur one time
  1151. once : true,
  1152. // callback should fire continuously whe evaluates to true
  1153. continuous : false,
  1154. // offset to use with scroll top
  1155. offset : 0,
  1156. // whether to include margin in elements position
  1157. includeMargin : false,
  1158. // scroll context for visibility checks
  1159. context : window,
  1160. // visibility check delay in ms (defaults to animationFrame)
  1161. throttle : false,
  1162. // special visibility type (image, fixed)
  1163. type : false,
  1164. // z-index to use with visibility 'fixed'
  1165. zIndex : '10',
  1166. // image only animation settings
  1167. transition : 'fade in',
  1168. duration : 1000,
  1169. // array of callbacks for percentage
  1170. onPassed : {},
  1171. // standard callbacks
  1172. onOnScreen : false,
  1173. onOffScreen : false,
  1174. onPassing : false,
  1175. onTopVisible : false,
  1176. onBottomVisible : false,
  1177. onTopPassed : false,
  1178. onBottomPassed : false,
  1179. // reverse callbacks
  1180. onPassingReverse : false,
  1181. onTopVisibleReverse : false,
  1182. onBottomVisibleReverse : false,
  1183. onTopPassedReverse : false,
  1184. onBottomPassedReverse : false,
  1185. // special callbacks for image
  1186. onLoad : function() {},
  1187. onAllLoaded : function() {},
  1188. // special callbacks for fixed position
  1189. onFixed : function() {},
  1190. onUnfixed : function() {},
  1191. // utility callbacks
  1192. onUpdate : false, // disabled by default for performance
  1193. onRefresh : function(){},
  1194. metadata : {
  1195. src: 'src'
  1196. },
  1197. className: {
  1198. fixed : 'fixed',
  1199. placeholder : 'placeholder',
  1200. visible : 'visible'
  1201. },
  1202. error : {
  1203. method : 'The method you called is not defined.',
  1204. visible : 'Element is hidden, you must call refresh after element becomes visible'
  1205. }
  1206. };
  1207. })( jQuery, window, document );