visibility.js 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. /*!
  2. * # Semantic UI 2.2.6 - 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. $module.transition(settings.transition, settings.duration, callback);
  353. }
  354. else {
  355. $module.fadeIn(settings.duration, callback);
  356. }
  357. }
  358. else {
  359. $module.show();
  360. }
  361. }
  362. },
  363. is: {
  364. onScreen: function() {
  365. var
  366. calculations = module.get.elementCalculations()
  367. ;
  368. return calculations.onScreen;
  369. },
  370. offScreen: function() {
  371. var
  372. calculations = module.get.elementCalculations()
  373. ;
  374. return calculations.offScreen;
  375. },
  376. visible: function() {
  377. if(module.cache && module.cache.element) {
  378. return !(module.cache.element.width === 0 && module.cache.element.offset.top === 0);
  379. }
  380. return false;
  381. }
  382. },
  383. refresh: function() {
  384. module.debug('Refreshing constants (width/height)');
  385. if(settings.type == 'fixed') {
  386. module.resetFixed();
  387. }
  388. module.reset();
  389. module.save.position();
  390. if(settings.checkOnRefresh) {
  391. module.checkVisibility();
  392. }
  393. settings.onRefresh.call(element);
  394. },
  395. resetFixed: function () {
  396. module.remove.fixed();
  397. module.remove.occurred();
  398. },
  399. reset: function() {
  400. module.verbose('Resetting all cached values');
  401. if( $.isPlainObject(module.cache) ) {
  402. module.cache.screen = {};
  403. module.cache.element = {};
  404. }
  405. },
  406. checkVisibility: function(scroll) {
  407. module.verbose('Checking visibility of element', module.cache.element);
  408. if( !disabled && module.is.visible() ) {
  409. // save scroll position
  410. module.save.scroll(scroll);
  411. // update calculations derived from scroll
  412. module.save.calculations();
  413. // percentage
  414. module.passed();
  415. // reverse (must be first)
  416. module.passingReverse();
  417. module.topVisibleReverse();
  418. module.bottomVisibleReverse();
  419. module.topPassedReverse();
  420. module.bottomPassedReverse();
  421. // one time
  422. module.onScreen();
  423. module.offScreen();
  424. module.passing();
  425. module.topVisible();
  426. module.bottomVisible();
  427. module.topPassed();
  428. module.bottomPassed();
  429. // on update callback
  430. if(settings.onUpdate) {
  431. settings.onUpdate.call(element, module.get.elementCalculations());
  432. }
  433. }
  434. },
  435. passed: function(amount, newCallback) {
  436. var
  437. calculations = module.get.elementCalculations(),
  438. amountInPixels
  439. ;
  440. // assign callback
  441. if(amount && newCallback) {
  442. settings.onPassed[amount] = newCallback;
  443. }
  444. else if(amount !== undefined) {
  445. return (module.get.pixelsPassed(amount) > calculations.pixelsPassed);
  446. }
  447. else if(calculations.passing) {
  448. $.each(settings.onPassed, function(amount, callback) {
  449. if(calculations.bottomVisible || calculations.pixelsPassed > module.get.pixelsPassed(amount)) {
  450. module.execute(callback, amount);
  451. }
  452. else if(!settings.once) {
  453. module.remove.occurred(callback);
  454. }
  455. });
  456. }
  457. },
  458. onScreen: function(newCallback) {
  459. var
  460. calculations = module.get.elementCalculations(),
  461. callback = newCallback || settings.onOnScreen,
  462. callbackName = 'onScreen'
  463. ;
  464. if(newCallback) {
  465. module.debug('Adding callback for onScreen', newCallback);
  466. settings.onOnScreen = newCallback;
  467. }
  468. if(calculations.onScreen) {
  469. module.execute(callback, callbackName);
  470. }
  471. else if(!settings.once) {
  472. module.remove.occurred(callbackName);
  473. }
  474. if(newCallback !== undefined) {
  475. return calculations.onOnScreen;
  476. }
  477. },
  478. offScreen: function(newCallback) {
  479. var
  480. calculations = module.get.elementCalculations(),
  481. callback = newCallback || settings.onOffScreen,
  482. callbackName = 'offScreen'
  483. ;
  484. if(newCallback) {
  485. module.debug('Adding callback for offScreen', newCallback);
  486. settings.onOffScreen = newCallback;
  487. }
  488. if(calculations.offScreen) {
  489. module.execute(callback, callbackName);
  490. }
  491. else if(!settings.once) {
  492. module.remove.occurred(callbackName);
  493. }
  494. if(newCallback !== undefined) {
  495. return calculations.onOffScreen;
  496. }
  497. },
  498. passing: function(newCallback) {
  499. var
  500. calculations = module.get.elementCalculations(),
  501. callback = newCallback || settings.onPassing,
  502. callbackName = 'passing'
  503. ;
  504. if(newCallback) {
  505. module.debug('Adding callback for passing', newCallback);
  506. settings.onPassing = newCallback;
  507. }
  508. if(calculations.passing) {
  509. module.execute(callback, callbackName);
  510. }
  511. else if(!settings.once) {
  512. module.remove.occurred(callbackName);
  513. }
  514. if(newCallback !== undefined) {
  515. return calculations.passing;
  516. }
  517. },
  518. topVisible: function(newCallback) {
  519. var
  520. calculations = module.get.elementCalculations(),
  521. callback = newCallback || settings.onTopVisible,
  522. callbackName = 'topVisible'
  523. ;
  524. if(newCallback) {
  525. module.debug('Adding callback for top visible', newCallback);
  526. settings.onTopVisible = newCallback;
  527. }
  528. if(calculations.topVisible) {
  529. module.execute(callback, callbackName);
  530. }
  531. else if(!settings.once) {
  532. module.remove.occurred(callbackName);
  533. }
  534. if(newCallback === undefined) {
  535. return calculations.topVisible;
  536. }
  537. },
  538. bottomVisible: function(newCallback) {
  539. var
  540. calculations = module.get.elementCalculations(),
  541. callback = newCallback || settings.onBottomVisible,
  542. callbackName = 'bottomVisible'
  543. ;
  544. if(newCallback) {
  545. module.debug('Adding callback for bottom visible', newCallback);
  546. settings.onBottomVisible = newCallback;
  547. }
  548. if(calculations.bottomVisible) {
  549. module.execute(callback, callbackName);
  550. }
  551. else if(!settings.once) {
  552. module.remove.occurred(callbackName);
  553. }
  554. if(newCallback === undefined) {
  555. return calculations.bottomVisible;
  556. }
  557. },
  558. topPassed: function(newCallback) {
  559. var
  560. calculations = module.get.elementCalculations(),
  561. callback = newCallback || settings.onTopPassed,
  562. callbackName = 'topPassed'
  563. ;
  564. if(newCallback) {
  565. module.debug('Adding callback for top passed', newCallback);
  566. settings.onTopPassed = newCallback;
  567. }
  568. if(calculations.topPassed) {
  569. module.execute(callback, callbackName);
  570. }
  571. else if(!settings.once) {
  572. module.remove.occurred(callbackName);
  573. }
  574. if(newCallback === undefined) {
  575. return calculations.topPassed;
  576. }
  577. },
  578. bottomPassed: function(newCallback) {
  579. var
  580. calculations = module.get.elementCalculations(),
  581. callback = newCallback || settings.onBottomPassed,
  582. callbackName = 'bottomPassed'
  583. ;
  584. if(newCallback) {
  585. module.debug('Adding callback for bottom passed', newCallback);
  586. settings.onBottomPassed = newCallback;
  587. }
  588. if(calculations.bottomPassed) {
  589. module.execute(callback, callbackName);
  590. }
  591. else if(!settings.once) {
  592. module.remove.occurred(callbackName);
  593. }
  594. if(newCallback === undefined) {
  595. return calculations.bottomPassed;
  596. }
  597. },
  598. passingReverse: function(newCallback) {
  599. var
  600. calculations = module.get.elementCalculations(),
  601. callback = newCallback || settings.onPassingReverse,
  602. callbackName = 'passingReverse'
  603. ;
  604. if(newCallback) {
  605. module.debug('Adding callback for passing reverse', newCallback);
  606. settings.onPassingReverse = newCallback;
  607. }
  608. if(!calculations.passing) {
  609. if(module.get.occurred('passing')) {
  610. module.execute(callback, callbackName);
  611. }
  612. }
  613. else if(!settings.once) {
  614. module.remove.occurred(callbackName);
  615. }
  616. if(newCallback !== undefined) {
  617. return !calculations.passing;
  618. }
  619. },
  620. topVisibleReverse: function(newCallback) {
  621. var
  622. calculations = module.get.elementCalculations(),
  623. callback = newCallback || settings.onTopVisibleReverse,
  624. callbackName = 'topVisibleReverse'
  625. ;
  626. if(newCallback) {
  627. module.debug('Adding callback for top visible reverse', newCallback);
  628. settings.onTopVisibleReverse = newCallback;
  629. }
  630. if(!calculations.topVisible) {
  631. if(module.get.occurred('topVisible')) {
  632. module.execute(callback, callbackName);
  633. }
  634. }
  635. else if(!settings.once) {
  636. module.remove.occurred(callbackName);
  637. }
  638. if(newCallback === undefined) {
  639. return !calculations.topVisible;
  640. }
  641. },
  642. bottomVisibleReverse: function(newCallback) {
  643. var
  644. calculations = module.get.elementCalculations(),
  645. callback = newCallback || settings.onBottomVisibleReverse,
  646. callbackName = 'bottomVisibleReverse'
  647. ;
  648. if(newCallback) {
  649. module.debug('Adding callback for bottom visible reverse', newCallback);
  650. settings.onBottomVisibleReverse = newCallback;
  651. }
  652. if(!calculations.bottomVisible) {
  653. if(module.get.occurred('bottomVisible')) {
  654. module.execute(callback, callbackName);
  655. }
  656. }
  657. else if(!settings.once) {
  658. module.remove.occurred(callbackName);
  659. }
  660. if(newCallback === undefined) {
  661. return !calculations.bottomVisible;
  662. }
  663. },
  664. topPassedReverse: function(newCallback) {
  665. var
  666. calculations = module.get.elementCalculations(),
  667. callback = newCallback || settings.onTopPassedReverse,
  668. callbackName = 'topPassedReverse'
  669. ;
  670. if(newCallback) {
  671. module.debug('Adding callback for top passed reverse', newCallback);
  672. settings.onTopPassedReverse = newCallback;
  673. }
  674. if(!calculations.topPassed) {
  675. if(module.get.occurred('topPassed')) {
  676. module.execute(callback, callbackName);
  677. }
  678. }
  679. else if(!settings.once) {
  680. module.remove.occurred(callbackName);
  681. }
  682. if(newCallback === undefined) {
  683. return !calculations.onTopPassed;
  684. }
  685. },
  686. bottomPassedReverse: function(newCallback) {
  687. var
  688. calculations = module.get.elementCalculations(),
  689. callback = newCallback || settings.onBottomPassedReverse,
  690. callbackName = 'bottomPassedReverse'
  691. ;
  692. if(newCallback) {
  693. module.debug('Adding callback for bottom passed reverse', newCallback);
  694. settings.onBottomPassedReverse = newCallback;
  695. }
  696. if(!calculations.bottomPassed) {
  697. if(module.get.occurred('bottomPassed')) {
  698. module.execute(callback, callbackName);
  699. }
  700. }
  701. else if(!settings.once) {
  702. module.remove.occurred(callbackName);
  703. }
  704. if(newCallback === undefined) {
  705. return !calculations.bottomPassed;
  706. }
  707. },
  708. execute: function(callback, callbackName) {
  709. var
  710. calculations = module.get.elementCalculations(),
  711. screen = module.get.screenCalculations()
  712. ;
  713. callback = callback || false;
  714. if(callback) {
  715. if(settings.continuous) {
  716. module.debug('Callback being called continuously', callbackName, calculations);
  717. callback.call(element, calculations, screen);
  718. }
  719. else if(!module.get.occurred(callbackName)) {
  720. module.debug('Conditions met', callbackName, calculations);
  721. callback.call(element, calculations, screen);
  722. }
  723. }
  724. module.save.occurred(callbackName);
  725. },
  726. remove: {
  727. fixed: function() {
  728. module.debug('Removing fixed position');
  729. $module
  730. .removeClass(className.fixed)
  731. .css({
  732. position : '',
  733. top : '',
  734. left : '',
  735. zIndex : ''
  736. })
  737. ;
  738. settings.onUnfixed.call(element);
  739. },
  740. placeholder: function() {
  741. module.debug('Removing placeholder content');
  742. if($placeholder) {
  743. $placeholder.remove();
  744. }
  745. },
  746. occurred: function(callback) {
  747. if(callback) {
  748. var
  749. occurred = module.cache.occurred
  750. ;
  751. if(occurred[callback] !== undefined && occurred[callback] === true) {
  752. module.debug('Callback can now be called again', callback);
  753. module.cache.occurred[callback] = false;
  754. }
  755. }
  756. else {
  757. module.cache.occurred = {};
  758. }
  759. }
  760. },
  761. save: {
  762. calculations: function() {
  763. module.verbose('Saving all calculations necessary to determine positioning');
  764. module.save.direction();
  765. module.save.screenCalculations();
  766. module.save.elementCalculations();
  767. },
  768. occurred: function(callback) {
  769. if(callback) {
  770. if(module.cache.occurred[callback] === undefined || (module.cache.occurred[callback] !== true)) {
  771. module.verbose('Saving callback occurred', callback);
  772. module.cache.occurred[callback] = true;
  773. }
  774. }
  775. },
  776. scroll: function(scrollPosition) {
  777. scrollPosition = scrollPosition + settings.offset || $context.scrollTop() + settings.offset;
  778. module.cache.scroll = scrollPosition;
  779. },
  780. direction: function() {
  781. var
  782. scroll = module.get.scroll(),
  783. lastScroll = module.get.lastScroll(),
  784. direction
  785. ;
  786. if(scroll > lastScroll && lastScroll) {
  787. direction = 'down';
  788. }
  789. else if(scroll < lastScroll && lastScroll) {
  790. direction = 'up';
  791. }
  792. else {
  793. direction = 'static';
  794. }
  795. module.cache.direction = direction;
  796. return module.cache.direction;
  797. },
  798. elementPosition: function() {
  799. var
  800. element = module.cache.element,
  801. screen = module.get.screenSize()
  802. ;
  803. module.verbose('Saving element position');
  804. // (quicker than $.extend)
  805. element.fits = (element.height < screen.height);
  806. element.offset = $module.offset();
  807. element.width = $module.outerWidth();
  808. element.height = $module.outerHeight();
  809. // store
  810. module.cache.element = element;
  811. return element;
  812. },
  813. elementCalculations: function() {
  814. var
  815. screen = module.get.screenCalculations(),
  816. element = module.get.elementPosition()
  817. ;
  818. // offset
  819. if(settings.includeMargin) {
  820. element.margin = {};
  821. element.margin.top = parseInt($module.css('margin-top'), 10);
  822. element.margin.bottom = parseInt($module.css('margin-bottom'), 10);
  823. element.top = element.offset.top - element.margin.top;
  824. element.bottom = element.offset.top + element.height + element.margin.bottom;
  825. }
  826. else {
  827. element.top = element.offset.top;
  828. element.bottom = element.offset.top + element.height;
  829. }
  830. // visibility
  831. element.topVisible = (screen.bottom >= element.top);
  832. element.topPassed = (screen.top >= element.top);
  833. element.bottomVisible = (screen.bottom >= element.bottom);
  834. element.bottomPassed = (screen.top >= element.bottom);
  835. element.pixelsPassed = 0;
  836. element.percentagePassed = 0;
  837. // meta calculations
  838. element.onScreen = (element.topVisible && !element.bottomPassed);
  839. element.passing = (element.topPassed && !element.bottomPassed);
  840. element.offScreen = (!element.onScreen);
  841. // passing calculations
  842. if(element.passing) {
  843. element.pixelsPassed = (screen.top - element.top);
  844. element.percentagePassed = (screen.top - element.top) / element.height;
  845. }
  846. module.cache.element = element;
  847. module.verbose('Updated element calculations', element);
  848. return element;
  849. },
  850. screenCalculations: function() {
  851. var
  852. scroll = module.get.scroll()
  853. ;
  854. module.save.direction();
  855. module.cache.screen.top = scroll;
  856. module.cache.screen.bottom = scroll + module.cache.screen.height;
  857. return module.cache.screen;
  858. },
  859. screenSize: function() {
  860. module.verbose('Saving window position');
  861. module.cache.screen = {
  862. height: $context.height()
  863. };
  864. },
  865. position: function() {
  866. module.save.screenSize();
  867. module.save.elementPosition();
  868. }
  869. },
  870. get: {
  871. pixelsPassed: function(amount) {
  872. var
  873. element = module.get.elementCalculations()
  874. ;
  875. if(amount.search('%') > -1) {
  876. return ( element.height * (parseInt(amount, 10) / 100) );
  877. }
  878. return parseInt(amount, 10);
  879. },
  880. occurred: function(callback) {
  881. return (module.cache.occurred !== undefined)
  882. ? module.cache.occurred[callback] || false
  883. : false
  884. ;
  885. },
  886. direction: function() {
  887. if(module.cache.direction === undefined) {
  888. module.save.direction();
  889. }
  890. return module.cache.direction;
  891. },
  892. elementPosition: function() {
  893. if(module.cache.element === undefined) {
  894. module.save.elementPosition();
  895. }
  896. return module.cache.element;
  897. },
  898. elementCalculations: function() {
  899. if(module.cache.element === undefined) {
  900. module.save.elementCalculations();
  901. }
  902. return module.cache.element;
  903. },
  904. screenCalculations: function() {
  905. if(module.cache.screen === undefined) {
  906. module.save.screenCalculations();
  907. }
  908. return module.cache.screen;
  909. },
  910. screenSize: function() {
  911. if(module.cache.screen === undefined) {
  912. module.save.screenSize();
  913. }
  914. return module.cache.screen;
  915. },
  916. scroll: function() {
  917. if(module.cache.scroll === undefined) {
  918. module.save.scroll();
  919. }
  920. return module.cache.scroll;
  921. },
  922. lastScroll: function() {
  923. if(module.cache.screen === undefined) {
  924. module.debug('First scroll event, no last scroll could be found');
  925. return false;
  926. }
  927. return module.cache.screen.top;
  928. }
  929. },
  930. setting: function(name, value) {
  931. if( $.isPlainObject(name) ) {
  932. $.extend(true, settings, name);
  933. }
  934. else if(value !== undefined) {
  935. settings[name] = value;
  936. }
  937. else {
  938. return settings[name];
  939. }
  940. },
  941. internal: function(name, value) {
  942. if( $.isPlainObject(name) ) {
  943. $.extend(true, module, name);
  944. }
  945. else if(value !== undefined) {
  946. module[name] = value;
  947. }
  948. else {
  949. return module[name];
  950. }
  951. },
  952. debug: function() {
  953. if(!settings.silent && settings.debug) {
  954. if(settings.performance) {
  955. module.performance.log(arguments);
  956. }
  957. else {
  958. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  959. module.debug.apply(console, arguments);
  960. }
  961. }
  962. },
  963. verbose: function() {
  964. if(!settings.silent && settings.verbose && settings.debug) {
  965. if(settings.performance) {
  966. module.performance.log(arguments);
  967. }
  968. else {
  969. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  970. module.verbose.apply(console, arguments);
  971. }
  972. }
  973. },
  974. error: function() {
  975. if(!settings.silent) {
  976. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  977. module.error.apply(console, arguments);
  978. }
  979. },
  980. performance: {
  981. log: function(message) {
  982. var
  983. currentTime,
  984. executionTime,
  985. previousTime
  986. ;
  987. if(settings.performance) {
  988. currentTime = new Date().getTime();
  989. previousTime = time || currentTime;
  990. executionTime = currentTime - previousTime;
  991. time = currentTime;
  992. performance.push({
  993. 'Name' : message[0],
  994. 'Arguments' : [].slice.call(message, 1) || '',
  995. 'Element' : element,
  996. 'Execution Time' : executionTime
  997. });
  998. }
  999. clearTimeout(module.performance.timer);
  1000. module.performance.timer = setTimeout(module.performance.display, 500);
  1001. },
  1002. display: function() {
  1003. var
  1004. title = settings.name + ':',
  1005. totalTime = 0
  1006. ;
  1007. time = false;
  1008. clearTimeout(module.performance.timer);
  1009. $.each(performance, function(index, data) {
  1010. totalTime += data['Execution Time'];
  1011. });
  1012. title += ' ' + totalTime + 'ms';
  1013. if(moduleSelector) {
  1014. title += ' \'' + moduleSelector + '\'';
  1015. }
  1016. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  1017. console.groupCollapsed(title);
  1018. if(console.table) {
  1019. console.table(performance);
  1020. }
  1021. else {
  1022. $.each(performance, function(index, data) {
  1023. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  1024. });
  1025. }
  1026. console.groupEnd();
  1027. }
  1028. performance = [];
  1029. }
  1030. },
  1031. invoke: function(query, passedArguments, context) {
  1032. var
  1033. object = instance,
  1034. maxDepth,
  1035. found,
  1036. response
  1037. ;
  1038. passedArguments = passedArguments || queryArguments;
  1039. context = element || context;
  1040. if(typeof query == 'string' && object !== undefined) {
  1041. query = query.split(/[\. ]/);
  1042. maxDepth = query.length - 1;
  1043. $.each(query, function(depth, value) {
  1044. var camelCaseValue = (depth != maxDepth)
  1045. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  1046. : query
  1047. ;
  1048. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  1049. object = object[camelCaseValue];
  1050. }
  1051. else if( object[camelCaseValue] !== undefined ) {
  1052. found = object[camelCaseValue];
  1053. return false;
  1054. }
  1055. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  1056. object = object[value];
  1057. }
  1058. else if( object[value] !== undefined ) {
  1059. found = object[value];
  1060. return false;
  1061. }
  1062. else {
  1063. module.error(error.method, query);
  1064. return false;
  1065. }
  1066. });
  1067. }
  1068. if ( $.isFunction( found ) ) {
  1069. response = found.apply(context, passedArguments);
  1070. }
  1071. else if(found !== undefined) {
  1072. response = found;
  1073. }
  1074. if($.isArray(returnedValue)) {
  1075. returnedValue.push(response);
  1076. }
  1077. else if(returnedValue !== undefined) {
  1078. returnedValue = [returnedValue, response];
  1079. }
  1080. else if(response !== undefined) {
  1081. returnedValue = response;
  1082. }
  1083. return found;
  1084. }
  1085. };
  1086. if(methodInvoked) {
  1087. if(instance === undefined) {
  1088. module.initialize();
  1089. }
  1090. instance.save.scroll();
  1091. instance.save.calculations();
  1092. module.invoke(query);
  1093. }
  1094. else {
  1095. if(instance !== undefined) {
  1096. instance.invoke('destroy');
  1097. }
  1098. module.initialize();
  1099. }
  1100. })
  1101. ;
  1102. return (returnedValue !== undefined)
  1103. ? returnedValue
  1104. : this
  1105. ;
  1106. };
  1107. $.fn.visibility.settings = {
  1108. name : 'Visibility',
  1109. namespace : 'visibility',
  1110. debug : false,
  1111. verbose : false,
  1112. performance : true,
  1113. // whether to use mutation observers to follow changes
  1114. observeChanges : true,
  1115. // check position immediately on init
  1116. initialCheck : true,
  1117. // whether to refresh calculations after all page images load
  1118. refreshOnLoad : true,
  1119. // whether to refresh calculations after page resize event
  1120. refreshOnResize : true,
  1121. // should call callbacks on refresh event (resize, etc)
  1122. checkOnRefresh : true,
  1123. // callback should only occur one time
  1124. once : true,
  1125. // callback should fire continuously whe evaluates to true
  1126. continuous : false,
  1127. // offset to use with scroll top
  1128. offset : 0,
  1129. // whether to include margin in elements position
  1130. includeMargin : false,
  1131. // scroll context for visibility checks
  1132. context : window,
  1133. // visibility check delay in ms (defaults to animationFrame)
  1134. throttle : false,
  1135. // special visibility type (image, fixed)
  1136. type : false,
  1137. // z-index to use with visibility 'fixed'
  1138. zIndex : '10',
  1139. // image only animation settings
  1140. transition : 'fade in',
  1141. duration : 1000,
  1142. // array of callbacks for percentage
  1143. onPassed : {},
  1144. // standard callbacks
  1145. onOnScreen : false,
  1146. onOffScreen : false,
  1147. onPassing : false,
  1148. onTopVisible : false,
  1149. onBottomVisible : false,
  1150. onTopPassed : false,
  1151. onBottomPassed : false,
  1152. // reverse callbacks
  1153. onPassingReverse : false,
  1154. onTopVisibleReverse : false,
  1155. onBottomVisibleReverse : false,
  1156. onTopPassedReverse : false,
  1157. onBottomPassedReverse : false,
  1158. // special callbacks for image
  1159. onLoad : function() {},
  1160. onAllLoaded : function() {},
  1161. // special callbacks for fixed position
  1162. onFixed : function() {},
  1163. onUnfixed : function() {},
  1164. // utility callbacks
  1165. onUpdate : false, // disabled by default for performance
  1166. onRefresh : function(){},
  1167. metadata : {
  1168. src: 'src'
  1169. },
  1170. className: {
  1171. fixed : 'fixed',
  1172. placeholder : 'placeholder'
  1173. },
  1174. error : {
  1175. method : 'The method you called is not defined.',
  1176. visible : 'Element is hidden, you must call refresh after element becomes visible'
  1177. }
  1178. };
  1179. })( jQuery, window, document );