├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github └── workflows │ ├── browserstack-dispatch.yml │ ├── browserstack.yml │ ├── codeql-analysis.yml │ └── node.js.yml ├── .gitignore ├── .release-it.js ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── bower.json ├── build ├── release │ ├── post-release.sh │ └── pre-release.sh └── tasks │ ├── build.mjs │ └── minify.mjs ├── eslint.config.mjs ├── jtr.yml ├── package-lock.json ├── package.json ├── src └── jquery.mousewheel.js └── test ├── index.html ├── manual.html ├── scroll.html └── unit.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | indent_style = tab 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.{json,yml}] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Use tab indents instead of spaces 2 | fc760b8691412e90492ff958cb806b03373bb237 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # JS files must always use LF for tools to work 5 | # JS files may have mjs or cjs extensions now as well 6 | *.js eol=lf 7 | *.cjs eol=lf 8 | *.mjs eol=lf 9 | -------------------------------------------------------------------------------- /.github/workflows/browserstack-dispatch.yml: -------------------------------------------------------------------------------- 1 | name: Browserstack (Manual Dispatch) 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | browser: 7 | description: 'Browser to test, in form of \"browser_[browserVersion | :device]_os_osVersion\"' 8 | required: false 9 | type: string 10 | default: 'chrome__windows_11' 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | environment: browserstack 16 | env: 17 | BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} 18 | BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} 19 | steps: 20 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | 22 | - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 23 | with: 24 | node-version: 20 25 | 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Build jQuery Mousewheel 30 | run: npm run build 31 | 32 | - name: Run tests 33 | run: npm run test:unit -- \ 34 | -v --browserstack ${{ inputs.browser }} \ 35 | -f module=${{ inputs.module }} 36 | -------------------------------------------------------------------------------- /.github/workflows/browserstack.yml: -------------------------------------------------------------------------------- 1 | name: Browserstack 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | environment: browserstack 12 | env: 13 | BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} 14 | BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} 15 | NODE_VERSION: 22.x 16 | name: ${{ matrix.BROWSER }} 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ matrix.BROWSER }} 19 | timeout-minutes: 30 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | BROWSER: 24 | - 'IE_11' 25 | - 'Safari_latest' 26 | - 'Safari_latest-1' 27 | - 'Chrome_latest' 28 | - 'Chrome_latest-1' 29 | - 'Opera_latest' 30 | - 'Edge_latest' 31 | - 'Edge_latest-1' 32 | - 'Firefox_latest' 33 | - 'Firefox_latest-1' 34 | - '__iOS_18' 35 | - '__iOS_17' 36 | - '__iOS_16' 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 40 | 41 | - name: Use Node.js ${{ env.NODE_VERSION }} 42 | uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 43 | with: 44 | node-version: ${{ env.NODE_VERSION }} 45 | 46 | - name: Cache 47 | uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 48 | with: 49 | path: ~/.npm 50 | key: ${{ runner.os }}-node-${{ env.NODE_VERSION }}-npm-lock-${{ hashFiles('**/package-lock.json') }} 51 | restore-keys: | 52 | ${{ runner.os }}-node-${{ env.NODE_VERSION }}-npm-lock- 53 | 54 | - name: Install dependencies 55 | run: npm install 56 | 57 | - name: Build jQuery Mousewheel 58 | run: npm run build 59 | 60 | - name: Run tests 61 | run: | 62 | npm run test:unit -- -v --retries 3 --hard-retries 1 \ 63 | --browserstack "${{ matrix.BROWSER }}" \ 64 | --run-id ${{ github.run_id }} 65 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches-ignore: 7 | - "dependabot/**" 8 | schedule: 9 | - cron: "0 4 * * 6" 10 | 11 | permissions: 12 | contents: read # to fetch code (actions/checkout) 13 | 14 | jobs: 15 | CodeQL-Build: 16 | permissions: 17 | contents: read # to fetch code (actions/checkout) 18 | security-events: write # (github/codeql-action/autobuild) 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 25 | with: 26 | # We must fetch at least the immediate parents so that if this is 27 | # a pull request then we can checkout the head. 28 | fetch-depth: 2 29 | 30 | # If this run was triggered by a pull request event, then checkout 31 | # the head of the pull request instead of the merge commit. 32 | - run: git checkout HEAD^2 33 | if: ${{ github.event_name == 'pull_request' }} 34 | 35 | # Initializes the CodeQL tools for scanning. 36 | - name: Initialize CodeQL 37 | uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 38 | # Override language selection by uncommenting this and choosing your languages 39 | # with: 40 | # languages: go, javascript, csharp, python, cpp, java 41 | 42 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 43 | # If this step fails, then you should remove it and run the build manually (see below) 44 | - name: Autobuild 45 | uses: github/codeql-action/autobuild@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 46 | 47 | # ℹ️ Command-line programs to run using the OS shell. 48 | # 📚 https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 49 | 50 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 51 | # and modify them (or add more) to build your code if your project 52 | # uses a compiled language 53 | 54 | #- run: | 55 | # make bootstrap 56 | # make release 57 | 58 | - name: Perform CodeQL Analysis 59 | uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 60 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches-ignore: 7 | - "dependabot/**" 8 | 9 | permissions: 10 | contents: read # to fetch code (actions/checkout) 11 | 12 | jobs: 13 | build-and-test: 14 | runs-on: ubuntu-latest 15 | name: ${{ matrix.NPM_SCRIPT }} - ${{ matrix.NAME }} (${{ matrix.NODE_VERSION }}) 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | include: 20 | - NAME: "Node" 21 | NODE_VERSION: "22.x" 22 | NPM_SCRIPT: "lint" 23 | - NAME: "Chrome/Firefox" 24 | NODE_VERSION: "22.x" 25 | NPM_SCRIPT: "test:browser" 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 29 | 30 | - name: Use Node.js ${{ matrix.NODE_VERSION }} 31 | uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 32 | with: 33 | node-version: ${{ matrix.NODE_VERSION }} 34 | 35 | - name: Cache 36 | uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 37 | with: 38 | path: ~/.npm 39 | key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-npm-lock-${{ hashFiles('**/package-lock.json') }} 40 | restore-keys: | 41 | ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-npm-lock- 42 | 43 | - name: Set download URL for Firefox ESR (old) 44 | run: | 45 | echo "FIREFOX_SOURCE_URL=https://download.mozilla.org/?product=firefox-esr-latest-ssl&lang=en-US&os=linux64" >> "$GITHUB_ENV" 46 | if: contains(matrix.NAME, 'Firefox ESR (old)') 47 | 48 | - name: Set download URL for Firefox ESR (new) 49 | run: | 50 | echo "FIREFOX_SOURCE_URL=https://download.mozilla.org/?product=firefox-esr-next-latest-ssl&lang=en-US&os=linux64" >> "$GITHUB_ENV" 51 | if: contains(matrix.NAME, 'Firefox ESR (new)') 52 | 53 | - name: Install Firefox ESR 54 | run: | 55 | wget --no-verbose $FIREFOX_SOURCE_URL -O - | tar -jx -C ${HOME} 56 | echo "PATH=${HOME}/firefox:$PATH" >> "$GITHUB_ENV" 57 | echo "FIREFOX_BIN=${HOME}/firefox/firefox" >> "$GITHUB_ENV" 58 | if: contains(matrix.NAME, 'Firefox ESR') 59 | 60 | - name: Install dependencies 61 | run: npm install 62 | 63 | - name: Build for linting 64 | run: npm run build 65 | if: contains(matrix.NPM_SCRIPT, 'lint') 66 | 67 | - name: Run tests 68 | run: npm run ${{ matrix.NPM_SCRIPT }} 69 | 70 | ie: 71 | runs-on: windows-latest 72 | env: 73 | NODE_VERSION: 22.x 74 | name: test:ie - IE 75 | steps: 76 | - name: Checkout 77 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 78 | 79 | - name: Use Node.js ${{ env.NODE_VERSION }} 80 | uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 81 | with: 82 | node-version: ${{ env.NODE_VERSION }} 83 | 84 | - name: Cache 85 | uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 86 | with: 87 | path: ~/.npm 88 | key: ${{ runner.os }}-node-${{ env.NODE_VERSION }}-npm-lock-${{ hashFiles('**/package-lock.json') }} 89 | restore-keys: | 90 | ${{ runner.os }}-node-${{ env.NODE_VERSION }}-npm-lock- 91 | 92 | - name: Install dependencies 93 | run: npm install 94 | 95 | - name: Run tests in Edge in IE mode 96 | run: npm run test:ie 97 | 98 | safari: 99 | runs-on: macos-latest 100 | env: 101 | NODE_VERSION: 22.x 102 | name: test:safari - Safari 103 | steps: 104 | - name: Checkout 105 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 106 | 107 | - name: Use Node.js ${{ env.NODE_VERSION }} 108 | uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 109 | with: 110 | node-version: ${{ env.NODE_VERSION }} 111 | 112 | - name: Cache 113 | uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 114 | with: 115 | path: ~/.npm 116 | key: ${{ runner.os }}-node-${{ env.NODE_VERSION }}-npm-lock-${{ hashFiles('**/package-lock.json') }} 117 | restore-keys: | 118 | ${{ runner.os }}-node-${{ env.NODE_VERSION }}-npm-lock- 119 | 120 | - name: Install dependencies 121 | run: npm install 122 | 123 | - name: Run tests 124 | run: npm run test:safari 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .settings 3 | *~ 4 | *.diff 5 | *.patch 6 | .DS_Store 7 | .bower.json 8 | .sizecache.json 9 | yarn.lock 10 | .eslintcache 11 | tmp 12 | 13 | npm-debug.log* 14 | 15 | /dist 16 | /node_modules 17 | 18 | # Ignore BrowserStack testing files 19 | local.log 20 | browserstack.err 21 | -------------------------------------------------------------------------------- /.release-it.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | preReleaseBase: 1, 5 | hooks: { 6 | "before:init": "bash ./build/release/pre-release.sh", 7 | "after:version:bump": 8 | "sed -i 's/main\\/AUTHORS.txt/${version}\\/AUTHORS.txt/' package.json", 9 | "after:bump": "cross-env VERSION=${version} npm run build", 10 | "before:git:release": "git add -f dist/", 11 | "after:release": "echo 'Run the following to complete the release:' && " + 12 | "echo './build/release/post-release.sh $\{version}'" 13 | }, 14 | git: { 15 | commitMessage: "Release: ${version}", 16 | getLatestTagFromAllRefs: true, 17 | pushRepo: "git@github.com:jquery/jquery-mousewheel.git", 18 | requireBranch: "main", 19 | requireCleanWorkingDir: true 20 | }, 21 | github: { 22 | pushRepo: "git@github.com:jquery/jquery-mousewheel.git", 23 | release: true, 24 | tokenRef: "JQUERY_GITHUB_TOKEN" 25 | }, 26 | npm: { 27 | publish: true 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Mouse Wheel Change Log 2 | 3 | ## 3.2.2 4 | 5 | * Include the minified file in the npm package 6 | 7 | ## 3.2.1 8 | 9 | * Update README 10 | 11 | ## 3.2.0 12 | 13 | * Use `.on()`/`.off()` for event binding where available 14 | * Don't clobber mouse offset properties if we don't adjust them (#165) 15 | * Remove moot `version` property from bower.json (#140) 16 | * Remove the executable bit from the library (#176) 17 | * Add jtr-based tests in GitHub Actions, migrate to ESLint flat config (#247) 18 | 19 | ## 3.1.13 20 | 21 | * Update copyright notice and license to remove years 22 | * Create the correct compressed version 23 | * Remove the obsolete jQuery Plugin Registry file 24 | 25 | ## 3.1.12 26 | 27 | * Fix possible 0 value for line height when in delta mode 1 28 | 29 | ## 3.1.11 30 | 31 | * Fix version number for package managers... 32 | 33 | ## 3.1.10 34 | 35 | * Fix issue with calculating line height when using older versions of jQuery 36 | * Add offsetX/Y normalization with setting to turn it off 37 | * Cleans up data on teardown 38 | 39 | ## 3.1.9 40 | 41 | * Fix bower.json file 42 | * Updated how the deltas are adjusted for older mousewheel based events that have deltas that are factors of 120. 43 | * Add $.event.special.mousewheel.settings.adjustOldDeltas (defaults to true) to turn off adjusting of old deltas that are factors of 120. You'd turn this off if you want to be as close to native scrolling as possible. 44 | 45 | ## 3.1.8 46 | 47 | * Even better handling of older browsers that use a wheelDelta based on 120 48 | * And fix version reported by `$.event.special.mousewheel` 49 | 50 | ## 3.1.7 51 | 52 | * Better handle the `deltaMode` values 1 (lines) and 2 (pages) 53 | * Attempt to better handle older browsers that use a wheelDelta based on 120 54 | 55 | ## 3.1.6 56 | 57 | * Deprecating `delta`, `deltaX`, and `deltaY` event handler arguments 58 | * Update actual event object with normalized `deltaX `and `deltaY` values (`event.deltaX`, `event.deltaY`) 59 | * Add `deltaFactor` to the event object (`event.deltaFactor`) 60 | * Handle `> 0` but `< 1` deltas better 61 | * Do not fire the event if `deltaX` and `deltaY` are `0` 62 | * Better handle different devices that give different `lowestDelta` values 63 | * Add `$.event.special.mousewheel.version` 64 | * Some clean up 65 | 66 | ## 3.1.5 67 | 68 | * Bad release because I did not update the new `$.event.special.mousewheel.version` 69 | 70 | ## 3.1.4 71 | 72 | * Always set the `deltaY` 73 | * Add back in the `deltaX` and `deltaY` support for older Firefox versions 74 | 75 | ## 3.1.3 76 | 77 | * Include `MozMousePixelScroll` in the to fix list to avoid inconsistent behavior in older Firefox 78 | 79 | ## 3.1.2 80 | 81 | * Include grunt utilities for development purposes (jshint and uglify) 82 | * Include support for browserify 83 | * Some basic cleaning up 84 | 85 | ## 3.1.1 86 | 87 | * Fix rounding issue with deltas less than zero 88 | 89 | 90 | ## 3.1.0 91 | 92 | * Fix Firefox 17+ issues by using new wheel event 93 | * Normalize delta values 94 | * Adds horizontal support for IE 9+ by using new wheel event 95 | * Support AMD loaders 96 | 97 | 98 | ## 3.0.6 99 | 100 | * Fix issue with delta being 0 in Firefox 101 | 102 | 103 | ## 3.0.5 104 | 105 | * jQuery 1.7 compatibility 106 | 107 | 108 | ## 3.0.4 109 | 110 | * Fix IE issue 111 | 112 | 113 | ## 3.0.3 114 | 115 | * Added `deltaX` and `deltaY` for horizontal scrolling support (Thanks to Seamus Leahy) 116 | 117 | 118 | ## 3.0.2 119 | 120 | * Fixed delta being opposite value in latest Opera 121 | * No longer fix `pageX`, `pageY` for older Mozilla browsers 122 | * Removed browser detection 123 | * Cleaned up the code 124 | 125 | 126 | ## 3.0.1 127 | 128 | * Bad release... creating a new release due to plugins.jquery.com issue :( 129 | 130 | 131 | ## 3.0 132 | 133 | * Uses new special events API in jQuery 1.2.2+ 134 | * You can now treat `mousewheel` as a normal event and use `.bind`, `.unbind` and `.trigger` 135 | * Using jQuery.data API for expandos 136 | 137 | 138 | ## 2.2 139 | 140 | * Fixed `pageX`, `pageY`, `clientX` and `clientY` event properties for Mozilla based browsers 141 | 142 | 143 | ## 2.1.1 144 | 145 | * Updated to work with jQuery 1.1.3 146 | * Used one instead of bind to do unload event for clean up 147 | 148 | 149 | ## 2.1 150 | 151 | * Fixed an issue with the unload handler 152 | 153 | 154 | ## 2.0 155 | 156 | * Major reduction in code size and complexity (internals have change a whole lot) 157 | 158 | 159 | ## 1.0 160 | 161 | * Fixed Opera issue 162 | * Fixed an issue with children elements that also have a mousewheel handler 163 | * Added ability to handle multiple handlers 164 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery-mousewheel 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery Mouse Wheel Plugin 2 | 3 | A [jQuery](https://jquery.com/) plugin that adds cross-browser mouse wheel support with delta normalization. 4 | 5 | In order to use the plugin, simply bind the `mousewheel` event to an element. 6 | 7 | The event object is updated with the normalized `deltaX` and `deltaY` properties. 8 | In addition, there is a new property on the event object called `deltaFactor`. Multiply 9 | the `deltaFactor` by `deltaX` or `deltaY` to get the scroll distance that the browser 10 | has reported. 11 | 12 | Here is an example of using both the bind and helper method syntax: 13 | 14 | ```js 15 | $( "#my_elem" ).on( "mousewheel", function( event ) { 16 | console.log( event.deltaX, event.deltaY, event.deltaFactor ); 17 | } ); 18 | ``` 19 | 20 | The old behavior of adding three arguments (`delta`, `deltaX`, and `deltaY`) to the 21 | event handler is now deprecated and will be removed in later releases. 22 | 23 | 24 | ## The Deltas... 25 | 26 | The combination of browsers, operating systems, and devices offer a huge range of possible delta values. In fact if the user 27 | uses a trackpad and then a physical mouse wheel the delta values can differ wildly. This plugin normalizes those 28 | values so you get a whole number starting at +-1 and going up in increments of +-1 according to the force or 29 | acceleration that is used. This number has the potential to be in the thousands depending on the device. 30 | 31 | ### Getting the scroll distance 32 | 33 | In some use-cases we prefer to have the normalized delta but in others we want to know how far the browser should 34 | scroll based on the users input. This can be done by multiplying the `deltaFactor` by the `deltaX` or `deltaY` 35 | event property to find the scroll distance the browser reported. 36 | 37 | The `deltaFactor` property was added to the event object in 3.1.5 so that the actual reported delta value can be 38 | extracted. This is a non-standard property. 39 | 40 | ## Building the code in the repo & running tests 41 | 42 | ```sh 43 | git clone git@github.com:jquery/jquery-mousewheel.git 44 | cd jquery-mousewheel/ 45 | npm install 46 | npm test 47 | ``` 48 | 49 | The unit tests are _very_ basic sanity checks; improvements welcome. 50 | To fully test the plugin, load [test/index.html](test/index.html) in each supported 51 | browser and follow the instructions at the top of the file after the unit tests finish. 52 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-mousewheel", 3 | "main": "./jquery.mousewheel.js", 4 | "ignore": [ 5 | "*.json", 6 | "*.markdown", 7 | "*.txt", 8 | ".*", 9 | "!LICENSE.txt", 10 | "test" 11 | ], 12 | "dependencies": { 13 | "jquery": ">=1.7.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /build/release/post-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -euo pipefail 4 | 5 | # $1: Version 6 | 7 | dist=tmp/release/dist 8 | 9 | if [[ -z "$1" ]] then 10 | echo "Version is not set (1st argument)" 11 | exit 1 12 | fi 13 | 14 | if [[ -z "$2" ]] then 15 | echo "Blog URL is not set (2nd argument)" 16 | exit 1 17 | fi 18 | 19 | # Restore AUTHORS URL 20 | sed -i "s/$1\/AUTHORS.txt/main\/AUTHORS.txt/" package.json 21 | git add package.json 22 | 23 | # Remove built files from tracking. 24 | npm run build:clean 25 | git rm --cached -r dist/ 26 | git commit -m "Release: remove dist files from main branch" 27 | 28 | # Wait for confirmation from user to push changes 29 | read -p "Press enter to push changes to main branch" 30 | git push 31 | -------------------------------------------------------------------------------- /build/release/pre-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -euo pipefail 4 | 5 | read -p "Press enter if you updated CHANGELOG.md; abort otherwise" 6 | 7 | # Install dependencies 8 | npm ci 9 | 10 | # Clean all release and build artifacts 11 | npm run build:clean 12 | 13 | # Run tests 14 | npm test 15 | -------------------------------------------------------------------------------- /build/tasks/build.mjs: -------------------------------------------------------------------------------- 1 | import fs from "node:fs/promises"; 2 | import path from "node:path"; 3 | import { exec as nodeExec } from "node:child_process"; 4 | import util from "node:util"; 5 | import { minify } from "./minify.mjs"; 6 | 7 | const exec = util.promisify( nodeExec ); 8 | 9 | const pkg = JSON.parse( await fs.readFile( "./package.json", "utf8" ) ); 10 | 11 | async function isCleanWorkingDir() { 12 | const { stdout } = await exec( "git status --untracked-files=no --porcelain" ); 13 | return !stdout.trim(); 14 | } 15 | 16 | async function versionForDist( { srcPath, destPath, version } ) { 17 | const code = await fs.readFile( srcPath, "utf8" ); 18 | const compiledContents = code.replace( /@VERSION/g, version ); 19 | 20 | await fs.mkdir( path.dirname( destPath ), { recursive: true } ); 21 | await fs.writeFile( destPath, compiledContents ); 22 | console.log( `${ destPath } v${ version } created.` ); 23 | } 24 | 25 | export async function build( { version = process.env.VERSION } = {} ) { 26 | 27 | // Add the short commit hash to the version string 28 | // when the version is not for a release. 29 | if ( !version ) { 30 | const { stdout } = await exec( "git rev-parse --short HEAD" ); 31 | const isClean = await isCleanWorkingDir(); 32 | 33 | // "+SHA" is semantically correct 34 | // Add ".dirty" as well if the working dir is not clean 35 | version = `${ pkg.version }+${ stdout.trim() }${ 36 | isClean ? "" : ".dirty" 37 | }`; 38 | } 39 | 40 | await versionForDist( { 41 | srcPath: "src/jquery.mousewheel.js", 42 | destPath: "dist/jquery.mousewheel.js", 43 | version 44 | } ); 45 | 46 | await minify( { 47 | srcPath: "dist/jquery.mousewheel.js", 48 | destPath: "dist/jquery.mousewheel.min.js", 49 | version 50 | } ); 51 | } 52 | -------------------------------------------------------------------------------- /build/tasks/minify.mjs: -------------------------------------------------------------------------------- 1 | import fs from "node:fs/promises"; 2 | import swc from "@swc/core"; 3 | import path from "node:path"; 4 | 5 | export async function minify( { srcPath, destPath, version } ) { 6 | const contents = await fs.readFile( srcPath, "utf8" ); 7 | 8 | await fs.mkdir( path.dirname( destPath ), { recursive: true } ); 9 | 10 | const { code } = await swc.minify( 11 | contents, 12 | { 13 | compress: { 14 | ecma: 5, 15 | hoist_funs: false, 16 | loops: false 17 | }, 18 | format: { 19 | ecma: 5, 20 | asciiOnly: true, 21 | comments: false, 22 | preamble: `/*! jQuery Mousewheel ${ version }` + 23 | " | (c) OpenJS Foundation and other contributors" + 24 | " | jquery.org/license */\n" 25 | }, 26 | inlineSourcesContent: false, 27 | mangle: true, 28 | module: false, 29 | sourceMap: false 30 | } 31 | ); 32 | 33 | await fs.writeFile( 34 | destPath, 35 | code 36 | ); 37 | 38 | console.log( `file ${ destPath } created.` ); 39 | } 40 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import jqueryConfig from "eslint-config-jquery"; 2 | import globals from "globals"; 3 | 4 | export default [ 5 | { 6 | ignores: [ "dist/**/*.js" ], 7 | rules: { 8 | ...jqueryConfig.rules, 9 | indent: [ "error", "tab" ] 10 | } 11 | }, 12 | 13 | { 14 | files: [ 15 | "src/**/*.js", 16 | "dist/**/*.js", 17 | "test/**/*.js" 18 | ], 19 | languageOptions: { 20 | 21 | // Support: IE 9 - 11+ 22 | ecmaVersion: 5, 23 | 24 | sourceType: "script", 25 | 26 | // Support: Non-browser envs like jsdom 27 | // Don't include `globals.browser`, take browser globals from `window`. 28 | globals: { 29 | window: "readonly", 30 | define: "readonly", 31 | module: "readonly", 32 | jQuery: "readonly" 33 | } 34 | } 35 | }, 36 | 37 | { 38 | files: [ "src/**/*.js" ], 39 | rules: { 40 | strict: [ "error", "function" ], 41 | indent: [ 42 | "error", 43 | "tab", 44 | { 45 | 46 | // This makes it so code within the wrapper is not indented. 47 | ignoredNodes: [ 48 | "Program > FunctionDeclaration > *" 49 | ] 50 | } 51 | ] 52 | } 53 | }, 54 | 55 | { 56 | files: [ "test/**/*.js" ], 57 | languageOptions: { 58 | globals: { 59 | QUnit: "readonly" 60 | } 61 | }, 62 | rules: { 63 | strict: [ "error", "global" ] 64 | } 65 | }, 66 | 67 | { 68 | files: [ "*.js" ], 69 | languageOptions: { 70 | ecmaVersion: "latest", 71 | sourceType: "commonjs", 72 | globals: { 73 | ...globals.node 74 | } 75 | }, 76 | rules: { 77 | ...jqueryConfig.rules 78 | } 79 | }, 80 | 81 | { 82 | files: [ "*.mjs", "build/**/*.mjs" ], 83 | languageOptions: { 84 | ecmaVersion: "latest", 85 | sourceType: "module", 86 | globals: { 87 | ...globals.node 88 | } 89 | }, 90 | rules: { 91 | ...jqueryConfig.rules 92 | } 93 | } 94 | ]; 95 | -------------------------------------------------------------------------------- /jtr.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | 3 | base-url: /test/ 4 | 5 | runs: 6 | jquery: 7 | - git 8 | - git.min 9 | - git.slim 10 | - git.slim.min 11 | - 3.x-git 12 | - 3.x-git.min 13 | - 3.x-git.slim 14 | - 3.x-git.slim.min 15 | - 3.7.1 16 | - 3.7.1.min 17 | - 3.7.1.slim 18 | - 3.7.1.slim.min 19 | - 3.6.4 20 | - 3.6.4.min 21 | - 3.6.4.slim 22 | - 3.6.4.slim.min 23 | - 2.2.4 24 | - 2.2.4.min 25 | - 1.12.4 26 | - 1.12.4.min 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-mousewheel", 3 | "version": "4.0.0-pre", 4 | "author": { 5 | "name": "jQuery Foundation and other contributors", 6 | "url": "https://github.com/jquery/jquery-mousewheel/blob/master/AUTHORS.txt" 7 | }, 8 | "description": "A jQuery plugin that adds cross-browser mouse wheel support.", 9 | "license": "MIT", 10 | "homepage": "https://github.com/jquery/jquery-mousewheel", 11 | "main": "dist/jquery.mousewheel.js", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/jquery/jquery-mousewheel.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/jquery/jquery-mousewheel/issues" 18 | }, 19 | "keywords": [ 20 | "jquery", 21 | "mouse", 22 | "wheel", 23 | "event", 24 | "mousewheel", 25 | "jquery-plugin", 26 | "browser" 27 | ], 28 | "files": [ 29 | "CHANGELOG.md", 30 | "dist/jquery.mousewheel.js", 31 | "dist/jquery.mousewheel.min.js", 32 | "README.md", 33 | "LICENSE.txt" 34 | ], 35 | "directories": { 36 | "test": "test" 37 | }, 38 | "scripts": { 39 | "build": "node --input-type=module -e \"import { build } from './build/tasks/build.mjs'; build()\"", 40 | "build:clean": "rimraf dist", 41 | "lint": "eslint .", 42 | "release": "release-it", 43 | "test:browser": "npm run build && npm run test:unit -- -b chrome -b firefox --headless", 44 | "test:chrome": "npm run build && npm run test:unit -- -v -b chrome --headless", 45 | "test:edge": "npm run build && npm run test:unit -- -v -b edge --headless", 46 | "test:firefox": "npm run build && npm run test:unit -- -v -b firefox --headless", 47 | "test:ie": "npm run build && npm run test:unit -- -v -b ie", 48 | "test:safari": "npm run build && npm run test:unit -- -b safari", 49 | "test:unit": "jtr", 50 | "test": "npm run build && npm run lint && npm run test:browser" 51 | }, 52 | "peerDependencies": { 53 | "jquery": ">=1.12.4 <2 || >=2.2.4 <3 || >=3.6.4" 54 | }, 55 | "devDependencies": { 56 | "@swc/core": "1.11.8", 57 | "eslint": "9.22.0", 58 | "eslint-config-jquery": "3.0.2", 59 | "globals": "16.0.0", 60 | "jquery": "3.7.1", 61 | "jquery-test-runner": "0.2.6", 62 | "qunit": "2.24.1", 63 | "requirejs": "2.3.7", 64 | "release-it": "19.0.2", 65 | "rimraf": "^6.0.1" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/jquery.mousewheel.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Mousewheel v@VERSION 3 | * https://github.com/jquery/jquery-mousewheel 4 | * 5 | * Copyright OpenJS Foundation and other contributors 6 | * Released under the MIT license 7 | * https://jquery.org/license 8 | */ 9 | ( function( factory ) { 10 | "use strict"; 11 | 12 | if ( typeof define === "function" && define.amd ) { 13 | 14 | // AMD. Register as an anonymous module. 15 | define( [ "jquery" ], factory ); 16 | } else if ( typeof exports === "object" ) { 17 | 18 | // Node/CommonJS style for Browserify 19 | module.exports = factory; 20 | } else { 21 | 22 | // Browser globals 23 | factory( jQuery ); 24 | } 25 | } )( function( $ ) { 26 | "use strict"; 27 | 28 | var nullLowestDeltaTimeout, lowestDelta, 29 | slice = Array.prototype.slice; 30 | 31 | if ( $.event.fixHooks ) { 32 | $.event.fixHooks.wheel = $.event.mouseHooks; 33 | } 34 | 35 | var special = $.event.special.mousewheel = { 36 | version: "@VERSION", 37 | 38 | setup: function() { 39 | this.addEventListener( "wheel", handler, false ); 40 | 41 | // Store the line height and page height for this particular element 42 | $.data( this, "mousewheel-line-height", special.getLineHeight( this ) ); 43 | $.data( this, "mousewheel-page-height", special.getPageHeight( this ) ); 44 | }, 45 | 46 | teardown: function() { 47 | this.removeEventListener( "wheel", handler, false ); 48 | 49 | // Clean up the data we added to the element 50 | $.removeData( this, "mousewheel-line-height" ); 51 | $.removeData( this, "mousewheel-page-height" ); 52 | }, 53 | 54 | getLineHeight: function( elem ) { 55 | var $elem = $( elem ), 56 | $parent = $elem.offsetParent(); 57 | if ( !$parent.length ) { 58 | $parent = $( "body" ); 59 | } 60 | return parseInt( $parent.css( "fontSize" ), 10 ) || 61 | parseInt( $elem.css( "fontSize" ), 10 ) || 16; 62 | }, 63 | 64 | getPageHeight: function( elem ) { 65 | return $( elem ).height(); 66 | }, 67 | 68 | settings: { 69 | normalizeOffset: true // calls getBoundingClientRect for each event 70 | } 71 | }; 72 | 73 | function handler( origEvent ) { 74 | var args = slice.call( arguments, 1 ), 75 | delta = 0, 76 | deltaX = 0, 77 | deltaY = 0, 78 | absDelta = 0, 79 | event = $.event.fix( origEvent ); 80 | 81 | event.type = "mousewheel"; 82 | 83 | // New school wheel delta (wheel event) 84 | if ( "deltaY" in origEvent ) { 85 | deltaY = origEvent.deltaY * -1; 86 | delta = deltaY; 87 | } 88 | if ( "deltaX" in origEvent ) { 89 | deltaX = origEvent.deltaX; 90 | if ( deltaY === 0 ) { 91 | delta = deltaX * -1; 92 | } 93 | } 94 | 95 | // No change actually happened, no reason to go any further 96 | if ( deltaY === 0 && deltaX === 0 ) { 97 | return; 98 | } 99 | 100 | // Need to convert lines and pages to pixels if we aren't already in pixels 101 | // There are three delta modes: 102 | // * deltaMode 0 is by pixels, nothing to do 103 | // * deltaMode 1 is by lines 104 | // * deltaMode 2 is by pages 105 | if ( origEvent.deltaMode === 1 ) { 106 | var lineHeight = $.data( this, "mousewheel-line-height" ); 107 | delta *= lineHeight; 108 | deltaY *= lineHeight; 109 | deltaX *= lineHeight; 110 | } else if ( origEvent.deltaMode === 2 ) { 111 | var pageHeight = $.data( this, "mousewheel-page-height" ); 112 | delta *= pageHeight; 113 | deltaY *= pageHeight; 114 | deltaX *= pageHeight; 115 | } 116 | 117 | // Store lowest absolute delta to normalize the delta values 118 | absDelta = Math.max( Math.abs( deltaY ), Math.abs( deltaX ) ); 119 | 120 | if ( !lowestDelta || absDelta < lowestDelta ) { 121 | lowestDelta = absDelta; 122 | } 123 | 124 | // Get a whole, normalized value for the deltas 125 | delta = Math[ delta >= 1 ? "floor" : "ceil" ]( delta / lowestDelta ); 126 | deltaX = Math[ deltaX >= 1 ? "floor" : "ceil" ]( deltaX / lowestDelta ); 127 | deltaY = Math[ deltaY >= 1 ? "floor" : "ceil" ]( deltaY / lowestDelta ); 128 | 129 | // Normalise offsetX and offsetY properties 130 | if ( special.settings.normalizeOffset && this.getBoundingClientRect ) { 131 | var boundingRect = this.getBoundingClientRect(); 132 | event.offsetX = event.clientX - boundingRect.left; 133 | event.offsetY = event.clientY - boundingRect.top; 134 | } 135 | 136 | // Add information to the event object 137 | event.deltaX = deltaX; 138 | event.deltaY = deltaY; 139 | event.deltaFactor = lowestDelta; 140 | 141 | // Go ahead and set deltaMode to 0 since we converted to pixels 142 | // Although this is a little odd since we overwrite the deltaX/Y 143 | // properties with normalized deltas. 144 | event.deltaMode = 0; 145 | 146 | // Add event and delta to the front of the arguments 147 | args.unshift( event, delta, deltaX, deltaY ); 148 | 149 | // Clear out lowestDelta after sometime to better 150 | // handle multiple device types that give different 151 | // a different lowestDelta 152 | // Ex: trackpad = 3 and mouse wheel = 120 153 | window.clearTimeout( nullLowestDeltaTimeout ); 154 | nullLowestDeltaTimeout = window.setTimeout( function() { 155 | lowestDelta = null; 156 | }, 200 ); 157 | 158 | return $.event.dispatch.apply( this, args ); 159 | } 160 | 161 | } ); 162 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |Add ?jquery=1.8.3
or ?jquery=3.7.1.min
to specify a jQuery version from code.jquery.com
.
mousewheel()
with a function passed in and does not prevent default. (Also logs the value of pageX
and pageY
event properties.)mouseover
and mouseout
event. Testing .off( "mousewheel" )
.Test1
Test2
Test3
Test4
Test5
240 |Test6
242 |Test7
wheelme
" ).appendTo( "body" ); 31 | 32 | markup.on( "mousewheel", function( e ) { 33 | assert.ok( true, "triggered a mousewheel event on " + e.target.innerText ); 34 | assert.ok( "deltaX" in e, "got a deltaX in the event" ); 35 | assert.ok( !isNaN( parseFloat( e.deltaY ) ), "deltaY is a number: " + e.deltaY ); 36 | } ); 37 | 38 | // First wheel event "calibrates" so we won't measure this one 39 | var event1 = makeWheelEvent( 0, 2.2 ); 40 | markup[ 0 ].dispatchEvent( event1 ); 41 | 42 | var event2 = makeWheelEvent( 0, 10.528 ); 43 | markup[ 0 ].dispatchEvent( event2 ); 44 | 45 | markup.remove(); 46 | } ); 47 | 48 | QUnit.test( "mouse event properties are passed through", function( assert ) { 49 | assert.expect( 4 ); 50 | 51 | var markup = jQuery( "wheelme
" ).appendTo( "body" ); 52 | 53 | markup.on( "mousewheel", function( e ) { 54 | var org = e.originalEvent; 55 | assert.equal( org.clientX, 342, "original event has clientX: " + org.clientX ); 56 | assert.equal( org.clientY, 301, "original event has clientY: " + org.clientY ); 57 | assert.ok( e.offsetX < org.clientX, "got plausible offsetX in the event: " + e.offsetX ); 58 | assert.ok( e.offsetY < org.clientY, "got plausible offsetY in the event: " + e.offsetY ); 59 | } ); 60 | 61 | // Not sure why this property is manipulating offsetX/Y but the behavior cannot 62 | // change in a minor version so it will stay since it's set to true right now. 63 | // For testing we just want to ensure that the properties get through. 64 | var event1 = makeWheelEvent( 0, 2.2 ); 65 | event1.offsetX = 1; 66 | event1.offsetY = 2; 67 | event1.clientX = 342; 68 | event1.clientY = 301; 69 | markup[ 0 ].dispatchEvent( event1 ); 70 | 71 | markup.remove(); 72 | } ); 73 | --------------------------------------------------------------------------------