@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 +2 -1
- package/.idea/vcs.xml +1 -0
- package/dist/sticky.compile.js +165 -4
- package/dist/sticky.min.js +1 -1
- package/dist/sticky.min.js.gz +0 -0
- package/package.json +5 -2
- package/src/sticky.js +152 -3
- package/.idea/checkstyle-idea.xml +0 -16
- package/.idea/sonarlint/issuestore/6/c/6c820c805f8673775cb16a38d8ca45931f445875 +0 -0
- package/.idea/sonarlint/issuestore/index.pb +0 -3
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="
|
|
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
package/dist/sticky.compile.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/sticky.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function _classCallCheck(t,
|
|
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);
|
package/dist/sticky.min.js.gz
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@traveledmap/sticky-js",
|
|
3
|
-
"version": "1.
|
|
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": "
|
|
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 = () =>
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
|
File without changes
|