You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1212 lines
38 KiB

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