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.

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