├── .bilirc ├── .gitignore ├── .eslintrc ├── .babelrc ├── CHANGELOG.md ├── LICENSE ├── src └── index.js ├── package.json ├── .circleci └── config.yml ├── test └── locker.spec.js └── readme.md /.bilirc: -------------------------------------------------------------------------------- 1 | { 2 | "moduleName": "createLocker" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage/ 4 | yarn-error.log 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "plugins": ["jest"], 4 | "env": { 5 | "jest/globals": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["@babel/plugin-transform-modules-commonjs"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | # 1.0.0 (2018-10-10) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Clark Du 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 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | class Locker { 2 | constructor (options = {}) { 3 | this.scrollElement = options.scrollElement || document.scrollingElement || document.documentElement 4 | this.lock = this.lock.bind(this) 5 | this.unlock = this.unlock.bind(this) 6 | } 7 | 8 | /** 9 | * Make body fixed in current position 10 | * @return {void} 11 | */ 12 | lock () { 13 | this.style = { 14 | top: this.scrollElement.style.top || '', 15 | width: this.scrollElement.style.width || '', 16 | position: this.scrollElement.style.position || '' 17 | } 18 | this.scrollTop = this.scrollElement.scrollTop 19 | this.scrollElement.style.cssText += `width:100%;position:fixed;top:-${this.scrollTop}px;` 20 | return this 21 | } 22 | 23 | /** 24 | * Resume body scroll and position 25 | * @return {void} 26 | */ 27 | unlock () { 28 | for (let key in this.style) { 29 | this.scrollElement.style[key] = this.style[key] 30 | } 31 | this.scrollElement.scrollTop = this.scrollTop 32 | 33 | delete this.style 34 | delete this.scrollTop 35 | 36 | return this 37 | } 38 | } 39 | 40 | export default function createLocker (options) { 41 | return new Locker(options) 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lock-body-scroll", 3 | "version": "1.0.0", 4 | "description": "Lock/unlock body scrolling when modal is opened", 5 | "main": "index.js", 6 | "browser": "dist/lock-body-scroll.min.js", 7 | "repository": "https://github.com/clarkdo/lock-body-scroll", 8 | "author": "Clark Du (clark.duxin@gmail.com)", 9 | "license": "MIT", 10 | "files": [ 11 | "dist", 12 | "src" 13 | ], 14 | "scripts": { 15 | "lint": "eslint --ignore-path .gitignore .", 16 | "test": "jest --coverage", 17 | "build": "yarn build:client && yarn build:server", 18 | "build:client": "bili --format umd,umd-min", 19 | "build:server": "bili --format cjs --target node", 20 | "release": "standard-version" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.1.2", 24 | "@babel/plugin-transform-modules-commonjs": "^7.1.0", 25 | "babel-core": "^7.0.0-bridge", 26 | "babel-jest": "^23.6.0", 27 | "bili": "^3.1.2", 28 | "codecov": "^3.1.0", 29 | "eslint": "^5.6.1", 30 | "eslint-config-standard": "^12.0.0", 31 | "eslint-plugin-import": "^2.14.0", 32 | "eslint-plugin-jest": "^21.24.1", 33 | "eslint-plugin-node": "^7.0.1", 34 | "eslint-plugin-promise": "^4.0.1", 35 | "eslint-plugin-standard": "^4.0.0", 36 | "jest": "^23.6.0", 37 | "standard-version": "^4.4.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | working_directory: /usr/src/app 3 | docker: 4 | - image: banian/node 5 | 6 | version: 2 7 | jobs: 8 | setup: 9 | <<: *defaults 10 | steps: 11 | # Checkout repository 12 | - checkout 13 | 14 | # Restore cache 15 | - restore_cache: 16 | key: yarn-{{ checksum "yarn.lock" }} 17 | 18 | # Install dependencies 19 | - run: 20 | name: Install Dependencies 21 | command: NODE_ENV=dev yarn 22 | 23 | # Keep cache 24 | - save_cache: 25 | key: yarn-{{ checksum "yarn.lock" }} 26 | paths: 27 | - "node_modules" 28 | 29 | - persist_to_workspace: 30 | root: /usr/src 31 | paths: 32 | - app 33 | lint: 34 | <<: *defaults 35 | steps: 36 | - attach_workspace: 37 | at: /usr/src 38 | 39 | # Lint 40 | - run: 41 | name: Lint 42 | command: yarn lint 43 | 44 | build: 45 | <<: *defaults 46 | steps: 47 | - attach_workspace: 48 | at: /usr/src 49 | 50 | # Build 51 | - run: 52 | name: Build 53 | command: yarn build 54 | 55 | test: 56 | <<: *defaults 57 | steps: 58 | - attach_workspace: 59 | at: /usr/src 60 | 61 | # Test 62 | - run: 63 | name: Test 64 | command: NODE_ENV=test yarn test 65 | 66 | # Coverage 67 | - run: 68 | name: Coverage 69 | command: yarn codecov 70 | 71 | workflows: 72 | version: 2 73 | build-and-test: 74 | jobs: 75 | - setup 76 | - lint: 77 | requires: 78 | - setup 79 | - build: 80 | requires: 81 | - setup 82 | - test: 83 | requires: 84 | - lint 85 | - build 86 | -------------------------------------------------------------------------------- /test/locker.spec.js: -------------------------------------------------------------------------------- 1 | import createLocker from '../src' 2 | 3 | describe('lock body scroll', () => { 4 | test('should create locker instance on documentElement', () => { 5 | const locker = createLocker() 6 | expect(locker.scrollElement).toBe(document.documentElement) 7 | }) 8 | 9 | test('should use scrollingElement with higher priority than documentElement', () => { 10 | document.scrollingElement = jest.fn() 11 | const locker = createLocker() 12 | expect(locker.scrollElement).toBe(document.scrollingElement) 13 | }) 14 | 15 | test('should lock the scrolling element', () => { 16 | const scrollElement = { 17 | style: { 18 | cssText: '' 19 | }, 20 | scrollTop: 100 21 | } 22 | const locker = createLocker({ scrollElement }) 23 | const lockReturnedLocker = locker.lock() 24 | 25 | expect(scrollElement.style.cssText).toBe('width:100%;position:fixed;top:-100px;') 26 | expect(lockReturnedLocker).toBe(locker) 27 | }) 28 | 29 | test('should keep state before locking the scrolling element', () => { 30 | const scrollElement = { 31 | style: { 32 | top: '10px', 33 | width: '90%', 34 | position: 'static', 35 | cssText: '' 36 | }, 37 | scrollTop: 100 38 | } 39 | const locker = createLocker({ scrollElement }).lock() 40 | 41 | expect(locker.style).toEqual({ 42 | top: '10px', 43 | width: '90%', 44 | position: 'static' 45 | }) 46 | expect(scrollElement.style.cssText).toBe('width:100%;position:fixed;top:-100px;') 47 | }) 48 | 49 | test('should unlock the scrolling element', () => { 50 | const scrollElement = { 51 | style: { 52 | cssText: '' 53 | }, 54 | scrollTop: 100 55 | } 56 | 57 | const locker = createLocker({ scrollElement }).lock().unlock() 58 | 59 | expect(locker.style).toBeUndefined() 60 | expect(locker.scrollTop).toBeUndefined() 61 | expect(scrollElement.style).toMatchObject({ 62 | top: '', 63 | width: '', 64 | position: '' 65 | }) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Lock body scroll 2 | 3 | > 🌈 Lock/unlock body scrolling when modal is opened 4 | 5 | [![Standard JS][standard-js-src]][standard-js-href] 6 | [![Circle CI][circle-ci-src]][circle-ci-href] 7 | [![Codecov][codecov-src]][codecov-href] 8 | [![npm version][npm-version-src]][npm-version-href] 9 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ yarn add lock-body-scroll 15 | 16 | or 17 | 18 | $ npm i lock-body-scroll 19 | ``` 20 | 21 | ## Usage 22 | 23 | ### ES modules 24 | 25 | ```js 26 | import createLocker from 'lock-body-scroll' 27 | 28 | const locker = createLocker() 29 | 30 | // or 31 | 32 | const locker = createLocker(options) 33 | 34 | locker.lock() 35 | 36 | locker.unlock() 37 | ``` 38 | 39 | ### CommonJS 40 | 41 | ```js 42 | const createLocker = require('lock-body-scroll') 43 | 44 | const locker = createLocker() 45 | 46 | locker.lock() 47 | 48 | locker.unlock() 49 | ``` 50 | 51 | ### Browser 52 | 53 | HTML: 54 | 55 | ```html 56 |
57 | 58 | 59 | ``` 60 | 61 | JavaScript: 62 | 63 | ```js 64 | const locker = window.createLocker() 65 | 66 | locker.lock() 67 | 68 | locker.unlock() 69 | ``` 70 | 71 | ## Options 72 | 73 | ### scrollElement 74 | 75 | Default: `document.scrollingElement || document.documentElement` 76 | 77 | The dom element which is for locking scrolling 78 | 79 | ## Contributing 80 | 81 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 82 | 83 | ## License 84 | 85 | [MIT][license--href] 86 | 87 | [standard-js-src]: https://flat.badgen.net/badge/code%20style/standard/green 88 | [standard-js-href]: https://standardjs.com 89 | [circle-ci-src]: https://flat.badgen.net/circleci/github/clarkdo/lock-body-scroll 90 | [circle-ci-href]: https://circleci.com/gh/clarkdo/lock-body-scroll 91 | [codecov-src]: https://flat.badgen.net/codecov/c/github/clarkdo/lock-body-scroll 92 | [codecov-href]: https://codecov.io/gh/clarkdo/lock-body-scroll 93 | [npm-version-src]: https://flat.badgen.net/npm/v/lock-body-scroll/latest 94 | [npm-version-href]: https://npmjs.com/package/lock-body-scroll 95 | [npm-downloads-src]: https://flat.badgen.net/npm/dt/lock-body-scroll 96 | [npm-downloads-href]: https://npmjs.com/package/lock-body-scroll 97 | [license--href]: https://github.com/clarkdo/lock-body-scroll/blob/master/LICENSE 98 | --------------------------------------------------------------------------------