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.

1155 lines
37 KiB

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