mirror of
https://github.com/twbs/bootstrap.git
synced 2025-08-10 07:37:27 +02:00
Refactor scrollbar.js to be used as a Class (#33947)
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
import EventHandler from './dom/event-handler'
|
||||
import Manipulator from './dom/manipulator'
|
||||
import SelectorEngine from './dom/selector-engine'
|
||||
import { getWidth as getScrollBarWidth, hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar'
|
||||
import ScrollBarHelper from './util/scrollbar'
|
||||
import BaseComponent from './base-component'
|
||||
import Backdrop from './util/backdrop'
|
||||
|
||||
@@ -83,6 +83,7 @@ class Modal extends BaseComponent {
|
||||
this._isShown = false
|
||||
this._ignoreBackdropClick = false
|
||||
this._isTransitioning = false
|
||||
this._scrollBar = new ScrollBarHelper()
|
||||
}
|
||||
|
||||
// Getters
|
||||
@@ -120,7 +121,7 @@ class Modal extends BaseComponent {
|
||||
this._isTransitioning = true
|
||||
}
|
||||
|
||||
scrollBarHide()
|
||||
this._scrollBar.hide()
|
||||
|
||||
document.body.classList.add(CLASS_NAME_OPEN)
|
||||
|
||||
@@ -301,7 +302,7 @@ class Modal extends BaseComponent {
|
||||
this._backdrop.hide(() => {
|
||||
document.body.classList.remove(CLASS_NAME_OPEN)
|
||||
this._resetAdjustments()
|
||||
scrollBarReset()
|
||||
this._scrollBar.reset()
|
||||
EventHandler.trigger(this._element, EVENT_HIDDEN)
|
||||
})
|
||||
}
|
||||
@@ -368,7 +369,7 @@ class Modal extends BaseComponent {
|
||||
|
||||
_adjustDialog() {
|
||||
const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight
|
||||
const scrollbarWidth = getScrollBarWidth()
|
||||
const scrollbarWidth = this._scrollBar.getWidth()
|
||||
const isBodyOverflowing = scrollbarWidth > 0
|
||||
|
||||
if ((!isBodyOverflowing && isModalOverflowing && !isRTL()) || (isBodyOverflowing && !isModalOverflowing && isRTL())) {
|
||||
|
@@ -12,7 +12,7 @@ import {
|
||||
isVisible,
|
||||
typeCheckConfig
|
||||
} from './util/index'
|
||||
import { hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar'
|
||||
import ScrollBarHelper from './util/scrollbar'
|
||||
import EventHandler from './dom/event-handler'
|
||||
import BaseComponent from './base-component'
|
||||
import SelectorEngine from './dom/selector-engine'
|
||||
@@ -108,7 +108,7 @@ class Offcanvas extends BaseComponent {
|
||||
this._backdrop.show()
|
||||
|
||||
if (!this._config.scroll) {
|
||||
scrollBarHide()
|
||||
new ScrollBarHelper().hide()
|
||||
this._enforceFocusOnElement(this._element)
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ class Offcanvas extends BaseComponent {
|
||||
this._element.style.visibility = 'hidden'
|
||||
|
||||
if (!this._config.scroll) {
|
||||
scrollBarReset()
|
||||
new ScrollBarHelper().reset()
|
||||
}
|
||||
|
||||
EventHandler.trigger(this._element, EVENT_HIDDEN)
|
||||
|
@@ -7,78 +7,91 @@
|
||||
|
||||
import SelectorEngine from '../dom/selector-engine'
|
||||
import Manipulator from '../dom/manipulator'
|
||||
import { isElement } from './index'
|
||||
|
||||
const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
|
||||
const SELECTOR_STICKY_CONTENT = '.sticky-top'
|
||||
|
||||
const getWidth = () => {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
|
||||
const documentWidth = document.documentElement.clientWidth
|
||||
return Math.abs(window.innerWidth - documentWidth)
|
||||
}
|
||||
|
||||
const hide = (width = getWidth()) => {
|
||||
_disableOverFlow()
|
||||
// give padding to element to balances the hidden scrollbar width
|
||||
_setElementAttributes('body', 'paddingRight', calculatedValue => calculatedValue + width)
|
||||
// trick: We adjust positive paddingRight and negative marginRight to sticky-top elements, to keep shown fullwidth
|
||||
_setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width)
|
||||
_setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width)
|
||||
}
|
||||
|
||||
const _disableOverFlow = () => {
|
||||
const actualValue = document.body.style.overflow
|
||||
if (actualValue) {
|
||||
Manipulator.setDataAttribute(document.body, 'overflow', actualValue)
|
||||
class ScrollBarHelper {
|
||||
constructor() {
|
||||
this._element = document.body
|
||||
}
|
||||
|
||||
document.body.style.overflow = 'hidden'
|
||||
}
|
||||
getWidth() {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
|
||||
const documentWidth = document.documentElement.clientWidth
|
||||
return Math.abs(window.innerWidth - documentWidth)
|
||||
}
|
||||
|
||||
const _setElementAttributes = (selector, styleProp, callback) => {
|
||||
const scrollbarWidth = getWidth()
|
||||
SelectorEngine.find(selector)
|
||||
.forEach(element => {
|
||||
if (element !== document.body && window.innerWidth > element.clientWidth + scrollbarWidth) {
|
||||
hide() {
|
||||
const width = this.getWidth()
|
||||
this._disableOverFlow()
|
||||
// give padding to element to balance the hidden scrollbar width
|
||||
this._setElementAttributes(this._element, 'paddingRight', calculatedValue => calculatedValue + width)
|
||||
// trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth
|
||||
this._setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width)
|
||||
this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width)
|
||||
}
|
||||
|
||||
_disableOverFlow() {
|
||||
this._saveInitialAttribute(this._element, 'overflow')
|
||||
this._element.style.overflow = 'hidden'
|
||||
}
|
||||
|
||||
_setElementAttributes(selector, styleProp, callback) {
|
||||
const scrollbarWidth = this.getWidth()
|
||||
const manipulationCallBack = element => {
|
||||
if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {
|
||||
return
|
||||
}
|
||||
|
||||
const actualValue = element.style[styleProp]
|
||||
if (actualValue) {
|
||||
Manipulator.setDataAttribute(element, styleProp, actualValue)
|
||||
}
|
||||
|
||||
this._saveInitialAttribute(element, styleProp)
|
||||
const calculatedValue = window.getComputedStyle(element)[styleProp]
|
||||
element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px`
|
||||
})
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
_resetElementAttributes('body', 'overflow')
|
||||
_resetElementAttributes('body', 'paddingRight')
|
||||
_resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight')
|
||||
_resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight')
|
||||
}
|
||||
|
||||
const _resetElementAttributes = (selector, styleProp) => {
|
||||
SelectorEngine.find(selector).forEach(element => {
|
||||
const value = Manipulator.getDataAttribute(element, styleProp)
|
||||
if (typeof value === 'undefined') {
|
||||
element.style.removeProperty(styleProp)
|
||||
} else {
|
||||
Manipulator.removeDataAttribute(element, styleProp)
|
||||
element.style[styleProp] = value
|
||||
}
|
||||
})
|
||||
|
||||
this._applyManipulationCallback(selector, manipulationCallBack)
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._resetElementAttributes(this._element, 'overflow')
|
||||
this._resetElementAttributes(this._element, 'paddingRight')
|
||||
this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight')
|
||||
this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight')
|
||||
}
|
||||
|
||||
_saveInitialAttribute(element, styleProp) {
|
||||
const actualValue = element.style[styleProp]
|
||||
if (actualValue) {
|
||||
Manipulator.setDataAttribute(element, styleProp, actualValue)
|
||||
}
|
||||
}
|
||||
|
||||
_resetElementAttributes(selector, styleProp) {
|
||||
const manipulationCallBack = element => {
|
||||
const value = Manipulator.getDataAttribute(element, styleProp)
|
||||
if (typeof value === 'undefined') {
|
||||
element.style.removeProperty(styleProp)
|
||||
} else {
|
||||
Manipulator.removeDataAttribute(element, styleProp)
|
||||
element.style[styleProp] = value
|
||||
}
|
||||
}
|
||||
|
||||
this._applyManipulationCallback(selector, manipulationCallBack)
|
||||
}
|
||||
|
||||
_applyManipulationCallback(selector, callBack) {
|
||||
if (isElement(selector)) {
|
||||
callBack(selector)
|
||||
} else {
|
||||
SelectorEngine.find(selector, this._element).forEach(callBack)
|
||||
}
|
||||
}
|
||||
|
||||
isOverflowing() {
|
||||
return this.getWidth() > 0
|
||||
}
|
||||
}
|
||||
|
||||
const isBodyOverflowing = () => {
|
||||
return getWidth() > 0
|
||||
}
|
||||
|
||||
export {
|
||||
getWidth,
|
||||
hide,
|
||||
isBodyOverflowing,
|
||||
reset
|
||||
}
|
||||
export default ScrollBarHelper
|
||||
|
Reference in New Issue
Block a user