@traveledmap/sticky-js 1.3.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/.idea/misc.xml +2 -1
- package/.idea/vcs.xml +1 -0
- package/README.md +1 -0
- package/dist/sticky.compile.js +250 -10
- package/dist/sticky.min.js +1 -1
- package/dist/sticky.min.js.gz +0 -0
- package/package.json +5 -2
- package/src/sticky.js +238 -10
- 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/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
|
|
package/dist/sticky.compile.js
CHANGED
|
@@ -39,9 +39,12 @@ 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);
|
|
46
|
+
this.scrollDirection = 'down';
|
|
47
|
+
this.previousScrollTop = 0;
|
|
45
48
|
this.updateScrollTopPosition();
|
|
46
49
|
window.addEventListener('load', this.updateScrollTopPosition);
|
|
47
50
|
window.addEventListener('scroll', this.updateScrollTopPosition);
|
|
@@ -84,11 +87,16 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
84
87
|
// create container for variables needed in future
|
|
85
88
|
element.sticky = {}; // set default variables
|
|
86
89
|
|
|
87
|
-
element.sticky.active = false;
|
|
90
|
+
element.sticky.active = false; // Keep track of temporary state used when sticky styles animate element size.
|
|
91
|
+
|
|
92
|
+
element.sticky.hasSyncedStickySize = false;
|
|
93
|
+
element.sticky.bottomLocked = false;
|
|
94
|
+
element.sticky.syncRenderedSizeTimeout = null;
|
|
88
95
|
element.sticky.marginTop = parseInt(element.getAttribute('data-margin-top')) || this.options.marginTop;
|
|
89
96
|
element.sticky.marginBottom = parseInt(element.getAttribute('data-margin-bottom')) || this.options.marginBottom;
|
|
90
97
|
element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;
|
|
91
98
|
element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass;
|
|
99
|
+
element.sticky.stickyHeight = element.getAttribute('data-sticky-height') || this.options.stickyHeight;
|
|
92
100
|
element.sticky.wrap = element.hasAttribute('data-sticky-wrap') ? true : this.options.wrap; // @todo attribute for stickyContainer
|
|
93
101
|
// element.sticky.stickyContainer = element.getAttribute('data-sticky-container') || this.options.stickyContainer;
|
|
94
102
|
|
|
@@ -99,7 +107,9 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
99
107
|
|
|
100
108
|
if (element.tagName.toLowerCase() === 'img') {
|
|
101
109
|
element.onload = function () {
|
|
102
|
-
|
|
110
|
+
element.sticky.rect = _this2.getRectangle(element);
|
|
111
|
+
|
|
112
|
+
_this2.updateElementRenderedSize(element);
|
|
103
113
|
};
|
|
104
114
|
}
|
|
105
115
|
|
|
@@ -131,7 +141,9 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
131
141
|
}, {
|
|
132
142
|
key: "activate",
|
|
133
143
|
value: function activate(element) {
|
|
134
|
-
|
|
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) {
|
|
135
147
|
element.sticky.active = true;
|
|
136
148
|
}
|
|
137
149
|
|
|
@@ -189,12 +201,13 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
189
201
|
key: "onResizeEvents",
|
|
190
202
|
value: function onResizeEvents(element) {
|
|
191
203
|
this.vp = this.getViewportSize();
|
|
192
|
-
|
|
204
|
+
this.updateElementRenderedSize(element);
|
|
193
205
|
element.sticky.container.rect = this.getRectangle(element.sticky.container);
|
|
206
|
+
var stickyHeight = this.getStickyStateHeight(element);
|
|
194
207
|
|
|
195
|
-
if (element.sticky.rect.top +
|
|
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) {
|
|
196
209
|
element.sticky.active = true;
|
|
197
|
-
} else if (element.sticky.rect.top +
|
|
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) {
|
|
198
211
|
element.sticky.active = false;
|
|
199
212
|
}
|
|
200
213
|
|
|
@@ -260,8 +273,9 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
260
273
|
top: '',
|
|
261
274
|
left: ''
|
|
262
275
|
});
|
|
276
|
+
var stickyHeight = this.getStickyStateHeight(element);
|
|
263
277
|
|
|
264
|
-
if (this.vp.height <
|
|
278
|
+
if (this.vp.height < stickyHeight || !element.sticky.active) {
|
|
265
279
|
return;
|
|
266
280
|
}
|
|
267
281
|
|
|
@@ -288,14 +302,41 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
288
302
|
if (element.sticky.stickyClass) {
|
|
289
303
|
element.classList.add(element.sticky.stickyClass);
|
|
290
304
|
}
|
|
305
|
+
|
|
306
|
+
element.sticky.bottomLocked = false;
|
|
307
|
+
|
|
308
|
+
if (this.syncRenderedSize(element)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
291
311
|
} else if (this.scrollTop > element.sticky.rect.top - element.sticky.marginTop) {
|
|
312
|
+
// Once we reached the container bottom while scrolling down, keep the
|
|
313
|
+
// element in the clamped state until the user scrolls back up.
|
|
314
|
+
if (element.sticky.bottomLocked && this.scrollDirection !== 'up') {
|
|
315
|
+
this.updateElementRenderedSize(element);
|
|
316
|
+
this.css(element, {
|
|
317
|
+
position: 'fixed',
|
|
318
|
+
width: element.sticky.rect.width + 'px',
|
|
319
|
+
left: element.sticky.rect.left + 'px',
|
|
320
|
+
top: element.sticky.container.rect.top + element.sticky.container.offsetHeight - (this.scrollTop + element.sticky.rect.height + element.sticky.marginBottom) + 'px'
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
if (element.sticky.stickyClass) {
|
|
324
|
+
element.classList.remove(element.sticky.stickyClass);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
292
330
|
this.css(element, {
|
|
293
331
|
position: 'fixed',
|
|
294
332
|
width: element.sticky.rect.width + 'px',
|
|
295
333
|
left: element.sticky.rect.left + 'px'
|
|
296
334
|
});
|
|
297
335
|
|
|
298
|
-
if (this.scrollTop +
|
|
336
|
+
if (this.scrollTop + stickyHeight + element.sticky.marginTop > element.sticky.container.rect.top + element.sticky.container.offsetHeight - element.sticky.marginBottom) {
|
|
337
|
+
element.sticky.bottomLocked = true;
|
|
338
|
+
this.updateElementRenderedSize(element);
|
|
339
|
+
|
|
299
340
|
if (element.sticky.stickyClass) {
|
|
300
341
|
element.classList.remove(element.sticky.stickyClass);
|
|
301
342
|
}
|
|
@@ -303,7 +344,15 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
303
344
|
this.css(element, {
|
|
304
345
|
top: element.sticky.container.rect.top + element.sticky.container.offsetHeight - (this.scrollTop + element.sticky.rect.height + element.sticky.marginBottom) + 'px'
|
|
305
346
|
});
|
|
347
|
+
|
|
348
|
+
if (this.syncRenderedSize(element)) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
306
351
|
} else {
|
|
352
|
+
if (this.scrollDirection === 'up') {
|
|
353
|
+
element.sticky.bottomLocked = false;
|
|
354
|
+
}
|
|
355
|
+
|
|
307
356
|
if (element.sticky.stickyClass) {
|
|
308
357
|
element.classList.add(element.sticky.stickyClass);
|
|
309
358
|
}
|
|
@@ -311,8 +360,16 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
311
360
|
this.css(element, {
|
|
312
361
|
top: element.sticky.marginTop + 'px'
|
|
313
362
|
});
|
|
363
|
+
|
|
364
|
+
if (this.syncRenderedSize(element)) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
314
367
|
}
|
|
315
368
|
} else {
|
|
369
|
+
element.sticky.hasSyncedStickySize = false;
|
|
370
|
+
element.sticky.bottomLocked = false;
|
|
371
|
+
this.clearScheduledRenderedSizeSync(element);
|
|
372
|
+
|
|
316
373
|
if (element.sticky.stickyClass) {
|
|
317
374
|
element.classList.remove(element.sticky.stickyClass);
|
|
318
375
|
}
|
|
@@ -344,7 +401,8 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
344
401
|
var _this5 = this;
|
|
345
402
|
|
|
346
403
|
this.forEach(this.elements, function (element) {
|
|
347
|
-
|
|
404
|
+
_this5.updateElementRenderedSize(element);
|
|
405
|
+
|
|
348
406
|
element.sticky.container.rect = _this5.getRectangle(element.sticky.container);
|
|
349
407
|
|
|
350
408
|
_this5.activate(element);
|
|
@@ -365,6 +423,8 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
365
423
|
window.removeEventListener('load', this.updateScrollTopPosition);
|
|
366
424
|
window.removeEventListener('scroll', this.updateScrollTopPosition);
|
|
367
425
|
this.forEach(this.elements, function (element) {
|
|
426
|
+
_this6.clearScheduledRenderedSizeSync(element);
|
|
427
|
+
|
|
368
428
|
_this6.destroyResizeEvents(element);
|
|
369
429
|
|
|
370
430
|
_this6.destroyScrollEvents(element);
|
|
@@ -451,6 +511,184 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
451
511
|
height: height
|
|
452
512
|
};
|
|
453
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
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Updates only the rendered width/height of a sticky element without resetting
|
|
589
|
+
* its stored document position. This keeps the original sticky trigger point
|
|
590
|
+
* while allowing update() to pick up size changes caused by sticky classes.
|
|
591
|
+
* @function
|
|
592
|
+
* @param {node} element - Sticky element
|
|
593
|
+
*/
|
|
594
|
+
|
|
595
|
+
}, {
|
|
596
|
+
key: "updateElementRenderedSize",
|
|
597
|
+
value: function updateElementRenderedSize(element) {
|
|
598
|
+
var renderedRect = element.getBoundingClientRect();
|
|
599
|
+
|
|
600
|
+
if (!element.sticky.rect) {
|
|
601
|
+
element.sticky.rect = this.getRectangle(element);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
element.sticky.rect.width = renderedRect.width;
|
|
605
|
+
element.sticky.rect.height = renderedRect.height;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Sync rendered size back into sticky measurements and rerun positioning once
|
|
609
|
+
* after sticky styles had time to animate their dimensions.
|
|
610
|
+
* @function
|
|
611
|
+
* @param {node} element - Sticky element
|
|
612
|
+
* @param {string} reason - Debug reason
|
|
613
|
+
* @return {boolean}
|
|
614
|
+
*/
|
|
615
|
+
|
|
616
|
+
}, {
|
|
617
|
+
key: "syncRenderedSize",
|
|
618
|
+
value: function syncRenderedSize(element) {
|
|
619
|
+
if (element.sticky.hasSyncedStickySize) {
|
|
620
|
+
return false;
|
|
621
|
+
} // If a delayed sync is already queued, let it finish instead of stacking
|
|
622
|
+
// more reflows while the sticky animation is still running.
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
if (element.sticky.syncRenderedSizeTimeout) {
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
var renderedRect = element.getBoundingClientRect();
|
|
630
|
+
var widthChanged = Math.abs(renderedRect.width - element.sticky.rect.width) > 0.5;
|
|
631
|
+
var heightChanged = Math.abs(renderedRect.height - element.sticky.rect.height) > 0.5;
|
|
632
|
+
|
|
633
|
+
if (!widthChanged && !heightChanged) {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (element.sticky.isSyncingRenderedSize) {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
this.scheduleRenderedSizeSync(element);
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Schedule one delayed rendered-size sync to let CSS transitions settle.
|
|
646
|
+
* @function
|
|
647
|
+
* @param {node} element - Sticky element
|
|
648
|
+
*/
|
|
649
|
+
|
|
650
|
+
}, {
|
|
651
|
+
key: "scheduleRenderedSizeSync",
|
|
652
|
+
value: function scheduleRenderedSizeSync(element) {
|
|
653
|
+
var _this9 = this;
|
|
654
|
+
|
|
655
|
+
element.sticky.syncRenderedSizeTimeout = window.setTimeout(function () {
|
|
656
|
+
element.sticky.syncRenderedSizeTimeout = null;
|
|
657
|
+
|
|
658
|
+
if (!element.sticky || element.isDisabled) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
var renderedRect = element.getBoundingClientRect(); // Sticky styles can animate height after the element becomes fixed, so we
|
|
663
|
+
// re-read the rendered box after a delay and then re-run positioning.
|
|
664
|
+
|
|
665
|
+
element.sticky.rect.width = renderedRect.width;
|
|
666
|
+
element.sticky.rect.height = renderedRect.height;
|
|
667
|
+
element.sticky.container.rect = _this9.getRectangle(element.sticky.container);
|
|
668
|
+
element.sticky.hasSyncedStickySize = true;
|
|
669
|
+
element.sticky.isSyncingRenderedSize = true;
|
|
670
|
+
|
|
671
|
+
_this9.setPosition(element);
|
|
672
|
+
|
|
673
|
+
element.sticky.isSyncingRenderedSize = false;
|
|
674
|
+
}, 500);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Clear a pending delayed rendered-size sync.
|
|
678
|
+
* @function
|
|
679
|
+
* @param {node} element - Sticky element
|
|
680
|
+
*/
|
|
681
|
+
|
|
682
|
+
}, {
|
|
683
|
+
key: "clearScheduledRenderedSizeSync",
|
|
684
|
+
value: function clearScheduledRenderedSizeSync(element) {
|
|
685
|
+
if (!element.sticky || !element.sticky.syncRenderedSizeTimeout) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
window.clearTimeout(element.sticky.syncRenderedSizeTimeout);
|
|
690
|
+
element.sticky.syncRenderedSizeTimeout = null;
|
|
691
|
+
}
|
|
454
692
|
/**
|
|
455
693
|
* Function that returns viewport dimensions
|
|
456
694
|
* @function
|
|
@@ -475,6 +713,8 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
475
713
|
key: "updateScrollTopPosition",
|
|
476
714
|
value: function updateScrollTopPosition() {
|
|
477
715
|
this.scrollTop = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0) || 0;
|
|
716
|
+
this.scrollDirection = this.scrollTop >= this.previousScrollTop ? 'down' : 'up';
|
|
717
|
+
this.previousScrollTop = this.scrollTop;
|
|
478
718
|
}
|
|
479
719
|
/**
|
|
480
720
|
* Helper function for loops
|
package/dist/sticky.min.js
CHANGED
|
@@ -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",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);
|
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.5.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
|
@@ -34,9 +34,12 @@ 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);
|
|
41
|
+
this.scrollDirection = 'down';
|
|
42
|
+
this.previousScrollTop = 0;
|
|
40
43
|
|
|
41
44
|
this.updateScrollTopPosition();
|
|
42
45
|
window.addEventListener('load', this.updateScrollTopPosition);
|
|
@@ -74,11 +77,16 @@ class Sticky {
|
|
|
74
77
|
|
|
75
78
|
// set default variables
|
|
76
79
|
element.sticky.active = false;
|
|
80
|
+
// Keep track of temporary state used when sticky styles animate element size.
|
|
81
|
+
element.sticky.hasSyncedStickySize = false;
|
|
82
|
+
element.sticky.bottomLocked = false;
|
|
83
|
+
element.sticky.syncRenderedSizeTimeout = null;
|
|
77
84
|
|
|
78
85
|
element.sticky.marginTop = parseInt(element.getAttribute('data-margin-top')) || this.options.marginTop;
|
|
79
86
|
element.sticky.marginBottom = parseInt(element.getAttribute('data-margin-bottom')) || this.options.marginBottom;
|
|
80
87
|
element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;
|
|
81
88
|
element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass;
|
|
89
|
+
element.sticky.stickyHeight = element.getAttribute('data-sticky-height') || this.options.stickyHeight;
|
|
82
90
|
element.sticky.wrap = element.hasAttribute('data-sticky-wrap') ? true : this.options.wrap;
|
|
83
91
|
// @todo attribute for stickyContainer
|
|
84
92
|
// element.sticky.stickyContainer = element.getAttribute('data-sticky-container') || this.options.stickyContainer;
|
|
@@ -91,7 +99,10 @@ class Sticky {
|
|
|
91
99
|
|
|
92
100
|
// fix when element is image that has not yet loaded and width, height = 0
|
|
93
101
|
if (element.tagName.toLowerCase() === 'img') {
|
|
94
|
-
element.onload = () =>
|
|
102
|
+
element.onload = () => {
|
|
103
|
+
element.sticky.rect = this.getRectangle(element);
|
|
104
|
+
this.updateElementRenderedSize(element);
|
|
105
|
+
};
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
if (element.sticky.wrap) {
|
|
@@ -119,9 +130,11 @@ class Sticky {
|
|
|
119
130
|
* @function
|
|
120
131
|
* @param {node} element - Element to be activated
|
|
121
132
|
*/
|
|
122
|
-
|
|
133
|
+
activate(element) {
|
|
134
|
+
const stickyHeight = this.getStickyStateHeight(element);
|
|
135
|
+
|
|
123
136
|
if (
|
|
124
|
-
((element.sticky.rect.top +
|
|
137
|
+
((element.sticky.rect.top + stickyHeight) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
|
|
125
138
|
&& (element.sticky.stickyFor < this.vp.width)
|
|
126
139
|
&& !element.sticky.active
|
|
127
140
|
) {
|
|
@@ -172,20 +185,21 @@ class Sticky {
|
|
|
172
185
|
* @function
|
|
173
186
|
* @param {node} element - Element for which event function is fired
|
|
174
187
|
*/
|
|
175
|
-
|
|
188
|
+
onResizeEvents(element) {
|
|
176
189
|
this.vp = this.getViewportSize();
|
|
177
190
|
|
|
178
|
-
|
|
191
|
+
this.updateElementRenderedSize(element);
|
|
179
192
|
element.sticky.container.rect = this.getRectangle(element.sticky.container);
|
|
193
|
+
const stickyHeight = this.getStickyStateHeight(element);
|
|
180
194
|
|
|
181
195
|
if (
|
|
182
|
-
((element.sticky.rect.top +
|
|
196
|
+
((element.sticky.rect.top + stickyHeight) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
|
|
183
197
|
&& (element.sticky.stickyFor < this.vp.width)
|
|
184
198
|
&& !element.sticky.active
|
|
185
199
|
) {
|
|
186
200
|
element.sticky.active = true;
|
|
187
201
|
} else if (
|
|
188
|
-
((element.sticky.rect.top +
|
|
202
|
+
((element.sticky.rect.top + stickyHeight) >= (element.sticky.container.rect.top + element.sticky.container.rect.height))
|
|
189
203
|
|| element.sticky.stickyFor >= this.vp.width
|
|
190
204
|
&& element.sticky.active
|
|
191
205
|
) {
|
|
@@ -240,7 +254,9 @@ class Sticky {
|
|
|
240
254
|
}
|
|
241
255
|
this.css(element, { position: '', width: '', top: '', left: '' });
|
|
242
256
|
|
|
243
|
-
|
|
257
|
+
const stickyHeight = this.getStickyStateHeight(element);
|
|
258
|
+
|
|
259
|
+
if ((this.vp.height < stickyHeight) || !element.sticky.active) {
|
|
244
260
|
return;
|
|
245
261
|
}
|
|
246
262
|
|
|
@@ -269,7 +285,31 @@ class Sticky {
|
|
|
269
285
|
if (element.sticky.stickyClass) {
|
|
270
286
|
element.classList.add(element.sticky.stickyClass);
|
|
271
287
|
}
|
|
288
|
+
element.sticky.bottomLocked = false;
|
|
289
|
+
if (this.syncRenderedSize(element)) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
272
292
|
} else if (this.scrollTop > (element.sticky.rect.top - element.sticky.marginTop)) {
|
|
293
|
+
// Once we reached the container bottom while scrolling down, keep the
|
|
294
|
+
// element in the clamped state until the user scrolls back up.
|
|
295
|
+
if (element.sticky.bottomLocked && this.scrollDirection !== 'up') {
|
|
296
|
+
this.updateElementRenderedSize(element);
|
|
297
|
+
|
|
298
|
+
this.css(element, {
|
|
299
|
+
position: 'fixed',
|
|
300
|
+
width: element.sticky.rect.width + 'px',
|
|
301
|
+
left: element.sticky.rect.left + 'px',
|
|
302
|
+
top: (element.sticky.container.rect.top + element.sticky.container.offsetHeight)
|
|
303
|
+
- (this.scrollTop + element.sticky.rect.height + element.sticky.marginBottom) + 'px',
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (element.sticky.stickyClass) {
|
|
307
|
+
element.classList.remove(element.sticky.stickyClass);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
273
313
|
this.css(element, {
|
|
274
314
|
position: 'fixed',
|
|
275
315
|
width: element.sticky.rect.width + 'px',
|
|
@@ -277,9 +317,11 @@ class Sticky {
|
|
|
277
317
|
});
|
|
278
318
|
|
|
279
319
|
if (
|
|
280
|
-
(this.scrollTop +
|
|
320
|
+
(this.scrollTop + stickyHeight + element.sticky.marginTop)
|
|
281
321
|
> (element.sticky.container.rect.top + element.sticky.container.offsetHeight - element.sticky.marginBottom)
|
|
282
322
|
) {
|
|
323
|
+
element.sticky.bottomLocked = true;
|
|
324
|
+
this.updateElementRenderedSize(element);
|
|
283
325
|
|
|
284
326
|
if (element.sticky.stickyClass) {
|
|
285
327
|
element.classList.remove(element.sticky.stickyClass);
|
|
@@ -288,14 +330,28 @@ class Sticky {
|
|
|
288
330
|
this.css(element, {
|
|
289
331
|
top: (element.sticky.container.rect.top + element.sticky.container.offsetHeight) - (this.scrollTop + element.sticky.rect.height + element.sticky.marginBottom) + 'px' }
|
|
290
332
|
);
|
|
333
|
+
if (this.syncRenderedSize(element)) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
291
336
|
} else {
|
|
337
|
+
if (this.scrollDirection === 'up') {
|
|
338
|
+
element.sticky.bottomLocked = false;
|
|
339
|
+
}
|
|
340
|
+
|
|
292
341
|
if (element.sticky.stickyClass) {
|
|
293
342
|
element.classList.add(element.sticky.stickyClass);
|
|
294
343
|
}
|
|
295
344
|
|
|
296
345
|
this.css(element, { top: element.sticky.marginTop + 'px' });
|
|
346
|
+
if (this.syncRenderedSize(element)) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
297
349
|
}
|
|
298
350
|
} else {
|
|
351
|
+
element.sticky.hasSyncedStickySize = false;
|
|
352
|
+
element.sticky.bottomLocked = false;
|
|
353
|
+
this.clearScheduledRenderedSizeSync(element);
|
|
354
|
+
|
|
299
355
|
if (element.sticky.stickyClass) {
|
|
300
356
|
element.classList.remove(element.sticky.stickyClass);
|
|
301
357
|
}
|
|
@@ -315,7 +371,7 @@ class Sticky {
|
|
|
315
371
|
*/
|
|
316
372
|
update() {
|
|
317
373
|
this.forEach(this.elements, (element) => {
|
|
318
|
-
|
|
374
|
+
this.updateElementRenderedSize(element);
|
|
319
375
|
element.sticky.container.rect = this.getRectangle(element.sticky.container);
|
|
320
376
|
|
|
321
377
|
this.activate(element);
|
|
@@ -333,6 +389,7 @@ class Sticky {
|
|
|
333
389
|
window.removeEventListener('scroll', this.updateScrollTopPosition);
|
|
334
390
|
|
|
335
391
|
this.forEach(this.elements, (element) => {
|
|
392
|
+
this.clearScheduledRenderedSizeSync(element);
|
|
336
393
|
this.destroyResizeEvents(element);
|
|
337
394
|
this.destroyScrollEvents(element);
|
|
338
395
|
delete element.sticky;
|
|
@@ -400,6 +457,175 @@ class Sticky {
|
|
|
400
457
|
}
|
|
401
458
|
|
|
402
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
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Updates only the rendered width/height of a sticky element without resetting
|
|
534
|
+
* its stored document position. This keeps the original sticky trigger point
|
|
535
|
+
* while allowing update() to pick up size changes caused by sticky classes.
|
|
536
|
+
* @function
|
|
537
|
+
* @param {node} element - Sticky element
|
|
538
|
+
*/
|
|
539
|
+
updateElementRenderedSize(element) {
|
|
540
|
+
const renderedRect = element.getBoundingClientRect();
|
|
541
|
+
|
|
542
|
+
if (!element.sticky.rect) {
|
|
543
|
+
element.sticky.rect = this.getRectangle(element);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
element.sticky.rect.width = renderedRect.width;
|
|
547
|
+
element.sticky.rect.height = renderedRect.height;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Sync rendered size back into sticky measurements and rerun positioning once
|
|
553
|
+
* after sticky styles had time to animate their dimensions.
|
|
554
|
+
* @function
|
|
555
|
+
* @param {node} element - Sticky element
|
|
556
|
+
* @param {string} reason - Debug reason
|
|
557
|
+
* @return {boolean}
|
|
558
|
+
*/
|
|
559
|
+
syncRenderedSize(element) {
|
|
560
|
+
if (element.sticky.hasSyncedStickySize) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// If a delayed sync is already queued, let it finish instead of stacking
|
|
565
|
+
// more reflows while the sticky animation is still running.
|
|
566
|
+
if (element.sticky.syncRenderedSizeTimeout) {
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const renderedRect = element.getBoundingClientRect();
|
|
571
|
+
const widthChanged = Math.abs(renderedRect.width - element.sticky.rect.width) > 0.5;
|
|
572
|
+
const heightChanged = Math.abs(renderedRect.height - element.sticky.rect.height) > 0.5;
|
|
573
|
+
|
|
574
|
+
if (!widthChanged && !heightChanged) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (element.sticky.isSyncingRenderedSize) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
this.scheduleRenderedSizeSync(element);
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Schedule one delayed rendered-size sync to let CSS transitions settle.
|
|
589
|
+
* @function
|
|
590
|
+
* @param {node} element - Sticky element
|
|
591
|
+
*/
|
|
592
|
+
scheduleRenderedSizeSync(element) {
|
|
593
|
+
element.sticky.syncRenderedSizeTimeout = window.setTimeout(() => {
|
|
594
|
+
element.sticky.syncRenderedSizeTimeout = null;
|
|
595
|
+
|
|
596
|
+
if (!element.sticky || element.isDisabled) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const renderedRect = element.getBoundingClientRect();
|
|
601
|
+
|
|
602
|
+
// Sticky styles can animate height after the element becomes fixed, so we
|
|
603
|
+
// re-read the rendered box after a delay and then re-run positioning.
|
|
604
|
+
element.sticky.rect.width = renderedRect.width;
|
|
605
|
+
element.sticky.rect.height = renderedRect.height;
|
|
606
|
+
element.sticky.container.rect = this.getRectangle(element.sticky.container);
|
|
607
|
+
element.sticky.hasSyncedStickySize = true;
|
|
608
|
+
|
|
609
|
+
element.sticky.isSyncingRenderedSize = true;
|
|
610
|
+
this.setPosition(element);
|
|
611
|
+
element.sticky.isSyncingRenderedSize = false;
|
|
612
|
+
}, 500);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Clear a pending delayed rendered-size sync.
|
|
618
|
+
* @function
|
|
619
|
+
* @param {node} element - Sticky element
|
|
620
|
+
*/
|
|
621
|
+
clearScheduledRenderedSizeSync(element) {
|
|
622
|
+
if (!element.sticky || !element.sticky.syncRenderedSizeTimeout) {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
window.clearTimeout(element.sticky.syncRenderedSizeTimeout);
|
|
627
|
+
element.sticky.syncRenderedSizeTimeout = null;
|
|
628
|
+
}
|
|
403
629
|
/**
|
|
404
630
|
* Function that returns viewport dimensions
|
|
405
631
|
* @function
|
|
@@ -420,6 +646,8 @@ class Sticky {
|
|
|
420
646
|
*/
|
|
421
647
|
updateScrollTopPosition() {
|
|
422
648
|
this.scrollTop = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0) || 0;
|
|
649
|
+
this.scrollDirection = this.scrollTop >= this.previousScrollTop ? 'down' : 'up';
|
|
650
|
+
this.previousScrollTop = this.scrollTop;
|
|
423
651
|
}
|
|
424
652
|
|
|
425
653
|
|
|
@@ -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
|