├── .gitignore ├── .npmrc ├── .prettierrc.json ├── .travis.yml ├── LICENSE ├── README.RU.md ├── README.md ├── demos ├── index.css ├── index.html ├── index.js ├── index.ru.html ├── modal │ ├── modal.css │ ├── modal.html │ └── modal.js └── sidebar │ ├── sidebar.css │ ├── sidebar.html │ └── sidebar.js ├── dist ├── scroll-lock.js └── scroll-lock.min.js ├── package-lock.json ├── package.json ├── src ├── scroll-lock.js └── tools.js ├── tests ├── add-lockable.test.js ├── add-remove-fill-gap.test.js ├── add-remove-scrollable.test.js ├── deprecated-methods.test.js ├── disable-enable-scroll.test.js └── set-fill-gap-method.test.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | *.code-workspace -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry = 'https://registry.npmjs.org/' -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "tabWidth": 4, 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "arrowParens": "always" 7 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '10' 5 | 6 | before_install: 7 | - curl -o- -L https://yarnpkg.com/install.sh | bash 8 | - export PATH="$HOME/.yarn/bin:$PATH" 9 | 10 | install: yarn --frozen-lockfile 11 | 12 | cache: 13 | yarn: true 14 | directories: 15 | - '.eslintcache' 16 | - 'node_modules' 17 | 18 | script: 19 | - yarn run test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 fl3nkey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.RU.md: -------------------------------------------------------------------------------- 1 |

2 | scroll-lock 3 |

4 |

5 | 6 | 7 | 8 |

9 |

Кроссбраузерная JavaScript библиотека для отключения прокрутки страницы

10 |

Лайв демо

11 | 12 | ## Новые возможности 2.0 13 | * Более придвинутый алгоритм обработки touch событий 14 | * Поддержка горизонтальной прокрутки 15 | * Поддержка вложенных прокручиваемых элементов 16 | * Поддержка вложенных textarea и contenteditable 17 | * Новый API 18 | 19 | ## Установка 20 | ### Через npm или yarn 21 | ```shell 22 | npm install scroll-lock 23 | # или 24 | yarn add scroll-lock 25 | ``` 26 | ```js 27 | //es6 import 28 | import { disablePageScroll, enablePageScroll } from 'scroll-lock'; 29 | 30 | //или 31 | import scrollLock from 'scroll-lock'; 32 | scrollLock.disablePageScroll(); 33 | //... 34 | 35 | //require 36 | const scrollLock = require('scroll-lock'); 37 | scrollLock.disablePageScroll(); 38 | //... 39 | ``` 40 | 41 | ### Черз тег script 42 | ```html 43 | 44 | 48 | ``` 49 | Дальше в примерах будет использован **es6 import**, но эти методы также будут доступны из объекта ```scrollLock```. 50 |
51 | 52 | ## Отключение прокрутки страницы 53 | Когда вы вызываете метод ```disablePageScroll```, также прокрутка отключается в iOS и других touch устройствах ([суть проблемы](https://toster.ru/q/461836)). Но прокрута на touch устройствах будет отключена на всех элементов, для этого надо явно указать, какой элемент будет прокручиваться на странице. 54 | ```javascript 55 | import { disablePageScroll, enablePageScroll } from 'scroll-lock'; 56 | 57 | //Получим элемент, который должен прокручиваться при отключеной прокрутки страницы 58 | const $scrollableElement = document.querySelector('.my-scrollable-element'); 59 | 60 | //Передадим элемент аргументом и отключим прокрутку на странице 61 | disablePageScroll($scrollableElement); 62 | 63 | //Также передадим аргументом элемент и активируем прокрутку на странице 64 | enablePageScroll($scrollableElement); 65 | ``` 66 | Или же вы можете указать атрибут ```data-scroll-lock-scrollable``` прокручиваемому элементу. 67 | ```html 68 |
69 | ``` 70 | 71 | #### Лайв демо: [https://fl3nkey.github.io/scroll-lock/demos/index.ru.html#ex-main](https://fl3nkey.github.io/scroll-lock/demos/index.ru.html#ex-main) 72 | 73 | #### ```textarea``` и ```contenteditable``` 74 | Если в прокручиваемом элементе будут вложены ```textarea``` или ```contenteditable``` то не переживайте, они будут прокручиваться без явного указания. 75 | 76 | #### Лайв демо: [https://fl3nkey.github.io/scroll-lock/demos/index.ru.html#ex-inputs](https://fl3nkey.github.io/scroll-lock/demos/index.ru.html#ex-inputs) 77 | 78 | ## Заполнение пробела 79 | Когда вызывается метод ```disablePageScroll```, scroll-lock указывает ```overflow: hidden;``` для ```body```, тем самым скрывая полосу прокрутки. В некоторых ОС полоса прокрутки имеет свою физическую ширину на странице, тем самым мы получае эффект "смещения": 80 |
81 |
82 | ![](https://i.imgur.com/SQ3IRNr.gif) 83 |
84 |
85 | Что бы предотвратить это, scroll-lock высчитывает ширину полосы прокрутки при вызове метода ```disablePageScroll``` и заполняет пробел для элемента ```body```. 86 |
87 |
88 | ![](https://i.imgur.com/ReJEcN8.gif) 89 |
90 |
91 | Но это не работает для элементов с позиционированием ```fixed```. Для этого надо явно указать, какому ещё элементу нужно заполнить пробел. 92 | ```javascript 93 | import { addFillGapTarget, addFillGapSelector } from 'scroll-lock'; 94 | 95 | //селектор 96 | addFillGapSelector('.my-fill-gap-selector'); 97 | 98 | //элемент 99 | const $fillGapElement = document.querySelector('.my-fill-gap-element'); 100 | addFillGapTarget($fillGapElement); 101 | ``` 102 | Или же вы можете указать атрибут ```data-scroll-lock-fill-gap```. 103 | ```html 104 |
105 | ``` 106 | 107 | #### Лайв демо: [https://fl3nkey.github.io/scroll-lock/demos/index.ru.html#ex-fill-gap](https://fl3nkey.github.io/scroll-lock/demos/index.ru.html#ex-fill-gap) 108 | 109 | ## Очередь 110 | Вызов метода ```disablePageScroll``` создает очередь вызовов. Если вы вызовите метод ```disablePageScroll``` два раза подряд, а потом ```enablePageScroll```, прокрутка страницы не активируется, т.к. метод ```enablePageScroll``` **нужно будет вызвать второй раз**. 111 |
112 | Если вам по каким то причинам надо активировать прокрутку страницы вне очереди, используйте метод ```clearQueueScrollLocks```: 113 | ```javascript 114 | import { disablePageScroll, clearQueueScrollLocks } from 'scroll-lock'; 115 | 116 | disablePageScroll(); 117 | disablePageScroll(); 118 | disablePageScroll(); 119 | disablePageScroll(); 120 | 121 | enablePageScroll(); 122 | console.log(getScrollState()); //false 123 | 124 | clearQueueScrollLocks(); 125 | enablePageScroll(); 126 | console.log(getScrollState()); //true 127 | ``` 128 | 129 | ## API 130 | 131 | #### ```disablePageScroll(scrollableTarget)``` 132 | Скрывает полосу прокрутки и отключает прокрутку страницы. 133 | * ```scrollableTarget``` - (```HTMLElement | NodeList | HTMLElement array```) прокручиваемый элемент 134 | ```javascript 135 | import { disablePageScroll } from 'scroll-lock'; 136 | 137 | const $scrollableElement = document.querySelector('.my-scrollable-element'); 138 | disablePageScroll($scrollableElement); 139 | ``` 140 | 141 | #### ```enablePageScroll(scrollableTarget)``` 142 | Показывает полосу прокрутки и активирует прокрутку страницы. 143 | * ```scrollableTarget``` - (```HTMLElement | NodeList | HTMLElement array```) прокручиваемый элемент 144 | ```javascript 145 | import { enablePageScroll } from 'scroll-lock'; 146 | 147 | const $scrollableElement = document.querySelector('.my-scrollable-element'); 148 | enablePageScroll($scrollableElement); 149 | ``` 150 | 151 | #### ```getScrollState()``` 152 | Возвращает состояние полосы прокрутки страницы. 153 | ```javascript 154 | import { disablePageScroll, getScrollState } from 'scroll-lock'; 155 | 156 | console.log(getScrollState()); //true 157 | disablePageScroll(); 158 | console.log(getScrollState()); //false 159 | ``` 160 | 161 | #### ```clearQueueScrollLocks()``` 162 | Очищает значение очереди. 163 | ```javascript 164 | import { disablePageScroll, enablePageScroll, clearQueueScrollLocks, getScrollState } from 'scroll-lock'; 165 | 166 | disablePageScroll(); 167 | disablePageScroll(); 168 | disablePageScroll(); 169 | disablePageScroll(); 170 | 171 | enablePageScroll(); 172 | console.log(getScrollState()); //false 173 | 174 | clearQueueScrollLocks(); 175 | enablePageScroll(); 176 | console.log(getScrollState()); //true 177 | ``` 178 | 179 | #### ```getPageScrollBarWidth(onlyExists)``` 180 | Возвращает ширину полосы прокрутки. 181 | * ```onlyExists``` - (```Boolean```) если полоса прокрутки найдена 182 |
**Стандартное значение:** ```false``` 183 | ```javascript 184 | import { getPageScrollBarWidth } from 'scroll-lock'; 185 | 186 | document.body.style.overflow = 'scroll'; 187 | console.log(getPageScrollBarWidth()); //Number 188 | disablePageScroll(); 189 | console.log(getPageScrollBarWidth(true)); //Number 190 | document.body.style.overflow = 'hidden'; 191 | console.log(getPageScrollBarWidth()); //Number 192 | console.log(getPageScrollBarWidth(true)); //0 193 | ``` 194 | 195 | 196 | #### ```getCurrentPageScrollBarWidth()``` 197 | Возвращает ширину полосы прокрутки в данный момент. 198 | ```javascript 199 | import { disablePageScroll, getCurrentPageScrollBarWidth } from 'scroll-lock'; 200 | 201 | console.log(getCurrentPageScrollBarWidth()); //Number 202 | disablePageScroll(); 203 | console.log(getCurrentPageScrollBarWidth()); //0 204 | ``` 205 | 206 | 207 | #### ```addScrollableSelector(scrollableSelector)``` 208 | Делает элементы с этим селектором прокручиваемыми. 209 | * ```scrollableSelector``` - (```String | String array```) селектор прокручиваемых элементов 210 |
**Начальное значение:** ```['[data-scroll-lock-scrollable]']``` 211 | ```javascript 212 | import { disablePageScroll, addScrollableSelector } from 'scroll-lock'; 213 | 214 | addScrollableSelector('.my-scrollable-selector'); 215 | disablePageScroll(); 216 | ``` 217 | 218 | #### ```removeScrollableSelector(scrollableSelector)``` 219 | Делает элементы с этим селектором не прокручиваемыми. 220 | * ```scrollableSelector``` - (```String | String array```) селектор прокручиваемых элементов 221 | ```javascript 222 | import { removeScrollableSelector } from 'scroll-lock'; 223 | 224 | removeScrollableSelector('.my-scrollable-selector'); 225 | ``` 226 | 227 | #### ```addScrollableTarget(scrollableTarget)``` 228 | Делает элемент прокручиваемым. 229 | * ```scrollableSelector``` - (```HTMLElement | NodeList | HTMLElement array```) прокручиваемый элемент 230 | ```javascript 231 | import { disablePageScroll, addScrollableTarget } from 'scroll-lock'; 232 | 233 | const $scrollableElement = document.querySelector('.my-scrollable-element'); 234 | addScrollableTarget($scrollableElement); 235 | disablePageScroll(); 236 | ``` 237 | 238 | #### ```removeScrollableTarget(scrollableTarget)``` 239 | Делает элемент не прокручиваемым. 240 | * ```scrollableSelector``` - (```HTMLElement | NodeList | HTMLElement array```) прокручиваемый элемент 241 | ```javascript 242 | import { removeScrollableTarget } from 'scroll-lock'; 243 | 244 | const $scrollableElement = document.querySelector('.my-scrollable-element'); 245 | removeScrollableTarget($scrollableElement); 246 | ``` 247 | 248 | 249 | 250 | #### ```addLockableSelector(lockableSelector)``` 251 | Делает элементы с этим селектором не прокручиваемыми в любом случае. 252 | * ```lockableSelector``` - (```String | String array```) селектор не прокручиваемых элементов 253 |
**Initial value:** ```['[data-scroll-lock-lockable]']``` 254 | ```javascript 255 | import { disablePageScroll, addLockableSelector } from 'scroll-lock'; 256 | 257 | addLockableSelector('.my-lockable-selector'); 258 | disablePageScroll(); 259 | ``` 260 | 261 | #### ```addLockableTarget(lockableTarget)``` 262 | Делает элемент не прокручиваемым в любом случае. 263 | * ```lockableTarget``` - (```HTMLElement | NodeList | HTMLElement array```) не прокручиваемый элемент 264 | ```javascript 265 | import { disablePageScroll, addLockableTarget } from 'scroll-lock'; 266 | 267 | const $lockableElement = document.querySelector('.my-lockable-element'); 268 | addLockableTarget($lockableElement); 269 | disablePageScroll(); 270 | ``` 271 | 272 | 273 | 274 | #### ```addFillGapSelector(fillGapSelector)``` 275 | Заполняет пробелы у элементов с этим селектором. 276 | * ```fillGapSelector``` - (```String | String array```) селектор заполненения пробела 277 |
**Начальное значение:** ```['body', '[data-scroll-lock-fill-gap]']``` 278 | ```javascript 279 | import { addFillGapSelector } from 'scroll-lock'; 280 | 281 | addFillGapSelector('.my-fill-gap-selector'); 282 | ``` 283 | 284 | #### ```removeFillGapSelector(fillGapSelector)``` 285 | Возвращает пробелы у элементов с этим селектором. 286 | * ```fillGapSelector``` - (```String | String array```) селектор заполненения пробела 287 | ```javascript 288 | import { removeFillGapSelector } from 'scroll-lock'; 289 | 290 | removeFillGapSelector('.my-fill-gap-selector'); 291 | ``` 292 | 293 | #### ```addFillGapTarget(fillGapTarget)``` 294 | Заполняет пробел у элемента. 295 | * ```fillGapTarget``` - (```HTMLElement | NodeList | HTMLElement array```) элемент заполнения пробела 296 | ```javascript 297 | import { addFillGapTarget } from 'scroll-lock'; 298 | 299 | const $fillGapElement = document.querySelector('.my-fill-gap-element'); 300 | addScrollableTarget($fillGapElement); 301 | ``` 302 | 303 | #### ```removeFillGapTarget(fillGapTarget)``` 304 | Возвращает пробел у элемента. 305 | * ```fillGapTarget``` - (```HTMLElement | NodeList | HTMLElement array```) элемент заполнения пробела 306 | ```javascript 307 | import { removeFillGapTarget } from 'scroll-lock'; 308 | 309 | const $fillGapElement = document.querySelector('.my-fill-gap-element'); 310 | removeFillGapTarget($fillGapElement); 311 | ``` 312 | 313 | #### ```setFillGapMethod(fillGapMethod)``` 314 | Изменяет метод заполнения пробела. 315 |
316 | * ```fillGapMethod``` - (```String: 'padding', 'margin', 'width', 'max-width', 'none'```) метод заполнения пробела 317 |
**Стандартное значение:** ```padding``` 318 | ```javascript 319 | import { setFillGapMethod } from 'scroll-lock'; 320 | 321 | setFillGapMethod('margin'); 322 | ``` 323 | 324 | #### ```refillGaps()``` 325 | Пересчитывает значения заполненых пробелов. 326 | ```javascript 327 | import { refillGaps } from 'scroll-lock'; 328 | 329 | refillGaps(); 330 | ``` 331 | 332 | ## Смотрите также 333 | * [How to fight the body scroll от Anton Korzunov](https://medium.com/react-camp/how-to-fight-the-body-scroll-2b00267b37ac) 334 | * [body-scroll-lock от willmcpo](https://github.com/willmcpo/body-scroll-lock) 335 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | DEPRECATED! 3 |

4 | 5 |

Please use the new version: https://github.com/fluejs/noscroll

6 | 7 |

8 | scroll-lock 9 |

10 |

11 | 12 | 13 | 14 |

15 |

Cross-browser JavaScript library to disable scrolling page

16 |

Live demo | README на русском

17 | 18 | ## New features 2.0 19 | * More advanced touch event handling algorithm 20 | * Horizontal scrolling support 21 | * Support for nested scrollable elements 22 | * Support for nested textarea and contenteditable 23 | * New API 24 | 25 | ## Installation 26 | ### Via npm or yarn 27 | ```shell 28 | npm install scroll-lock 29 | # or 30 | yarn add scroll-lock 31 | ``` 32 | ```js 33 | //es6 import 34 | import { disablePageScroll, enablePageScroll } from 'scroll-lock'; 35 | 36 | //or 37 | import scrollLock from 'scroll-lock'; 38 | scrollLock.disablePageScroll(); 39 | //... 40 | 41 | //require 42 | const scrollLock = require('scroll-lock'); 43 | scrollLock.disablePageScroll(); 44 | //... 45 | ``` 46 | 47 | ### Via script tag 48 | ```html 49 | 50 | 54 | ``` 55 | The **es6 import** will be used further in the examples, but these methods will also be available from the ```scrollLock``` object. 56 |
57 | 58 | ## Disable page scrolling 59 | When you call the ```disablePageScroll``` method, scrolling is also disabled in iOS and other touch devices ([essence of the problem](https://stackoverflow.com/questions/28790889/css-how-to-prevent-scrolling-on-ios-safari)). But scrolling on touch devices will be disabled on all elements. To do this, you must explicitly specify which element will scroll on the page. 60 | ```javascript 61 | import { disablePageScroll, enablePageScroll } from 'scroll-lock'; 62 | 63 | //Get the element that should scroll when page scrolling is disabled 64 | const $scrollableElement = document.querySelector('.my-scrollable-element'); 65 | 66 | //Pass the element to the argument and disable scrolling on the page 67 | disablePageScroll($scrollableElement); 68 | 69 | // Also, pass the element to the argument and enable scrolling on the page 70 | enablePageScroll($scrollableElement); 71 | ``` 72 | Alternatively, you can specify the ```data-scroll-lock-scrollable``` attribute of the scrollable element. 73 | ```html 74 |
75 | ``` 76 | 77 | #### Live demo: [https://fl3nkey.github.io/scroll-lock/demos/index.html#ex-main](https://fl3nkey.github.io/scroll-lock/demos/index.html#ex-main) 78 | 79 | #### ```textarea``` and ```contenteditable``` 80 | If a ```textarea``` or ```contenteditable``` is nested in a scrollable element, then do not worry, they will scroll without explicit indication. 81 | 82 | #### Live demo: [https://fl3nkey.github.io/scroll-lock/demos/index.html#ex-inputs](https://fl3nkey.github.io/scroll-lock/demos/index.html#ex-inputs) 83 | 84 | ## Filling the gap 85 | When the ```disablePageScroll``` method is called, the scroll-lock indicates ```overflow: hidden;``` for ```body```, thereby hiding the scroll bar. In some operating systems, the scroll bar has its physical width on the page, thus we get the effect of "displacement": 86 |
87 |
88 | ![](https://i.imgur.com/SQ3IRNr.gif) 89 |
90 |
91 | To prevent this, scroll-lock calculates the scroll bar width when calling the ```disablePageScroll``` method and fills in the space for the ```body``` element. 92 |
93 |
94 | ![](https://i.imgur.com/ReJEcN8.gif) 95 |
96 |
97 | But this does not work for elements with ```fixed``` positioning. To do this, you must explicitly indicate which element needs to fill in the space. 98 | ```javascript 99 | import { addFillGapTarget, addFillGapSelector } from 'scroll-lock'; 100 | 101 | //selector 102 | addFillGapSelector('.my-fill-gap-selector'); 103 | 104 | //element 105 | const $fillGapElement = document.querySelector('.my-fill-gap-element'); 106 | addFillGapTarget($fillGapElement); 107 | ``` 108 | Or you can specify the ```data-scroll-lock-fill-gap``` attribute. 109 | ```html 110 |
111 | ``` 112 | 113 | #### Live demo: [https://fl3nkey.github.io/scroll-lock/demos/index.html#ex-fill-gap](https://fl3nkey.github.io/scroll-lock/demos/index.html#ex-fill-gap) 114 | 115 | ## Queue 116 | A call to the ```disablePageScroll``` method creates a queue of calls. If you call the ```disablePageScroll``` method twice in a row, and then ```enablePageScroll```, the page scrolling is not activated, because the ```enablePageScroll``` method **will need to be called a second time**. 117 |
118 | If for some reason you need to activate scrolling the page out of turn, use the ```clearQueueScrollLocks``` method: 119 | ```javascript 120 | import { disablePageScroll, clearQueueScrollLocks } from 'scroll-lock'; 121 | 122 | disablePageScroll(); 123 | disablePageScroll(); 124 | disablePageScroll(); 125 | disablePageScroll(); 126 | 127 | enablePageScroll(); 128 | console.log(getScrollState()); //false 129 | 130 | clearQueueScrollLocks(); 131 | enablePageScroll(); 132 | console.log(getScrollState()); //true 133 | ``` 134 | 135 | ## API 136 | 137 | #### ```disablePageScroll(scrollableTarget)``` 138 | Hides the scroll bar and disables page scrolling. 139 | * ```scrollableTarget``` - (```HTMLElement | NodeList | HTMLElement array```) scrollable element 140 | ```javascript 141 | import { disablePageScroll } from 'scroll-lock'; 142 | 143 | const $scrollableElement = document.querySelector('.my-scrollable-element'); 144 | disablePageScroll($scrollableElement); 145 | ``` 146 | 147 | #### ```enablePageScroll(scrollableTarget)``` 148 | Shows the scroll bar and enables page scrolling. 149 | * ```scrollableTarget``` - (```HTMLElement | NodeList | HTMLElement array```) scrollable element 150 | ```javascript 151 | import { enablePageScroll } from 'scroll-lock'; 152 | 153 | const $scrollableElement = document.querySelector('.my-scrollable-element'); 154 | enablePageScroll($scrollableElement); 155 | ``` 156 | 157 | #### ```getScrollState()``` 158 | Returns the state of the page scroll bar. 159 | ```javascript 160 | import { disablePageScroll, getScrollState } from 'scroll-lock'; 161 | 162 | console.log(getScrollState()); //true 163 | disablePageScroll(); 164 | console.log(getScrollState()); //false 165 | ``` 166 | 167 | #### ```clearQueueScrollLocks()``` 168 | Clears the queue value. 169 | ```javascript 170 | import { disablePageScroll, enablePageScroll, clearQueueScrollLocks, getScrollState } from 'scroll-lock'; 171 | 172 | disablePageScroll(); 173 | disablePageScroll(); 174 | disablePageScroll(); 175 | disablePageScroll(); 176 | 177 | enablePageScroll(); 178 | console.log(getScrollState()); //false 179 | 180 | clearQueueScrollLocks(); 181 | enablePageScroll(); 182 | console.log(getScrollState()); //true 183 | ``` 184 | 185 | #### ```getPageScrollBarWidth(onlyExists)``` 186 | Returns the width of the scroll bar. 187 | * ```onlyExists``` - (```Boolean```) only if scroll bar is exists 188 |
**Default value:** ```false``` 189 | ```javascript 190 | import { getPageScrollBarWidth } from 'scroll-lock'; 191 | 192 | document.body.style.overflow = 'scroll'; 193 | console.log(getPageScrollBarWidth()); //Number 194 | disablePageScroll(); 195 | console.log(getPageScrollBarWidth(true)); //Number 196 | document.body.style.overflow = 'hidden'; 197 | console.log(getPageScrollBarWidth()); //Number 198 | console.log(getPageScrollBarWidth(true)); //0 199 | ``` 200 | 201 | 202 | #### ```getCurrentPageScrollBarWidth()``` 203 | Returns the width of the scroll bar to specific moment. 204 | ```javascript 205 | import { disablePageScroll, getCurrentPageScrollBarWidth } from 'scroll-lock'; 206 | 207 | console.log(getCurrentPageScrollBarWidth()); //Number 208 | disablePageScroll(); 209 | console.log(getCurrentPageScrollBarWidth()); //0 210 | ``` 211 | 212 | 213 | #### ```addScrollableSelector(scrollableSelector)``` 214 | Makes elements with this selector scrollable. 215 | * ```scrollableSelector``` - (```String | String array```) scrollable selector 216 |
**Initial value:** ```['[data-scroll-lock-scrollable]']``` 217 | ```javascript 218 | import { disablePageScroll, addScrollableSelector } from 'scroll-lock'; 219 | 220 | addScrollableSelector('.my-scrollable-selector'); 221 | disablePageScroll(); 222 | ``` 223 | 224 | #### ```removeScrollableSelector(scrollableSelector)``` 225 | Makes elements with this selector not scrollable. 226 | * ```scrollableSelector``` - (```String | String array```) scrollable selector 227 | ```javascript 228 | import { removeScrollableSelector } from 'scroll-lock'; 229 | 230 | removeScrollableSelector('.my-scrollable-selector'); 231 | ``` 232 | 233 | #### ```addScrollableTarget(scrollableTarget)``` 234 | Makes the element scrollable. 235 | * ```scrollableSelector``` - (```HTMLElement | NodeList | HTMLElement array```) scrollable element 236 | ```javascript 237 | import { disablePageScroll, addScrollableTarget } from 'scroll-lock'; 238 | 239 | const $scrollableElement = document.querySelector('.my-scrollable-element'); 240 | addScrollableTarget($scrollableElement); 241 | disablePageScroll(); 242 | ``` 243 | 244 | #### ```removeScrollableTarget(scrollableTarget)``` 245 | Makes the element not scrollable. 246 | * ```scrollableSelector``` - (```HTMLElement | NodeList | HTMLElement array```) scrollable element 247 | ```javascript 248 | import { removeScrollableTarget } from 'scroll-lock'; 249 | 250 | const $scrollableElement = document.querySelector('.my-scrollable-element'); 251 | removeScrollableTarget($scrollableElement); 252 | ``` 253 | 254 | 255 | 256 | #### ```addLockableSelector(lockableSelector)``` 257 | Makes elements with this selector lockable. 258 | * ```lockableSelector``` - (```String | String array```) lockable selector 259 |
**Initial value:** ```['[data-scroll-lock-lockable]']``` 260 | ```javascript 261 | import { disablePageScroll, addLockableSelector } from 'scroll-lock'; 262 | 263 | addLockableSelector('.my-lockable-selector'); 264 | disablePageScroll(); 265 | ``` 266 | 267 | #### ```addLockableTarget(lockableTarget)``` 268 | Makes the element lockable. 269 | * ```lockableTarget``` - (```HTMLElement | NodeList | HTMLElement array```) lockable element 270 | ```javascript 271 | import { disablePageScroll, addLockableTarget } from 'scroll-lock'; 272 | 273 | const $lockableElement = document.querySelector('.my-lockable-element'); 274 | addLockableTarget($lockableElement); 275 | disablePageScroll(); 276 | ``` 277 | 278 | 279 | 280 | #### ```addFillGapSelector(fillGapSelector)``` 281 | Fills the gap with elements with this selector. 282 | * ```fillGapSelector``` - (```String | String array```) a fill gap selector 283 |
**Initial value:** ```['body', '[data-scroll-lock-fill-gap]']``` 284 | ```javascript 285 | import { addFillGapSelector } from 'scroll-lock'; 286 | 287 | addFillGapSelector('.my-fill-gap-selector'); 288 | ``` 289 | 290 | #### ```removeFillGapSelector(fillGapSelector)``` 291 | Returns the gap for elements with this selector. 292 | * ```fillGapSelector``` - (```String | String array```) a fill gap selector 293 | ```javascript 294 | import { removeFillGapSelector } from 'scroll-lock'; 295 | 296 | removeFillGapSelector('.my-fill-gap-selector'); 297 | ``` 298 | 299 | #### ```addFillGapTarget(fillGapTarget)``` 300 | Fills the gap at the element. 301 | * ```fillGapTarget``` - (```HTMLElement | NodeList | HTMLElement array```) a fill gap element 302 | ```javascript 303 | import { addFillGapTarget } from 'scroll-lock'; 304 | 305 | const $fillGapElement = document.querySelector('.my-fill-gap-element'); 306 | addScrollableTarget($fillGapElement); 307 | ``` 308 | 309 | #### ```removeFillGapTarget(fillGapTarget)``` 310 | Returns the gap at the element. 311 | * ```fillGapTarget``` - (```HTMLElement | NodeList | HTMLElement array```) a fill gap element 312 | ```javascript 313 | import { removeFillGapTarget } from 'scroll-lock'; 314 | 315 | const $fillGapElement = document.querySelector('.my-fill-gap-element'); 316 | removeFillGapTarget($fillGapElement); 317 | ``` 318 | 319 | #### ```setFillGapMethod(fillGapMethod)``` 320 | Changes the method of filling the gap. 321 |
322 | * ```fillGapMethod``` - (```String: 'padding', 'margin', 'width', 'max-width', 'none'```) gap-filling method 323 |
**Default value:** ```padding``` 324 | ```javascript 325 | import { setFillGapMethod } from 'scroll-lock'; 326 | 327 | setFillGapMethod('margin'); 328 | ``` 329 | 330 | #### ```refillGaps()``` 331 | Recalculates filled gaps. 332 | ```javascript 333 | import { refillGaps } from 'scroll-lock'; 334 | 335 | refillGaps(); 336 | ``` 337 | 338 | ## See also 339 | * [How to fight the body scroll by Anton Korzunov](https://medium.com/react-camp/how-to-fight-the-body-scroll-2b00267b37ac) 340 | * [body-scroll-lock by willmcpo](https://github.com/willmcpo/body-scroll-lock) 341 | 342 | --- 343 | 🙌 🙌 I would like to thank “Armani” for the translation. 🙌 🙌 344 | -------------------------------------------------------------------------------- /demos/index.css: -------------------------------------------------------------------------------- 1 | [data-demo-state-disable] { 2 | display: none; 3 | } 4 | 5 | .demo-head { 6 | height: 200px; 7 | } 8 | 9 | .demo-head__item { 10 | position: absolute; 11 | left: 0; 12 | height: 100px; 13 | width: 100%; 14 | border: 1px solid #999; 15 | display: flex; 16 | align-items: center; 17 | justify-content: flex-end; 18 | z-index: 10; 19 | box-sizing: border-box; 20 | font-size: 1.2em; 21 | } 22 | 23 | .demo-head__item:nth-child(2) { 24 | transform: translateY(99px); 25 | } -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | scroll-lock 2.0 demo 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

scroll-lock 2.0

17 |

18 | scroll-lock - cross-browser JavaScript library to disable page scrolling. 19 |
20 | A few examples of what scroll-lock 2.0 is capable of. 21 |

22 | 23 |

More demos

24 | 28 | 29 |
30 |

Usual example

31 |

A usual example of disabling page scrolling with a scrollable element.
There is also support for 32 | horizontal 33 | scrolling.

34 | 42 |
43 |
44 |

scrollable element:

45 |
46 |
47 |

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem 48 | Ipsum has been the 49 | industry's standard dummy text ever since the 1500s, when an unknown printer took a 50 | galley of type and 51 | scrambled it to make a type specimen book. It has survived not only five centuries, 52 | but also the leap into 53 | electronic typesetting, remaining essentially unchanged. It was popularised in the 54 | 1960s with the release of 55 | Letraset sheets containing Lorem Ipsum passages, and more recently with desktop 56 | publishing software like 57 | Aldus 58 | PageMaker including versions of Lorem Ipsum.

59 |

Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a 60 | piece of classical 61 | Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a 62 | Latin professor at 63 | Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, 64 | consectetur, from a Lorem 65 | Ipsum passage, and going through the cites of the word in classical literature, 66 | discovered the undoubtable 67 | source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum 68 | et Malorum" (The Extremes 69 | of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory 70 | of ethics, very popular 71 | during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit 72 | amet..", comes from a line in 73 | section 1.10.32.

74 |

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem 75 | Ipsum has been the 76 | industry's standard dummy text ever since the 1500s, when an unknown printer took a 77 | galley of type and 78 | scrambled it to make a type specimen book. It has survived not only five centuries, 79 | but also the leap into 80 | electronic typesetting, remaining essentially unchanged. It was popularised in the 81 | 1960s with the release of 82 | Letraset sheets containing Lorem Ipsum passages, and more recently with desktop 83 | publishing software like 84 | Aldus 85 | PageMaker including versions of Lorem Ipsum.

86 |

Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a 87 | piece of classical 88 | Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a 89 | Latin professor at 90 | Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, 91 | consectetur, from a Lorem 92 | Ipsum passage, and going through the cites of the word in classical literature, 93 | discovered the undoubtable 94 | source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum 95 | et Malorum" (The Extremes 96 | of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory 97 | of ethics, very popular 98 | during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit 99 | amet..", comes from a line in 100 | section 1.10.32.

101 |
102 |
103 |
104 |
var $scrollableElement = document.querySelector('.my-scrollable-element');
105 | disablePageScroll($scrollableElement);
106 |
107 |
Page scroll is enabled
108 |
Page scroll is disabled
109 |
110 | 111 | 112 |
113 |
114 | 115 |
116 |

Textarea, contenteditable or range example

117 |

If textarea, contenteditable range is nested in a scrollable 118 | element, they will 119 | also 120 | scroll.

121 |
    122 |
  • 123 | Example 124 |
  • 125 |
  • 126 | Code 127 |
  • 128 |
129 |
130 |
131 |

scrollable element:

132 |
133 |

range

134 | 135 |

textarea

136 | 138 |

contenteditable

139 |
Lorem Ipsum is simply 140 | dummy text of 141 | the 142 | printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy 143 | text ever since the 144 | 1500s, when an unknown printer took a galley of type and scrambled it to make a type 145 | specimen book. It has 146 | survived not only five centuries, but also the leap into electronic typesetting, 147 | remaining essentially 148 | unchanged. It was popularised in the 1960s with the release of Letraset sheets 149 | containing Lorem Ipsum 150 | passages, and more recently with desktop publishing software like Aldus PageMaker 151 | including versions of 152 | Lorem 153 | Ipsum.Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem 154 | Ipsum has been the 155 | industry's standard dummy text ever since the 1500s, when an unknown printer took a 156 | galley of type and 157 | scrambled it to make a type specimen book. It has survived not only five centuries, but 158 | also the leap into 159 | electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s 160 | with the release of 161 | Letraset sheets containing Lorem Ipsum passages, and more recently with desktop 162 | publishing software like 163 | Aldus 164 | PageMaker including versions of Lorem Ipsum.
165 |
166 |
167 |
var $scrollableElement = document.querySelector('.my-scrollable-element');
168 | disablePageScroll($scrollableElement);
169 | //Actually nothing more is needed, scroll-lock will do everything for you!
170 |
171 |
Page scroll is enabled
172 |
Page scroll is disabled
173 |
174 | 175 | 176 |
177 |
178 | 179 |
180 |

Nested scrollable elements

181 |

If scrollable elements are nested inside a scrollable element, they will also scroll.

182 |
    183 |
  • 184 | Example 185 |
  • 186 |
  • 187 | Code 188 |
  • 189 |
190 |
191 |
192 |

scrollable element:

193 |
194 |
195 |

1

196 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut sint voluptatum 197 | sunt nisi quibusdam ex 198 | consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur adipisicing elit. 199 | Commodi natus eos ullam 200 | modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo sapiente voluptas ad 201 | fuga ea quis ipsa neque? 202 | Voluptatibus.

203 |

scrollable element:

204 |
206 |
207 |

2

208 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut sint 209 | voluptatum sunt nisi quibusdam 210 | ex 211 | consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur 212 | adipisicing elit. Commodi natus eos 213 | ullam 214 | modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo sapiente 215 | voluptas ad fuga ea quis ipsa 216 | neque? 217 | Voluptatibus.

218 |

scrollable element:

219 |
221 |
222 |

3

223 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut 224 | sint voluptatum sunt nisi quibusdam 225 | ex consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur 226 | adipisicing elit. Commodi natus eos 227 | ullam modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo 228 | sapiente voluptas ad fuga ea quis 229 | ipsa 230 | neque? Voluptatibus.

231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
var $scrollableElement1 = document.querySelector('.my-scrollable-element-1');
239 | var $scrollableElement2 = document.querySelector('.my-scrollable-element-2');
240 | var $scrollableElement3 = document.querySelector('.my-scrollable-element-3');
241 | disablePageScroll([
242 |     $scrollableElement1,
243 |     $scrollableElement2,
244 |     $scrollableElement3
245 | ]);
246 |
247 |
Page scroll is enabled
248 |
Page scroll is disabled
249 |
250 | 251 | 252 |
253 |
254 | 255 |
256 |

Gap fill example

257 |

Filling the gap of the elements, which leaves behind a scroll bar.
More 259 | info

260 |
    261 |
  • 262 | Example 263 |
  • 264 |
  • 265 | Code 266 |
  • 267 |
268 |
269 |
270 |
271 |
Demo element with 272 | filling the gap
273 |
Demo element without filling 274 | the gap
275 |
276 |
Gap-filling method:
277 | 284 |
285 |
var method = 'padding'; //Available methods: none, padding, margin, width, max-width
286 | setFillGapMethod(method);
287 | 
288 | var $fillGapElement = document.querySelector('.my-fill-gap-target');
289 | addFillGapTarget($fillGapTarget);
290 |
291 |
Page scroll is enabled
292 |
Page scroll is disabled
293 |
294 | 295 | 296 |
297 |
298 | 299 |
300 |

Make Lockable elements

301 |

Disable scrolling could be not only for body!

302 |
    303 |
  • 304 | Example 305 |
  • 306 |
  • 307 | Code 308 |
  • 309 |
310 |
311 |
312 |

lockable element:

313 |
314 |
315 |

scrollable element:

316 |
318 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut sint 319 | voluptatum sunt nisi quibusdam ex 320 | consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur adipisicing 321 | elit. Commodi natus eos ullam 322 | modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo sapiente voluptas 323 | ad fuga ea quis ipsa neque? 324 | Voluptatibus.

325 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut sint 326 | voluptatum sunt nisi quibusdam ex 327 | consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur adipisicing 328 | elit. Commodi natus eos ullam 329 | modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo sapiente voluptas 330 | ad fuga ea quis ipsa neque? 331 | Voluptatibus.

332 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut sint 333 | voluptatum sunt nisi quibusdam ex 334 | consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur adipisicing 335 | elit. Commodi natus eos ullam 336 | modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo sapiente voluptas 337 | ad fuga ea quis ipsa neque? 338 | Voluptatibus.

339 |
340 |
341 |
342 |
343 |
var $lockableElement = document.querySelector('.my-lockable-element');
344 | var $scrollableElement = document.querySelector('.my-scrollable-element');
345 | addLockableTarget($scrollableElement);
346 | disablePageScroll($scrollableElement);
347 |
348 |
Page scroll is enabled
349 |
Page scroll is disabled
350 |
351 | 352 | 353 |
354 |
355 |
356 |
357 | 358 | 369 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | -------------------------------------------------------------------------------- /demos/index.js: -------------------------------------------------------------------------------- 1 | var eachNode = function(nodeList, callback) { 2 | if (nodeList && callback) { 3 | for (let i = 0; i < nodeList.length; i++) { 4 | if (callback(nodeList[i], i, nodeList.length) === true) { 5 | break; 6 | } 7 | } 8 | } 9 | }; 10 | 11 | var $disableScrollEls = document.querySelectorAll('[data-demo-disable-scroll]'); 12 | var $enableScrollEls = document.querySelectorAll('[data-demo-enable-scroll]'); 13 | var $stateEnableEls = document.querySelectorAll('[data-demo-state-enable]'); 14 | var $stateDisableEls = document.querySelectorAll('[data-demo-state-disable]'); 15 | var $fillGapMethodSelect = document.querySelector('[data-demo-fill-gap-method-select]'); 16 | 17 | var disableStates = function() { 18 | eachNode($stateEnableEls, function($el) { 19 | $el.style.display = 'none'; 20 | }); 21 | eachNode($stateDisableEls, function($el) { 22 | $el.style.display = 'block'; 23 | }); 24 | }; 25 | var enableStates = function() { 26 | eachNode($stateEnableEls, function($el) { 27 | $el.style.display = 'block'; 28 | }); 29 | eachNode($stateDisableEls, function($el) { 30 | $el.style.display = 'none'; 31 | }); 32 | }; 33 | 34 | var disableScroll = function() { 35 | if (scrollLock.getScrollState()) { 36 | scrollLock.disablePageScroll(); 37 | disableStates(); 38 | } 39 | }; 40 | var enableScroll = function() { 41 | if (!scrollLock.getScrollState()) { 42 | scrollLock.enablePageScroll(); 43 | enableStates(); 44 | } 45 | }; 46 | 47 | eachNode($disableScrollEls, function($el) { 48 | $el.addEventListener('click', disableScroll); 49 | }); 50 | eachNode($enableScrollEls, function($el) { 51 | $el.addEventListener('click', enableScroll); 52 | }); 53 | 54 | $fillGapMethodSelect.addEventListener('change', function(e) { 55 | var value = e.target.value; 56 | scrollLock.setFillGapMethod(value); 57 | }); 58 | -------------------------------------------------------------------------------- /demos/index.ru.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | scroll-lock 2.0 демо 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

scroll-lock 2.0

17 |

18 | scroll-lock - кроссбраузерная JavaScript библиотека для отключения прокрутки страницы. 19 |
20 | Несколько примеров на что способен scroll-lock 2.0. 21 |

22 | 23 |

Больше демок

24 | 28 | 29 |
30 |

Стандартный пример

31 |

Стандартный пример отключения прокрутки страницы с прокручиваемым элементом.
Также имеется 32 | поддержка 33 | горизонтальной прокрутки.

34 | 42 |
43 |
44 |

Прокручиваемый элемент:

45 |
46 |
47 |

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem 48 | Ipsum has been the 49 | industry's standard dummy text ever since the 1500s, when an unknown printer took a 50 | galley of type and 51 | scrambled it to make a type specimen book. It has survived not only five centuries, 52 | but also the leap into 53 | electronic typesetting, remaining essentially unchanged. It was popularised in the 54 | 1960s with the release of 55 | Letraset sheets containing Lorem Ipsum passages, and more recently with desktop 56 | publishing software like 57 | Aldus 58 | PageMaker including versions of Lorem Ipsum.

59 |

Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a 60 | piece of classical 61 | Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a 62 | Latin professor at 63 | Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, 64 | consectetur, from a Lorem 65 | Ipsum passage, and going through the cites of the word in classical literature, 66 | discovered the undoubtable 67 | source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum 68 | et Malorum" (The Extremes 69 | of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory 70 | of ethics, very popular 71 | during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit 72 | amet..", comes from a line in 73 | section 1.10.32.

74 |

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem 75 | Ipsum has been the 76 | industry's standard dummy text ever since the 1500s, when an unknown printer took a 77 | galley of type and 78 | scrambled it to make a type specimen book. It has survived not only five centuries, 79 | but also the leap into 80 | electronic typesetting, remaining essentially unchanged. It was popularised in the 81 | 1960s with the release of 82 | Letraset sheets containing Lorem Ipsum passages, and more recently with desktop 83 | publishing software like 84 | Aldus 85 | PageMaker including versions of Lorem Ipsum.

86 |

Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a 87 | piece of classical 88 | Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a 89 | Latin professor at 90 | Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, 91 | consectetur, from a Lorem 92 | Ipsum passage, and going through the cites of the word in classical literature, 93 | discovered the undoubtable 94 | source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum 95 | et Malorum" (The Extremes 96 | of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory 97 | of ethics, very popular 98 | during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit 99 | amet..", comes from a line in 100 | section 1.10.32.

101 |
102 |
103 |
104 |
var $scrollableElement = document.querySelector('.my-scrollable-element');
105 | disablePageScroll($scrollableElement);
106 |
107 |
Прокрутка страницы активна
108 |
Прокрутка страницы отключена
109 |
110 | 111 | 112 |
113 |
114 | 115 |
116 |

Пример с textarea и contenteditable

117 |

Если в прокручиваемый элемент вложены textarea или contenteditable, они 118 | также будут 119 | прокручиваться.

120 | 128 |
129 |
130 |

Прокручиваемый элемент:

131 |
132 |

textarea

133 | 135 |

contenteditable

136 |
Lorem Ipsum is simply 137 | dummy text of 138 | the 139 | printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy 140 | text ever since the 141 | 1500s, when an unknown printer took a galley of type and scrambled it to make a type 142 | specimen book. It has 143 | survived not only five centuries, but also the leap into electronic typesetting, 144 | remaining essentially 145 | unchanged. It was popularised in the 1960s with the release of Letraset sheets 146 | containing Lorem Ipsum 147 | passages, and more recently with desktop publishing software like Aldus PageMaker 148 | including versions of 149 | Lorem 150 | Ipsum.Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem 151 | Ipsum has been the 152 | industry's standard dummy text ever since the 1500s, when an unknown printer took a 153 | galley of type and 154 | scrambled it to make a type specimen book. It has survived not only five centuries, but 155 | also the leap into 156 | electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s 157 | with the release of 158 | Letraset sheets containing Lorem Ipsum passages, and more recently with desktop 159 | publishing software like 160 | Aldus 161 | PageMaker including versions of Lorem Ipsum.
162 |
163 |
164 |
var $scrollableElement = document.querySelector('.my-scrollable-element');
165 | disablePageScroll($scrollableElement);
166 | //На самом деле больше ничего не нужно, scroll-lock сделает всё за вас!
167 |
168 |
Прокрутка страницы активна
169 |
Прокрутка страницы отключена
170 |
171 | 172 | 173 |
174 |
175 | 176 |
177 |

Вложенные прокручиваемые элементы

178 |

Если внутри прокручиваемого элемента вложены прокручиваемые элементы, они также будут прокручиваться. 179 |

180 | 188 |
189 |
190 |

Прокручиваемый элемент:

191 |
192 |
193 |

1

194 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut sint voluptatum 195 | sunt nisi quibusdam ex 196 | consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur adipisicing elit. 197 | Commodi natus eos ullam 198 | modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo sapiente voluptas ad 199 | fuga ea quis ipsa neque? 200 | Voluptatibus.

201 |

Прокручиваемый элемент:

202 |
204 |
205 |

2

206 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut sint 207 | voluptatum sunt nisi quibusdam 208 | ex 209 | consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur 210 | adipisicing elit. Commodi natus eos 211 | ullam 212 | modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo sapiente 213 | voluptas ad fuga ea quis ipsa 214 | neque? 215 | Voluptatibus.

216 |

Прокручиваемый элемент:

217 |
219 |
220 |

3

221 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut 222 | sint voluptatum sunt nisi quibusdam 223 | ex consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur 224 | adipisicing elit. Commodi natus eos 225 | ullam modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo 226 | sapiente voluptas ad fuga ea quis 227 | ipsa 228 | neque? Voluptatibus.

229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
var $scrollableElement1 = document.querySelector('.my-scrollable-element-1');
237 | var $scrollableElement2 = document.querySelector('.my-scrollable-element-2');
238 | var $scrollableElement3 = document.querySelector('.my-scrollable-element-3');
239 | disablePageScroll([
240 |     $scrollableElement1,
241 |     $scrollableElement2,
242 |     $scrollableElement3
243 | ]);
244 |
245 |
Прокрутка страницы активна
246 |
Прокрутка страницы отключена
247 |
248 | 249 | 250 |
251 |
252 | 253 |
254 |

Пример с заполнением пробела

255 |

Заполнение пробела у элементов, которая оставляет после себя полоса прокрутки.
Подробнее 257 |

258 | 266 |
267 |
268 |
269 |
Демо элемент с 270 | заполнением пробела
271 |
Демо элемент без заполнения 272 | пробела
273 |
274 |
Метод заполнения пробела:
275 | 282 |
283 |
var method = 'padding'; //Доступные методы: none, padding, margin, width, max-width
284 | setFillGapMethod(method);
285 | 
286 | var $fillGapElement = document.querySelector('.my-fill-gap-target');
287 | addFillGapTarget($fillGapTarget);
288 |
289 |
Прокрутка страницы активна
290 |
Прокрутка страницы отключена
291 |
292 | 293 | 294 |
295 |
296 | 297 |
298 |

Не прокручиваемые элементы

299 |

Отключить прокрутку можно не только у body!

300 | 308 |
309 |
310 |

не прокручиваемый элемент:

311 |
312 |
313 |

прокручиваемый элемент:

314 |
316 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut sint 317 | voluptatum sunt nisi quibusdam ex 318 | consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur adipisicing 319 | elit. Commodi natus eos ullam 320 | modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo sapiente voluptas 321 | ad fuga ea quis ipsa neque? 322 | Voluptatibus.

323 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut sint 324 | voluptatum sunt nisi quibusdam ex 325 | consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur adipisicing 326 | elit. Commodi natus eos ullam 327 | modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo sapiente voluptas 328 | ad fuga ea quis ipsa neque? 329 | Voluptatibus.

330 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum aut sint 331 | voluptatum sunt nisi quibusdam ex 332 | consectetur perspiciatis. Lorem, ipsum dolor sit amet consectetur adipisicing 333 | elit. Commodi natus eos ullam 334 | modi reiciendis sed ex eius perspiciatis, nostrum maxime, quo sapiente voluptas 335 | ad fuga ea quis ipsa neque? 336 | Voluptatibus.

337 |
338 |
339 |
340 |
341 |
var $lockableElement = document.querySelector('.my-lockable-element');
342 | var $scrollableElement = document.querySelector('.my-scrollable-element');
343 | addLockableTarget($scrollableElement);
344 | disablePageScroll($scrollableElement);
345 |
346 |
Прокрутка страницы активна
347 |
Прокрутка страницы отключена
348 |
349 | 350 | 351 |
352 |
353 |
354 |
355 | 356 | 367 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | -------------------------------------------------------------------------------- /demos/modal/modal.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .modal-backdoor { 6 | display: none; 7 | position: fixed; 8 | left: 0; 9 | top: 0; 10 | width: 100%; 11 | height: 100%; 12 | z-index: 999; 13 | background-color: rgba(0, 0, 0, 0.4); 14 | } 15 | 16 | .modal { 17 | display: none; 18 | position: fixed; 19 | left: 0; 20 | top: 0; 21 | width: 100%; 22 | height: 100%; 23 | z-index: 1000; 24 | overflow-y: scroll; 25 | overflow-x: hidden; 26 | -webkit-overflow-scrolling: touch; 27 | padding: 16px; 28 | } 29 | 30 | .modal-container { 31 | margin: 60px auto; 32 | width: 100%; 33 | max-width: 600px; 34 | } 35 | 36 | .uk-sticky-fixed { 37 | width: 100% !important; 38 | } -------------------------------------------------------------------------------- /demos/modal/modal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | scroll-lock modal example 9 | 10 | 11 | 12 | 13 | 14 | 15 | 87 | 88 |
89 | 104 |
105 | 106 |
107 |
108 |
109 |

Lorem heading

110 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor 111 | incididunt 112 | ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 113 | nisi ut 114 | aliquip.

115 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore 116 | et dolore 117 | magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex 118 | ea commodo 119 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum 120 | dolore eu fugiat 121 | nulla pariatur. 122 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id 123 | est laborum...

124 |

125 | 126 |

127 |
128 |
129 |

Lorem another heading

130 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor 131 | incididunt

132 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore 133 | et dolore ut 134 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 135 | nisi ut 136 | aliquip. magna aliqua...

137 |

138 | 139 |

140 |
141 |
142 |

Lorem heading???

143 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore 144 | et dolore ut 145 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 146 | nisi ut 147 | aliquip. magna aliqua...

148 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore 149 | et dolore ut 150 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 151 | nisi ut 152 | aliquip. magna aliqua...

153 |

154 | 155 |

156 |
157 | 158 |

Code

159 |
var eachNode = function(nodeList, callback) {
160 |     if (nodeList && callback) {
161 |         for (let i = 0; i < nodeList.length; i++) {
162 |             if (callback(nodeList[i], i, nodeList.length) === true) {
163 |                 break;
164 |             }
165 |         }
166 |     }
167 | };
168 | 
169 | var $modal = document.querySelector('.js-modal');
170 | var $modalScroll = document.querySelector('.js-modal-scroll');
171 | var $modalBackdoor = document.querySelector('.js-modal-backdoor');
172 | var $openModalButtons = document.querySelectorAll('.js-open-modal');
173 | var $closeModalButtons = document.querySelectorAll('.js-close-modal');
174 | 
175 | var $navbarFillGap = document.querySelectorAll('.js-navbar-fill-gap');
176 | scrollLock.addFillGapTarget($navbarFillGap);
177 | 
178 | function openModal() {
179 |     $modalBackdoor.style.display = 'block';
180 |     $modal.style.display = 'block';
181 |     scrollLock.disablePageScroll($modalScroll);
182 | }
183 | 
184 | function closeModal() {
185 |     $modalBackdoor.style.display = '';
186 |     $modal.style.display = '';
187 |     scrollLock.enablePageScroll($modalScroll);
188 | }
189 | 
190 | eachNode($openModalButtons, function($el) {
191 |     $el.addEventListener('click', openModal);
192 | });
193 | 
194 | eachNode($closeModalButtons, function($el) {
195 |     $el.addEventListener('click', closeModal);
196 | });
197 | 
198 |
199 |
200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /demos/modal/modal.js: -------------------------------------------------------------------------------- 1 | var eachNode = function(nodeList, callback) { 2 | if (nodeList && callback) { 3 | for (let i = 0; i < nodeList.length; i++) { 4 | if (callback(nodeList[i], i, nodeList.length) === true) { 5 | break; 6 | } 7 | } 8 | } 9 | }; 10 | 11 | var $modal = document.querySelector('.js-modal'); 12 | var $modalScroll = document.querySelector('.js-modal-scroll'); 13 | var $modalBackdoor = document.querySelector('.js-modal-backdoor'); 14 | var $openModalButtons = document.querySelectorAll('.js-open-modal'); 15 | var $closeModalButtons = document.querySelectorAll('.js-close-modal'); 16 | 17 | var $navbarFillGap = document.querySelectorAll('.js-navbar-fill-gap'); 18 | scrollLock.addFillGapTarget($navbarFillGap); 19 | 20 | function openModal() { 21 | $modalBackdoor.style.display = 'block'; 22 | $modal.style.display = 'block'; 23 | scrollLock.disablePageScroll($modalScroll); 24 | } 25 | 26 | function closeModal() { 27 | $modalBackdoor.style.display = ''; 28 | $modal.style.display = ''; 29 | scrollLock.enablePageScroll($modalScroll); 30 | } 31 | 32 | eachNode($openModalButtons, function($el) { 33 | $el.addEventListener('click', openModal); 34 | }); 35 | 36 | eachNode($closeModalButtons, function($el) { 37 | $el.addEventListener('click', closeModal); 38 | }); 39 | -------------------------------------------------------------------------------- /demos/sidebar/sidebar.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .modal-backdoor { 6 | display: none; 7 | position: fixed; 8 | width: calc(100% - 200px); 9 | right: 0; 10 | top: 0; 11 | height: 100%; 12 | z-index: 999; 13 | background-color: rgba(0, 0, 0, 0.4); 14 | } 15 | 16 | .modal { 17 | display: none; 18 | position: fixed; 19 | right: 0; 20 | top: 0; 21 | width: calc(100% - 200px); 22 | height: 100%; 23 | z-index: 1000; 24 | overflow-y: scroll; 25 | overflow-x: hidden; 26 | -webkit-overflow-scrolling: touch; 27 | padding: 16px; 28 | } 29 | 30 | .modal-container { 31 | margin: 60px auto; 32 | width: 100%; 33 | max-width: 600px; 34 | } 35 | 36 | .content { 37 | padding-left: 200px; 38 | } 39 | 40 | .uk-sticky-fixed { 41 | left: 200px !important; 42 | width: calc(100% - 200px) !important; 43 | } 44 | 45 | .sidebar { 46 | position: fixed; 47 | left: 0; 48 | top: 0; 49 | width: 200px; 50 | height: 100%; 51 | overflow-y: scroll; 52 | overflow-x: hidden; 53 | -webkit-overflow-scrolling: touch; 54 | padding: 16px; 55 | } -------------------------------------------------------------------------------- /demos/sidebar/sidebar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | scroll-lock sidebar example 9 | 10 | 11 | 12 | 13 | 14 | 15 | 87 | 88 |
89 | 104 |
105 | 106 | 201 |
202 |
203 |
204 |
205 |

Lorem heading

206 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 207 | tempor incididunt 208 | ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco 209 | laboris nisi ut 210 | aliquip.

211 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut 212 | labore et dolore 213 | magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip 214 | ex ea commodo 215 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum 216 | dolore eu fugiat 217 | nulla pariatur. 218 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim 219 | id est laborum...

220 |

221 | 222 |

223 |
224 |
225 |

Lorem another heading

226 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 227 | tempor incididunt

228 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut 229 | labore et dolore 230 | ut 231 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco 232 | laboris nisi ut 233 | aliquip. magna aliqua...

234 |

235 | 236 |

237 |
238 |
239 |

Lorem heading???

240 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut 241 | labore et dolore 242 | ut 243 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco 244 | laboris nisi ut 245 | aliquip. magna aliqua...

246 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut 247 | labore et dolore 248 | ut 249 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco 250 | laboris nisi ut 251 | aliquip. magna aliqua...

252 |

253 | 254 |

255 |
256 | 257 |

Code

258 |
var eachNode = function(nodeList, callback) {
259 |     if (nodeList && callback) {
260 |         for (let i = 0; i < nodeList.length; i++) {
261 |             if (callback(nodeList[i], i, nodeList.length) === true) {
262 |                 break;
263 |             }
264 |         }
265 |     }
266 | };
267 | 
268 | var $modal = document.querySelector('.js-modal');
269 | var $modalScroll = document.querySelector('.js-modal-scroll');
270 | var $modalBackdoor = document.querySelector('.js-modal-backdoor');
271 | var $openModalButtons = document.querySelectorAll('.js-open-modal');
272 | var $closeModalButtons = document.querySelectorAll('.js-close-modal');
273 | 
274 | var $sidebarScroll = document.querySelector('.js-sidebar-scroll');
275 | scrollLock.addLockableTarget($sidebarScroll);
276 | 
277 | var $navbarFillGap = document.querySelectorAll('.js-navbar-fill-gap');
278 | scrollLock.addFillGapTarget($navbarFillGap);
279 | 
280 | function openModal() {
281 |     $modalBackdoor.style.display = 'block';
282 |     $modal.style.display = 'block';
283 |     scrollLock.disablePageScroll($modalScroll);
284 | }
285 | 
286 | function closeModal() {
287 |     $modalBackdoor.style.display = '';
288 |     $modal.style.display = '';
289 |     scrollLock.enablePageScroll($modalScroll);
290 | }
291 | 
292 | eachNode($openModalButtons, function($el) {
293 |     $el.addEventListener('click', openModal);
294 | });
295 | 
296 | eachNode($closeModalButtons, function($el) {
297 |     $el.addEventListener('click', closeModal);
298 | });
299 |
300 |
301 |
302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | -------------------------------------------------------------------------------- /demos/sidebar/sidebar.js: -------------------------------------------------------------------------------- 1 | var eachNode = function(nodeList, callback) { 2 | if (nodeList && callback) { 3 | for (let i = 0; i < nodeList.length; i++) { 4 | if (callback(nodeList[i], i, nodeList.length) === true) { 5 | break; 6 | } 7 | } 8 | } 9 | }; 10 | 11 | var $modal = document.querySelector('.js-modal'); 12 | var $modalScroll = document.querySelector('.js-modal-scroll'); 13 | var $modalBackdoor = document.querySelector('.js-modal-backdoor'); 14 | var $openModalButtons = document.querySelectorAll('.js-open-modal'); 15 | var $closeModalButtons = document.querySelectorAll('.js-close-modal'); 16 | 17 | var $sidebarScroll = document.querySelector('.js-sidebar-scroll'); 18 | scrollLock.addLockableTarget($sidebarScroll); 19 | 20 | var $navbarFillGap = document.querySelectorAll('.js-navbar-fill-gap'); 21 | scrollLock.addFillGapTarget($navbarFillGap); 22 | 23 | function openModal() { 24 | $modalBackdoor.style.display = 'block'; 25 | $modal.style.display = 'block'; 26 | scrollLock.disablePageScroll($modalScroll); 27 | } 28 | 29 | function closeModal() { 30 | $modalBackdoor.style.display = ''; 31 | $modal.style.display = ''; 32 | scrollLock.enablePageScroll($modalScroll); 33 | } 34 | 35 | eachNode($openModalButtons, function($el) { 36 | $el.addEventListener('click', openModal); 37 | }); 38 | 39 | eachNode($closeModalButtons, function($el) { 40 | $el.addEventListener('click', closeModal); 41 | }); 42 | -------------------------------------------------------------------------------- /dist/scroll-lock.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else if(typeof exports === 'object') 7 | exports["scrollLock"] = factory(); 8 | else 9 | root["scrollLock"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) { 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ } 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ i: moduleId, 25 | /******/ l: false, 26 | /******/ exports: {} 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.l = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // define getter function for harmony exports 47 | /******/ __webpack_require__.d = function(exports, name, getter) { 48 | /******/ if(!__webpack_require__.o(exports, name)) { 49 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 50 | /******/ } 51 | /******/ }; 52 | /******/ 53 | /******/ // define __esModule on exports 54 | /******/ __webpack_require__.r = function(exports) { 55 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 56 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 57 | /******/ } 58 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 59 | /******/ }; 60 | /******/ 61 | /******/ // create a fake namespace object 62 | /******/ // mode & 1: value is a module id, require it 63 | /******/ // mode & 2: merge all properties of value into the ns 64 | /******/ // mode & 4: return value when already ns object 65 | /******/ // mode & 8|1: behave like require 66 | /******/ __webpack_require__.t = function(value, mode) { 67 | /******/ if(mode & 1) value = __webpack_require__(value); 68 | /******/ if(mode & 8) return value; 69 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 70 | /******/ var ns = Object.create(null); 71 | /******/ __webpack_require__.r(ns); 72 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 73 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 74 | /******/ return ns; 75 | /******/ }; 76 | /******/ 77 | /******/ // getDefaultExport function for compatibility with non-harmony modules 78 | /******/ __webpack_require__.n = function(module) { 79 | /******/ var getter = module && module.__esModule ? 80 | /******/ function getDefault() { return module['default']; } : 81 | /******/ function getModuleExports() { return module; }; 82 | /******/ __webpack_require__.d(getter, 'a', getter); 83 | /******/ return getter; 84 | /******/ }; 85 | /******/ 86 | /******/ // Object.prototype.hasOwnProperty.call 87 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 88 | /******/ 89 | /******/ // __webpack_public_path__ 90 | /******/ __webpack_require__.p = ""; 91 | /******/ 92 | /******/ 93 | /******/ // Load entry module and return exports 94 | /******/ return __webpack_require__(__webpack_require__.s = 0); 95 | /******/ }) 96 | /************************************************************************/ 97 | /******/ ([ 98 | /* 0 */ 99 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 100 | 101 | "use strict"; 102 | __webpack_require__.r(__webpack_exports__); 103 | 104 | // CONCATENATED MODULE: ./src/tools.js 105 | var argumentAsArray = function argumentAsArray(argument) { 106 | return Array.isArray(argument) ? argument : [argument]; 107 | }; 108 | var isElement = function isElement(target) { 109 | return target instanceof Node; 110 | }; 111 | var isElementList = function isElementList(nodeList) { 112 | return nodeList instanceof NodeList; 113 | }; 114 | var eachNode = function eachNode(nodeList, callback) { 115 | if (nodeList && callback) { 116 | nodeList = isElementList(nodeList) ? nodeList : [nodeList]; 117 | 118 | for (var i = 0; i < nodeList.length; i++) { 119 | if (callback(nodeList[i], i, nodeList.length) === true) { 120 | break; 121 | } 122 | } 123 | } 124 | }; 125 | var throwError = function throwError(message) { 126 | return console.error("[scroll-lock] ".concat(message)); 127 | }; 128 | var arrayAsSelector = function arrayAsSelector(array) { 129 | if (Array.isArray(array)) { 130 | var selector = array.join(', '); 131 | return selector; 132 | } 133 | }; 134 | var nodeListAsArray = function nodeListAsArray(nodeList) { 135 | var nodes = []; 136 | eachNode(nodeList, function (node) { 137 | return nodes.push(node); 138 | }); 139 | return nodes; 140 | }; 141 | var findParentBySelector = function findParentBySelector($el, selector) { 142 | var self = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 143 | var $root = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : document; 144 | 145 | if (self && nodeListAsArray($root.querySelectorAll(selector)).indexOf($el) !== -1) { 146 | return $el; 147 | } 148 | 149 | while (($el = $el.parentElement) && nodeListAsArray($root.querySelectorAll(selector)).indexOf($el) === -1) { 150 | ; 151 | } 152 | 153 | return $el; 154 | }; 155 | var elementHasSelector = function elementHasSelector($el, selector) { 156 | var $root = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : document; 157 | var has = nodeListAsArray($root.querySelectorAll(selector)).indexOf($el) !== -1; 158 | return has; 159 | }; 160 | var elementHasOverflowHidden = function elementHasOverflowHidden($el) { 161 | if ($el) { 162 | var computedStyle = getComputedStyle($el); 163 | var overflowIsHidden = computedStyle.overflow === 'hidden'; 164 | return overflowIsHidden; 165 | } 166 | }; 167 | var elementScrollTopOnStart = function elementScrollTopOnStart($el) { 168 | if ($el) { 169 | if (elementHasOverflowHidden($el)) { 170 | return true; 171 | } 172 | 173 | var scrollTop = $el.scrollTop; 174 | return scrollTop <= 0; 175 | } 176 | }; 177 | var elementScrollTopOnEnd = function elementScrollTopOnEnd($el) { 178 | if ($el) { 179 | if (elementHasOverflowHidden($el)) { 180 | return true; 181 | } 182 | 183 | var scrollTop = $el.scrollTop; 184 | var scrollHeight = $el.scrollHeight; 185 | var scrollTopWithHeight = scrollTop + $el.offsetHeight; 186 | return scrollTopWithHeight >= scrollHeight; 187 | } 188 | }; 189 | var elementScrollLeftOnStart = function elementScrollLeftOnStart($el) { 190 | if ($el) { 191 | if (elementHasOverflowHidden($el)) { 192 | return true; 193 | } 194 | 195 | var scrollLeft = $el.scrollLeft; 196 | return scrollLeft <= 0; 197 | } 198 | }; 199 | var elementScrollLeftOnEnd = function elementScrollLeftOnEnd($el) { 200 | if ($el) { 201 | if (elementHasOverflowHidden($el)) { 202 | return true; 203 | } 204 | 205 | var scrollLeft = $el.scrollLeft; 206 | var scrollWidth = $el.scrollWidth; 207 | var scrollLeftWithWidth = scrollLeft + $el.offsetWidth; 208 | return scrollLeftWithWidth >= scrollWidth; 209 | } 210 | }; 211 | var elementIsScrollableField = function elementIsScrollableField($el) { 212 | var selector = 'textarea, [contenteditable="true"]'; 213 | return elementHasSelector($el, selector); 214 | }; 215 | var elementIsInputRange = function elementIsInputRange($el) { 216 | var selector = 'input[type="range"]'; 217 | return elementHasSelector($el, selector); 218 | }; 219 | // CONCATENATED MODULE: ./src/scroll-lock.js 220 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "disablePageScroll", function() { return disablePageScroll; }); 221 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "enablePageScroll", function() { return enablePageScroll; }); 222 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getScrollState", function() { return getScrollState; }); 223 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "clearQueueScrollLocks", function() { return clearQueueScrollLocks; }); 224 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getTargetScrollBarWidth", function() { return scroll_lock_getTargetScrollBarWidth; }); 225 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getCurrentTargetScrollBarWidth", function() { return scroll_lock_getCurrentTargetScrollBarWidth; }); 226 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getPageScrollBarWidth", function() { return getPageScrollBarWidth; }); 227 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getCurrentPageScrollBarWidth", function() { return getCurrentPageScrollBarWidth; }); 228 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addScrollableTarget", function() { return scroll_lock_addScrollableTarget; }); 229 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeScrollableTarget", function() { return scroll_lock_removeScrollableTarget; }); 230 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addScrollableSelector", function() { return scroll_lock_addScrollableSelector; }); 231 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeScrollableSelector", function() { return scroll_lock_removeScrollableSelector; }); 232 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addLockableTarget", function() { return scroll_lock_addLockableTarget; }); 233 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addLockableSelector", function() { return scroll_lock_addLockableSelector; }); 234 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "setFillGapMethod", function() { return scroll_lock_setFillGapMethod; }); 235 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addFillGapTarget", function() { return scroll_lock_addFillGapTarget; }); 236 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeFillGapTarget", function() { return scroll_lock_removeFillGapTarget; }); 237 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addFillGapSelector", function() { return scroll_lock_addFillGapSelector; }); 238 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeFillGapSelector", function() { return scroll_lock_removeFillGapSelector; }); 239 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "refillGaps", function() { return refillGaps; }); 240 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } 241 | 242 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 243 | 244 | 245 | var FILL_GAP_AVAILABLE_METHODS = ['padding', 'margin', 'width', 'max-width', 'none']; 246 | var TOUCH_DIRECTION_DETECT_OFFSET = 3; 247 | var state = { 248 | scroll: true, 249 | queue: 0, 250 | scrollableSelectors: ['[data-scroll-lock-scrollable]'], 251 | lockableSelectors: ['body', '[data-scroll-lock-lockable]'], 252 | fillGapSelectors: ['body', '[data-scroll-lock-fill-gap]', '[data-scroll-lock-lockable]'], 253 | fillGapMethod: FILL_GAP_AVAILABLE_METHODS[0], 254 | // 255 | startTouchY: 0, 256 | startTouchX: 0 257 | }; 258 | var disablePageScroll = function disablePageScroll(target) { 259 | if (state.queue <= 0) { 260 | state.scroll = false; 261 | scroll_lock_hideLockableOverflow(); 262 | fillGaps(); 263 | } 264 | 265 | scroll_lock_addScrollableTarget(target); 266 | state.queue++; 267 | }; 268 | var enablePageScroll = function enablePageScroll(target) { 269 | state.queue > 0 && state.queue--; 270 | 271 | if (state.queue <= 0) { 272 | state.scroll = true; 273 | scroll_lock_showLockableOverflow(); 274 | unfillGaps(); 275 | } 276 | 277 | scroll_lock_removeScrollableTarget(target); 278 | }; 279 | var getScrollState = function getScrollState() { 280 | return state.scroll; 281 | }; 282 | var clearQueueScrollLocks = function clearQueueScrollLocks() { 283 | state.queue = 0; 284 | }; 285 | var scroll_lock_getTargetScrollBarWidth = function getTargetScrollBarWidth($target) { 286 | var onlyExists = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 287 | 288 | if (isElement($target)) { 289 | var currentOverflowYProperty = $target.style.overflowY; 290 | 291 | if (onlyExists) { 292 | if (!getScrollState()) { 293 | $target.style.overflowY = $target.getAttribute('data-scroll-lock-saved-overflow-y-property'); 294 | } 295 | } else { 296 | $target.style.overflowY = 'scroll'; 297 | } 298 | 299 | var width = scroll_lock_getCurrentTargetScrollBarWidth($target); 300 | $target.style.overflowY = currentOverflowYProperty; 301 | return width; 302 | } else { 303 | return 0; 304 | } 305 | }; 306 | var scroll_lock_getCurrentTargetScrollBarWidth = function getCurrentTargetScrollBarWidth($target) { 307 | if (isElement($target)) { 308 | if ($target === document.body) { 309 | var documentWidth = document.documentElement.clientWidth; 310 | var windowWidth = window.innerWidth; 311 | var currentWidth = windowWidth - documentWidth; 312 | return currentWidth; 313 | } else { 314 | var borderLeftWidthCurrentProperty = $target.style.borderLeftWidth; 315 | var borderRightWidthCurrentProperty = $target.style.borderRightWidth; 316 | $target.style.borderLeftWidth = '0px'; 317 | $target.style.borderRightWidth = '0px'; 318 | 319 | var _currentWidth = $target.offsetWidth - $target.clientWidth; 320 | 321 | $target.style.borderLeftWidth = borderLeftWidthCurrentProperty; 322 | $target.style.borderRightWidth = borderRightWidthCurrentProperty; 323 | return _currentWidth; 324 | } 325 | } else { 326 | return 0; 327 | } 328 | }; 329 | var getPageScrollBarWidth = function getPageScrollBarWidth() { 330 | var onlyExists = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 331 | return scroll_lock_getTargetScrollBarWidth(document.body, onlyExists); 332 | }; 333 | var getCurrentPageScrollBarWidth = function getCurrentPageScrollBarWidth() { 334 | return scroll_lock_getCurrentTargetScrollBarWidth(document.body); 335 | }; 336 | var scroll_lock_addScrollableTarget = function addScrollableTarget(target) { 337 | if (target) { 338 | var targets = argumentAsArray(target); 339 | targets.map(function ($targets) { 340 | eachNode($targets, function ($target) { 341 | if (isElement($target)) { 342 | $target.setAttribute('data-scroll-lock-scrollable', ''); 343 | } else { 344 | throwError("\"".concat($target, "\" is not a Element.")); 345 | } 346 | }); 347 | }); 348 | } 349 | }; 350 | var scroll_lock_removeScrollableTarget = function removeScrollableTarget(target) { 351 | if (target) { 352 | var targets = argumentAsArray(target); 353 | targets.map(function ($targets) { 354 | eachNode($targets, function ($target) { 355 | if (isElement($target)) { 356 | $target.removeAttribute('data-scroll-lock-scrollable'); 357 | } else { 358 | throwError("\"".concat($target, "\" is not a Element.")); 359 | } 360 | }); 361 | }); 362 | } 363 | }; 364 | var scroll_lock_addScrollableSelector = function addScrollableSelector(selector) { 365 | if (selector) { 366 | var selectors = argumentAsArray(selector); 367 | selectors.map(function (selector) { 368 | state.scrollableSelectors.push(selector); 369 | }); 370 | } 371 | }; 372 | var scroll_lock_removeScrollableSelector = function removeScrollableSelector(selector) { 373 | if (selector) { 374 | var selectors = argumentAsArray(selector); 375 | selectors.map(function (selector) { 376 | state.scrollableSelectors = state.scrollableSelectors.filter(function (sSelector) { 377 | return sSelector !== selector; 378 | }); 379 | }); 380 | } 381 | }; 382 | var scroll_lock_addLockableTarget = function addLockableTarget(target) { 383 | if (target) { 384 | var targets = argumentAsArray(target); 385 | targets.map(function ($targets) { 386 | eachNode($targets, function ($target) { 387 | if (isElement($target)) { 388 | $target.setAttribute('data-scroll-lock-lockable', ''); 389 | } else { 390 | throwError("\"".concat($target, "\" is not a Element.")); 391 | } 392 | }); 393 | }); 394 | 395 | if (!getScrollState()) { 396 | scroll_lock_hideLockableOverflow(); 397 | } 398 | } 399 | }; 400 | var scroll_lock_addLockableSelector = function addLockableSelector(selector) { 401 | if (selector) { 402 | var selectors = argumentAsArray(selector); 403 | selectors.map(function (selector) { 404 | state.lockableSelectors.push(selector); 405 | }); 406 | 407 | if (!getScrollState()) { 408 | scroll_lock_hideLockableOverflow(); 409 | } 410 | 411 | scroll_lock_addFillGapSelector(selector); 412 | } 413 | }; 414 | var scroll_lock_setFillGapMethod = function setFillGapMethod(method) { 415 | if (method) { 416 | if (FILL_GAP_AVAILABLE_METHODS.indexOf(method) !== -1) { 417 | state.fillGapMethod = method; 418 | refillGaps(); 419 | } else { 420 | var methods = FILL_GAP_AVAILABLE_METHODS.join(', '); 421 | throwError("\"".concat(method, "\" method is not available!\nAvailable fill gap methods: ").concat(methods, ".")); 422 | } 423 | } 424 | }; 425 | var scroll_lock_addFillGapTarget = function addFillGapTarget(target) { 426 | if (target) { 427 | var targets = argumentAsArray(target); 428 | targets.map(function ($targets) { 429 | eachNode($targets, function ($target) { 430 | if (isElement($target)) { 431 | $target.setAttribute('data-scroll-lock-fill-gap', ''); 432 | 433 | if (!state.scroll) { 434 | scroll_lock_fillGapTarget($target); 435 | } 436 | } else { 437 | throwError("\"".concat($target, "\" is not a Element.")); 438 | } 439 | }); 440 | }); 441 | } 442 | }; 443 | var scroll_lock_removeFillGapTarget = function removeFillGapTarget(target) { 444 | if (target) { 445 | var targets = argumentAsArray(target); 446 | targets.map(function ($targets) { 447 | eachNode($targets, function ($target) { 448 | if (isElement($target)) { 449 | $target.removeAttribute('data-scroll-lock-fill-gap'); 450 | 451 | if (!state.scroll) { 452 | scroll_lock_unfillGapTarget($target); 453 | } 454 | } else { 455 | throwError("\"".concat($target, "\" is not a Element.")); 456 | } 457 | }); 458 | }); 459 | } 460 | }; 461 | var scroll_lock_addFillGapSelector = function addFillGapSelector(selector) { 462 | if (selector) { 463 | var selectors = argumentAsArray(selector); 464 | selectors.map(function (selector) { 465 | if (state.fillGapSelectors.indexOf(selector) === -1) { 466 | state.fillGapSelectors.push(selector); 467 | 468 | if (!state.scroll) { 469 | scroll_lock_fillGapSelector(selector); 470 | } 471 | } 472 | }); 473 | } 474 | }; 475 | var scroll_lock_removeFillGapSelector = function removeFillGapSelector(selector) { 476 | if (selector) { 477 | var selectors = argumentAsArray(selector); 478 | selectors.map(function (selector) { 479 | state.fillGapSelectors = state.fillGapSelectors.filter(function (fSelector) { 480 | return fSelector !== selector; 481 | }); 482 | 483 | if (!state.scroll) { 484 | scroll_lock_unfillGapSelector(selector); 485 | } 486 | }); 487 | } 488 | }; 489 | var refillGaps = function refillGaps() { 490 | if (!state.scroll) { 491 | fillGaps(); 492 | } 493 | }; 494 | 495 | var scroll_lock_hideLockableOverflow = function hideLockableOverflow() { 496 | var selector = arrayAsSelector(state.lockableSelectors); 497 | scroll_lock_hideLockableOverflowSelector(selector); 498 | }; 499 | 500 | var scroll_lock_showLockableOverflow = function showLockableOverflow() { 501 | var selector = arrayAsSelector(state.lockableSelectors); 502 | scroll_lock_showLockableOverflowSelector(selector); 503 | }; 504 | 505 | var scroll_lock_hideLockableOverflowSelector = function hideLockableOverflowSelector(selector) { 506 | var $targets = document.querySelectorAll(selector); 507 | eachNode($targets, function ($target) { 508 | scroll_lock_hideLockableOverflowTarget($target); 509 | }); 510 | }; 511 | 512 | var scroll_lock_showLockableOverflowSelector = function showLockableOverflowSelector(selector) { 513 | var $targets = document.querySelectorAll(selector); 514 | eachNode($targets, function ($target) { 515 | scroll_lock_showLockableOverflowTarget($target); 516 | }); 517 | }; 518 | 519 | var scroll_lock_hideLockableOverflowTarget = function hideLockableOverflowTarget($target) { 520 | if (isElement($target) && $target.getAttribute('data-scroll-lock-locked') !== 'true') { 521 | var computedStyle = window.getComputedStyle($target); 522 | $target.setAttribute('data-scroll-lock-saved-overflow-y-property', computedStyle.overflowY); 523 | $target.setAttribute('data-scroll-lock-saved-inline-overflow-property', $target.style.overflow); 524 | $target.setAttribute('data-scroll-lock-saved-inline-overflow-y-property', $target.style.overflowY); 525 | $target.style.overflow = 'hidden'; 526 | $target.setAttribute('data-scroll-lock-locked', 'true'); 527 | } 528 | }; 529 | 530 | var scroll_lock_showLockableOverflowTarget = function showLockableOverflowTarget($target) { 531 | if (isElement($target) && $target.getAttribute('data-scroll-lock-locked') === 'true') { 532 | $target.style.overflow = $target.getAttribute('data-scroll-lock-saved-inline-overflow-property'); 533 | $target.style.overflowY = $target.getAttribute('data-scroll-lock-saved-inline-overflow-y-property'); 534 | $target.removeAttribute('data-scroll-lock-saved-overflow-property'); 535 | $target.removeAttribute('data-scroll-lock-saved-inline-overflow-property'); 536 | $target.removeAttribute('data-scroll-lock-saved-inline-overflow-y-property'); 537 | $target.removeAttribute('data-scroll-lock-locked'); 538 | } 539 | }; 540 | 541 | var fillGaps = function fillGaps() { 542 | state.fillGapSelectors.map(function (selector) { 543 | scroll_lock_fillGapSelector(selector); 544 | }); 545 | }; 546 | 547 | var unfillGaps = function unfillGaps() { 548 | state.fillGapSelectors.map(function (selector) { 549 | scroll_lock_unfillGapSelector(selector); 550 | }); 551 | }; 552 | 553 | var scroll_lock_fillGapSelector = function fillGapSelector(selector) { 554 | var $targets = document.querySelectorAll(selector); 555 | var isLockable = state.lockableSelectors.indexOf(selector) !== -1; 556 | eachNode($targets, function ($target) { 557 | scroll_lock_fillGapTarget($target, isLockable); 558 | }); 559 | }; 560 | 561 | var scroll_lock_fillGapTarget = function fillGapTarget($target) { 562 | var isLockable = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 563 | 564 | if (isElement($target)) { 565 | var scrollBarWidth; 566 | 567 | if ($target.getAttribute('data-scroll-lock-lockable') === '' || isLockable) { 568 | scrollBarWidth = scroll_lock_getTargetScrollBarWidth($target, true); 569 | } else { 570 | var $lockableParent = findParentBySelector($target, arrayAsSelector(state.lockableSelectors)); 571 | scrollBarWidth = scroll_lock_getTargetScrollBarWidth($lockableParent, true); 572 | } 573 | 574 | if ($target.getAttribute('data-scroll-lock-filled-gap') === 'true') { 575 | scroll_lock_unfillGapTarget($target); 576 | } 577 | 578 | var computedStyle = window.getComputedStyle($target); 579 | $target.setAttribute('data-scroll-lock-filled-gap', 'true'); 580 | $target.setAttribute('data-scroll-lock-current-fill-gap-method', state.fillGapMethod); 581 | 582 | if (state.fillGapMethod === 'margin') { 583 | var currentMargin = parseFloat(computedStyle.marginRight); 584 | $target.style.marginRight = "".concat(currentMargin + scrollBarWidth, "px"); 585 | } else if (state.fillGapMethod === 'width') { 586 | $target.style.width = "calc(100% - ".concat(scrollBarWidth, "px)"); 587 | } else if (state.fillGapMethod === 'max-width') { 588 | $target.style.maxWidth = "calc(100% - ".concat(scrollBarWidth, "px)"); 589 | } else if (state.fillGapMethod === 'padding') { 590 | var currentPadding = parseFloat(computedStyle.paddingRight); 591 | $target.style.paddingRight = "".concat(currentPadding + scrollBarWidth, "px"); 592 | } 593 | } 594 | }; 595 | 596 | var scroll_lock_unfillGapSelector = function unfillGapSelector(selector) { 597 | var $targets = document.querySelectorAll(selector); 598 | eachNode($targets, function ($target) { 599 | scroll_lock_unfillGapTarget($target); 600 | }); 601 | }; 602 | 603 | var scroll_lock_unfillGapTarget = function unfillGapTarget($target) { 604 | if (isElement($target)) { 605 | if ($target.getAttribute('data-scroll-lock-filled-gap') === 'true') { 606 | var currentFillGapMethod = $target.getAttribute('data-scroll-lock-current-fill-gap-method'); 607 | $target.removeAttribute('data-scroll-lock-filled-gap'); 608 | $target.removeAttribute('data-scroll-lock-current-fill-gap-method'); 609 | 610 | if (currentFillGapMethod === 'margin') { 611 | $target.style.marginRight = ""; 612 | } else if (currentFillGapMethod === 'width') { 613 | $target.style.width = ""; 614 | } else if (currentFillGapMethod === 'max-width') { 615 | $target.style.maxWidth = ""; 616 | } else if (currentFillGapMethod === 'padding') { 617 | $target.style.paddingRight = ""; 618 | } 619 | } 620 | } 621 | }; 622 | 623 | var onResize = function onResize(e) { 624 | refillGaps(); 625 | }; 626 | 627 | var onTouchStart = function onTouchStart(e) { 628 | if (!state.scroll) { 629 | state.startTouchY = e.touches[0].clientY; 630 | state.startTouchX = e.touches[0].clientX; 631 | } 632 | }; 633 | 634 | var scroll_lock_onTouchMove = function onTouchMove(e) { 635 | if (!state.scroll) { 636 | var startTouchY = state.startTouchY, 637 | startTouchX = state.startTouchX; 638 | var currentClientY = e.touches[0].clientY; 639 | var currentClientX = e.touches[0].clientX; 640 | 641 | if (e.touches.length < 2) { 642 | var selector = arrayAsSelector(state.scrollableSelectors); 643 | var direction = { 644 | up: startTouchY < currentClientY, 645 | down: startTouchY > currentClientY, 646 | left: startTouchX < currentClientX, 647 | right: startTouchX > currentClientX 648 | }; 649 | var directionWithOffset = { 650 | up: startTouchY + TOUCH_DIRECTION_DETECT_OFFSET < currentClientY, 651 | down: startTouchY - TOUCH_DIRECTION_DETECT_OFFSET > currentClientY, 652 | left: startTouchX + TOUCH_DIRECTION_DETECT_OFFSET < currentClientX, 653 | right: startTouchX - TOUCH_DIRECTION_DETECT_OFFSET > currentClientX 654 | }; 655 | 656 | var handle = function handle($el) { 657 | var skip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 658 | 659 | if ($el) { 660 | var parentScrollableEl = findParentBySelector($el, selector, false); 661 | 662 | if (elementIsInputRange($el)) { 663 | return false; 664 | } 665 | 666 | if (skip || elementIsScrollableField($el) && findParentBySelector($el, selector) || elementHasSelector($el, selector)) { 667 | var prevent = false; 668 | 669 | if (elementScrollLeftOnStart($el) && elementScrollLeftOnEnd($el)) { 670 | if (direction.up && elementScrollTopOnStart($el) || direction.down && elementScrollTopOnEnd($el)) { 671 | prevent = true; 672 | } 673 | } else if (elementScrollTopOnStart($el) && elementScrollTopOnEnd($el)) { 674 | if (direction.left && elementScrollLeftOnStart($el) || direction.right && elementScrollLeftOnEnd($el)) { 675 | prevent = true; 676 | } 677 | } else if (directionWithOffset.up && elementScrollTopOnStart($el) || directionWithOffset.down && elementScrollTopOnEnd($el) || directionWithOffset.left && elementScrollLeftOnStart($el) || directionWithOffset.right && elementScrollLeftOnEnd($el)) { 678 | prevent = true; 679 | } 680 | 681 | if (prevent) { 682 | if (parentScrollableEl) { 683 | handle(parentScrollableEl, true); 684 | } else { 685 | if (e.cancelable) { 686 | e.preventDefault(); 687 | } 688 | } 689 | } 690 | } else { 691 | handle(parentScrollableEl); 692 | } 693 | } else { 694 | if (e.cancelable) { 695 | e.preventDefault(); 696 | } 697 | } 698 | }; 699 | 700 | handle(e.target); 701 | } 702 | } 703 | }; 704 | 705 | var onTouchEnd = function onTouchEnd(e) { 706 | if (!state.scroll) { 707 | state.startTouchY = 0; 708 | state.startTouchX = 0; 709 | } 710 | }; 711 | 712 | if (typeof window !== 'undefined') { 713 | window.addEventListener('resize', onResize); 714 | } 715 | 716 | if (typeof document !== 'undefined') { 717 | document.addEventListener('touchstart', onTouchStart); 718 | document.addEventListener('touchmove', scroll_lock_onTouchMove, { 719 | passive: false 720 | }); 721 | document.addEventListener('touchend', onTouchEnd); 722 | } 723 | 724 | var deprecatedMethods = { 725 | hide: function hide(target) { 726 | throwError('"hide" is deprecated! Use "disablePageScroll" instead. \n https://github.com/FL3NKEY/scroll-lock#disablepagescrollscrollabletarget'); 727 | disablePageScroll(target); 728 | }, 729 | show: function show(target) { 730 | throwError('"show" is deprecated! Use "enablePageScroll" instead. \n https://github.com/FL3NKEY/scroll-lock#enablepagescrollscrollabletarget'); 731 | enablePageScroll(target); 732 | }, 733 | toggle: function toggle(target) { 734 | throwError('"toggle" is deprecated! Do not use it.'); 735 | 736 | if (getScrollState()) { 737 | disablePageScroll(); 738 | } else { 739 | enablePageScroll(target); 740 | } 741 | }, 742 | getState: function getState() { 743 | throwError('"getState" is deprecated! Use "getScrollState" instead. \n https://github.com/FL3NKEY/scroll-lock#getscrollstate'); 744 | return getScrollState(); 745 | }, 746 | getWidth: function getWidth() { 747 | throwError('"getWidth" is deprecated! Use "getPageScrollBarWidth" instead. \n https://github.com/FL3NKEY/scroll-lock#getpagescrollbarwidth'); 748 | return getPageScrollBarWidth(); 749 | }, 750 | getCurrentWidth: function getCurrentWidth() { 751 | throwError('"getCurrentWidth" is deprecated! Use "getCurrentPageScrollBarWidth" instead. \n https://github.com/FL3NKEY/scroll-lock#getcurrentpagescrollbarwidth'); 752 | return getCurrentPageScrollBarWidth(); 753 | }, 754 | setScrollableTargets: function setScrollableTargets(target) { 755 | throwError('"setScrollableTargets" is deprecated! Use "addScrollableTarget" instead. \n https://github.com/FL3NKEY/scroll-lock#addscrollabletargetscrollabletarget'); 756 | scroll_lock_addScrollableTarget(target); 757 | }, 758 | setFillGapSelectors: function setFillGapSelectors(selector) { 759 | throwError('"setFillGapSelectors" is deprecated! Use "addFillGapSelector" instead. \n https://github.com/FL3NKEY/scroll-lock#addfillgapselectorfillgapselector'); 760 | scroll_lock_addFillGapSelector(selector); 761 | }, 762 | setFillGapTargets: function setFillGapTargets(target) { 763 | throwError('"setFillGapTargets" is deprecated! Use "addFillGapTarget" instead. \n https://github.com/FL3NKEY/scroll-lock#addfillgaptargetfillgaptarget'); 764 | scroll_lock_addFillGapTarget(target); 765 | }, 766 | clearQueue: function clearQueue() { 767 | throwError('"clearQueue" is deprecated! Use "clearQueueScrollLocks" instead. \n https://github.com/FL3NKEY/scroll-lock#clearqueuescrolllocks'); 768 | clearQueueScrollLocks(); 769 | } 770 | }; 771 | 772 | var scrollLock = _objectSpread({ 773 | disablePageScroll: disablePageScroll, 774 | enablePageScroll: enablePageScroll, 775 | getScrollState: getScrollState, 776 | clearQueueScrollLocks: clearQueueScrollLocks, 777 | getTargetScrollBarWidth: scroll_lock_getTargetScrollBarWidth, 778 | getCurrentTargetScrollBarWidth: scroll_lock_getCurrentTargetScrollBarWidth, 779 | getPageScrollBarWidth: getPageScrollBarWidth, 780 | getCurrentPageScrollBarWidth: getCurrentPageScrollBarWidth, 781 | addScrollableSelector: scroll_lock_addScrollableSelector, 782 | removeScrollableSelector: scroll_lock_removeScrollableSelector, 783 | addScrollableTarget: scroll_lock_addScrollableTarget, 784 | removeScrollableTarget: scroll_lock_removeScrollableTarget, 785 | addLockableSelector: scroll_lock_addLockableSelector, 786 | addLockableTarget: scroll_lock_addLockableTarget, 787 | addFillGapSelector: scroll_lock_addFillGapSelector, 788 | removeFillGapSelector: scroll_lock_removeFillGapSelector, 789 | addFillGapTarget: scroll_lock_addFillGapTarget, 790 | removeFillGapTarget: scroll_lock_removeFillGapTarget, 791 | setFillGapMethod: scroll_lock_setFillGapMethod, 792 | refillGaps: refillGaps, 793 | _state: state 794 | }, deprecatedMethods); 795 | 796 | /* harmony default export */ var scroll_lock = __webpack_exports__["default"] = (scrollLock); 797 | 798 | /***/ }) 799 | /******/ ])["default"]; 800 | }); -------------------------------------------------------------------------------- /dist/scroll-lock.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.scrollLock=t():e.scrollLock=t()}(this,function(){return function(l){var r={};function o(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return l[e].call(t.exports,t,t.exports,o),t.l=!0,t.exports}return o.m=l,o.c=r,o.d=function(e,t,l){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:l})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var l=Object.create(null);if(o.r(l),Object.defineProperty(l,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)o.d(l,r,function(e){return t[e]}.bind(null,r));return l},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=0)}([function(e,t,l){"use strict";l.r(t);var r=function(e){return Array.isArray(e)?e:[e]},a=function(e){return e instanceof Node},o=function(e,t){if(e&&t){e=e instanceof NodeList?e:[e];for(var l=0;l { 33 | if (state.queue <= 0) { 34 | state.scroll = false; 35 | hideLockableOverflow(); 36 | fillGaps(); 37 | } 38 | 39 | addScrollableTarget(target); 40 | state.queue++; 41 | }; 42 | export const enablePageScroll = (target) => { 43 | state.queue > 0 && state.queue--; 44 | if (state.queue <= 0) { 45 | state.scroll = true; 46 | showLockableOverflow(); 47 | unfillGaps(); 48 | } 49 | 50 | removeScrollableTarget(target); 51 | }; 52 | export const getScrollState = () => { 53 | return state.scroll; 54 | }; 55 | export const clearQueueScrollLocks = () => { 56 | state.queue = 0; 57 | }; 58 | export const getTargetScrollBarWidth = ($target, onlyExists = false) => { 59 | if (isElement($target)) { 60 | const currentOverflowYProperty = $target.style.overflowY; 61 | if (onlyExists) { 62 | if (!getScrollState()) { 63 | $target.style.overflowY = $target.getAttribute('data-scroll-lock-saved-overflow-y-property'); 64 | } 65 | } else { 66 | $target.style.overflowY = 'scroll'; 67 | } 68 | const width = getCurrentTargetScrollBarWidth($target); 69 | $target.style.overflowY = currentOverflowYProperty; 70 | 71 | return width; 72 | } else { 73 | return 0; 74 | } 75 | }; 76 | export const getCurrentTargetScrollBarWidth = ($target) => { 77 | if (isElement($target)) { 78 | if ($target === document.body) { 79 | const documentWidth = document.documentElement.clientWidth; 80 | const windowWidth = window.innerWidth; 81 | const currentWidth = windowWidth - documentWidth; 82 | 83 | return currentWidth; 84 | } else { 85 | const borderLeftWidthCurrentProperty = $target.style.borderLeftWidth; 86 | const borderRightWidthCurrentProperty = $target.style.borderRightWidth; 87 | $target.style.borderLeftWidth = '0px'; 88 | $target.style.borderRightWidth = '0px'; 89 | const currentWidth = $target.offsetWidth - $target.clientWidth; 90 | $target.style.borderLeftWidth = borderLeftWidthCurrentProperty; 91 | $target.style.borderRightWidth = borderRightWidthCurrentProperty; 92 | 93 | return currentWidth; 94 | } 95 | } else { 96 | return 0; 97 | } 98 | }; 99 | export const getPageScrollBarWidth = (onlyExists = false) => { 100 | return getTargetScrollBarWidth(document.body, onlyExists); 101 | }; 102 | export const getCurrentPageScrollBarWidth = () => { 103 | return getCurrentTargetScrollBarWidth(document.body); 104 | }; 105 | export const addScrollableTarget = (target) => { 106 | if (target) { 107 | const targets = argumentAsArray(target); 108 | targets.map(($targets) => { 109 | eachNode($targets, ($target) => { 110 | if (isElement($target)) { 111 | $target.setAttribute('data-scroll-lock-scrollable', ''); 112 | } else { 113 | throwError(`"${$target}" is not a Element.`); 114 | } 115 | }); 116 | }); 117 | } 118 | }; 119 | export const removeScrollableTarget = (target) => { 120 | if (target) { 121 | const targets = argumentAsArray(target); 122 | targets.map(($targets) => { 123 | eachNode($targets, ($target) => { 124 | if (isElement($target)) { 125 | $target.removeAttribute('data-scroll-lock-scrollable'); 126 | } else { 127 | throwError(`"${$target}" is not a Element.`); 128 | } 129 | }); 130 | }); 131 | } 132 | }; 133 | export const addScrollableSelector = (selector) => { 134 | if (selector) { 135 | const selectors = argumentAsArray(selector); 136 | selectors.map((selector) => { 137 | state.scrollableSelectors.push(selector); 138 | }); 139 | } 140 | }; 141 | export const removeScrollableSelector = (selector) => { 142 | if (selector) { 143 | const selectors = argumentAsArray(selector); 144 | selectors.map((selector) => { 145 | state.scrollableSelectors = state.scrollableSelectors.filter((sSelector) => sSelector !== selector); 146 | }); 147 | } 148 | }; 149 | export const addLockableTarget = (target) => { 150 | if (target) { 151 | const targets = argumentAsArray(target); 152 | targets.map(($targets) => { 153 | eachNode($targets, ($target) => { 154 | if (isElement($target)) { 155 | $target.setAttribute('data-scroll-lock-lockable', ''); 156 | } else { 157 | throwError(`"${$target}" is not a Element.`); 158 | } 159 | }); 160 | }); 161 | if (!getScrollState()) { 162 | hideLockableOverflow(); 163 | } 164 | } 165 | }; 166 | export const addLockableSelector = (selector) => { 167 | if (selector) { 168 | const selectors = argumentAsArray(selector); 169 | selectors.map((selector) => { 170 | state.lockableSelectors.push(selector); 171 | }); 172 | if (!getScrollState()) { 173 | hideLockableOverflow(); 174 | } 175 | addFillGapSelector(selector); 176 | } 177 | }; 178 | export const setFillGapMethod = (method) => { 179 | if (method) { 180 | if (FILL_GAP_AVAILABLE_METHODS.indexOf(method) !== -1) { 181 | state.fillGapMethod = method; 182 | refillGaps(); 183 | } else { 184 | const methods = FILL_GAP_AVAILABLE_METHODS.join(', '); 185 | throwError(`"${method}" method is not available!\nAvailable fill gap methods: ${methods}.`); 186 | } 187 | } 188 | }; 189 | export const addFillGapTarget = (target) => { 190 | if (target) { 191 | const targets = argumentAsArray(target); 192 | targets.map(($targets) => { 193 | eachNode($targets, ($target) => { 194 | if (isElement($target)) { 195 | $target.setAttribute('data-scroll-lock-fill-gap', ''); 196 | if (!state.scroll) { 197 | fillGapTarget($target); 198 | } 199 | } else { 200 | throwError(`"${$target}" is not a Element.`); 201 | } 202 | }); 203 | }); 204 | } 205 | }; 206 | export const removeFillGapTarget = (target) => { 207 | if (target) { 208 | const targets = argumentAsArray(target); 209 | targets.map(($targets) => { 210 | eachNode($targets, ($target) => { 211 | if (isElement($target)) { 212 | $target.removeAttribute('data-scroll-lock-fill-gap'); 213 | if (!state.scroll) { 214 | unfillGapTarget($target); 215 | } 216 | } else { 217 | throwError(`"${$target}" is not a Element.`); 218 | } 219 | }); 220 | }); 221 | } 222 | }; 223 | export const addFillGapSelector = (selector) => { 224 | if (selector) { 225 | const selectors = argumentAsArray(selector); 226 | selectors.map((selector) => { 227 | if (state.fillGapSelectors.indexOf(selector) === -1) { 228 | state.fillGapSelectors.push(selector); 229 | if (!state.scroll) { 230 | fillGapSelector(selector); 231 | } 232 | } 233 | }); 234 | } 235 | }; 236 | export const removeFillGapSelector = (selector) => { 237 | if (selector) { 238 | const selectors = argumentAsArray(selector); 239 | selectors.map((selector) => { 240 | state.fillGapSelectors = state.fillGapSelectors.filter((fSelector) => fSelector !== selector); 241 | if (!state.scroll) { 242 | unfillGapSelector(selector); 243 | } 244 | }); 245 | } 246 | }; 247 | 248 | export const refillGaps = () => { 249 | if (!state.scroll) { 250 | fillGaps(); 251 | } 252 | }; 253 | 254 | const hideLockableOverflow = () => { 255 | const selector = arrayAsSelector(state.lockableSelectors); 256 | hideLockableOverflowSelector(selector); 257 | }; 258 | const showLockableOverflow = () => { 259 | const selector = arrayAsSelector(state.lockableSelectors); 260 | showLockableOverflowSelector(selector); 261 | }; 262 | const hideLockableOverflowSelector = (selector) => { 263 | const $targets = document.querySelectorAll(selector); 264 | eachNode($targets, ($target) => { 265 | hideLockableOverflowTarget($target); 266 | }); 267 | }; 268 | const showLockableOverflowSelector = (selector) => { 269 | const $targets = document.querySelectorAll(selector); 270 | eachNode($targets, ($target) => { 271 | showLockableOverflowTarget($target); 272 | }); 273 | }; 274 | const hideLockableOverflowTarget = ($target) => { 275 | if (isElement($target) && $target.getAttribute('data-scroll-lock-locked') !== 'true') { 276 | const computedStyle = window.getComputedStyle($target); 277 | $target.setAttribute('data-scroll-lock-saved-overflow-y-property', computedStyle.overflowY); 278 | $target.setAttribute('data-scroll-lock-saved-inline-overflow-property', $target.style.overflow); 279 | $target.setAttribute('data-scroll-lock-saved-inline-overflow-y-property', $target.style.overflowY); 280 | 281 | $target.style.overflow = 'hidden'; 282 | $target.setAttribute('data-scroll-lock-locked', 'true'); 283 | } 284 | }; 285 | const showLockableOverflowTarget = ($target) => { 286 | if (isElement($target) && $target.getAttribute('data-scroll-lock-locked') === 'true') { 287 | $target.style.overflow = $target.getAttribute('data-scroll-lock-saved-inline-overflow-property'); 288 | $target.style.overflowY = $target.getAttribute('data-scroll-lock-saved-inline-overflow-y-property'); 289 | 290 | $target.removeAttribute('data-scroll-lock-saved-overflow-property'); 291 | $target.removeAttribute('data-scroll-lock-saved-inline-overflow-property'); 292 | $target.removeAttribute('data-scroll-lock-saved-inline-overflow-y-property'); 293 | $target.removeAttribute('data-scroll-lock-locked'); 294 | } 295 | }; 296 | 297 | const fillGaps = () => { 298 | state.fillGapSelectors.map((selector) => { 299 | fillGapSelector(selector); 300 | }); 301 | }; 302 | const unfillGaps = () => { 303 | state.fillGapSelectors.map((selector) => { 304 | unfillGapSelector(selector); 305 | }); 306 | }; 307 | const fillGapSelector = (selector) => { 308 | const $targets = document.querySelectorAll(selector); 309 | const isLockable = state.lockableSelectors.indexOf(selector) !== -1; 310 | eachNode($targets, ($target) => { 311 | fillGapTarget($target, isLockable); 312 | }); 313 | }; 314 | const fillGapTarget = ($target, isLockable = false) => { 315 | if (isElement($target)) { 316 | let scrollBarWidth; 317 | if ($target.getAttribute('data-scroll-lock-lockable') === '' || isLockable) { 318 | scrollBarWidth = getTargetScrollBarWidth($target, true); 319 | } else { 320 | const $lockableParent = findParentBySelector($target, arrayAsSelector(state.lockableSelectors)); 321 | scrollBarWidth = getTargetScrollBarWidth($lockableParent, true); 322 | } 323 | 324 | if ($target.getAttribute('data-scroll-lock-filled-gap') === 'true') { 325 | unfillGapTarget($target); 326 | } 327 | 328 | const computedStyle = window.getComputedStyle($target); 329 | $target.setAttribute('data-scroll-lock-filled-gap', 'true'); 330 | $target.setAttribute('data-scroll-lock-current-fill-gap-method', state.fillGapMethod); 331 | 332 | if (state.fillGapMethod === 'margin') { 333 | const currentMargin = parseFloat(computedStyle.marginRight); 334 | $target.style.marginRight = `${currentMargin + scrollBarWidth}px`; 335 | } else if (state.fillGapMethod === 'width') { 336 | $target.style.width = `calc(100% - ${scrollBarWidth}px)`; 337 | } else if (state.fillGapMethod === 'max-width') { 338 | $target.style.maxWidth = `calc(100% - ${scrollBarWidth}px)`; 339 | } else if (state.fillGapMethod === 'padding') { 340 | const currentPadding = parseFloat(computedStyle.paddingRight); 341 | $target.style.paddingRight = `${currentPadding + scrollBarWidth}px`; 342 | } 343 | } 344 | }; 345 | const unfillGapSelector = (selector) => { 346 | const $targets = document.querySelectorAll(selector); 347 | eachNode($targets, ($target) => { 348 | unfillGapTarget($target); 349 | }); 350 | }; 351 | const unfillGapTarget = ($target) => { 352 | if (isElement($target)) { 353 | if ($target.getAttribute('data-scroll-lock-filled-gap') === 'true') { 354 | const currentFillGapMethod = $target.getAttribute('data-scroll-lock-current-fill-gap-method'); 355 | $target.removeAttribute('data-scroll-lock-filled-gap'); 356 | $target.removeAttribute('data-scroll-lock-current-fill-gap-method'); 357 | 358 | if (currentFillGapMethod === 'margin') { 359 | $target.style.marginRight = ``; 360 | } else if (currentFillGapMethod === 'width') { 361 | $target.style.width = ``; 362 | } else if (currentFillGapMethod === 'max-width') { 363 | $target.style.maxWidth = ``; 364 | } else if (currentFillGapMethod === 'padding') { 365 | $target.style.paddingRight = ``; 366 | } 367 | } 368 | } 369 | }; 370 | 371 | const onResize = (e) => { 372 | refillGaps(); 373 | }; 374 | 375 | const onTouchStart = (e) => { 376 | if (!state.scroll) { 377 | state.startTouchY = e.touches[0].clientY; 378 | state.startTouchX = e.touches[0].clientX; 379 | } 380 | }; 381 | const onTouchMove = (e) => { 382 | if (!state.scroll) { 383 | const { startTouchY, startTouchX } = state; 384 | const currentClientY = e.touches[0].clientY; 385 | const currentClientX = e.touches[0].clientX; 386 | 387 | if (e.touches.length < 2) { 388 | const selector = arrayAsSelector(state.scrollableSelectors); 389 | const direction = { 390 | up: startTouchY < currentClientY, 391 | down: startTouchY > currentClientY, 392 | left: startTouchX < currentClientX, 393 | right: startTouchX > currentClientX, 394 | }; 395 | const directionWithOffset = { 396 | up: startTouchY + TOUCH_DIRECTION_DETECT_OFFSET < currentClientY, 397 | down: startTouchY - TOUCH_DIRECTION_DETECT_OFFSET > currentClientY, 398 | left: startTouchX + TOUCH_DIRECTION_DETECT_OFFSET < currentClientX, 399 | right: startTouchX - TOUCH_DIRECTION_DETECT_OFFSET > currentClientX, 400 | }; 401 | const handle = ($el, skip = false) => { 402 | if ($el) { 403 | const parentScrollableEl = findParentBySelector($el, selector, false); 404 | if (elementIsInputRange($el)) { 405 | return false; 406 | } 407 | 408 | if ( 409 | skip || 410 | (elementIsScrollableField($el) && findParentBySelector($el, selector)) || 411 | elementHasSelector($el, selector) 412 | ) { 413 | let prevent = false; 414 | if (elementScrollLeftOnStart($el) && elementScrollLeftOnEnd($el)) { 415 | if ( 416 | (direction.up && elementScrollTopOnStart($el)) || 417 | (direction.down && elementScrollTopOnEnd($el)) 418 | ) { 419 | prevent = true; 420 | } 421 | } else if (elementScrollTopOnStart($el) && elementScrollTopOnEnd($el)) { 422 | if ( 423 | (direction.left && elementScrollLeftOnStart($el)) || 424 | (direction.right && elementScrollLeftOnEnd($el)) 425 | ) { 426 | prevent = true; 427 | } 428 | } else if ( 429 | (directionWithOffset.up && elementScrollTopOnStart($el)) || 430 | (directionWithOffset.down && elementScrollTopOnEnd($el)) || 431 | (directionWithOffset.left && elementScrollLeftOnStart($el)) || 432 | (directionWithOffset.right && elementScrollLeftOnEnd($el)) 433 | ) { 434 | prevent = true; 435 | } 436 | if (prevent) { 437 | if (parentScrollableEl) { 438 | handle(parentScrollableEl, true); 439 | } else { 440 | if (e.cancelable) { 441 | e.preventDefault(); 442 | } 443 | } 444 | } 445 | } else { 446 | handle(parentScrollableEl); 447 | } 448 | } else { 449 | if (e.cancelable) { 450 | e.preventDefault(); 451 | } 452 | } 453 | }; 454 | 455 | handle(e.target); 456 | } 457 | } 458 | }; 459 | const onTouchEnd = (e) => { 460 | if (!state.scroll) { 461 | state.startTouchY = 0; 462 | state.startTouchX = 0; 463 | } 464 | }; 465 | 466 | if (typeof window !== 'undefined') { 467 | window.addEventListener('resize', onResize); 468 | } 469 | if (typeof document !== 'undefined') { 470 | document.addEventListener('touchstart', onTouchStart); 471 | document.addEventListener('touchmove', onTouchMove, { 472 | passive: false, 473 | }); 474 | document.addEventListener('touchend', onTouchEnd); 475 | } 476 | 477 | const deprecatedMethods = { 478 | hide(target) { 479 | throwError( 480 | '"hide" is deprecated! Use "disablePageScroll" instead. \n https://github.com/FL3NKEY/scroll-lock#disablepagescrollscrollabletarget' 481 | ); 482 | 483 | disablePageScroll(target); 484 | }, 485 | show(target) { 486 | throwError( 487 | '"show" is deprecated! Use "enablePageScroll" instead. \n https://github.com/FL3NKEY/scroll-lock#enablepagescrollscrollabletarget' 488 | ); 489 | 490 | enablePageScroll(target); 491 | }, 492 | toggle(target) { 493 | throwError('"toggle" is deprecated! Do not use it.'); 494 | 495 | if (getScrollState()) { 496 | disablePageScroll(); 497 | } else { 498 | enablePageScroll(target); 499 | } 500 | }, 501 | getState() { 502 | throwError( 503 | '"getState" is deprecated! Use "getScrollState" instead. \n https://github.com/FL3NKEY/scroll-lock#getscrollstate' 504 | ); 505 | 506 | return getScrollState(); 507 | }, 508 | getWidth() { 509 | throwError( 510 | '"getWidth" is deprecated! Use "getPageScrollBarWidth" instead. \n https://github.com/FL3NKEY/scroll-lock#getpagescrollbarwidth' 511 | ); 512 | 513 | return getPageScrollBarWidth(); 514 | }, 515 | getCurrentWidth() { 516 | throwError( 517 | '"getCurrentWidth" is deprecated! Use "getCurrentPageScrollBarWidth" instead. \n https://github.com/FL3NKEY/scroll-lock#getcurrentpagescrollbarwidth' 518 | ); 519 | 520 | return getCurrentPageScrollBarWidth(); 521 | }, 522 | setScrollableTargets(target) { 523 | throwError( 524 | '"setScrollableTargets" is deprecated! Use "addScrollableTarget" instead. \n https://github.com/FL3NKEY/scroll-lock#addscrollabletargetscrollabletarget' 525 | ); 526 | 527 | addScrollableTarget(target); 528 | }, 529 | setFillGapSelectors(selector) { 530 | throwError( 531 | '"setFillGapSelectors" is deprecated! Use "addFillGapSelector" instead. \n https://github.com/FL3NKEY/scroll-lock#addfillgapselectorfillgapselector' 532 | ); 533 | 534 | addFillGapSelector(selector); 535 | }, 536 | setFillGapTargets(target) { 537 | throwError( 538 | '"setFillGapTargets" is deprecated! Use "addFillGapTarget" instead. \n https://github.com/FL3NKEY/scroll-lock#addfillgaptargetfillgaptarget' 539 | ); 540 | 541 | addFillGapTarget(target); 542 | }, 543 | clearQueue() { 544 | throwError( 545 | '"clearQueue" is deprecated! Use "clearQueueScrollLocks" instead. \n https://github.com/FL3NKEY/scroll-lock#clearqueuescrolllocks' 546 | ); 547 | 548 | clearQueueScrollLocks(); 549 | }, 550 | }; 551 | 552 | const scrollLock = { 553 | disablePageScroll, 554 | enablePageScroll, 555 | 556 | getScrollState, 557 | clearQueueScrollLocks, 558 | getTargetScrollBarWidth, 559 | getCurrentTargetScrollBarWidth, 560 | getPageScrollBarWidth, 561 | getCurrentPageScrollBarWidth, 562 | 563 | addScrollableSelector, 564 | removeScrollableSelector, 565 | 566 | addScrollableTarget, 567 | removeScrollableTarget, 568 | 569 | addLockableSelector, 570 | 571 | addLockableTarget, 572 | 573 | addFillGapSelector, 574 | removeFillGapSelector, 575 | 576 | addFillGapTarget, 577 | removeFillGapTarget, 578 | 579 | setFillGapMethod, 580 | refillGaps, 581 | 582 | _state: state, 583 | 584 | ...deprecatedMethods, 585 | }; 586 | 587 | export default scrollLock; 588 | -------------------------------------------------------------------------------- /src/tools.js: -------------------------------------------------------------------------------- 1 | export const argumentAsArray = (argument) => (Array.isArray(argument) ? argument : [argument]); 2 | export const isElement = (target) => target instanceof Node; 3 | export const isElementList = (nodeList) => nodeList instanceof NodeList; 4 | export const eachNode = (nodeList, callback) => { 5 | if (nodeList && callback) { 6 | nodeList = isElementList(nodeList) ? nodeList : [nodeList]; 7 | for (let i = 0; i < nodeList.length; i++) { 8 | if (callback(nodeList[i], i, nodeList.length) === true) { 9 | break; 10 | } 11 | } 12 | } 13 | }; 14 | export const throwError = (message) => console.error(`[scroll-lock] ${message}`); 15 | export const arrayAsSelector = (array) => { 16 | if (Array.isArray(array)) { 17 | const selector = array.join(', '); 18 | return selector; 19 | } 20 | }; 21 | export const nodeListAsArray = (nodeList) => { 22 | const nodes = []; 23 | eachNode(nodeList, (node) => nodes.push(node)); 24 | 25 | return nodes; 26 | }; 27 | export const findParentBySelector = ($el, selector, self = true, $root = document) => { 28 | if (self && nodeListAsArray($root.querySelectorAll(selector)).indexOf($el) !== -1) { 29 | return $el; 30 | } 31 | 32 | while (($el = $el.parentElement) && nodeListAsArray($root.querySelectorAll(selector)).indexOf($el) === -1); 33 | return $el; 34 | }; 35 | export const elementHasSelector = ($el, selector, $root = document) => { 36 | const has = nodeListAsArray($root.querySelectorAll(selector)).indexOf($el) !== -1; 37 | return has; 38 | }; 39 | export const elementHasOverflowHidden = ($el) => { 40 | if ($el) { 41 | const computedStyle = getComputedStyle($el); 42 | const overflowIsHidden = computedStyle.overflow === 'hidden'; 43 | return overflowIsHidden; 44 | } 45 | }; 46 | export const elementScrollTopOnStart = ($el) => { 47 | if ($el) { 48 | if (elementHasOverflowHidden($el)) { 49 | return true; 50 | } 51 | 52 | const scrollTop = $el.scrollTop; 53 | return scrollTop <= 0; 54 | } 55 | }; 56 | export const elementScrollTopOnEnd = ($el) => { 57 | if ($el) { 58 | if (elementHasOverflowHidden($el)) { 59 | return true; 60 | } 61 | 62 | const scrollTop = $el.scrollTop; 63 | const scrollHeight = $el.scrollHeight; 64 | const scrollTopWithHeight = scrollTop + $el.offsetHeight; 65 | return scrollTopWithHeight >= scrollHeight; 66 | } 67 | }; 68 | export const elementScrollLeftOnStart = ($el) => { 69 | if ($el) { 70 | if (elementHasOverflowHidden($el)) { 71 | return true; 72 | } 73 | 74 | const scrollLeft = $el.scrollLeft; 75 | return scrollLeft <= 0; 76 | } 77 | }; 78 | export const elementScrollLeftOnEnd = ($el) => { 79 | if ($el) { 80 | if (elementHasOverflowHidden($el)) { 81 | return true; 82 | } 83 | 84 | const scrollLeft = $el.scrollLeft; 85 | const scrollWidth = $el.scrollWidth; 86 | const scrollLeftWithWidth = scrollLeft + $el.offsetWidth; 87 | return scrollLeftWithWidth >= scrollWidth; 88 | } 89 | }; 90 | export const elementIsScrollableField = ($el) => { 91 | const selector = 'textarea, [contenteditable="true"]'; 92 | return elementHasSelector($el, selector); 93 | }; 94 | export const elementIsInputRange = ($el) => { 95 | const selector = 'input[type="range"]'; 96 | return elementHasSelector($el, selector); 97 | }; 98 | -------------------------------------------------------------------------------- /tests/add-lockable.test.js: -------------------------------------------------------------------------------- 1 | const scrollLock = require('../dist/scroll-lock'); 2 | 3 | test('add lockable selector', () => { 4 | const initialLockableSelectors = JSON.parse(JSON.stringify(scrollLock._state.lockableSelectors)); 5 | scrollLock.addLockableSelector('.lockable-selector'); 6 | initialLockableSelectors.push('.lockable-selector'); 7 | expect(scrollLock._state.lockableSelectors).toEqual(initialLockableSelectors); 8 | 9 | scrollLock.addLockableSelector(['.lockable-selector-1', '.lockable-selector-2']); 10 | initialLockableSelectors.push('.lockable-selector-1'); 11 | initialLockableSelectors.push('.lockable-selector-2'); 12 | expect(scrollLock._state.lockableSelectors).toEqual(initialLockableSelectors); 13 | }); 14 | 15 | test('add lockable target', () => { 16 | document.body.innerHTML = ` 17 |
18 | 19 |
20 |
21 |
22 | `; 23 | 24 | const $lockableTarget = document.querySelector('#lockable-target'); 25 | expect($lockableTarget.getAttribute('data-scroll-lock-lockable')).toBe(null); 26 | scrollLock.addLockableTarget($lockableTarget); 27 | expect($lockableTarget.getAttribute('data-scroll-lock-lockable')).toBe(''); 28 | 29 | const $lockableTargets = document.querySelectorAll('.lockable-target'); 30 | for (let i = 0; i < $lockableTargets.length; i++) { 31 | expect($lockableTargets[i].getAttribute('data-scroll-lock-lockable')).toBe(null); 32 | } 33 | scrollLock.addLockableTarget($lockableTargets); 34 | for (let i = 0; i < $lockableTargets.length; i++) { 35 | expect($lockableTargets[i].getAttribute('data-scroll-lock-lockable')).toBe(''); 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /tests/add-remove-fill-gap.test.js: -------------------------------------------------------------------------------- 1 | const scrollLock = require('../dist/scroll-lock'); 2 | 3 | test('add fill gap selector', () => { 4 | document.body.innerHTML = ` 5 |
6 |
7 |
8 | `; 9 | 10 | scrollLock.disablePageScroll(); 11 | 12 | const initialFillGapSelectors = JSON.parse(JSON.stringify(scrollLock._state.fillGapSelectors)); 13 | const $fillGapSelector = document.querySelector('.fill-gap-selector'); 14 | expect($fillGapSelector.getAttribute('data-scroll-lock-filled-gap')).toEqual(null); 15 | scrollLock.addFillGapSelector('.fill-gap-selector'); 16 | initialFillGapSelectors.push('.fill-gap-selector'); 17 | expect(scrollLock._state.fillGapSelectors).toEqual(initialFillGapSelectors); 18 | expect($fillGapSelector.getAttribute('data-scroll-lock-filled-gap')).toEqual('true'); 19 | 20 | const $fillGapSelector1 = document.querySelector('.fill-gap-selector-1'); 21 | const $fillGapSelector2 = document.querySelector('.fill-gap-selector-2'); 22 | expect($fillGapSelector1.getAttribute('data-scroll-lock-filled-gap')).toEqual(null); 23 | expect($fillGapSelector2.getAttribute('data-scroll-lock-filled-gap')).toEqual(null); 24 | scrollLock.addFillGapSelector(['.fill-gap-selector-1', '.fill-gap-selector-2']); 25 | initialFillGapSelectors.push('.fill-gap-selector-1'); 26 | initialFillGapSelectors.push('.fill-gap-selector-2'); 27 | expect(scrollLock._state.fillGapSelectors).toEqual(initialFillGapSelectors); 28 | expect($fillGapSelector1.getAttribute('data-scroll-lock-filled-gap')).toEqual('true'); 29 | expect($fillGapSelector2.getAttribute('data-scroll-lock-filled-gap')).toEqual('true'); 30 | 31 | scrollLock.enablePageScroll(); 32 | expect($fillGapSelector1.getAttribute('data-scroll-lock-filled-gap')).toEqual(null); 33 | expect($fillGapSelector2.getAttribute('data-scroll-lock-filled-gap')).toEqual(null); 34 | }); 35 | 36 | test('remove fill gap selector', () => { 37 | scrollLock.disablePageScroll(); 38 | 39 | let initialFillGapSelectors = JSON.parse(JSON.stringify(scrollLock._state.fillGapSelectors)); 40 | const $fillGapSelector = document.querySelector('.fill-gap-selector'); 41 | expect($fillGapSelector.getAttribute('data-scroll-lock-filled-gap')).toEqual('true'); 42 | scrollLock.removeFillGapSelector('.fill-gap-selector'); 43 | initialFillGapSelectors = initialFillGapSelectors.filter((s) => s !== '.fill-gap-selector'); 44 | expect(scrollLock._state.fillGapSelectors).toEqual(initialFillGapSelectors); 45 | expect($fillGapSelector.getAttribute('data-scroll-lock-filled-gap')).toEqual(null); 46 | 47 | const $fillGapSelector1 = document.querySelector('.fill-gap-selector-1'); 48 | const $fillGapSelector2 = document.querySelector('.fill-gap-selector-2'); 49 | expect($fillGapSelector1.getAttribute('data-scroll-lock-filled-gap')).toEqual('true'); 50 | expect($fillGapSelector2.getAttribute('data-scroll-lock-filled-gap')).toEqual('true'); 51 | scrollLock.removeFillGapSelector(['.fill-gap-selector-1', '.fill-gap-selector-2']); 52 | initialFillGapSelectors = initialFillGapSelectors.filter((s) => s !== '.fill-gap-selector-1'); 53 | initialFillGapSelectors = initialFillGapSelectors.filter((s) => s !== '.fill-gap-selector-2'); 54 | expect(scrollLock._state.fillGapSelectors).toEqual(initialFillGapSelectors); 55 | expect($fillGapSelector1.getAttribute('data-scroll-lock-filled-gap')).toEqual(null); 56 | expect($fillGapSelector2.getAttribute('data-scroll-lock-filled-gap')).toEqual(null); 57 | 58 | scrollLock.enablePageScroll(); 59 | }); 60 | 61 | test('add fill gap target', () => { 62 | document.body.innerHTML = ` 63 |
64 | 65 |
66 |
67 |
68 | `; 69 | 70 | const $fillGapTarget = document.querySelector('#fill-gap-target'); 71 | expect($fillGapTarget.getAttribute('data-scroll-lock-fill-gap')).toBe(null); 72 | scrollLock.addFillGapTarget($fillGapTarget); 73 | expect($fillGapTarget.getAttribute('data-scroll-lock-fill-gap')).toBe(''); 74 | expect($fillGapTarget.getAttribute('data-scroll-lock-filled-gap')).toBe(null); 75 | scrollLock.disablePageScroll(); 76 | expect($fillGapTarget.getAttribute('data-scroll-lock-filled-gap')).toBe('true'); 77 | scrollLock.enablePageScroll(); 78 | expect($fillGapTarget.getAttribute('data-scroll-lock-filled-gap')).toBe(null); 79 | 80 | const $fillGapTargets = document.querySelectorAll('.fill-gap-target'); 81 | for (let i = 0; i < $fillGapTargets.length; i++) { 82 | expect($fillGapTargets[i].getAttribute('data-scroll-lock-fill-gap')).toBe(null); 83 | } 84 | scrollLock.addFillGapTarget($fillGapTargets); 85 | for (let i = 0; i < $fillGapTargets.length; i++) { 86 | expect($fillGapTargets[i].getAttribute('data-scroll-lock-fill-gap')).toBe(''); 87 | expect($fillGapTargets[i].getAttribute('data-scroll-lock-filled-gap')).toBe(null); 88 | } 89 | scrollLock.disablePageScroll(); 90 | for (let i = 0; i < $fillGapTargets.length; i++) { 91 | expect($fillGapTargets[i].getAttribute('data-scroll-lock-filled-gap')).toBe('true'); 92 | } 93 | scrollLock.enablePageScroll(); 94 | for (let i = 0; i < $fillGapTargets.length; i++) { 95 | expect($fillGapTargets[i].getAttribute('data-scroll-lock-filled-gap')).toBe(null); 96 | } 97 | }); 98 | 99 | test('remove fill gap target', () => { 100 | scrollLock.disablePageScroll(); 101 | 102 | const $fillGapTarget = document.querySelector('#fill-gap-target'); 103 | expect($fillGapTarget.getAttribute('data-scroll-lock-fill-gap')).toBe(''); 104 | expect($fillGapTarget.getAttribute('data-scroll-lock-filled-gap')).toBe('true'); 105 | scrollLock.removeFillGapTarget($fillGapTarget); 106 | expect($fillGapTarget.getAttribute('data-scroll-lock-fill-gap')).toBe(null); 107 | expect($fillGapTarget.getAttribute('data-scroll-lock-filled-gap')).toBe(null); 108 | 109 | const $fillGapTargets = document.querySelectorAll('.fill-gap-target'); 110 | for (let i = 0; i < $fillGapTargets.length; i++) { 111 | expect($fillGapTargets[i].getAttribute('data-scroll-lock-fill-gap')).toBe(''); 112 | expect($fillGapTargets[i].getAttribute('data-scroll-lock-filled-gap')).toBe('true'); 113 | } 114 | scrollLock.removeFillGapTarget($fillGapTargets); 115 | for (let i = 0; i < $fillGapTargets.length; i++) { 116 | expect($fillGapTargets[i].getAttribute('data-scroll-lock-fill-gap')).toBe(null); 117 | expect($fillGapTargets[i].getAttribute('data-scroll-lock-filled-gap')).toBe(null); 118 | } 119 | }); 120 | 121 | test('confirm you can not add duplicate selectors', () => { 122 | scrollLock.disablePageScroll(); 123 | const initialFillGapSelectors = JSON.parse(JSON.stringify(scrollLock._state.fillGapSelectors)); 124 | scrollLock.addFillGapSelector('.duplicate-gap-selector'); 125 | scrollLock.addFillGapSelector('.duplicate-gap-selector'); 126 | initialFillGapSelectors.push('.duplicate-gap-selector'); 127 | expect(scrollLock._state.fillGapSelectors).toEqual(initialFillGapSelectors); 128 | }); 129 | -------------------------------------------------------------------------------- /tests/add-remove-scrollable.test.js: -------------------------------------------------------------------------------- 1 | const scrollLock = require('../dist/scroll-lock'); 2 | 3 | test('add scrollable selector', () => { 4 | const initialScrollableSelectors = JSON.parse(JSON.stringify(scrollLock._state.scrollableSelectors)); 5 | scrollLock.addScrollableSelector('.scrollable-selector'); 6 | initialScrollableSelectors.push('.scrollable-selector'); 7 | expect(scrollLock._state.scrollableSelectors).toEqual(initialScrollableSelectors); 8 | 9 | scrollLock.addScrollableSelector(['.scrollable-selector-1', '.scrollable-selector-2']); 10 | initialScrollableSelectors.push('.scrollable-selector-1'); 11 | initialScrollableSelectors.push('.scrollable-selector-2'); 12 | expect(scrollLock._state.scrollableSelectors).toEqual(initialScrollableSelectors); 13 | }); 14 | 15 | test('remove scrollable selector', () => { 16 | let initialScrollableSelectors = JSON.parse(JSON.stringify(scrollLock._state.scrollableSelectors)); 17 | scrollLock.removeScrollableSelector('.scrollable-selector'); 18 | initialScrollableSelectors = initialScrollableSelectors.filter((s) => s !== '.scrollable-selector'); 19 | expect(scrollLock._state.scrollableSelectors).toEqual(initialScrollableSelectors); 20 | 21 | scrollLock.removeScrollableSelector(['.scrollable-selector-1', '.scrollable-selector-2']); 22 | initialScrollableSelectors = initialScrollableSelectors.filter((s) => s !== '.scrollable-selector-1'); 23 | initialScrollableSelectors = initialScrollableSelectors.filter((s) => s !== '.scrollable-selector-2'); 24 | expect(scrollLock._state.scrollableSelectors).toEqual(initialScrollableSelectors); 25 | }); 26 | 27 | test('add scrollable target', () => { 28 | document.body.innerHTML = ` 29 |
30 | 31 |
32 |
33 |
34 | `; 35 | 36 | const $scrollableTarget = document.querySelector('#scrollable-target'); 37 | expect($scrollableTarget.getAttribute('data-scroll-lock-scrollable')).toBe(null); 38 | scrollLock.addScrollableTarget($scrollableTarget); 39 | expect($scrollableTarget.getAttribute('data-scroll-lock-scrollable')).toBe(''); 40 | 41 | const $scrollableTargets = document.querySelectorAll('.scrollable-target'); 42 | for (let i = 0; i < $scrollableTargets.length; i++) { 43 | expect($scrollableTargets[i].getAttribute('data-scroll-lock-scrollable')).toBe(null); 44 | } 45 | scrollLock.addScrollableTarget($scrollableTargets); 46 | for (let i = 0; i < $scrollableTargets.length; i++) { 47 | expect($scrollableTargets[i].getAttribute('data-scroll-lock-scrollable')).toBe(''); 48 | } 49 | }); 50 | 51 | test('remove scrollable target', () => { 52 | const $scrollableTarget = document.querySelector('#scrollable-target'); 53 | expect($scrollableTarget.getAttribute('data-scroll-lock-scrollable')).toBe(''); 54 | scrollLock.removeScrollableTarget($scrollableTarget); 55 | expect($scrollableTarget.getAttribute('data-scroll-lock-scrollable')).toBe(null); 56 | 57 | const $scrollableTargets = document.querySelectorAll('.scrollable-target'); 58 | for (let i = 0; i < $scrollableTargets.length; i++) { 59 | expect($scrollableTargets[i].getAttribute('data-scroll-lock-scrollable')).toBe(''); 60 | } 61 | scrollLock.removeScrollableTarget($scrollableTargets); 62 | for (let i = 0; i < $scrollableTargets.length; i++) { 63 | expect($scrollableTargets[i].getAttribute('data-scroll-lock-scrollable')).toBe(null); 64 | } 65 | }); 66 | -------------------------------------------------------------------------------- /tests/deprecated-methods.test.js: -------------------------------------------------------------------------------- 1 | const scrollLock = require('../dist/scroll-lock'); 2 | 3 | test('deprecated methods', () => { 4 | expect(scrollLock.hide).toBeInstanceOf(Function); 5 | expect(scrollLock.show).toBeInstanceOf(Function); 6 | expect(scrollLock.toggle).toBeInstanceOf(Function); 7 | expect(scrollLock.getState).toBeInstanceOf(Function); 8 | expect(scrollLock.getWidth).toBeInstanceOf(Function); 9 | expect(scrollLock.getCurrentWidth).toBeInstanceOf(Function); 10 | expect(scrollLock.setScrollableTargets).toBeInstanceOf(Function); 11 | expect(scrollLock.setFillGapSelectors).toBeInstanceOf(Function); 12 | expect(scrollLock.setFillGapTargets).toBeInstanceOf(Function); 13 | expect(scrollLock.clearQueue).toBeInstanceOf(Function); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/disable-enable-scroll.test.js: -------------------------------------------------------------------------------- 1 | const scrollLock = require('../dist/scroll-lock'); 2 | 3 | test('page scroll disable/enable', () => { 4 | const $body = document.body; 5 | expect(scrollLock.getScrollState()).toBe(true); 6 | expect($body.style.overflow).toBe(''); 7 | scrollLock.disablePageScroll(); 8 | expect(scrollLock.getScrollState()).toBe(false); 9 | expect($body.style.overflow).toBe('hidden'); 10 | scrollLock.enablePageScroll(); 11 | expect(scrollLock.getScrollState()).toBe(true); 12 | expect($body.style.overflow).toBe(''); 13 | }); 14 | 15 | test('page scroll disable/enable with queue', () => { 16 | const $body = document.body; 17 | expect(scrollLock.getScrollState()).toBe(true); 18 | expect($body.style.overflow).toBe(''); 19 | scrollLock.disablePageScroll(); 20 | scrollLock.disablePageScroll(); 21 | scrollLock.disablePageScroll(); 22 | scrollLock.disablePageScroll(); 23 | expect(scrollLock.getScrollState()).toBe(false); 24 | expect($body.style.overflow).toBe('hidden'); 25 | scrollLock.enablePageScroll(); 26 | expect(scrollLock.getScrollState()).toBe(false); 27 | expect($body.style.overflow).toBe('hidden'); 28 | scrollLock.clearQueueScrollLocks(); 29 | scrollLock.enablePageScroll(); 30 | expect(scrollLock.getScrollState()).toBe(true); 31 | expect($body.style.overflow).toBe(''); 32 | }); 33 | -------------------------------------------------------------------------------- /tests/set-fill-gap-method.test.js: -------------------------------------------------------------------------------- 1 | const scrollLock = require('../dist/scroll-lock'); 2 | 3 | test('set fill gap method', () => { 4 | document.body.innerHTML = ` 5 |
6 | `; 7 | 8 | const $fillGapTarget = document.querySelector('#fill-gap-target'); 9 | 10 | scrollLock.disablePageScroll(); 11 | 12 | expect(scrollLock._state.fillGapMethod).toBe('padding'); 13 | expect($fillGapTarget.getAttribute('data-scroll-lock-current-fill-gap-method')).toBe('padding'); 14 | 15 | scrollLock.setFillGapMethod('margin'); 16 | expect(scrollLock._state.fillGapMethod).toBe('margin'); 17 | expect($fillGapTarget.getAttribute('data-scroll-lock-current-fill-gap-method')).toBe('margin'); 18 | 19 | scrollLock.setFillGapMethod('width'); 20 | expect(scrollLock._state.fillGapMethod).toBe('width'); 21 | expect($fillGapTarget.getAttribute('data-scroll-lock-current-fill-gap-method')).toBe('width'); 22 | 23 | scrollLock.setFillGapMethod('max-width'); 24 | expect(scrollLock._state.fillGapMethod).toBe('max-width'); 25 | expect($fillGapTarget.getAttribute('data-scroll-lock-current-fill-gap-method')).toBe('max-width'); 26 | 27 | scrollLock.setFillGapMethod('none'); 28 | expect(scrollLock._state.fillGapMethod).toBe('none'); 29 | expect($fillGapTarget.getAttribute('data-scroll-lock-current-fill-gap-method')).toBe('none'); 30 | 31 | const errorSpy = jest.spyOn(global.console, 'error').mockImplementation(() => {}); 32 | scrollLock.setFillGapMethod('unsupported value'); 33 | expect(errorSpy).toHaveBeenCalled(); 34 | expect(scrollLock._state.fillGapMethod).toBe('none'); 35 | expect($fillGapTarget.getAttribute('data-scroll-lock-current-fill-gap-method')).toBe('none'); 36 | }); 37 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const BrowserSyncPlugin = require('browser-sync-webpack-plugin'); 4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 5 | 6 | const isProduction = process.env.NODE_ENV == 'production' ? true : false; 7 | 8 | const webpack_config = { 9 | plugins: [], 10 | module: { 11 | rules: [] 12 | }, 13 | optimization: {} 14 | }; 15 | 16 | webpack_config.mode = process.env.NODE_ENV; 17 | 18 | webpack_config.entry = { 19 | 'scroll-lock': './src/scroll-lock.js' 20 | }; 21 | 22 | if (isProduction) { 23 | webpack_config.entry['scroll-lock.min'] = './src/scroll-lock.js'; 24 | } 25 | 26 | webpack_config.output = { 27 | path: path.resolve(__dirname, 'dist'), 28 | filename: '[name].js', 29 | library: 'scrollLock', 30 | libraryTarget: 'umd', 31 | libraryExport: 'default', 32 | globalObject: 'this' 33 | }; 34 | 35 | webpack_config.plugins.push( 36 | new webpack.DefinePlugin({ 37 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 38 | }) 39 | ); 40 | 41 | webpack_config.module.rules.push({ 42 | test: /\.js$/, 43 | exclude: [/node_modules/], 44 | loader: 'babel-loader', 45 | options: { 46 | presets: ['@babel/preset-env'] 47 | } 48 | }); 49 | 50 | if (isProduction) { 51 | webpack_config.optimization.minimizer = [ 52 | new UglifyJsPlugin({ 53 | include: /\.min\.js$/, 54 | cache: true, 55 | parallel: true, 56 | uglifyOptions: { 57 | compress: true, 58 | warnings: false, 59 | output: { 60 | comments: false, 61 | beautify: false 62 | } 63 | } 64 | }) 65 | ]; 66 | } else { 67 | webpack_config.plugins.push( 68 | new BrowserSyncPlugin({ 69 | host: 'localhost', 70 | port: 1337, 71 | files: ['./dist/**/*', './demos/**/*'], 72 | server: { 73 | baseDir: ['./'], 74 | index: '/demos/index.html' 75 | } 76 | }) 77 | ); 78 | 79 | webpack_config.watch = true; 80 | webpack_config.watchOptions = { 81 | aggregateTimeout: 100, 82 | ignored: /node_modules/ 83 | }; 84 | 85 | webpack_config.devtool = 'cheap-inline-module-source-map'; 86 | } 87 | 88 | module.exports = webpack_config; 89 | --------------------------------------------------------------------------------