scrollspy-native.js 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /* Native Javascript for Bootstrap 3 | ScrollSpy
  2. -----------------------------------------------*/
  3. // SCROLLSPY DEFINITION
  4. // ====================
  5. var ScrollSpy = function(element, options) {
  6. // initialization element, the element we spy on
  7. element = queryElement(element);
  8. // DATA API
  9. var targetData = queryElement(element[getAttribute](dataTarget)),
  10. offsetData = element[getAttribute]('data-offset');
  11. // set options
  12. options = options || {};
  13. // invalidate
  14. if ( !options[target] && !targetData ) { return; }
  15. // event targets, constants
  16. var self = this, spyTarget = options[target] && queryElement(options[target]) || targetData,
  17. links = spyTarget && spyTarget[getElementsByTagName]('A'),
  18. offset = parseInt(options['offset'] || offsetData) || 10,
  19. items = [], targetItems = [], scrollOffset,
  20. scrollTarget = element[offsetHeight] < element[scrollHeight] ? element : globalObject, // determine which is the real scrollTarget
  21. isWindow = scrollTarget === globalObject;
  22. // populate items and targets
  23. for (var i=0, il=links[length]; i<il; i++) {
  24. var href = links[i][getAttribute]('href'),
  25. targetItem = href && href.charAt(0) === '#' && href.slice(-1) !== '#' && queryElement(href);
  26. if ( !!targetItem ) {
  27. items[push](links[i]);
  28. targetItems[push](targetItem);
  29. }
  30. }
  31. // private methods
  32. var updateItem = function(index) {
  33. var parent = items[index][parentNode], // item's parent LI element
  34. targetItem = targetItems[index], // the menu item targets this element
  35. dropdown = getClosest(parent,'.dropdown'),
  36. targetRect = isWindow && targetItem[getBoundingClientRect](),
  37. isActive = hasClass(parent,active) || false,
  38. topEdge = (isWindow ? targetRect[top] + scrollOffset : targetItem[offsetTop]) - offset,
  39. bottomEdge = isWindow ? targetRect[bottom] + scrollOffset - offset : targetItems[index+1] ? targetItems[index+1][offsetTop] - offset : element[scrollHeight],
  40. inside = scrollOffset >= topEdge && bottomEdge > scrollOffset;
  41. if ( !isActive && inside ) {
  42. if ( parent.tagName === 'LI' && !hasClass(parent,active) ) {
  43. addClass(parent,active);
  44. if (dropdown && !hasClass(dropdown,active) ) {
  45. addClass(dropdown,active);
  46. }
  47. bootstrapCustomEvent.call(element, 'activate', 'scrollspy', items[index]);
  48. }
  49. } else if ( !inside ) {
  50. if ( parent.tagName === 'LI' && hasClass(parent,active) ) {
  51. removeClass(parent,active);
  52. if (dropdown && hasClass(dropdown,active) && !getElementsByClassName(parent[parentNode],active).length ) {
  53. removeClass(dropdown,active);
  54. }
  55. }
  56. } else if ( !inside && !isActive || isActive && inside ) {
  57. return;
  58. }
  59. },
  60. updateItems = function(){
  61. scrollOffset = isWindow ? getScroll().y : element[scrollTop];
  62. for (var index=0, itl=items[length]; index<itl; index++) {
  63. updateItem(index)
  64. }
  65. };
  66. // public method
  67. this.refresh = function () {
  68. updateItems();
  69. }
  70. // init
  71. if ( !(stringScrollSpy in element) ) { // prevent adding event handlers twice
  72. on( scrollTarget, scrollEvent, self.refresh, passiveHandler );
  73. !isIE8 && on( globalObject, resizeEvent, self.refresh, passiveHandler );
  74. }
  75. self.refresh();
  76. element[stringScrollSpy] = self;
  77. };
  78. // SCROLLSPY DATA API
  79. // ==================
  80. supports[push]( [ stringScrollSpy, ScrollSpy, '['+dataSpy+'="scroll"]' ] );