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.

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