N°4021 - Change approach for sticky header, use the ScrollMagic lib as in the scrolling tabs to use the same abstraction level everywhere

This commit is contained in:
Molkobain
2021-05-27 17:42:29 +02:00
parent e669cfcea1
commit b8ef4885e5
3 changed files with 42 additions and 59 deletions

View File

@@ -3,6 +3,9 @@
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
*/ */
$ibo-panel-within-modal--sticky-sentinel-top--top: -1 * $ibo-vendors-jqueryui--ui-dialog-content--padding-y !default;
$ibo-panel-within-modal--sticky-sentinel-top--height: $ibo-vendors-jqueryui--ui-dialog-content--padding-y !default;
$ibo-panel-within-modal--header--top--is-sticky: -1 * $ibo-vendors-jqueryui--ui-dialog-content--padding-y !default; $ibo-panel-within-modal--header--top--is-sticky: -1 * $ibo-vendors-jqueryui--ui-dialog-content--padding-y !default;
.ui-dialog-content { .ui-dialog-content {
@@ -13,12 +16,14 @@ $ibo-panel-within-modal--header--top--is-sticky: -1 * $ibo-vendors-jqueryui--ui-
* - We don't want to hardcode the modal's markup selector if not necessary as it could change in the future (and is already different in read-only vs edition) * - We don't want to hardcode the modal's markup selector if not necessary as it could change in the future (and is already different in read-only vs edition)
* - Unlike in JS, there no easy way to find the closest descendant * - Unlike in JS, there no easy way to find the closest descendant
*/ */
.ibo-panel { .ibo-panel.ibo-has-sticky-header {
/* Sticky header rules */ /* Sticky header rules */
> .ibo-panel--header { > .ibo-sticky-sentinel-top {
&.ibo-is-sticking { top: $ibo-panel-within-modal--sticky-sentinel-top--top;
height: $ibo-panel-within-modal--sticky-sentinel-top--height;
}
> .ibo-panel--header.ibo-is-sticking {
top: $ibo-panel-within-modal--header--top--is-sticky; top: $ibo-panel-within-modal--header--top--is-sticky;
}
} }
} }
} }

View File

@@ -267,8 +267,8 @@ $ibo-panel-colors: (
/* All transitions should have a specific duration except for the header's "top" property otherwise it feels laggy */ /* All transitions should have a specific duration except for the header's "top" property otherwise it feels laggy */
/* - The header itself */ /* - The header itself */
transition-property: $ibo-panel--base-transition-property, top; transition-property: $ibo-panel--base-transition-property, top, background-color;
transition-duration: $ibo-panel--base-transition-duration, 0s; transition-duration: $ibo-panel--base-transition-duration, 0s, 0s;
transition-timing-function: $ibo-panel--base-transition-timing-function; transition-timing-function: $ibo-panel--base-transition-timing-function;
/* - Impacted descendants (we don't put "*" as it can get shaky otherwise) */ /* - Impacted descendants (we don't put "*" as it can get shaky otherwise) */
.ibo-panel--title, .ibo-panel--title,

View File

@@ -36,7 +36,6 @@ $(function () {
is_sticking: 'ibo-is-sticking', is_sticking: 'ibo-is-sticking',
sticky_sentinel: 'ibo-sticky-sentinel', sticky_sentinel: 'ibo-sticky-sentinel',
sticky_sentinel_top: 'ibo-sticky-sentinel-top', sticky_sentinel_top: 'ibo-sticky-sentinel-top',
sticky_sentinel_bottom: 'ibo-sticky-sentinel-bottom',
}, },
js_selectors: js_selectors:
{ {
@@ -44,10 +43,9 @@ $(function () {
modal_content: '.ui-dialog-content', modal_content: '.ui-dialog-content',
panel_header: '[data-role="ibo-panel--header"]:first', panel_header: '[data-role="ibo-panel--header"]:first',
panel_header_sticky_sentinel_top: '[data-role="ibo-panel--header--sticky-sentinel-top"]', panel_header_sticky_sentinel_top: '[data-role="ibo-panel--header--sticky-sentinel-top"]',
panel_header_sticky_sentinel_bottom: '[data-role="ibo-panel--header--sticky-sentinel-bottom"]',
}, },
// {IntersectionObserver} Observer for the sticky header // {ScrollMagic.Controller} SM controller for the sticky header
sticky_header_observer: null, sticky_header_controller: null,
// the constructor // the constructor
_create: function () { _create: function () {
@@ -64,17 +62,14 @@ $(function () {
if (this._isHeaderVisibleOnScroll()) { if (this._isHeaderVisibleOnScroll()) {
this.element.addClass(this.css_classes.has_sticky_header); this.element.addClass(this.css_classes.has_sticky_header);
// Add sentinels to the markup to detect when the element changes between scrolling & sticked states // Add sentinel to the markup to detect when the element changes between scrolling & sticked states
$('<div></div>') $('<div></div>')
.addClass(this.css_classes.sticky_sentinel) .addClass(this.css_classes.sticky_sentinel)
.addClass(this.css_classes.sticky_sentinel_top) .addClass(this.css_classes.sticky_sentinel_top)
.attr('data-role', 'ibo-panel--header--sticky-sentinel-top') .attr('data-role', 'ibo-panel--header--sticky-sentinel-top')
.prependTo(this.element); .prependTo(this.element);
$('<div></div>')
.addClass(this.css_classes.sticky_sentinel) this._updateStickyHeaderHandler();
.addClass(this.css_classes.sticky_sentinel_bottom)
.attr('data-role', 'ibo-panel--header--sticky-sentinel-bottom')
.appendTo(this.element);
} }
}, },
_bindEvents: function () { _bindEvents: function () {
@@ -82,36 +77,27 @@ $(function () {
const oBodyElem = $('body'); const oBodyElem = $('body');
if (this._isHeaderVisibleOnScroll()) { if (this._isHeaderVisibleOnScroll()) {
this._observeStickyHeaderChanges();
// When a modal opens, it could have been for this panel. As the panel is moved into the modal's markup after this JS widget is instantiated // When a modal opens, it could have been for this panel. As the panel is moved into the modal's markup after this JS widget is instantiated
// the viewport is not the right one and we need to update it. // the viewport is not the right one and we need to update it.
oBodyElem.on('dialogopen', function(){ oBodyElem.on('dialogopen', function(){
me._observeStickyHeaderChanges(); me._updateStickyHeaderHandler();
}); });
} }
}, },
// Stickey header helpers // Stickey header helpers
/** /**
* Instantiate an observer on the header to toggle its "sticky" state and fire an event * Create or update an handler on the header to toggle its "sticky" state.
* Update is needed when the panel was moved to other DOM node.
* @private * @private
*/ */
_observeStickyHeaderChanges: function () { _updateStickyHeaderHandler: function () {
const me = this; const me = this;
// Determine in which kind of container the panel is // Determine in which kind of container the panel is
let oNewViewportElem = null; let oNewViewportElem = this.element.scrollParent()[0];
// - In a modal
if (this.element.closest(this.js_selectors.modal_content).length > 0) {
oNewViewportElem = this.element.closest(this.js_selectors.modal_content)[0];
}
// - In a standard page
else if (this.element.closest('#ibo-center-container').length > 0) {
oNewViewportElem = this.element.closest('#ibo-center-container')[0];
}
// If viewport hasn't changed, there is no need to refresh the observer // If viewport hasn't changed, there is no need to refresh the SM controller
if (oNewViewportElem === this.options.viewport_elem) { if (oNewViewportElem === this.options.viewport_elem) {
return; return;
} }
@@ -119,37 +105,29 @@ $(function () {
// Update the reference viewport // Update the reference viewport
this.options.viewport_elem = oNewViewportElem; this.options.viewport_elem = oNewViewportElem;
// Clean observer if there was already one // Clean SM controller if there was already one
if (null !== this.sticky_header_observer) { if (null !== this.sticky_header_controller) {
this.sticky_header_observer.disconnect(); this.sticky_header_controller.destroy(true);
} }
// Prepare observer options // Prepare SM controller
const oOptions = { this.sticky_header_controller = new ScrollMagic.Controller({
root: this.options.viewport_elem, container: this.options.viewport_elem,
threshold: 0, });
};
// Instantiate observer and callback for the top sentinel let oSMScene = new ScrollMagic.Scene({
this.sticky_header_observer = new IntersectionObserver(function (aEntries, oObserver) { triggerElement: this.element.find(this.js_selectors.panel_header_sticky_sentinel_top)[0],
for (const oEntry of aEntries) { triggerHook: 0,
const oSentinelInfo = oEntry.boundingClientRect; duration: this.element.outerHeight(),
const oRootInfo = oEntry.rootBounds; offset: this.element.find(this.js_selectors.panel_header_sticky_sentinel_top).outerHeight()
})
// Started sticking. .on('enter', function(){
if (oSentinelInfo.bottom < oRootInfo.top) { me._onHeaderBecomesSticky();
me._onHeaderBecomesSticky(); })
.on('leave', function(){
} me._onHeaderStopsBeingSticky();
})
// Stopped sticking. .addTo(this.sticky_header_controller);
if (oSentinelInfo.bottom >= oRootInfo.top &&
oSentinelInfo.bottom < oRootInfo.bottom) {
me._onHeaderStopsBeingSticky();
}
}
}, oOptions);
this.sticky_header_observer.observe(this.element.find(this.js_selectors.panel_header_sticky_sentinel_top)[0]);
}, },
_onHeaderBecomesSticky: function () { _onHeaderBecomesSticky: function () {
this.element.find(this.js_selectors.panel_header).addClass(this.css_classes.is_sticking); this.element.find(this.js_selectors.panel_header).addClass(this.css_classes.is_sticking);