1
0
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:
GeoSot
2021-06-06 09:26:36 +03:00
committed by GitHub
parent 08139c2280
commit cb47b8c964
6 changed files with 141 additions and 118 deletions

View File

@@ -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())) {

View File

@@ -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)

View File

@@ -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