bootstrap-italia
Version:
Bootstrap Italia è un tema Bootstrap 5 per la creazione di applicazioni web nel pieno rispetto delle linee guida di design per i siti internet e i servizi digitali della PA
255 lines (218 loc) • 6.9 kB
JavaScript
import BaseComponent from './base-component.js'
import {
getSelectorFromElement, //in base al bs-target
} from './util/index'
import EventHandler from './dom/event-handler'
import SelectorEngine from './dom/selector-engine'
import Manipulator from './dom/manipulator'
import { isScreenMobile } from './util/device'
import onDocumentScroll from './util/on-document-scroll'
const NAME = 'sticky'
const DATA_KEY = 'bs.sticky'
const EVENT_KEY = `.${DATA_KEY}`
const EVENT_RESIZE = `resize${EVENT_KEY}`
const EVENT_STICKY_ON = `on${EVENT_KEY}`
const EVENT_STICKY_OFF = `off${EVENT_KEY}`
const CLASS_NAME_WRAPPER = 'bs-it-sticky-wrapper'
const CLASS_NAME_STICKY = 'bs-is-sticky'
const CLASS_NAME_FIXED = 'bs-is-fixed'
const DATA_TARGET_MOBILE = 'data-bs-target-mobile'
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="sticky"]'
const Default = {
positionType: 'sticky', //fixed
stickyClassName: '',
stackable: false,
paddingTop: 0,
}
class Sticky extends BaseComponent {
constructor(element, config) {
super(element)
this._config = this._getConfig(config)
this._isSticky = false
this._wrapper = null
this._stickyTarget = SelectorEngine.findOne(getSelectorFromElement(this._element), this._element) || this._element
this._stickyTargetMobile = SelectorEngine.findOne(this._element.getAttribute(DATA_TARGET_MOBILE), this._element) || this._stickyTarget //needed?
this._stickyLimit = 0
this._stickyLimitMobile = 0
this._setLimit()
this._scrollCb = null
this._isMobile = isScreenMobile()
this._prevTop = 0
this._onScroll()
this._bindEvents()
}
dispose() {
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
EventHandler.off(window, EVENT_RESIZE)
this._scrollCb.dispose()
super.dispose()
}
}
// Getters
static get NAME() {
return NAME
}
// Public
// Private
_getConfig(config) {
config = {
...Default,
...Manipulator.getDataAttributes(this._element),
...(typeof config === 'object' ? config : {}),
}
return config
}
_bindEvents() {
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
EventHandler.on(window, EVENT_RESIZE, () => this._onResize())
this._scrollCb = onDocumentScroll(() => this._onScroll())
}
}
_onResize() {
this._isMobile = isScreenMobile()
this._setLimit()
}
_onScroll() {
this._checkSticky()
}
_setLimit() {
this._stickyLimit = this._cumulativeOffset(this._stickyTarget).top
this._stickyLimitMobile = this._cumulativeOffset(this._stickyTargetMobile).top
}
_getLimit() {
let newLimit = this._isMobile ? this._stickyLimitMobile : this._stickyLimit
if (this._config.stackable) {
this._getStickySimblings().forEach((sticky, idx) => {
const data = sticky.getBoundingClientRect()
newLimit -= data.height + (idx === 0 ? parseFloat(sticky.style.top) : 0)
})
}
return newLimit > 0 ? newLimit : 0
}
/**
* get the absolute position of an element
* @param {*} element - the target element
* @returns {Object} - absolute position top and left of the element
*/
_cumulativeOffset(element) {
let top = 0
let left = 0
do {
top += element.offsetTop || 0
left += element.offsetLeft || 0
element = element.offsetParent
} while (element)
return {
top: top,
left: left,
}
}
_isTypeSticky() {
return this._config.positionType === 'sticky'
}
_checkSticky() {
if (!this._isSticky) {
//the target position can change dinamically
this._setLimit()
}
const limit = this._getLimit()
if (typeof window !== 'undefined' && window.pageYOffset > limit) {
this._setSticky()
} else {
this._unsetSticky()
}
}
_setSticky() {
if (!this._isSticky) {
this._isSticky = true
let cssClass = CLASS_NAME_STICKY
if (!this._isTypeSticky()) {
cssClass = CLASS_NAME_FIXED
this._wrapper = this._createWrapper()
}
this._element.classList.add(cssClass)
if (this._config.stickyClassName) {
this._element.classList.add(this._config.stickyClassName)
}
this._prevTop = this._element.style.top
this._element.style.top = this._getPositionTop() + 'px'
EventHandler.trigger(this._element, EVENT_STICKY_ON)
}
}
_unsetSticky() {
if (this._isSticky) {
let cssClass = CLASS_NAME_STICKY
if (!this._isTypeSticky()) {
cssClass = CLASS_NAME_FIXED
this._destroyWrapper()
}
this._element.classList.remove(cssClass)
if (this._config.stickyClassName) {
this._element.classList.remove(this._config.stickyClassName)
}
this._element.style.top = this._prevTop
this._isSticky = false
EventHandler.trigger(this._element, EVENT_STICKY_OFF)
}
}
_createWrapper() {
if (typeof document === 'undefined') {
return
}
const wrapper = document.createElement('div')
wrapper.classList.add(CLASS_NAME_WRAPPER)
wrapper.style.width = '100%' //this._element.getBoundingClientRect().width + 'px'
wrapper.style.height = this._element.getBoundingClientRect().height + 'px'
wrapper.style.overflow = 'hidden'
// insert wrapper before el in the DOM tree
this._element.parentNode.insertBefore(wrapper, this._element)
// move el into wrapper
wrapper.appendChild(this._element)
return wrapper
}
_destroyWrapper() {
if (this._wrapper) {
this._wrapper.parentNode.insertBefore(this._element, this._wrapper)
this._wrapper.remove()
}
}
_getStickySimblings() {
const stickies = SelectorEngine.find(SELECTOR_DATA_TOGGLE)
return stickies.filter((sticky) => {
const instance = Sticky.getInstance(sticky)
if (instance && instance._isSticky && sticky !== this._element) {
return true
}
return false
})
}
/**
* returns the top position of the element considering the config and the other sticky elements
*/
_getPositionTop() {
let newTop = 0
if (this._config.stackable) {
this._getStickySimblings().forEach((sticky, idx) => {
const data = sticky.getBoundingClientRect()
newTop += data.height + (idx === 0 ? parseFloat(sticky.style.top) : 0)
})
return newTop
} else {
return newTop + this._config.paddingTop
}
}
}
/**
* ------------------------------------------------------------------------
* Data Api implementation
* ------------------------------------------------------------------------
*/
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
onDocumentScroll(() => {
const stickies = SelectorEngine.find(SELECTOR_DATA_TOGGLE)
stickies.map((sticky) => {
Sticky.getOrCreateInstance(sticky)
})
})
}
export default Sticky