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.

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