affix.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /**
  2. * angular-strap
  3. * @version v2.3.9 - 2016-06-10
  4. * @link http://mgcrea.github.io/angular-strap
  5. * @author Olivier Louvignes <olivier@mg-crea.com> (https://github.com/mgcrea)
  6. * @license MIT License, http://www.opensource.org/licenses/MIT
  7. */
  8. 'use strict';
  9. angular.module('mgcrea.ngStrap.affix', [ 'mgcrea.ngStrap.helpers.dimensions', 'mgcrea.ngStrap.helpers.debounce' ]).provider('$affix', function() {
  10. var defaults = this.defaults = {
  11. offsetTop: 'auto',
  12. inlineStyles: true
  13. };
  14. this.$get = [ '$window', 'debounce', 'dimensions', function($window, debounce, dimensions) {
  15. var bodyEl = angular.element($window.document.body);
  16. var windowEl = angular.element($window);
  17. function AffixFactory(element, config) {
  18. var $affix = {};
  19. var options = angular.extend({}, defaults, config);
  20. var targetEl = options.target;
  21. var reset = 'affix affix-top affix-bottom';
  22. var setWidth = false;
  23. var initialAffixTop = 0;
  24. var initialOffsetTop = 0;
  25. var offsetTop = 0;
  26. var offsetBottom = 0;
  27. var affixed = null;
  28. var unpin = null;
  29. var parent = element.parent();
  30. if (options.offsetParent) {
  31. if (options.offsetParent.match(/^\d+$/)) {
  32. for (var i = 0; i < options.offsetParent * 1 - 1; i++) {
  33. parent = parent.parent();
  34. }
  35. } else {
  36. parent = angular.element(options.offsetParent);
  37. }
  38. }
  39. $affix.init = function() {
  40. this.$parseOffsets();
  41. initialOffsetTop = dimensions.offset(element[0]).top + initialAffixTop;
  42. setWidth = !element[0].style.width;
  43. targetEl.on('scroll', this.checkPosition);
  44. targetEl.on('click', this.checkPositionWithEventLoop);
  45. windowEl.on('resize', this.$debouncedOnResize);
  46. this.checkPosition();
  47. this.checkPositionWithEventLoop();
  48. };
  49. $affix.destroy = function() {
  50. targetEl.off('scroll', this.checkPosition);
  51. targetEl.off('click', this.checkPositionWithEventLoop);
  52. windowEl.off('resize', this.$debouncedOnResize);
  53. };
  54. $affix.checkPositionWithEventLoop = function() {
  55. setTimeout($affix.checkPosition, 1);
  56. };
  57. $affix.checkPosition = function() {
  58. var scrollTop = getScrollTop();
  59. var position = dimensions.offset(element[0]);
  60. var elementHeight = dimensions.height(element[0]);
  61. var affix = getRequiredAffixClass(unpin, position, elementHeight);
  62. if (affixed === affix) return;
  63. affixed = affix;
  64. if (affix === 'top') {
  65. unpin = null;
  66. if (setWidth) {
  67. element.css('width', '');
  68. }
  69. if (options.inlineStyles) {
  70. element.css('position', options.offsetParent ? '' : 'relative');
  71. element.css('top', '');
  72. }
  73. } else if (affix === 'bottom') {
  74. if (options.offsetUnpin) {
  75. unpin = -(options.offsetUnpin * 1);
  76. } else {
  77. unpin = position.top - scrollTop;
  78. }
  79. if (setWidth) {
  80. element.css('width', '');
  81. }
  82. if (options.inlineStyles) {
  83. element.css('position', options.offsetParent ? '' : 'relative');
  84. element.css('top', options.offsetParent ? '' : bodyEl[0].offsetHeight - offsetBottom - elementHeight - initialOffsetTop + 'px');
  85. }
  86. } else {
  87. unpin = null;
  88. if (setWidth) {
  89. element.css('width', element[0].offsetWidth + 'px');
  90. }
  91. if (options.inlineStyles) {
  92. element.css('position', 'fixed');
  93. element.css('top', initialAffixTop + 'px');
  94. }
  95. }
  96. element.removeClass(reset).addClass('affix' + (affix !== 'middle' ? '-' + affix : ''));
  97. };
  98. $affix.$onResize = function() {
  99. $affix.$parseOffsets();
  100. $affix.checkPosition();
  101. };
  102. $affix.$debouncedOnResize = debounce($affix.$onResize, 50);
  103. $affix.$parseOffsets = function() {
  104. var initialPosition = element.css('position');
  105. if (options.inlineStyles) {
  106. element.css('position', options.offsetParent ? '' : 'relative');
  107. }
  108. if (options.offsetTop) {
  109. if (options.offsetTop === 'auto') {
  110. options.offsetTop = '+0';
  111. }
  112. if (options.offsetTop.match(/^[-+]\d+$/)) {
  113. initialAffixTop = -options.offsetTop * 1;
  114. if (options.offsetParent) {
  115. offsetTop = dimensions.offset(parent[0]).top + options.offsetTop * 1;
  116. } else {
  117. offsetTop = dimensions.offset(element[0]).top - dimensions.css(element[0], 'marginTop', true) + options.offsetTop * 1;
  118. }
  119. } else {
  120. offsetTop = options.offsetTop * 1;
  121. }
  122. }
  123. if (options.offsetBottom) {
  124. if (options.offsetParent && options.offsetBottom.match(/^[-+]\d+$/)) {
  125. offsetBottom = getScrollHeight() - (dimensions.offset(parent[0]).top + dimensions.height(parent[0])) + options.offsetBottom * 1 + 1;
  126. } else {
  127. offsetBottom = options.offsetBottom * 1;
  128. }
  129. }
  130. if (options.inlineStyles) {
  131. element.css('position', initialPosition);
  132. }
  133. };
  134. function getRequiredAffixClass(_unpin, position, elementHeight) {
  135. var scrollTop = getScrollTop();
  136. var scrollHeight = getScrollHeight();
  137. if (scrollTop <= offsetTop) {
  138. return 'top';
  139. } else if (_unpin !== null && scrollTop + _unpin <= position.top) {
  140. return 'middle';
  141. } else if (offsetBottom !== null && position.top + elementHeight + initialAffixTop >= scrollHeight - offsetBottom) {
  142. return 'bottom';
  143. }
  144. return 'middle';
  145. }
  146. function getScrollTop() {
  147. return targetEl[0] === $window ? $window.pageYOffset : targetEl[0].scrollTop;
  148. }
  149. function getScrollHeight() {
  150. return targetEl[0] === $window ? $window.document.body.scrollHeight : targetEl[0].scrollHeight;
  151. }
  152. $affix.init();
  153. return $affix;
  154. }
  155. return AffixFactory;
  156. } ];
  157. }).directive('bsAffix', [ '$affix', '$window', function($affix, $window) {
  158. return {
  159. restrict: 'EAC',
  160. require: '^?bsAffixTarget',
  161. link: function postLink(scope, element, attr, affixTarget) {
  162. var options = {
  163. scope: scope,
  164. target: affixTarget ? affixTarget.$element : angular.element($window)
  165. };
  166. angular.forEach([ 'offsetTop', 'offsetBottom', 'offsetParent', 'offsetUnpin', 'inlineStyles' ], function(key) {
  167. if (angular.isDefined(attr[key])) {
  168. var option = attr[key];
  169. if (/true/i.test(option)) option = true;
  170. if (/false/i.test(option)) option = false;
  171. options[key] = option;
  172. }
  173. });
  174. var affix = $affix(element, options);
  175. scope.$on('$destroy', function() {
  176. if (affix) affix.destroy();
  177. options = null;
  178. affix = null;
  179. });
  180. }
  181. };
  182. } ]).directive('bsAffixTarget', function() {
  183. return {
  184. controller: [ '$element', function($element) {
  185. this.$element = $element;
  186. } ]
  187. };
  188. });