theme/boost/amd/src/bootstrap/util/scrollbar.js

  1. /**
  2. * --------------------------------------------------------------------------
  3. * Bootstrap util/scrollBar.js
  4. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
  5. * --------------------------------------------------------------------------
  6. */
  7. import Manipulator from '../dom/manipulator'
  8. import SelectorEngine from '../dom/selector-engine'
  9. import { isElement } from './index'
  10. /**
  11. * Constants
  12. */
  13. const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
  14. const SELECTOR_STICKY_CONTENT = '.sticky-top'
  15. const PROPERTY_PADDING = 'padding-right'
  16. const PROPERTY_MARGIN = 'margin-right'
  17. /**
  18. * Class definition
  19. */
  20. class ScrollBarHelper {
  21. constructor() {
  22. this._element = document.body
  23. }
  24. // Public
  25. getWidth() {
  26. // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
  27. const documentWidth = document.documentElement.clientWidth
  28. return Math.abs(window.innerWidth - documentWidth)
  29. }
  30. hide() {
  31. const width = this.getWidth()
  32. this._disableOverFlow()
  33. // give padding to element to balance the hidden scrollbar width
  34. this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
  35. // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth
  36. this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
  37. this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)
  38. }
  39. reset() {
  40. this._resetElementAttributes(this._element, 'overflow')
  41. this._resetElementAttributes(this._element, PROPERTY_PADDING)
  42. this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)
  43. this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)
  44. }
  45. isOverflowing() {
  46. return this.getWidth() > 0
  47. }
  48. // Private
  49. _disableOverFlow() {
  50. this._saveInitialAttribute(this._element, 'overflow')
  51. this._element.style.overflow = 'hidden'
  52. }
  53. _setElementAttributes(selector, styleProperty, callback) {
  54. const scrollbarWidth = this.getWidth()
  55. const manipulationCallBack = element => {
  56. if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {
  57. return
  58. }
  59. this._saveInitialAttribute(element, styleProperty)
  60. const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)
  61. element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)
  62. }
  63. this._applyManipulationCallback(selector, manipulationCallBack)
  64. }
  65. _saveInitialAttribute(element, styleProperty) {
  66. const actualValue = element.style.getPropertyValue(styleProperty)
  67. if (actualValue) {
  68. Manipulator.setDataAttribute(element, styleProperty, actualValue)
  69. }
  70. }
  71. _resetElementAttributes(selector, styleProperty) {
  72. const manipulationCallBack = element => {
  73. const value = Manipulator.getDataAttribute(element, styleProperty)
  74. // We only want to remove the property if the value is `null`; the value can also be zero
  75. if (value === null) {
  76. element.style.removeProperty(styleProperty)
  77. return
  78. }
  79. Manipulator.removeDataAttribute(element, styleProperty)
  80. element.style.setProperty(styleProperty, value)
  81. }
  82. this._applyManipulationCallback(selector, manipulationCallBack)
  83. }
  84. _applyManipulationCallback(selector, callBack) {
  85. if (isElement(selector)) {
  86. callBack(selector)
  87. return
  88. }
  89. for (const sel of SelectorEngine.find(selector, this._element)) {
  90. callBack(sel)
  91. }
  92. }
  93. }
  94. export default ScrollBarHelper