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.

1234 lines
39 KiB

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