@traveledmap/sticky-js 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -120,6 +120,7 @@ Option | Type | Default | Description
120
120
  ------ | ---- | ------- | ----
121
121
  data-sticky-wrap | boolean | false | When it's `true` sticky element is wrapped in `<span></span>` which has sticky element dimensions. Prevents content from "jumping".
122
122
  data-margin-top | number | 0 | Margin between page and sticky element when scrolled
123
+ data-sticky-height | string/number | null | Optional sticky-state height used for sticky constraint calculations when the element changes size once it becomes sticky
123
124
  data-sticky-for | number | 0 | Breakpoint which when is bigger than viewport width, sticky is activated and when is smaller, then sticky is destroyed
124
125
  data-sticky-class | string | null | Class added to sticky element when it is stuck
125
126
 
@@ -39,7 +39,8 @@ var Sticky = /*#__PURE__*/function () {
39
39
  marginBottom: options.marginBottom || 0,
40
40
  stickyFor: options.stickyFor || 0,
41
41
  stickyClass: options.stickyClass || null,
42
- stickyContainer: options.stickyContainer || 'body'
42
+ stickyContainer: options.stickyContainer || 'body',
43
+ stickyHeight: options.stickyHeight || null
43
44
  };
44
45
  this.updateScrollTopPosition = this.updateScrollTopPosition.bind(this);
45
46
  this.scrollDirection = 'down';
@@ -95,6 +96,7 @@ var Sticky = /*#__PURE__*/function () {
95
96
  element.sticky.marginBottom = parseInt(element.getAttribute('data-margin-bottom')) || this.options.marginBottom;
96
97
  element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;
97
98
  element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass;
99
+ element.sticky.stickyHeight = element.getAttribute('data-sticky-height') || this.options.stickyHeight;
98
100
  element.sticky.wrap = element.hasAttribute('data-sticky-wrap') ? true : this.options.wrap; // @todo attribute for stickyContainer
99
101
  // element.sticky.stickyContainer = element.getAttribute('data-sticky-container') || this.options.stickyContainer;
100
102
 
@@ -139,7 +141,9 @@ var Sticky = /*#__PURE__*/function () {
139
141
  }, {
140
142
  key: "activate",
141
143
  value: function activate(element) {
142
- if (element.sticky.rect.top + element.sticky.rect.height < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) {
144
+ var stickyHeight = this.getStickyStateHeight(element);
145
+
146
+ if (element.sticky.rect.top + stickyHeight < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) {
143
147
  element.sticky.active = true;
144
148
  }
145
149
 
@@ -199,10 +203,11 @@ var Sticky = /*#__PURE__*/function () {
199
203
  this.vp = this.getViewportSize();
200
204
  this.updateElementRenderedSize(element);
201
205
  element.sticky.container.rect = this.getRectangle(element.sticky.container);
206
+ var stickyHeight = this.getStickyStateHeight(element);
202
207
 
203
- if (element.sticky.rect.top + element.sticky.rect.height < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) {
208
+ if (element.sticky.rect.top + stickyHeight < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) {
204
209
  element.sticky.active = true;
205
- } else if (element.sticky.rect.top + element.sticky.rect.height >= element.sticky.container.rect.top + element.sticky.container.rect.height || element.sticky.stickyFor >= this.vp.width && element.sticky.active) {
210
+ } else if (element.sticky.rect.top + stickyHeight >= element.sticky.container.rect.top + element.sticky.container.rect.height || element.sticky.stickyFor >= this.vp.width && element.sticky.active) {
206
211
  element.sticky.active = false;
207
212
  }
208
213
 
@@ -268,8 +273,9 @@ var Sticky = /*#__PURE__*/function () {
268
273
  top: '',
269
274
  left: ''
270
275
  });
276
+ var stickyHeight = this.getStickyStateHeight(element);
271
277
 
272
- if (this.vp.height < element.sticky.rect.height || !element.sticky.active) {
278
+ if (this.vp.height < stickyHeight || !element.sticky.active) {
273
279
  return;
274
280
  }
275
281
 
@@ -327,7 +333,7 @@ var Sticky = /*#__PURE__*/function () {
327
333
  left: element.sticky.rect.left + 'px'
328
334
  });
329
335
 
330
- if (this.scrollTop + element.sticky.rect.height + element.sticky.marginTop > element.sticky.container.rect.top + element.sticky.container.offsetHeight - element.sticky.marginBottom) {
336
+ if (this.scrollTop + stickyHeight + element.sticky.marginTop > element.sticky.container.rect.top + element.sticky.container.offsetHeight - element.sticky.marginBottom) {
331
337
  element.sticky.bottomLocked = true;
332
338
  this.updateElementRenderedSize(element);
333
339
 
@@ -505,6 +511,79 @@ var Sticky = /*#__PURE__*/function () {
505
511
  height: height
506
512
  };
507
513
  }
514
+ /**
515
+ * Returns the height that should be used for sticky-state constraint checks.
516
+ * When an explicit stickyHeight is provided, use that target height instead of
517
+ * the currently rendered height to avoid sticky/non-sticky ping-pong.
518
+ * @function
519
+ * @param {node} element - Sticky element
520
+ * @return {number}
521
+ */
522
+
523
+ }, {
524
+ key: "getStickyStateHeight",
525
+ value: function getStickyStateHeight(element) {
526
+ var explicitStickyHeight = this.resolveStickyHeight(element);
527
+
528
+ if (explicitStickyHeight !== null) {
529
+ return explicitStickyHeight;
530
+ }
531
+
532
+ return element.sticky.rect.height;
533
+ }
534
+ /**
535
+ * Resolves the configured stickyHeight option/attribute into rendered pixels.
536
+ * The value is measured against a hidden fixed-position element so viewport
537
+ * units and percentages follow the sticky element's fixed positioning rules.
538
+ * @function
539
+ * @param {node} element - Sticky element
540
+ * @return {number|null}
541
+ */
542
+
543
+ }, {
544
+ key: "resolveStickyHeight",
545
+ value: function resolveStickyHeight(element) {
546
+ var stickyHeight = element.sticky.stickyHeight;
547
+
548
+ if (stickyHeight === null || typeof stickyHeight === 'undefined' || stickyHeight === '') {
549
+ return null;
550
+ }
551
+
552
+ if (typeof stickyHeight === 'number') {
553
+ return stickyHeight;
554
+ }
555
+
556
+ var numericStickyHeight = Number(stickyHeight);
557
+
558
+ if (!Number.isNaN(numericStickyHeight) && Number.isFinite(numericStickyHeight)) {
559
+ return numericStickyHeight;
560
+ }
561
+
562
+ if (typeof stickyHeight !== 'string') {
563
+ return null;
564
+ }
565
+
566
+ var measurementElement = document.createElement('div');
567
+ measurementElement.setAttribute('aria-hidden', 'true');
568
+ this.css(measurementElement, {
569
+ position: 'fixed',
570
+ visibility: 'hidden',
571
+ pointerEvents: 'none',
572
+ top: '0',
573
+ left: '0',
574
+ width: '0',
575
+ height: stickyHeight,
576
+ padding: '0',
577
+ border: '0',
578
+ margin: '0',
579
+ boxSizing: 'border-box',
580
+ fontSize: window.getComputedStyle(element).fontSize
581
+ });
582
+ this.body.appendChild(measurementElement);
583
+ var measuredHeight = measurementElement.getBoundingClientRect().height;
584
+ this.body.removeChild(measurementElement);
585
+ return measuredHeight || null;
586
+ }
508
587
  /**
509
588
  * Updates only the rendered width/height of a sticky element without resetting
510
589
  * its stored document position. This keeps the original sticky trigger point
@@ -1 +1 @@
1
- function _classCallCheck(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function _defineProperties(t,e){for(var i=0;i<e.length;i++){var s=e[i];s.enumerable=s.enumerable||!1,s.configurable=!0,"value"in s&&(s.writable=!0),Object.defineProperty(t,s.key,s)}}function _createClass(t,e,i){return e&&_defineProperties(t.prototype,e),i&&_defineProperties(t,i),t}var Sticky=function(){function i(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:"",e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{};_classCallCheck(this,i),this.selector=t,this.elements=[],this.version="1.3.0",this.vp=this.getViewportSize(),this.body=document.querySelector("body"),this.options={wrap:e.wrap||!1,wrapWith:e.wrapWith||"<span></span>",marginTop:e.marginTop||0,marginBottom:e.marginBottom||0,stickyFor:e.stickyFor||0,stickyClass:e.stickyClass||null,stickyContainer:e.stickyContainer||"body"},this.updateScrollTopPosition=this.updateScrollTopPosition.bind(this),this.scrollDirection="down",this.previousScrollTop=0,this.updateScrollTopPosition(),window.addEventListener("load",this.updateScrollTopPosition),window.addEventListener("scroll",this.updateScrollTopPosition),this.run()}return _createClass(i,[{key:"run",value:function(){var e=this,i=setInterval(function(){if("complete"===document.readyState){clearInterval(i);var t=document.querySelectorAll(e.selector);e.forEach(t,function(t){return e.renderElement(t)})}},10)}},{key:"renderElement",value:function(t){var e=this;t.sticky={},t.sticky.active=!1,t.sticky.hasSyncedStickySize=!1,t.sticky.bottomLocked=!1,t.sticky.syncRenderedSizeTimeout=null,t.sticky.marginTop=parseInt(t.getAttribute("data-margin-top"))||this.options.marginTop,t.sticky.marginBottom=parseInt(t.getAttribute("data-margin-bottom"))||this.options.marginBottom,t.sticky.stickyFor=parseInt(t.getAttribute("data-sticky-for"))||this.options.stickyFor,t.sticky.stickyClass=t.getAttribute("data-sticky-class")||this.options.stickyClass,t.sticky.wrap=!!t.hasAttribute("data-sticky-wrap")||this.options.wrap,t.sticky.stickyContainer=this.options.stickyContainer,t.sticky.container=this.getStickyContainer(t),t.sticky.container.rect=this.getRectangle(t.sticky.container),t.sticky.rect=this.getRectangle(t),"img"===t.tagName.toLowerCase()&&(t.onload=function(){t.sticky.rect=e.getRectangle(t),e.updateElementRenderedSize(t)}),t.sticky.wrap&&this.wrapElement(t),this.activate(t)}},{key:"wrapElement",value:function(t){t.insertAdjacentHTML("beforebegin",t.getAttribute("data-sticky-wrapWith")||this.options.wrapWith),t.previousSibling.appendChild(t)}},{key:"activate",value:function(t){t.sticky.rect.top+t.sticky.rect.height<t.sticky.container.rect.top+t.sticky.container.rect.height&&t.sticky.stickyFor<this.vp.width&&!t.sticky.active&&(t.sticky.active=!0),this.elements.indexOf(t)<0&&this.elements.push(t),t.sticky.resizeEvent||(this.initResizeEvents(t),t.sticky.resizeEvent=!0),t.sticky.scrollEvent||(this.initScrollEvents(t),t.sticky.scrollEvent=!0),this.setPosition(t)}},{key:"initResizeEvents",value:function(t){var e=this;t.sticky.resizeListener=function(){return e.onResizeEvents(t)},window.addEventListener("resize",t.sticky.resizeListener)}},{key:"destroyResizeEvents",value:function(t){window.removeEventListener("resize",t.sticky.resizeListener)}},{key:"onResizeEvents",value:function(t){this.vp=this.getViewportSize(),this.updateElementRenderedSize(t),t.sticky.container.rect=this.getRectangle(t.sticky.container),t.sticky.rect.top+t.sticky.rect.height<t.sticky.container.rect.top+t.sticky.container.rect.height&&t.sticky.stickyFor<this.vp.width&&!t.sticky.active?t.sticky.active=!0:(t.sticky.rect.top+t.sticky.rect.height>=t.sticky.container.rect.top+t.sticky.container.rect.height||t.sticky.stickyFor>=this.vp.width&&t.sticky.active)&&(t.sticky.active=!1),this.setPosition(t)}},{key:"initScrollEvents",value:function(t){var e=this;t.sticky.scrollListener=function(){return e.onScrollEvents(t)},window.addEventListener("scroll",t.sticky.scrollListener)}},{key:"destroyScrollEvents",value:function(t){window.removeEventListener("scroll",t.sticky.scrollListener)}},{key:"onScrollEvents",value:function(t){t.sticky&&t.sticky.active&&this.setPosition(t)}},{key:"setPosition",value:function(t){if(!t.isDisabled&&(this.css(t,{position:"",width:"",top:"",left:""}),!(this.vp.height<t.sticky.rect.height)&&t.sticky.active))if(t.sticky.rect.width||(t.sticky.rect=this.getRectangle(t)),t.sticky.wrap&&this.css(t.parentNode,{display:"block",width:t.sticky.rect.width+"px",height:t.sticky.rect.height+"px"}),0===t.sticky.rect.top&&t.sticky.container===this.body){if(this.css(t,{position:"fixed",top:t.sticky.rect.top+"px",left:t.sticky.rect.left+"px",width:t.sticky.rect.width+"px"}),t.sticky.stickyClass&&t.classList.add(t.sticky.stickyClass),t.sticky.bottomLocked=!1,this.syncRenderedSize(t))return}else if(this.scrollTop>t.sticky.rect.top-t.sticky.marginTop){if(t.sticky.bottomLocked&&"up"!==this.scrollDirection)return this.updateElementRenderedSize(t),this.css(t,{position:"fixed",width:t.sticky.rect.width+"px",left:t.sticky.rect.left+"px",top:t.sticky.container.rect.top+t.sticky.container.offsetHeight-(this.scrollTop+t.sticky.rect.height+t.sticky.marginBottom)+"px"}),void(t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass));if(this.css(t,{position:"fixed",width:t.sticky.rect.width+"px",left:t.sticky.rect.left+"px"}),this.scrollTop+t.sticky.rect.height+t.sticky.marginTop>t.sticky.container.rect.top+t.sticky.container.offsetHeight-t.sticky.marginBottom){if(t.sticky.bottomLocked=!0,this.updateElementRenderedSize(t),t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass),this.css(t,{top:t.sticky.container.rect.top+t.sticky.container.offsetHeight-(this.scrollTop+t.sticky.rect.height+t.sticky.marginBottom)+"px"}),this.syncRenderedSize(t))return}else if("up"===this.scrollDirection&&(t.sticky.bottomLocked=!1),t.sticky.stickyClass&&t.classList.add(t.sticky.stickyClass),this.css(t,{top:t.sticky.marginTop+"px"}),this.syncRenderedSize(t))return}else t.sticky.hasSyncedStickySize=!1,t.sticky.bottomLocked=!1,this.clearScheduledRenderedSizeSync(t),t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass),this.css(t,{position:"",width:"",top:"",left:""}),t.sticky.wrap&&this.css(t.parentNode,{display:"",width:"",height:""})}},{key:"update",value:function(){var e=this;this.forEach(this.elements,function(t){e.updateElementRenderedSize(t),t.sticky.container.rect=e.getRectangle(t.sticky.container),e.activate(t),e.setPosition(t)})}},{key:"destroy",value:function(){var e=this;window.removeEventListener("load",this.updateScrollTopPosition),window.removeEventListener("scroll",this.updateScrollTopPosition),this.forEach(this.elements,function(t){e.clearScheduledRenderedSizeSync(t),e.destroyResizeEvents(t),e.destroyScrollEvents(t),delete t.sticky})}},{key:"enable",value:function(){var e=this;this.forEach(this.elements,function(t){t.isDisabled=!1,e.setPosition(t)})}},{key:"disable",value:function(){var e=this;this.forEach(this.elements,function(t){t.isDisabled=!0,e.css(t,{position:"",width:"",top:"",left:""})})}},{key:"getStickyContainer",value:function(t){for(var e=t.parentNode;!e.hasAttribute("data-sticky-container")&&!e.parentNode.querySelector(t.sticky.stickyContainer)&&e!==this.body;)e=e.parentNode;return e}},{key:"getRectangle",value:function(t){this.css(t,{position:"",width:"",top:"",left:""});for(var e=Math.max(t.offsetWidth,t.clientWidth,t.scrollWidth),i=Math.max(t.offsetHeight,t.clientHeight,t.scrollHeight),s=0,c=0;s+=t.offsetTop||0,c+=t.offsetLeft||0,t=t.offsetParent;);return{top:s,left:c,width:e,height:i}}},{key:"updateElementRenderedSize",value:function(t){var e=t.getBoundingClientRect();t.sticky.rect||(t.sticky.rect=this.getRectangle(t)),t.sticky.rect.width=e.width,t.sticky.rect.height=e.height}},{key:"syncRenderedSize",value:function(t){if(t.sticky.hasSyncedStickySize)return!1;if(t.sticky.syncRenderedSizeTimeout)return!0;var e=t.getBoundingClientRect(),i=.5<Math.abs(e.width-t.sticky.rect.width),s=.5<Math.abs(e.height-t.sticky.rect.height);return(i||s)&&(!t.sticky.isSyncingRenderedSize&&(this.scheduleRenderedSizeSync(t),!0))}},{key:"scheduleRenderedSizeSync",value:function(e){var i=this;e.sticky.syncRenderedSizeTimeout=window.setTimeout(function(){if(e.sticky.syncRenderedSizeTimeout=null,e.sticky&&!e.isDisabled){var t=e.getBoundingClientRect();e.sticky.rect.width=t.width,e.sticky.rect.height=t.height,e.sticky.container.rect=i.getRectangle(e.sticky.container),e.sticky.hasSyncedStickySize=!0,e.sticky.isSyncingRenderedSize=!0,i.setPosition(e),e.sticky.isSyncingRenderedSize=!1}},500)}},{key:"clearScheduledRenderedSizeSync",value:function(t){t.sticky&&t.sticky.syncRenderedSizeTimeout&&(window.clearTimeout(t.sticky.syncRenderedSizeTimeout),t.sticky.syncRenderedSizeTimeout=null)}},{key:"getViewportSize",value:function(){return{width:Math.max(document.documentElement.clientWidth,window.innerWidth||0),height:Math.max(document.documentElement.clientHeight,window.innerHeight||0)}}},{key:"updateScrollTopPosition",value:function(){this.scrollTop=(window.pageYOffset||document.scrollTop)-(document.clientTop||0)||0,this.scrollDirection=this.scrollTop>=this.previousScrollTop?"down":"up",this.previousScrollTop=this.scrollTop}},{key:"forEach",value:function(t,e){for(var i=0,s=t.length;i<s;i++)e(t[i])}},{key:"css",value:function(t,e){for(var i in e)e.hasOwnProperty(i)&&(t.style[i]=e[i])}}]),i}();!function(t,e){"undefined"!=typeof exports?module.exports=e:"function"==typeof define&&define.amd?define([],function(){return e}):t.Sticky=e}(this,Sticky);
1
+ function _classCallCheck(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function _defineProperties(t,e){for(var i=0;i<e.length;i++){var s=e[i];s.enumerable=s.enumerable||!1,s.configurable=!0,"value"in s&&(s.writable=!0),Object.defineProperty(t,s.key,s)}}function _createClass(t,e,i){return e&&_defineProperties(t.prototype,e),i&&_defineProperties(t,i),t}var Sticky=function(){function i(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:"",e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{};_classCallCheck(this,i),this.selector=t,this.elements=[],this.version="1.3.0",this.vp=this.getViewportSize(),this.body=document.querySelector("body"),this.options={wrap:e.wrap||!1,wrapWith:e.wrapWith||"<span></span>",marginTop:e.marginTop||0,marginBottom:e.marginBottom||0,stickyFor:e.stickyFor||0,stickyClass:e.stickyClass||null,stickyContainer:e.stickyContainer||"body",stickyHeight:e.stickyHeight||null},this.updateScrollTopPosition=this.updateScrollTopPosition.bind(this),this.scrollDirection="down",this.previousScrollTop=0,this.updateScrollTopPosition(),window.addEventListener("load",this.updateScrollTopPosition),window.addEventListener("scroll",this.updateScrollTopPosition),this.run()}return _createClass(i,[{key:"run",value:function(){var e=this,i=setInterval(function(){if("complete"===document.readyState){clearInterval(i);var t=document.querySelectorAll(e.selector);e.forEach(t,function(t){return e.renderElement(t)})}},10)}},{key:"renderElement",value:function(t){var e=this;t.sticky={},t.sticky.active=!1,t.sticky.hasSyncedStickySize=!1,t.sticky.bottomLocked=!1,t.sticky.syncRenderedSizeTimeout=null,t.sticky.marginTop=parseInt(t.getAttribute("data-margin-top"))||this.options.marginTop,t.sticky.marginBottom=parseInt(t.getAttribute("data-margin-bottom"))||this.options.marginBottom,t.sticky.stickyFor=parseInt(t.getAttribute("data-sticky-for"))||this.options.stickyFor,t.sticky.stickyClass=t.getAttribute("data-sticky-class")||this.options.stickyClass,t.sticky.stickyHeight=t.getAttribute("data-sticky-height")||this.options.stickyHeight,t.sticky.wrap=!!t.hasAttribute("data-sticky-wrap")||this.options.wrap,t.sticky.stickyContainer=this.options.stickyContainer,t.sticky.container=this.getStickyContainer(t),t.sticky.container.rect=this.getRectangle(t.sticky.container),t.sticky.rect=this.getRectangle(t),"img"===t.tagName.toLowerCase()&&(t.onload=function(){t.sticky.rect=e.getRectangle(t),e.updateElementRenderedSize(t)}),t.sticky.wrap&&this.wrapElement(t),this.activate(t)}},{key:"wrapElement",value:function(t){t.insertAdjacentHTML("beforebegin",t.getAttribute("data-sticky-wrapWith")||this.options.wrapWith),t.previousSibling.appendChild(t)}},{key:"activate",value:function(t){var e=this.getStickyStateHeight(t);t.sticky.rect.top+e<t.sticky.container.rect.top+t.sticky.container.rect.height&&t.sticky.stickyFor<this.vp.width&&!t.sticky.active&&(t.sticky.active=!0),this.elements.indexOf(t)<0&&this.elements.push(t),t.sticky.resizeEvent||(this.initResizeEvents(t),t.sticky.resizeEvent=!0),t.sticky.scrollEvent||(this.initScrollEvents(t),t.sticky.scrollEvent=!0),this.setPosition(t)}},{key:"initResizeEvents",value:function(t){var e=this;t.sticky.resizeListener=function(){return e.onResizeEvents(t)},window.addEventListener("resize",t.sticky.resizeListener)}},{key:"destroyResizeEvents",value:function(t){window.removeEventListener("resize",t.sticky.resizeListener)}},{key:"onResizeEvents",value:function(t){this.vp=this.getViewportSize(),this.updateElementRenderedSize(t),t.sticky.container.rect=this.getRectangle(t.sticky.container);var e=this.getStickyStateHeight(t);t.sticky.rect.top+e<t.sticky.container.rect.top+t.sticky.container.rect.height&&t.sticky.stickyFor<this.vp.width&&!t.sticky.active?t.sticky.active=!0:(t.sticky.rect.top+e>=t.sticky.container.rect.top+t.sticky.container.rect.height||t.sticky.stickyFor>=this.vp.width&&t.sticky.active)&&(t.sticky.active=!1),this.setPosition(t)}},{key:"initScrollEvents",value:function(t){var e=this;t.sticky.scrollListener=function(){return e.onScrollEvents(t)},window.addEventListener("scroll",t.sticky.scrollListener)}},{key:"destroyScrollEvents",value:function(t){window.removeEventListener("scroll",t.sticky.scrollListener)}},{key:"onScrollEvents",value:function(t){t.sticky&&t.sticky.active&&this.setPosition(t)}},{key:"setPosition",value:function(t){if(!t.isDisabled){this.css(t,{position:"",width:"",top:"",left:""});var e=this.getStickyStateHeight(t);if(!(this.vp.height<e)&&t.sticky.active)if(t.sticky.rect.width||(t.sticky.rect=this.getRectangle(t)),t.sticky.wrap&&this.css(t.parentNode,{display:"block",width:t.sticky.rect.width+"px",height:t.sticky.rect.height+"px"}),0===t.sticky.rect.top&&t.sticky.container===this.body){if(this.css(t,{position:"fixed",top:t.sticky.rect.top+"px",left:t.sticky.rect.left+"px",width:t.sticky.rect.width+"px"}),t.sticky.stickyClass&&t.classList.add(t.sticky.stickyClass),t.sticky.bottomLocked=!1,this.syncRenderedSize(t))return}else if(this.scrollTop>t.sticky.rect.top-t.sticky.marginTop){if(t.sticky.bottomLocked&&"up"!==this.scrollDirection)return this.updateElementRenderedSize(t),this.css(t,{position:"fixed",width:t.sticky.rect.width+"px",left:t.sticky.rect.left+"px",top:t.sticky.container.rect.top+t.sticky.container.offsetHeight-(this.scrollTop+t.sticky.rect.height+t.sticky.marginBottom)+"px"}),void(t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass));if(this.css(t,{position:"fixed",width:t.sticky.rect.width+"px",left:t.sticky.rect.left+"px"}),this.scrollTop+e+t.sticky.marginTop>t.sticky.container.rect.top+t.sticky.container.offsetHeight-t.sticky.marginBottom){if(t.sticky.bottomLocked=!0,this.updateElementRenderedSize(t),t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass),this.css(t,{top:t.sticky.container.rect.top+t.sticky.container.offsetHeight-(this.scrollTop+t.sticky.rect.height+t.sticky.marginBottom)+"px"}),this.syncRenderedSize(t))return}else if("up"===this.scrollDirection&&(t.sticky.bottomLocked=!1),t.sticky.stickyClass&&t.classList.add(t.sticky.stickyClass),this.css(t,{top:t.sticky.marginTop+"px"}),this.syncRenderedSize(t))return}else t.sticky.hasSyncedStickySize=!1,t.sticky.bottomLocked=!1,this.clearScheduledRenderedSizeSync(t),t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass),this.css(t,{position:"",width:"",top:"",left:""}),t.sticky.wrap&&this.css(t.parentNode,{display:"",width:"",height:""})}}},{key:"update",value:function(){var e=this;this.forEach(this.elements,function(t){e.updateElementRenderedSize(t),t.sticky.container.rect=e.getRectangle(t.sticky.container),e.activate(t),e.setPosition(t)})}},{key:"destroy",value:function(){var e=this;window.removeEventListener("load",this.updateScrollTopPosition),window.removeEventListener("scroll",this.updateScrollTopPosition),this.forEach(this.elements,function(t){e.clearScheduledRenderedSizeSync(t),e.destroyResizeEvents(t),e.destroyScrollEvents(t),delete t.sticky})}},{key:"enable",value:function(){var e=this;this.forEach(this.elements,function(t){t.isDisabled=!1,e.setPosition(t)})}},{key:"disable",value:function(){var e=this;this.forEach(this.elements,function(t){t.isDisabled=!0,e.css(t,{position:"",width:"",top:"",left:""})})}},{key:"getStickyContainer",value:function(t){for(var e=t.parentNode;!e.hasAttribute("data-sticky-container")&&!e.parentNode.querySelector(t.sticky.stickyContainer)&&e!==this.body;)e=e.parentNode;return e}},{key:"getRectangle",value:function(t){this.css(t,{position:"",width:"",top:"",left:""});for(var e=Math.max(t.offsetWidth,t.clientWidth,t.scrollWidth),i=Math.max(t.offsetHeight,t.clientHeight,t.scrollHeight),s=0,n=0;s+=t.offsetTop||0,n+=t.offsetLeft||0,t=t.offsetParent;);return{top:s,left:n,width:e,height:i}}},{key:"getStickyStateHeight",value:function(t){var e=this.resolveStickyHeight(t);return null!==e?e:t.sticky.rect.height}},{key:"resolveStickyHeight",value:function(t){var e=t.sticky.stickyHeight;if(null==e||""===e)return null;if("number"==typeof e)return e;var i=Number(e);if(!Number.isNaN(i)&&Number.isFinite(i))return i;if("string"!=typeof e)return null;var s=document.createElement("div");s.setAttribute("aria-hidden","true"),this.css(s,{position:"fixed",visibility:"hidden",pointerEvents:"none",top:"0",left:"0",width:"0",height:e,padding:"0",border:"0",margin:"0",boxSizing:"border-box",fontSize:window.getComputedStyle(t).fontSize}),this.body.appendChild(s);var n=s.getBoundingClientRect().height;return this.body.removeChild(s),n||null}},{key:"updateElementRenderedSize",value:function(t){var e=t.getBoundingClientRect();t.sticky.rect||(t.sticky.rect=this.getRectangle(t)),t.sticky.rect.width=e.width,t.sticky.rect.height=e.height}},{key:"syncRenderedSize",value:function(t){if(t.sticky.hasSyncedStickySize)return!1;if(t.sticky.syncRenderedSizeTimeout)return!0;var e=t.getBoundingClientRect(),i=.5<Math.abs(e.width-t.sticky.rect.width),s=.5<Math.abs(e.height-t.sticky.rect.height);return(i||s)&&(!t.sticky.isSyncingRenderedSize&&(this.scheduleRenderedSizeSync(t),!0))}},{key:"scheduleRenderedSizeSync",value:function(e){var i=this;e.sticky.syncRenderedSizeTimeout=window.setTimeout(function(){if(e.sticky.syncRenderedSizeTimeout=null,e.sticky&&!e.isDisabled){var t=e.getBoundingClientRect();e.sticky.rect.width=t.width,e.sticky.rect.height=t.height,e.sticky.container.rect=i.getRectangle(e.sticky.container),e.sticky.hasSyncedStickySize=!0,e.sticky.isSyncingRenderedSize=!0,i.setPosition(e),e.sticky.isSyncingRenderedSize=!1}},500)}},{key:"clearScheduledRenderedSizeSync",value:function(t){t.sticky&&t.sticky.syncRenderedSizeTimeout&&(window.clearTimeout(t.sticky.syncRenderedSizeTimeout),t.sticky.syncRenderedSizeTimeout=null)}},{key:"getViewportSize",value:function(){return{width:Math.max(document.documentElement.clientWidth,window.innerWidth||0),height:Math.max(document.documentElement.clientHeight,window.innerHeight||0)}}},{key:"updateScrollTopPosition",value:function(){this.scrollTop=(window.pageYOffset||document.scrollTop)-(document.clientTop||0)||0,this.scrollDirection=this.scrollTop>=this.previousScrollTop?"down":"up",this.previousScrollTop=this.scrollTop}},{key:"forEach",value:function(t,e){for(var i=0,s=t.length;i<s;i++)e(t[i])}},{key:"css",value:function(t,e){for(var i in e)e.hasOwnProperty(i)&&(t.style[i]=e[i])}}]),i}();!function(t,e){"undefined"!=typeof exports?module.exports=e:"function"==typeof define&&define.amd?define([],function(){return e}):t.Sticky=e}(this,Sticky);
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@traveledmap/sticky-js",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Sticky elements",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/src/sticky.js CHANGED
@@ -34,6 +34,7 @@ class Sticky {
34
34
  stickyFor: options.stickyFor || 0,
35
35
  stickyClass: options.stickyClass || null,
36
36
  stickyContainer: options.stickyContainer || 'body',
37
+ stickyHeight: options.stickyHeight || null,
37
38
  };
38
39
 
39
40
  this.updateScrollTopPosition = this.updateScrollTopPosition.bind(this);
@@ -85,6 +86,7 @@ class Sticky {
85
86
  element.sticky.marginBottom = parseInt(element.getAttribute('data-margin-bottom')) || this.options.marginBottom;
86
87
  element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;
87
88
  element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass;
89
+ element.sticky.stickyHeight = element.getAttribute('data-sticky-height') || this.options.stickyHeight;
88
90
  element.sticky.wrap = element.hasAttribute('data-sticky-wrap') ? true : this.options.wrap;
89
91
  // @todo attribute for stickyContainer
90
92
  // element.sticky.stickyContainer = element.getAttribute('data-sticky-container') || this.options.stickyContainer;
@@ -128,9 +130,11 @@ class Sticky {
128
130
  * @function
129
131
  * @param {node} element - Element to be activated
130
132
  */
131
- activate(element) {
133
+ activate(element) {
134
+ const stickyHeight = this.getStickyStateHeight(element);
135
+
132
136
  if (
133
- ((element.sticky.rect.top + element.sticky.rect.height) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
137
+ ((element.sticky.rect.top + stickyHeight) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
134
138
  && (element.sticky.stickyFor < this.vp.width)
135
139
  && !element.sticky.active
136
140
  ) {
@@ -181,20 +185,21 @@ class Sticky {
181
185
  * @function
182
186
  * @param {node} element - Element for which event function is fired
183
187
  */
184
- onResizeEvents(element) {
188
+ onResizeEvents(element) {
185
189
  this.vp = this.getViewportSize();
186
190
 
187
191
  this.updateElementRenderedSize(element);
188
192
  element.sticky.container.rect = this.getRectangle(element.sticky.container);
193
+ const stickyHeight = this.getStickyStateHeight(element);
189
194
 
190
195
  if (
191
- ((element.sticky.rect.top + element.sticky.rect.height) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
196
+ ((element.sticky.rect.top + stickyHeight) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
192
197
  && (element.sticky.stickyFor < this.vp.width)
193
198
  && !element.sticky.active
194
199
  ) {
195
200
  element.sticky.active = true;
196
201
  } else if (
197
- ((element.sticky.rect.top + element.sticky.rect.height) >= (element.sticky.container.rect.top + element.sticky.container.rect.height))
202
+ ((element.sticky.rect.top + stickyHeight) >= (element.sticky.container.rect.top + element.sticky.container.rect.height))
198
203
  || element.sticky.stickyFor >= this.vp.width
199
204
  && element.sticky.active
200
205
  ) {
@@ -249,7 +254,9 @@ class Sticky {
249
254
  }
250
255
  this.css(element, { position: '', width: '', top: '', left: '' });
251
256
 
252
- if ((this.vp.height < element.sticky.rect.height) || !element.sticky.active) {
257
+ const stickyHeight = this.getStickyStateHeight(element);
258
+
259
+ if ((this.vp.height < stickyHeight) || !element.sticky.active) {
253
260
  return;
254
261
  }
255
262
 
@@ -310,7 +317,7 @@ class Sticky {
310
317
  });
311
318
 
312
319
  if (
313
- (this.scrollTop + element.sticky.rect.height + element.sticky.marginTop)
320
+ (this.scrollTop + stickyHeight + element.sticky.marginTop)
314
321
  > (element.sticky.container.rect.top + element.sticky.container.offsetHeight - element.sticky.marginBottom)
315
322
  ) {
316
323
  element.sticky.bottomLocked = true;
@@ -450,6 +457,78 @@ class Sticky {
450
457
  }
451
458
 
452
459
 
460
+ /**
461
+ * Returns the height that should be used for sticky-state constraint checks.
462
+ * When an explicit stickyHeight is provided, use that target height instead of
463
+ * the currently rendered height to avoid sticky/non-sticky ping-pong.
464
+ * @function
465
+ * @param {node} element - Sticky element
466
+ * @return {number}
467
+ */
468
+ getStickyStateHeight(element) {
469
+ const explicitStickyHeight = this.resolveStickyHeight(element);
470
+
471
+ if (explicitStickyHeight !== null) {
472
+ return explicitStickyHeight;
473
+ }
474
+
475
+ return element.sticky.rect.height;
476
+ }
477
+
478
+
479
+ /**
480
+ * Resolves the configured stickyHeight option/attribute into rendered pixels.
481
+ * The value is measured against a hidden fixed-position element so viewport
482
+ * units and percentages follow the sticky element's fixed positioning rules.
483
+ * @function
484
+ * @param {node} element - Sticky element
485
+ * @return {number|null}
486
+ */
487
+ resolveStickyHeight(element) {
488
+ const { stickyHeight } = element.sticky;
489
+
490
+ if (stickyHeight === null || typeof stickyHeight === 'undefined' || stickyHeight === '') {
491
+ return null;
492
+ }
493
+
494
+ if (typeof stickyHeight === 'number') {
495
+ return stickyHeight;
496
+ }
497
+
498
+ const numericStickyHeight = Number(stickyHeight);
499
+ if (!Number.isNaN(numericStickyHeight) && Number.isFinite(numericStickyHeight)) {
500
+ return numericStickyHeight;
501
+ }
502
+
503
+ if (typeof stickyHeight !== 'string') {
504
+ return null;
505
+ }
506
+
507
+ const measurementElement = document.createElement('div');
508
+ measurementElement.setAttribute('aria-hidden', 'true');
509
+ this.css(measurementElement, {
510
+ position: 'fixed',
511
+ visibility: 'hidden',
512
+ pointerEvents: 'none',
513
+ top: '0',
514
+ left: '0',
515
+ width: '0',
516
+ height: stickyHeight,
517
+ padding: '0',
518
+ border: '0',
519
+ margin: '0',
520
+ boxSizing: 'border-box',
521
+ fontSize: window.getComputedStyle(element).fontSize,
522
+ });
523
+
524
+ this.body.appendChild(measurementElement);
525
+ const measuredHeight = measurementElement.getBoundingClientRect().height;
526
+ this.body.removeChild(measurementElement);
527
+
528
+ return measuredHeight || null;
529
+ }
530
+
531
+
453
532
  /**
454
533
  * Updates only the rendered width/height of a sticky element without resetting
455
534
  * its stored document position. This keeps the original sticky trigger point