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.

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