@traveledmap/sticky-js 1.3.0 → 1.4.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/.idea/misc.xml CHANGED
@@ -1,6 +1,7 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <project version="4">
3
- <component name="ProjectRootManager">
3
+ <component name="KubernetesApiProvider"><![CDATA[{}]]></component>
4
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
4
5
  <output url="file://$PROJECT_DIR$/out" />
5
6
  </component>
6
7
  </project>
package/.idea/vcs.xml CHANGED
@@ -2,5 +2,6 @@
2
2
  <project version="4">
3
3
  <component name="VcsDirectoryMappings">
4
4
  <mapping directory="" vcs="Git" />
5
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
6
  </component>
6
7
  </project>
@@ -42,6 +42,8 @@ var Sticky = /*#__PURE__*/function () {
42
42
  stickyContainer: options.stickyContainer || 'body'
43
43
  };
44
44
  this.updateScrollTopPosition = this.updateScrollTopPosition.bind(this);
45
+ this.scrollDirection = 'down';
46
+ this.previousScrollTop = 0;
45
47
  this.updateScrollTopPosition();
46
48
  window.addEventListener('load', this.updateScrollTopPosition);
47
49
  window.addEventListener('scroll', this.updateScrollTopPosition);
@@ -84,7 +86,11 @@ var Sticky = /*#__PURE__*/function () {
84
86
  // create container for variables needed in future
85
87
  element.sticky = {}; // set default variables
86
88
 
87
- element.sticky.active = false;
89
+ element.sticky.active = false; // Keep track of temporary state used when sticky styles animate element size.
90
+
91
+ element.sticky.hasSyncedStickySize = false;
92
+ element.sticky.bottomLocked = false;
93
+ element.sticky.syncRenderedSizeTimeout = null;
88
94
  element.sticky.marginTop = parseInt(element.getAttribute('data-margin-top')) || this.options.marginTop;
89
95
  element.sticky.marginBottom = parseInt(element.getAttribute('data-margin-bottom')) || this.options.marginBottom;
90
96
  element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;
@@ -99,7 +105,9 @@ var Sticky = /*#__PURE__*/function () {
99
105
 
100
106
  if (element.tagName.toLowerCase() === 'img') {
101
107
  element.onload = function () {
102
- return element.sticky.rect = _this2.getRectangle(element);
108
+ element.sticky.rect = _this2.getRectangle(element);
109
+
110
+ _this2.updateElementRenderedSize(element);
103
111
  };
104
112
  }
105
113
 
@@ -189,7 +197,7 @@ var Sticky = /*#__PURE__*/function () {
189
197
  key: "onResizeEvents",
190
198
  value: function onResizeEvents(element) {
191
199
  this.vp = this.getViewportSize();
192
- element.sticky.rect = this.getRectangle(element);
200
+ this.updateElementRenderedSize(element);
193
201
  element.sticky.container.rect = this.getRectangle(element.sticky.container);
194
202
 
195
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) {
@@ -288,7 +296,31 @@ var Sticky = /*#__PURE__*/function () {
288
296
  if (element.sticky.stickyClass) {
289
297
  element.classList.add(element.sticky.stickyClass);
290
298
  }
299
+
300
+ element.sticky.bottomLocked = false;
301
+
302
+ if (this.syncRenderedSize(element)) {
303
+ return;
304
+ }
291
305
  } else if (this.scrollTop > element.sticky.rect.top - element.sticky.marginTop) {
306
+ // Once we reached the container bottom while scrolling down, keep the
307
+ // element in the clamped state until the user scrolls back up.
308
+ if (element.sticky.bottomLocked && this.scrollDirection !== 'up') {
309
+ this.updateElementRenderedSize(element);
310
+ this.css(element, {
311
+ position: 'fixed',
312
+ width: element.sticky.rect.width + 'px',
313
+ left: element.sticky.rect.left + 'px',
314
+ top: element.sticky.container.rect.top + element.sticky.container.offsetHeight - (this.scrollTop + element.sticky.rect.height + element.sticky.marginBottom) + 'px'
315
+ });
316
+
317
+ if (element.sticky.stickyClass) {
318
+ element.classList.remove(element.sticky.stickyClass);
319
+ }
320
+
321
+ return;
322
+ }
323
+
292
324
  this.css(element, {
293
325
  position: 'fixed',
294
326
  width: element.sticky.rect.width + 'px',
@@ -296,6 +328,9 @@ var Sticky = /*#__PURE__*/function () {
296
328
  });
297
329
 
298
330
  if (this.scrollTop + element.sticky.rect.height + element.sticky.marginTop > element.sticky.container.rect.top + element.sticky.container.offsetHeight - element.sticky.marginBottom) {
331
+ element.sticky.bottomLocked = true;
332
+ this.updateElementRenderedSize(element);
333
+
299
334
  if (element.sticky.stickyClass) {
300
335
  element.classList.remove(element.sticky.stickyClass);
301
336
  }
@@ -303,7 +338,15 @@ var Sticky = /*#__PURE__*/function () {
303
338
  this.css(element, {
304
339
  top: element.sticky.container.rect.top + element.sticky.container.offsetHeight - (this.scrollTop + element.sticky.rect.height + element.sticky.marginBottom) + 'px'
305
340
  });
341
+
342
+ if (this.syncRenderedSize(element)) {
343
+ return;
344
+ }
306
345
  } else {
346
+ if (this.scrollDirection === 'up') {
347
+ element.sticky.bottomLocked = false;
348
+ }
349
+
307
350
  if (element.sticky.stickyClass) {
308
351
  element.classList.add(element.sticky.stickyClass);
309
352
  }
@@ -311,8 +354,16 @@ var Sticky = /*#__PURE__*/function () {
311
354
  this.css(element, {
312
355
  top: element.sticky.marginTop + 'px'
313
356
  });
357
+
358
+ if (this.syncRenderedSize(element)) {
359
+ return;
360
+ }
314
361
  }
315
362
  } else {
363
+ element.sticky.hasSyncedStickySize = false;
364
+ element.sticky.bottomLocked = false;
365
+ this.clearScheduledRenderedSizeSync(element);
366
+
316
367
  if (element.sticky.stickyClass) {
317
368
  element.classList.remove(element.sticky.stickyClass);
318
369
  }
@@ -344,7 +395,8 @@ var Sticky = /*#__PURE__*/function () {
344
395
  var _this5 = this;
345
396
 
346
397
  this.forEach(this.elements, function (element) {
347
- element.sticky.rect = _this5.getRectangle(element);
398
+ _this5.updateElementRenderedSize(element);
399
+
348
400
  element.sticky.container.rect = _this5.getRectangle(element.sticky.container);
349
401
 
350
402
  _this5.activate(element);
@@ -365,6 +417,8 @@ var Sticky = /*#__PURE__*/function () {
365
417
  window.removeEventListener('load', this.updateScrollTopPosition);
366
418
  window.removeEventListener('scroll', this.updateScrollTopPosition);
367
419
  this.forEach(this.elements, function (element) {
420
+ _this6.clearScheduledRenderedSizeSync(element);
421
+
368
422
  _this6.destroyResizeEvents(element);
369
423
 
370
424
  _this6.destroyScrollEvents(element);
@@ -451,6 +505,111 @@ var Sticky = /*#__PURE__*/function () {
451
505
  height: height
452
506
  };
453
507
  }
508
+ /**
509
+ * Updates only the rendered width/height of a sticky element without resetting
510
+ * its stored document position. This keeps the original sticky trigger point
511
+ * while allowing update() to pick up size changes caused by sticky classes.
512
+ * @function
513
+ * @param {node} element - Sticky element
514
+ */
515
+
516
+ }, {
517
+ key: "updateElementRenderedSize",
518
+ value: function updateElementRenderedSize(element) {
519
+ var renderedRect = element.getBoundingClientRect();
520
+
521
+ if (!element.sticky.rect) {
522
+ element.sticky.rect = this.getRectangle(element);
523
+ }
524
+
525
+ element.sticky.rect.width = renderedRect.width;
526
+ element.sticky.rect.height = renderedRect.height;
527
+ }
528
+ /**
529
+ * Sync rendered size back into sticky measurements and rerun positioning once
530
+ * after sticky styles had time to animate their dimensions.
531
+ * @function
532
+ * @param {node} element - Sticky element
533
+ * @param {string} reason - Debug reason
534
+ * @return {boolean}
535
+ */
536
+
537
+ }, {
538
+ key: "syncRenderedSize",
539
+ value: function syncRenderedSize(element) {
540
+ if (element.sticky.hasSyncedStickySize) {
541
+ return false;
542
+ } // If a delayed sync is already queued, let it finish instead of stacking
543
+ // more reflows while the sticky animation is still running.
544
+
545
+
546
+ if (element.sticky.syncRenderedSizeTimeout) {
547
+ return true;
548
+ }
549
+
550
+ var renderedRect = element.getBoundingClientRect();
551
+ var widthChanged = Math.abs(renderedRect.width - element.sticky.rect.width) > 0.5;
552
+ var heightChanged = Math.abs(renderedRect.height - element.sticky.rect.height) > 0.5;
553
+
554
+ if (!widthChanged && !heightChanged) {
555
+ return false;
556
+ }
557
+
558
+ if (element.sticky.isSyncingRenderedSize) {
559
+ return false;
560
+ }
561
+
562
+ this.scheduleRenderedSizeSync(element);
563
+ return true;
564
+ }
565
+ /**
566
+ * Schedule one delayed rendered-size sync to let CSS transitions settle.
567
+ * @function
568
+ * @param {node} element - Sticky element
569
+ */
570
+
571
+ }, {
572
+ key: "scheduleRenderedSizeSync",
573
+ value: function scheduleRenderedSizeSync(element) {
574
+ var _this9 = this;
575
+
576
+ element.sticky.syncRenderedSizeTimeout = window.setTimeout(function () {
577
+ element.sticky.syncRenderedSizeTimeout = null;
578
+
579
+ if (!element.sticky || element.isDisabled) {
580
+ return;
581
+ }
582
+
583
+ var renderedRect = element.getBoundingClientRect(); // Sticky styles can animate height after the element becomes fixed, so we
584
+ // re-read the rendered box after a delay and then re-run positioning.
585
+
586
+ element.sticky.rect.width = renderedRect.width;
587
+ element.sticky.rect.height = renderedRect.height;
588
+ element.sticky.container.rect = _this9.getRectangle(element.sticky.container);
589
+ element.sticky.hasSyncedStickySize = true;
590
+ element.sticky.isSyncingRenderedSize = true;
591
+
592
+ _this9.setPosition(element);
593
+
594
+ element.sticky.isSyncingRenderedSize = false;
595
+ }, 500);
596
+ }
597
+ /**
598
+ * Clear a pending delayed rendered-size sync.
599
+ * @function
600
+ * @param {node} element - Sticky element
601
+ */
602
+
603
+ }, {
604
+ key: "clearScheduledRenderedSizeSync",
605
+ value: function clearScheduledRenderedSizeSync(element) {
606
+ if (!element.sticky || !element.sticky.syncRenderedSizeTimeout) {
607
+ return;
608
+ }
609
+
610
+ window.clearTimeout(element.sticky.syncRenderedSizeTimeout);
611
+ element.sticky.syncRenderedSizeTimeout = null;
612
+ }
454
613
  /**
455
614
  * Function that returns viewport dimensions
456
615
  * @function
@@ -475,6 +634,8 @@ var Sticky = /*#__PURE__*/function () {
475
634
  key: "updateScrollTopPosition",
476
635
  value: function updateScrollTopPosition() {
477
636
  this.scrollTop = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0) || 0;
637
+ this.scrollDirection = this.scrollTop >= this.previousScrollTop ? 'down' : 'up';
638
+ this.previousScrollTop = this.scrollTop;
478
639
  }
479
640
  /**
480
641
  * Helper function for loops
@@ -1 +1 @@
1
- function _classCallCheck(t,i){if(!(t instanceof i))throw new TypeError("Cannot call a class as a function")}function _defineProperties(t,i){for(var e=0;e<i.length;e++){var s=i[e];s.enumerable=s.enumerable||!1,s.configurable=!0,"value"in s&&(s.writable=!0),Object.defineProperty(t,s.key,s)}}function _createClass(t,i,e){return i&&_defineProperties(t.prototype,i),e&&_defineProperties(t,e),t}var Sticky=function(){function e(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:"",i=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{};_classCallCheck(this,e),this.selector=t,this.elements=[],this.version="1.3.0",this.vp=this.getViewportSize(),this.body=document.querySelector("body"),this.options={wrap:i.wrap||!1,wrapWith:i.wrapWith||"<span></span>",marginTop:i.marginTop||0,marginBottom:i.marginBottom||0,stickyFor:i.stickyFor||0,stickyClass:i.stickyClass||null,stickyContainer:i.stickyContainer||"body"},this.updateScrollTopPosition=this.updateScrollTopPosition.bind(this),this.updateScrollTopPosition(),window.addEventListener("load",this.updateScrollTopPosition),window.addEventListener("scroll",this.updateScrollTopPosition),this.run()}return _createClass(e,[{key:"run",value:function(){var i=this,e=setInterval(function(){var t;"complete"===document.readyState&&(clearInterval(e),t=document.querySelectorAll(i.selector),i.forEach(t,function(t){return i.renderElement(t)}))},10)}},{key:"renderElement",value:function(t){var i=this;t.sticky={},t.sticky.active=!1,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(){return t.sticky.rect=i.getRectangle(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 i=this;t.sticky.resizeListener=function(){return i.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(),t.sticky.rect=this.getRectangle(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 i=this;t.sticky.scrollListener=function(){return i.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){t.isDisabled||(this.css(t,{position:"",width:"",top:"",left:""}),this.vp.height<t.sticky.rect.height||!t.sticky.active||(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?(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)):this.scrollTop>t.sticky.rect.top-t.sticky.marginTop?(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?(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"})):(t.sticky.stickyClass&&t.classList.add(t.sticky.stickyClass),this.css(t,{top:t.sticky.marginTop+"px"}))):(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 i=this;this.forEach(this.elements,function(t){t.sticky.rect=i.getRectangle(t),t.sticky.container.rect=i.getRectangle(t.sticky.container),i.activate(t),i.setPosition(t)})}},{key:"destroy",value:function(){var i=this;window.removeEventListener("load",this.updateScrollTopPosition),window.removeEventListener("scroll",this.updateScrollTopPosition),this.forEach(this.elements,function(t){i.destroyResizeEvents(t),i.destroyScrollEvents(t),delete t.sticky})}},{key:"enable",value:function(){var i=this;this.forEach(this.elements,function(t){t.isDisabled=!1,i.setPosition(t)})}},{key:"disable",value:function(){var i=this;this.forEach(this.elements,function(t){t.isDisabled=!0,i.css(t,{position:"",width:"",top:"",left:""})})}},{key:"getStickyContainer",value:function(t){for(var i=t.parentNode;!i.hasAttribute("data-sticky-container")&&!i.parentNode.querySelector(t.sticky.stickyContainer)&&i!==this.body;)i=i.parentNode;return i}},{key:"getRectangle",value:function(t){this.css(t,{position:"",width:"",top:"",left:""});for(var i=Math.max(t.offsetWidth,t.clientWidth,t.scrollWidth),e=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:i,height:e}}},{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}},{key:"forEach",value:function(t,i){for(var e=0,s=t.length;e<s;e++)i(t[e])}},{key:"css",value:function(t,i){for(var e in i)i.hasOwnProperty(e)&&(t.style[e]=i[e])}}]),e}();!function(t,i){"undefined"!=typeof exports?module.exports=i:"function"==typeof define&&define.amd?define([],function(){return i}):t.Sticky=i}(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"},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);
Binary file
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@traveledmap/sticky-js",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Sticky elements",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
+ "overrides": {
10
+ "make-error-cause": "1.2.2"
11
+ },
9
12
  "scripts": {
10
13
  "start": "gulp",
11
14
  "build": "gulp build",
@@ -22,7 +25,7 @@
22
25
  "scroll",
23
26
  "javascript"
24
27
  ],
25
- "author": "rgalus <biuro@rafalgalus.pl>",
28
+ "author": "qlerebours <quentin@qlerebours.dev>",
26
29
  "license": "MIT",
27
30
  "bugs": {
28
31
  "url": "https://github.com/TraveledMap/sticky-js/issues"
package/src/sticky.js CHANGED
@@ -37,6 +37,8 @@ class Sticky {
37
37
  };
38
38
 
39
39
  this.updateScrollTopPosition = this.updateScrollTopPosition.bind(this);
40
+ this.scrollDirection = 'down';
41
+ this.previousScrollTop = 0;
40
42
 
41
43
  this.updateScrollTopPosition();
42
44
  window.addEventListener('load', this.updateScrollTopPosition);
@@ -74,6 +76,10 @@ class Sticky {
74
76
 
75
77
  // set default variables
76
78
  element.sticky.active = false;
79
+ // Keep track of temporary state used when sticky styles animate element size.
80
+ element.sticky.hasSyncedStickySize = false;
81
+ element.sticky.bottomLocked = false;
82
+ element.sticky.syncRenderedSizeTimeout = null;
77
83
 
78
84
  element.sticky.marginTop = parseInt(element.getAttribute('data-margin-top')) || this.options.marginTop;
79
85
  element.sticky.marginBottom = parseInt(element.getAttribute('data-margin-bottom')) || this.options.marginBottom;
@@ -91,7 +97,10 @@ class Sticky {
91
97
 
92
98
  // fix when element is image that has not yet loaded and width, height = 0
93
99
  if (element.tagName.toLowerCase() === 'img') {
94
- element.onload = () => element.sticky.rect = this.getRectangle(element);
100
+ element.onload = () => {
101
+ element.sticky.rect = this.getRectangle(element);
102
+ this.updateElementRenderedSize(element);
103
+ };
95
104
  }
96
105
 
97
106
  if (element.sticky.wrap) {
@@ -175,7 +184,7 @@ class Sticky {
175
184
  onResizeEvents(element) {
176
185
  this.vp = this.getViewportSize();
177
186
 
178
- element.sticky.rect = this.getRectangle(element);
187
+ this.updateElementRenderedSize(element);
179
188
  element.sticky.container.rect = this.getRectangle(element.sticky.container);
180
189
 
181
190
  if (
@@ -269,7 +278,31 @@ class Sticky {
269
278
  if (element.sticky.stickyClass) {
270
279
  element.classList.add(element.sticky.stickyClass);
271
280
  }
281
+ element.sticky.bottomLocked = false;
282
+ if (this.syncRenderedSize(element)) {
283
+ return;
284
+ }
272
285
  } else if (this.scrollTop > (element.sticky.rect.top - element.sticky.marginTop)) {
286
+ // Once we reached the container bottom while scrolling down, keep the
287
+ // element in the clamped state until the user scrolls back up.
288
+ if (element.sticky.bottomLocked && this.scrollDirection !== 'up') {
289
+ this.updateElementRenderedSize(element);
290
+
291
+ this.css(element, {
292
+ position: 'fixed',
293
+ width: element.sticky.rect.width + 'px',
294
+ left: element.sticky.rect.left + 'px',
295
+ top: (element.sticky.container.rect.top + element.sticky.container.offsetHeight)
296
+ - (this.scrollTop + element.sticky.rect.height + element.sticky.marginBottom) + 'px',
297
+ });
298
+
299
+ if (element.sticky.stickyClass) {
300
+ element.classList.remove(element.sticky.stickyClass);
301
+ }
302
+
303
+ return;
304
+ }
305
+
273
306
  this.css(element, {
274
307
  position: 'fixed',
275
308
  width: element.sticky.rect.width + 'px',
@@ -280,6 +313,8 @@ class Sticky {
280
313
  (this.scrollTop + element.sticky.rect.height + element.sticky.marginTop)
281
314
  > (element.sticky.container.rect.top + element.sticky.container.offsetHeight - element.sticky.marginBottom)
282
315
  ) {
316
+ element.sticky.bottomLocked = true;
317
+ this.updateElementRenderedSize(element);
283
318
 
284
319
  if (element.sticky.stickyClass) {
285
320
  element.classList.remove(element.sticky.stickyClass);
@@ -288,14 +323,28 @@ class Sticky {
288
323
  this.css(element, {
289
324
  top: (element.sticky.container.rect.top + element.sticky.container.offsetHeight) - (this.scrollTop + element.sticky.rect.height + element.sticky.marginBottom) + 'px' }
290
325
  );
326
+ if (this.syncRenderedSize(element)) {
327
+ return;
328
+ }
291
329
  } else {
330
+ if (this.scrollDirection === 'up') {
331
+ element.sticky.bottomLocked = false;
332
+ }
333
+
292
334
  if (element.sticky.stickyClass) {
293
335
  element.classList.add(element.sticky.stickyClass);
294
336
  }
295
337
 
296
338
  this.css(element, { top: element.sticky.marginTop + 'px' });
339
+ if (this.syncRenderedSize(element)) {
340
+ return;
341
+ }
297
342
  }
298
343
  } else {
344
+ element.sticky.hasSyncedStickySize = false;
345
+ element.sticky.bottomLocked = false;
346
+ this.clearScheduledRenderedSizeSync(element);
347
+
299
348
  if (element.sticky.stickyClass) {
300
349
  element.classList.remove(element.sticky.stickyClass);
301
350
  }
@@ -315,7 +364,7 @@ class Sticky {
315
364
  */
316
365
  update() {
317
366
  this.forEach(this.elements, (element) => {
318
- element.sticky.rect = this.getRectangle(element);
367
+ this.updateElementRenderedSize(element);
319
368
  element.sticky.container.rect = this.getRectangle(element.sticky.container);
320
369
 
321
370
  this.activate(element);
@@ -333,6 +382,7 @@ class Sticky {
333
382
  window.removeEventListener('scroll', this.updateScrollTopPosition);
334
383
 
335
384
  this.forEach(this.elements, (element) => {
385
+ this.clearScheduledRenderedSizeSync(element);
336
386
  this.destroyResizeEvents(element);
337
387
  this.destroyScrollEvents(element);
338
388
  delete element.sticky;
@@ -400,6 +450,103 @@ class Sticky {
400
450
  }
401
451
 
402
452
 
453
+ /**
454
+ * Updates only the rendered width/height of a sticky element without resetting
455
+ * its stored document position. This keeps the original sticky trigger point
456
+ * while allowing update() to pick up size changes caused by sticky classes.
457
+ * @function
458
+ * @param {node} element - Sticky element
459
+ */
460
+ updateElementRenderedSize(element) {
461
+ const renderedRect = element.getBoundingClientRect();
462
+
463
+ if (!element.sticky.rect) {
464
+ element.sticky.rect = this.getRectangle(element);
465
+ }
466
+
467
+ element.sticky.rect.width = renderedRect.width;
468
+ element.sticky.rect.height = renderedRect.height;
469
+ }
470
+
471
+
472
+ /**
473
+ * Sync rendered size back into sticky measurements and rerun positioning once
474
+ * after sticky styles had time to animate their dimensions.
475
+ * @function
476
+ * @param {node} element - Sticky element
477
+ * @param {string} reason - Debug reason
478
+ * @return {boolean}
479
+ */
480
+ syncRenderedSize(element) {
481
+ if (element.sticky.hasSyncedStickySize) {
482
+ return false;
483
+ }
484
+
485
+ // If a delayed sync is already queued, let it finish instead of stacking
486
+ // more reflows while the sticky animation is still running.
487
+ if (element.sticky.syncRenderedSizeTimeout) {
488
+ return true;
489
+ }
490
+
491
+ const renderedRect = element.getBoundingClientRect();
492
+ const widthChanged = Math.abs(renderedRect.width - element.sticky.rect.width) > 0.5;
493
+ const heightChanged = Math.abs(renderedRect.height - element.sticky.rect.height) > 0.5;
494
+
495
+ if (!widthChanged && !heightChanged) {
496
+ return false;
497
+ }
498
+
499
+ if (element.sticky.isSyncingRenderedSize) {
500
+ return false;
501
+ }
502
+
503
+ this.scheduleRenderedSizeSync(element);
504
+ return true;
505
+ }
506
+
507
+
508
+ /**
509
+ * Schedule one delayed rendered-size sync to let CSS transitions settle.
510
+ * @function
511
+ * @param {node} element - Sticky element
512
+ */
513
+ scheduleRenderedSizeSync(element) {
514
+ element.sticky.syncRenderedSizeTimeout = window.setTimeout(() => {
515
+ element.sticky.syncRenderedSizeTimeout = null;
516
+
517
+ if (!element.sticky || element.isDisabled) {
518
+ return;
519
+ }
520
+
521
+ const renderedRect = element.getBoundingClientRect();
522
+
523
+ // Sticky styles can animate height after the element becomes fixed, so we
524
+ // re-read the rendered box after a delay and then re-run positioning.
525
+ element.sticky.rect.width = renderedRect.width;
526
+ element.sticky.rect.height = renderedRect.height;
527
+ element.sticky.container.rect = this.getRectangle(element.sticky.container);
528
+ element.sticky.hasSyncedStickySize = true;
529
+
530
+ element.sticky.isSyncingRenderedSize = true;
531
+ this.setPosition(element);
532
+ element.sticky.isSyncingRenderedSize = false;
533
+ }, 500);
534
+ }
535
+
536
+
537
+ /**
538
+ * Clear a pending delayed rendered-size sync.
539
+ * @function
540
+ * @param {node} element - Sticky element
541
+ */
542
+ clearScheduledRenderedSizeSync(element) {
543
+ if (!element.sticky || !element.sticky.syncRenderedSizeTimeout) {
544
+ return;
545
+ }
546
+
547
+ window.clearTimeout(element.sticky.syncRenderedSizeTimeout);
548
+ element.sticky.syncRenderedSizeTimeout = null;
549
+ }
403
550
  /**
404
551
  * Function that returns viewport dimensions
405
552
  * @function
@@ -420,6 +567,8 @@ class Sticky {
420
567
  */
421
568
  updateScrollTopPosition() {
422
569
  this.scrollTop = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0) || 0;
570
+ this.scrollDirection = this.scrollTop >= this.previousScrollTop ? 'down' : 'up';
571
+ this.previousScrollTop = this.scrollTop;
423
572
  }
424
573
 
425
574
 
@@ -1,16 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="CheckStyle-IDEA">
4
- <option name="configuration">
5
- <map>
6
- <entry key="checkstyle-version" value="8.43" />
7
- <entry key="copy-libs" value="false" />
8
- <entry key="location-0" value="BUNDLED:(bundled):Sun Checks" />
9
- <entry key="location-1" value="BUNDLED:(bundled):Google Checks" />
10
- <entry key="scan-before-checkin" value="false" />
11
- <entry key="scanscope" value="JavaOnly" />
12
- <entry key="suppress-errors" value="false" />
13
- </map>
14
- </option>
15
- </component>
16
- </project>
@@ -1,3 +0,0 @@
1
-
2
- F
3
- dist/sticky.compile.js,6/c/6c820c805f8673775cb16a38d8ca45931f445875