├── .gitignore ├── .npmignore ├── FUNDING.yml ├── LICENSE ├── README.md ├── RELEASES.md ├── bun.lockb ├── images ├── anim-all.gif ├── anim-drag.gif ├── anim-tap.gif ├── gestures0001.png ├── gestures0002.png ├── gestures0003.png ├── gestures0004.png ├── gestures0005.png ├── gestures0006.png ├── gestures0007.png ├── gestures0008.png └── swipe-cone.png ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | test/ 4 | playground* 5 | dist/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | test 3 | images -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: robinrodricks 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jerry Bendy 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.md: -------------------------------------------------------------------------------- 1 | # vue3-touch-events [![](https://img.shields.io/npm/v/vue3-touch-events.svg)](https://www.npmjs.com/package/vue3-touch-events) [![](https://img.shields.io/npm/dt/vue3-touch-events)](https://www.npmjs.com/package/vue3-touch-events) 2 | 3 | **Enable tap, swipe, touch, hold, mouse down, mouse up events on any HTML DOM Element in vue.js 3.x.** 4 | 5 | The easiest way to make your interactive vue.js content mobile-friendly. When you add `v-touch` events to your elements, it works on desktop and mobile using a fully declarative syntax. Unlike other libraries, you do not need to add any special code to your components to make this work. You simply have to register the library globally and it enables new events throughout your application. 6 | 7 | ![Events](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/anim-all.gif) 8 | 9 | Released under the permissive MIT License. 10 | 11 | 12 | ## What's New! 13 | 14 | - [Cone swiping](#swipe) and [Zoom](#zoom) is new in version 5! 15 | - We now support sending [drag](#drag-and-drop) events when users drag outside an object! (set `dragOutside: true`) 16 | - If you encounter any issues with these, please file an issue! 17 | 18 | ## Features 19 | 20 | - Declarative syntax for common touch events, such as [tap, hold](#touch-and-tap), [swipe](#swipe), [zoom](#zoom) and [drag](#drag-and-drop) 21 | - Most events support desktop (mouse) and mobile devices (touch screen) with the same syntax 22 | - Automatically add styling on hover and tap using `v-touch-class` directive 23 | - Bind multiple touch events on one DOM element 24 | - Customizable events with native-like events handler 25 | - Customizable throttling for all events to control how often they are fired, and prevent crashing your application 26 | - Global configuration that applies to all events in the application 27 | - Ability to override global configuration on any element using `v-touch-options` directive 28 | - Bindings for TypeScript included and also works in pure-JavaScript projects 29 | 30 | Version: 31 | 32 | > Note: This library is for **vue.js 3.x** only. For **vue.js 2.x** see the [older library](https://github.com/jerrybendy/vue-touch-events). 33 | 34 | 35 | 36 | ## Installation 37 | 38 | To install: 39 | 40 | | Package Manager | Command | 41 | |-----------------|--------------------------------------------| 42 | | npm | `npm install vue3-touch-events` | 43 | | bun | `bun add vue3-touch-events` | 44 | | yarn | `yarn add vue3-touch-events` | 45 | 46 | 47 | Release notes are found in the [RELEASES.md](RELEASES.md) file. 48 | 49 | 50 | ### TypeScript 51 | 52 | You need to register this plugin with vue.js in your main application file: 53 | 54 | 55 | ```js 56 | import Vue from "vue"; 57 | import Vue3TouchEvents from "vue3-touch-events"; 58 | 59 | Vue.use(Vue3TouchEvents); 60 | ``` 61 | 62 | ### TypeScript Vue 3.4+ 63 | 64 | You need to register this plugin with your vue.js app: 65 | 66 | ```js 67 | import Vue3TouchEvents, { 68 | type Vue3TouchEventsOptions, 69 | } from "vue3-touch-events"; 70 | 71 | app.use(Vue3TouchEvents, { 72 | disableClick: false 73 | // any other global options... 74 | }) 75 | ``` 76 | 77 | Global options that can be added are [listed here](#global-configuration) and listed in each [feature section](#touch-and-tap). 78 | 79 | 80 | ### Nuxt 3+ 81 | 82 | Add the following to a file in plugins directory: 83 | ```ts 84 | import Vue3TouchEvents from 'vue3-touch-events'; 85 | 86 | export default defineNuxtPlugin((nuxtApp) => { 87 | nuxtApp.vueApp.use(Vue3TouchEvents) 88 | }); 89 | ``` 90 | Add `v-touch` events to any Nuxt component: (this is sample code only) 91 | 92 | ```ts 93 |
94 | ``` 95 | 96 | 97 | ### JavaScript 98 | 99 | You need to include the [UMD script](https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/master/index.js) of this plugin and you do not need to register this plugin with vue.js. 100 | 101 | ```html 102 | 103 | 104 | ``` 105 | 106 | 107 | 108 | ## Examples 109 | 110 | In your `.vue` component files, use the `v-touch` directive add touch events to elements. 111 | 112 | Specify the event using the first argument, for example `v-touch:tap` or `v-touch:swipe`. 113 | 114 | ```html 115 | 116 | Tap Me 117 | 118 | 119 | Tap Me 120 | 121 | 122 | Swipe Here 123 | 124 | 125 | Swipe Left Here 126 | 127 | 128 | Long Tap Event 129 | 130 | 131 | Press and Release Events 132 | 133 | 134 | Triggered once when starting to move and tapTolerance is exceeded 135 | Continuously triggered while dragging 136 | 137 | 138 | Touch and hold on the screen for a while 139 | 140 | 141 | Mix Multiple Events 142 | 143 | 144 | Different options 145 | 146 | 147 | Customize touch class 148 | 149 | Customize touch class 150 | 151 | 152 | Use multi-touch to zoom in 153 | 154 | 155 | Use multi-touch to zoom out 156 | ``` 157 | 158 | 159 | ## Usage 160 | 161 | ### Simple callback 162 | 163 | If you simply want to execute a callback function on a `v-touch` event, use this pattern: 164 | 165 | ```html 166 |
Button
167 | ``` 168 | 169 | ```js 170 | methods: { 171 | onTapItem(mouseEvent) { // you can remove the `mouseEvent` argument 172 | console.log("Tapped!"); 173 | }, 174 | }, 175 | ``` 176 | 177 | ### Passing parameters to the event handler 178 | 179 | If you want to add extra parameters to your `v-touch` event handler, you need to return a delegate in the event handler. You can pass as many attributes as you need. 180 | 181 | ```html 182 |
183 |
Button
184 |
185 | ``` 186 | 187 | ```js 188 | methods: { 189 | onSwipeItem(item, i) { 190 | return function (direction, mouseEvent) { 191 | console.log("Swiped item ", i, ": ", item, " in direction ", direction); 192 | }; 193 | }, 194 | }, 195 | ``` 196 | 197 | 198 | 199 | 200 | ## Touch and Tap 201 | 202 | ![Events](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/anim-tap.gif) 203 | 204 | These events are provided by this library. **Most of these work on Desktop & Mobile.** 205 | 206 | | Event         | Behaviour | 207 | | ---------------------------------------------------- | ------------------ | 208 | | `v-touch`
`v-touch:tap` | **Desktop:** Triggered when the user clicks on the element (press and release).
**Mobile:** Triggered when the user taps on the element (tap and release) | 209 | | `v-touch:longtap` | **Desktop:** Triggered when the user holds on the element for `longTapTimeInterval` MS and then releases it (press and release).
**Mobile:** Triggered when the user taps and holds on the element for `longTapTimeInterval` MS and then releases it (tap and release) | 210 | | `v-touch:hold` | Triggered when the user holds the mouse button down for `touchHoldTolerance` MS while over the element (press and hold).
This will be triggered before your finger is released, similar to what native mobile apps do. | 211 | | `v-touch:rollover` | **Desktop only:** Triggered when the user moves his mouse over the element.
This event is throttled to prevent too many events per second.
This event will fire every `rollOverFrequency` MS. | 212 | 213 | ### Tap Settings 214 | 215 | These settings can be optionally specified in the [Global Config](#global-configuration) or [Object Config](#v-touch-options). If they are not specified, defaults are used. 216 | 217 | | Setting | Units | Comment | 218 | |------------------------|--------------|-------------------------------------------------------------------------| 219 | | `touchHoldTolerance` | milliseconds | The timeout for a `hold` event. **Default:** `400` MS | 220 | | `longTapTimeInterval` | milliseconds | The minimum time interval to detect whether long tap event effective or not. **Default:** `400` MS | 221 | | `rollOverFrequency` | milliseconds | How often should `rollover` events be fired. **Default:** `100` MS (10 times a second) | 222 | 223 | ### My rollover events are laggy and stuttering! 224 | 225 | This is because the library only sends 10 rollover events per second by default, which can appear laggy. 226 | 227 | To increase the frequency of rollover events, and prevent any stuttering, set the `rollOverFrequency` to `10` (for 100 FPS) or `16` (for 60 FPS). 228 | 229 | You need to add this into the [Global Config](#global-configuration) or [Object Config](#v-touch-options). 230 | 231 | ### System events 232 | 233 | These are the default interactivity events supported by vue.js 3.x. 234 | 235 | * You do not need to install this library to use them. 236 | * They are always available for your usage alongside this library. 237 | * The system default `mousemove` event is similar to `v-touch:rollover`, however the system event is not throttled and it will trigger hundreds of times per second, potentially crashing your application if any logic is performed in the event handler 238 | 239 | **[Desktop devices](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent):** 240 | - `v-on:click` - Triggered when the user presses and releases the element. 241 | - `v-on:mousedown` - Triggered when the user presses the element. 242 | - `v-on:mousemove` - Triggered when the user moves the mouse over the element. 243 | - `v-on:mouseup` - Triggered when the user presses and releases the element. 244 | - `v-on:mouseenter` - Triggered when the user moves his mouse into the element. 245 | - `v-on:mouseleave` - Triggered when the user moves his mouse away from the element. 246 | 247 | **[Mobile devices](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events):** 248 | - `v-on:touchstart` - Triggered when the user presses the element. 249 | - `v-on:touchmove` - Triggered when the user presses and drags over the element. 250 | - `v-on:touchcancel` - Triggered when the user presses the element, and releases outside the element, thereby cancelling his tap. 251 | - `v-on:touchend` - Triggered when the user taps the element (press and release). 252 | 253 | 254 | 255 | 256 | ## Drag and Drop 257 | 258 | ![Events](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/anim-drag.gif) 259 | 260 | These drag-and-drop events are provided by this library. **All these work on Desktop & Mobile.** 261 | 262 | |
Event
| Behaviour | 263 | | ---------------------------------------- | ------------------ | 264 | | `v-touch:press` | **Desktop:** Triggered when the user presses the element (mouse down).
**Mobile:** Triggered when the user taps the element without releasing. | 265 | | `v-touch:drag.once` | Triggered when the user presses and drags the element.
Only fired once, the moment the user first drags on the element. | 266 | | `v-touch:drag` | Triggered when the user presses and drags the element.
Fired every time the mouse moves while dragging the element.
This event is throttled to prevent too many events per second.
This event will fire every `dragFrequency` MS. Normally only fired when the mouse is **within** the element, but you can set `dragOutside` to fire it when the mouse is dragged **outside** the element too. | 267 | | `v-touch:release` | **Desktop:** Triggered when the user releases the element (mouse up).
**Mobile:** Triggered when the user taps and releases the element. | 268 | 269 | ### Drag Settings 270 | 271 | These settings can be optionally specified in the [Global Config](#global-configuration) or [Object Config](#v-touch-options). If they are not specified, defaults are used. 272 | 273 | | Setting | Units | Comment | 274 | |------------------|--------------|------------------------------------------------------------------------| 275 | | `tapTolerance` | pixels | How many pixels the user must drag on the element for it to register as a `tap` event. **Default:** `10` pixels. | 276 | | `dragFrequency` | milliseconds | How often should `drag` events be fired. **Default:** `10` MS (100 times a second). | 277 | | `dragOutside` | boolean | If the `drag` event should be fired when the mouse is dragged outside the object as well. Useful to implement drag-and-drop behaviour when the object being moved is the same element you have added `v-touch` events on. **Default:** `false` | 278 | 279 | ### My drag events are laggy and stuttering! 280 | 281 | This is because the library only sends 10 drag events per second by default, which can appear laggy. 282 | 283 | To increase the frequency of drag events, and prevent any stuttering, set the `dragFrequency` to `10` (for 100 FPS) or `16` (for 60 FPS). 284 | 285 | You need to add this into the [Global Config](#global-configuration) or [Object Config](#v-touch-options). 286 | 287 | 288 | 289 | 290 | ## Swipe 291 | 292 | These swiping events are provided by this library. **All these work on Desktop & Mobile.** 293 | 294 | | Gesture | Event | Behaviour | 295 | | ----------- | -------- | ------------------ | 296 | | ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0005.png) | `v-touch:swipe` | Triggered when the user drags on the element (swipe).
It will detect the direction of the swipe and send it to your callback.
First argument of the callback must be `direction` attribute, which can be `left`, `right`, `top` or `bottom`.
Example callback: `onSwipe(direction){ ... }` | 297 | | ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0004.png) | `v-touch:swipe.left` | Triggered when the user drags on the element within the left cone. | 298 | | ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0003.png) | `v-touch:swipe.right` | Triggered when the user drags on the element within the right cone. | 299 | | ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0001.png) | `v-touch:swipe.top` | Triggered when the user drags on the element within the top cone. | 300 | |![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0002.png) | `v-touch:swipe.bottom` | Triggered when the user drags on the element within the bottom cone. | 301 | 302 | ### Swipe Settings 303 | 304 | These settings can be optionally specified in the [Global Config](#global-configuration) or [Object Config](#v-touch-options). If they are not specified, defaults are used. 305 | 306 | | Setting | Units | Comment | 307 | |------------------|--------------|------------------------------------------------------------------------| 308 | | `swipeTolerance` | pixels | How many pixels the user must drag on the element for it to register as a `swipe` event. **Default:** `100` pixels. | 309 | | `swipeConeSize` | number (0-1) | How wide should the "swipe cone" be. The wider the cone, the more off-axis gestures are considered as swipes. **Default:** `0.75` | 310 | 311 | 312 | ![Cone](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/swipe-cone.png) 313 | 314 | 315 | 316 | 317 | 318 | ## Zoom 319 | 320 | These zooming events are provided by this library. **These are mobile-only as they require multi-touch (at least 2 fingers) to trigger.** 321 | 322 | | Gesture | Event | Behaviour | 323 | | ----------- | -------- | ------------------ | 324 | | ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0008.png) | `v-touch:zoom` | **Mobile only:** Triggered when the user presses 2 fingers down and moves them inward or outward. This event is continuously fired as the user is zooming.
First argument of the callback will recieve the zoom factor.
Example callback: `onZoom(zoomFactor){ ... }` | 325 | | ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0006.png) | `v-touch:zoom.in` | **Mobile only:** Triggered when the user presses 2 fingers down and moves them towards each other (the normal "zoom in" gesture) | 326 | | ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0007.png) | `v-touch:zoom.out` | **Mobile only:** Triggered when the user presses 2 fingers down and moves them away from each other (the normal "zoom out" gesture) | 327 | 328 | ### Zoom Settings 329 | 330 | These settings can be optionally specified in the [Global Config](#global-configuration) or [Object Config](#v-touch-options). If they are not specified, defaults are used. 331 | 332 | | Setting | Units | Comment | 333 | |------------------|--------------|------------------------------------------------------------------------| 334 | | `zoomFrequency` | milliseconds | How often should `zoom` / `zoom.in` / `zoom.out` events be fired. **Default:** `10` MS (100 times a second). | 335 | | `zoomDistance` | pixels | How many pixels should the user move their fingers to trigger a `zoom` event. **Default:** `10` | 336 | | `zoomInOutDistance` | pixels | How many pixels should the user move their fingers to trigger a `zoom.in` or `zoom.out` event. **Default:** `100` | 337 | 338 | 339 | 340 | 341 | 342 | ## Options 343 | 344 | These additional directives can be added to each element. 345 | 346 | ### v-touch-options 347 | 348 | `v-touch-options` directive allows you set a different configuration for a specified component. It will override global configurations. 349 | 350 | ### v-touch-class 351 | 352 | `v-touch-class` directive allows you automatically add a class when the element is rolled over (desktop) or tapped (mobile). It overrides the class specified in the global config option `touchClass`. 353 | 354 | - By default the `touchClass` is added when the element is pressed (`mousedown`), and removed when the element is released (`mouseup`). 355 | - If desktop events are enabled (`disableClick: false`), then the `touchClass` is added on roll over (`mouseenter`) and roll out (`mouseleave`) as well. 356 | - You can use this instead of `:active` and `:hover` pseudo classes, as it works on both desktop and mobile 357 | 358 | Behaviour: 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 |
DeviceEvent nameEffectCondition
Desktop onlyMouse enter (roll over)`touchClass` addeddesktop events must be enabled
Mouse leave (roll out)`touchClass` removeddesktop events must be enabled
Mobile onlyMouse down (press)`touchClass` added
Mouse up (release)`touchClass` removed
390 | 391 | For example: 392 | 393 | ```html 394 | Tap Me 395 | ``` 396 | 397 | Now, when you press the element, it will add an extra `active` class automatically, and when you release the element the class will be removed. 398 | 399 | So that you can use this feature to instead of `:active` and `:hover` pseudo class, for a better user experience. 400 | 401 | ```css 402 | /* before */ 403 | span:active, 404 | span:hover { 405 | background: green; 406 | } 407 | 408 | /* now, you can write like this */ 409 | span.active { 410 | background: green; 411 | } 412 | ``` 413 | 414 | 415 | ## Global configuration 416 | 417 | ```js 418 | Vue.use(Vue3TouchEvents, { 419 | disableClick: false, ... 420 | }); 421 | ``` 422 | 423 | General settings: 424 | 425 | | Name | Units | Comment | 426 | |----------------|----------|-------------------------------------------------------------------------------------------------------| 427 | | `disableClick` | boolean | Whether to disable desktop events. **Default:** `false`.
Keep the default value or `false` if your application is used on desktop and mobile devices.
If your application is only for mobile use, set this to `true` to get a better user experience, because it can resolve some touch pass-through issues encountered on mobile devices. | 428 | | `touchClass` | string | Which CSS class to add while an element is rolled over (desktop) or tapped (mobile). **Default:** `''`.
This is a global config, and you can use `v-touch-class` directive to override this setting for a single element. | 429 | | `namespace` | string | Allows you to customize which Vue namespace this plugin uses. The default namespace is `touch` which adds the Vue directives: `touch`, `touch-class` and `touch-options`. Changing it to another value, for example `yolo`, would add the Vue directives: `yolo`, `yolo-class` and `yolo-options`. | 430 | 431 | 432 | 433 | ## Migration from Vue 2.x 434 | 435 | Some events have been renamed from the vue 2.x version of this library, in order to expose a cleaner, more consistant and more descriptive naming scheme. 436 | 437 | | Old event name | New event name | 438 | | ---------------------------- | ------------------- | 439 | | `v-touch:touchhold` | `v-touch:hold` | 440 | | `v-touch:start` | `v-touch:press` | 441 | | `v-touch:end` | `v-touch:release` | 442 | | `v-touch:moved` | `v-touch:drag.once` | 443 | | `v-touch:moving` | `v-touch:drag` | 444 | 445 | 446 | 447 | ## Building from Source 448 | 449 | 1. First install `bun` on your system. Node is not required. 450 | 2. Run these commands: 451 | 452 | ``` 453 | bun install 454 | bun run build 455 | ``` 456 | 457 | ## Credits 458 | 459 | - Credits go to Jerry Bendy for creating the original project [vue2-touch-events](https://github.com/jerrybendy/vue-touch-events) 460 | - Special thanks to Xavier Julien for creating the [Vue 3.x port](https://github.com/XjulI1/vue-touch-events/tree/migrate-to-vuejs-3) 461 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | ### Version 5.0.13 2 | 3 | - Fix issue where `rollover` events were not working if `dragOutside` was enabled 4 | 5 | ### Version 5.0.12 6 | 7 | - Fix issue where swipe events were not working on mobile devices 8 | 9 | ### Version 5.0.11 10 | 11 | - Converted the main source file from JavaScript into TypeScript 12 | - Fixed issue with Vue not recognizing the TS type definitions 13 | - Added TSC into the build toolchain to build the `index.d.ts` type definition file 14 | 15 | ### Version 5.0.10 16 | 17 | - Rewrote the event handling system to track events on a per-object basis. 18 | - Added a new dragging option `dragOutside` which allows for drag events to be sent when the mouse is outside the object as well. 19 | - Fixed all issues arising with the implementation of `dragOutside` event handling. 20 | - Docs: Greatly improved the documentation for swipe, zoom and split it feature-wise 21 | - Docs: Added more images and animations into the docs 22 | 23 | ### Version 5.0.0 24 | 25 | - Changed the default value of `dragFrequency` to `10 MS` (previously was `100 MS`), meaning that drag events will be fired at 100 FPS by default rather than only at 10 FPS. 26 | - Changed the swipe detection to use a cone rather than using blocks, allowing for more variance in swipe gesture direction. 27 | - Added swipe setting `swipeConeSize` to control the cone size. 28 | - Added zoom events `zoom`, `zoom.in` and `zoom.out` using multi-touch gesture tracking. 29 | - Added zoom options `zoomFrequency`,`zoomDistance`,`zoomInOutDistance` to customize the zoom behaviour. 30 | 31 | ### Version 4.1.8 32 | 33 | - Added null check to the timer clear event 34 | 35 | ### Version 4.1.7 36 | 37 | - Remove the touch-hold timer when unmounted 38 | 39 | ### Version 4.1.6 40 | 41 | - Minor change to the swipe detection algorithm 42 | 43 | ### Version 4.1.5 44 | 45 | - Add some missing options into the typescript definition 46 | 47 | ### Version 4.1.4 48 | 49 | - Fix types and module declaration to work with Vue 3 50 | 51 | ### Version 4.1.2 52 | 53 | - Fix module exports to work with Vite 54 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/bun.lockb -------------------------------------------------------------------------------- /images/anim-all.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/anim-all.gif -------------------------------------------------------------------------------- /images/anim-drag.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/anim-drag.gif -------------------------------------------------------------------------------- /images/anim-tap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/anim-tap.gif -------------------------------------------------------------------------------- /images/gestures0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/gestures0001.png -------------------------------------------------------------------------------- /images/gestures0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/gestures0002.png -------------------------------------------------------------------------------- /images/gestures0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/gestures0003.png -------------------------------------------------------------------------------- /images/gestures0004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/gestures0004.png -------------------------------------------------------------------------------- /images/gestures0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/gestures0005.png -------------------------------------------------------------------------------- /images/gestures0006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/gestures0006.png -------------------------------------------------------------------------------- /images/gestures0007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/gestures0007.png -------------------------------------------------------------------------------- /images/gestures0008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/gestures0008.png -------------------------------------------------------------------------------- /images/swipe-cone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinrodricks/vue3-touch-events/14bd907721ae9556c22c0139e715f5a0bcc9b1bf/images/swipe-cone.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-touch-events", 3 | "version": "5.0.13", 4 | "description": "Simple touch events support for vue.js 3", 5 | "type": "module", 6 | "main": "dist/index.js", 7 | "module": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "files": [ 10 | "dist" 11 | ], 12 | "scripts": { 13 | "build": "bun build ./src/index.ts --outdir ./dist && bunx tsc", 14 | "dev": "bun build ./src/index.ts --outdir ./dist --watch & bunx tsc --watch", 15 | "test": "bun test" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/robinrodricks/vue3-touch-events.git" 20 | }, 21 | "keywords": [ 22 | "vue", 23 | "touch", 24 | "tap", 25 | "swipe", 26 | "longtap" 27 | ], 28 | "author": "Robin Rodricks", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/robinrodricks/vue3-touch-events/issues" 32 | }, 33 | "homepage": "https://github.com/robinrodricks/vue3-touch-events#readme", 34 | "devDependencies": { 35 | "bun-types": "latest", 36 | "typescript": "^5.7.3" 37 | }, 38 | "peerDependencies": { 39 | "vue": "^3.0.0" 40 | } 41 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @project vue3-touch-events 3 | * @author Robin Rodricks, Xavier Julien, Jerry Bendy 4 | * @since 30/4/2021 5 | * @url https://github.com/robinrodricks/vue3-touch-events 6 | */ 7 | 8 | // Types for Vue and DOM events 9 | import { App, Plugin, Directive, DirectiveBinding } from 'vue' 10 | 11 | // Interface for touch event options 12 | export interface Vue3TouchEventsOptions { 13 | 14 | // CORE 15 | touchClass?: string, 16 | namespace?: string, 17 | 18 | // CLICK/TAP 19 | disableClick?: boolean, 20 | tapTolerance?: number, 21 | touchHoldTolerance?: number, 22 | longTapTimeInterval?: number, 23 | rollOverFrequency?: number, 24 | 25 | // DRAG 26 | dragFrequency?: number, 27 | dragOutside?: boolean, 28 | 29 | // SWIPE 30 | swipeTolerance?: number, 31 | swipeConeSize?: number, 32 | 33 | // ZOOM 34 | zoomFrequency?: number, 35 | zoomDistance?: number, 36 | zoomInOutDistance?: number, 37 | } 38 | 39 | // Interface for touch object internal state 40 | interface TouchObject { 41 | element: HTMLElement 42 | callbacks: { [key: string]: DirectiveBinding[] } 43 | hasBindTouchEvents: boolean 44 | options: Vue3TouchEventsOptions 45 | events: { [key: string]: [EventTarget, EventListener] } 46 | touchStarted?: boolean 47 | touchMoved?: boolean 48 | swipeOutBounded?: boolean 49 | isZooming?: boolean 50 | startX?: number 51 | startY?: number 52 | currentX?: number 53 | currentY?: number 54 | touchStartTime?: number 55 | lastTouchStartTime?: number 56 | lastTouchEndTime?: number 57 | touchHoldTimer?: ReturnType | null 58 | touchRollTime?: number 59 | touchDragTime?: number 60 | touchZoomTime?: number 61 | initialZoomDistance?: number 62 | hasSwipe?: boolean 63 | hasZoom?: boolean 64 | } 65 | 66 | // Helper function to get X coordinate from touch/mouse event 67 | function touchX(event: MouseEvent | TouchEvent): number { 68 | if (event.type.indexOf('mouse') !== -1) { 69 | return (event as MouseEvent).clientX; 70 | } 71 | return (event as TouchEvent).touches?.[0]?.clientX ?? 0; 72 | } 73 | 74 | // Helper function to get Y coordinate from touch/mouse event 75 | function touchY(event: MouseEvent | TouchEvent): number { 76 | if (event.type.indexOf('mouse') !== -1) { 77 | return (event as MouseEvent).clientY; 78 | } 79 | return (event as TouchEvent).touches?.[0]?.clientY ?? 0; 80 | } 81 | 82 | // Check for passive event listener support 83 | const isPassiveSupported = (function (): boolean { 84 | let supportsPassive = false; 85 | try { 86 | const opts = Object.defineProperty({}, 'passive', { 87 | get: function () { 88 | supportsPassive = true; 89 | return true; 90 | } 91 | }); 92 | window.addEventListener('test', null as any, opts); 93 | } catch (e) { } 94 | return supportsPassive; 95 | })(); 96 | 97 | // Main Vue Touch Events plugin 98 | const Vue3TouchEvents: Plugin> = { 99 | install(app: App, constructorOptions?: Partial) { 100 | 101 | // Default global options merged with constructor options 102 | var globalOptions: Vue3TouchEventsOptions = Object.assign({}, { 103 | 104 | // CORE 105 | touchClass: '', 106 | namespace: 'touch', 107 | 108 | // CLICK/TAP 109 | disableClick: false, 110 | tapTolerance: 10, // px 111 | touchHoldTolerance: 400, // ms 112 | longTapTimeInterval: 400, // ms 113 | rollOverFrequency: 100, // ms 114 | 115 | // DRAG 116 | dragFrequency: 10, // ms 117 | dragOutside: false, 118 | 119 | // SWIPE 120 | swipeTolerance: 100, // px 121 | swipeConeSize: 0.75, // number between 0 to 1 122 | 123 | // ZOOM 124 | zoomFrequency: 10, // ms 125 | zoomDistance: 10, // px 126 | zoomInOutDistance: 100, // px 127 | 128 | // NOTE: When adding props here, update `index.d.ts` as well!! 129 | 130 | 131 | }, constructorOptions); 132 | 133 | /** Fired when the user performs a MOUSE DOWN on the object */ 134 | function touchStartEvent(event: MouseEvent | TouchEvent) { 135 | var $this = this.$$touchObj, 136 | isTouchEvent = event.type.indexOf('touch') >= 0, 137 | isMouseEvent = event.type.indexOf('mouse') >= 0, 138 | $el = this; 139 | 140 | if (isTouchEvent) { 141 | $this.lastTouchStartTime = event.timeStamp; 142 | } 143 | 144 | if (isMouseEvent && $this.lastTouchStartTime && event.timeStamp - $this.lastTouchStartTime < 350) { 145 | return; 146 | } 147 | 148 | if ($this.touchStarted) { 149 | return; 150 | } 151 | 152 | addTouchClass(this); 153 | 154 | $this.touchStarted = true; // always true while the element is being PRESSED 155 | 156 | $this.touchMoved = false; // true only when the element is PRESSED and DRAGGED a bit 157 | $this.swipeOutBounded = false; 158 | $this.isZooming = false; 159 | 160 | $this.startX = touchX(event); 161 | $this.startY = touchY(event); 162 | 163 | $this.currentX = 0; // always updated with the last mouse X/Y while over the element 164 | $this.currentY = 0; 165 | 166 | $this.touchStartTime = event.timeStamp; 167 | 168 | // performance: only process swipe events if `swipe.*` event is registered on this element 169 | $this.hasSwipe = hasEvent($this, 'swipe') 170 | || hasEvent($this, 'swipe.left') || hasEvent($this, 'swipe.right') 171 | || hasEvent($this, 'swipe.top') || hasEvent($this, 'swipe.bottom'); 172 | 173 | // performance: only process zoom events if `zoom.*` event is registered on this element 174 | $this.hasZoom = hasEvent($this, 'zoom') || hasEvent($this, 'zoom.in') || hasEvent($this, 'zoom.out'); 175 | 176 | // performance: only start hold timer if the `hold` event is registered on this element 177 | if (hasEvent($this, 'hold')) { 178 | 179 | // Trigger touchhold event after `touchHoldTolerance` MS 180 | $this.touchHoldTimer = setTimeout(function () { 181 | $this.touchHoldTimer = null; 182 | triggerEvent(event, $el, 'hold'); 183 | }, $this.options.touchHoldTolerance); 184 | } 185 | 186 | triggerEvent(event, this, 'press'); 187 | } 188 | 189 | /** 190 | Fired when the user MOVES the mouse over the window. 191 | */ 192 | function touchMoveEventWindow(event: MouseEvent | TouchEvent) { 193 | 194 | // only process if pressed 195 | var $this = this.$$touchObj as TouchObject; 196 | if ($this.touchStarted == true) { 197 | 198 | // process event and pass 'this' onward 199 | touchMoveEvent(event, $this, false); 200 | 201 | } else { 202 | 203 | // TODO: mobile support 204 | // if rollover events are wanted and this is a mouse event 205 | if (hasEvent($this, 'rollover') && event.clientX != null) { 206 | var mouseEvent: MouseEvent = event as MouseEvent; 207 | 208 | // if the mouse is actually over this object (within this obj's bounds) 209 | const rect = $this.element.getBoundingClientRect(); 210 | if (mouseEvent.clientX >= rect.left && mouseEvent.clientX <= rect.right && 211 | mouseEvent.clientY >= rect.top && mouseEvent.clientY <= rect.bottom) { 212 | 213 | // process rollovers only and pass 'this' onward 214 | touchMoveEvent(event, $this, true); 215 | } 216 | } 217 | } 218 | } 219 | 220 | /** 221 | Fired when the user DRAGS the object or MOVES the mouse over the object. 222 | */ 223 | function touchMoveEvent(event: MouseEvent | TouchEvent, $this: TouchObject = null, onlyProcessRollover: boolean = false) { 224 | 225 | if ($this == null) $this = this.$$touchObj as TouchObject; 226 | 227 | var curX = touchX(event); 228 | var curY = touchY(event); 229 | 230 | var movedAgain = ($this.currentX != curX) || ($this.currentY != curY); 231 | 232 | $this.currentX = curX; 233 | $this.currentY = curY; 234 | 235 | // dont process if only rollover wanted! 236 | if (!onlyProcessRollover) { 237 | 238 | //-------------------------------------------------------------------------------------- 239 | // DRAG ONCE 240 | //-------------------------------------------------------------------------------------- 241 | if (!$this.touchMoved) { 242 | var tapTolerance = $this.options.tapTolerance; 243 | 244 | $this.touchMoved = Math.abs($this.startX - $this.currentX) > tapTolerance || 245 | Math.abs($this.startY - $this.currentY) > tapTolerance; 246 | 247 | // trigger `drag.once` only once after mouse FIRST moved while dragging the element 248 | // (`touchMoved` is the flag that indicates we no longer need to trigger this) 249 | if ($this.touchMoved) { 250 | cancelTouchHoldTimer($this); 251 | triggerEvent(event, $this.element, 'drag.once'); 252 | } 253 | 254 | } 255 | 256 | //-------------------------------------------------------------------------------------- 257 | // SWIPE 258 | //-------------------------------------------------------------------------------------- 259 | // performance: only process swipe events if `swipe.*` event is registered on this element 260 | else if ($this.hasSwipe && !$this.swipeOutBounded) { 261 | var swipeOutBounded = $this.options.swipeTolerance; 262 | 263 | // Process swipe events using cones 264 | if (Math.abs($this.startX - $this.currentX) / Math.abs($this.startY - $this.currentY) > $this.options.swipeConeSize && 265 | Math.abs($this.startY - $this.currentY) / Math.abs($this.startX - $this.currentX) > $this.options.swipeConeSize) { 266 | $this.swipeOutBounded = (Math.abs($this.startY - $this.currentY) < swipeOutBounded) && (Math.abs($this.startX - $this.currentX) < swipeOutBounded) 267 | } 268 | } 269 | } 270 | 271 | //-------------------------------------------------------------------------------------- 272 | // ROLL OVER 273 | //-------------------------------------------------------------------------------------- 274 | // only trigger `rollover` event if cursor actually moved over this element 275 | if (movedAgain && hasEvent($this, 'rollover')) { 276 | 277 | // throttle the `rollover` event based on `rollOverFrequency` 278 | var now = event.timeStamp; 279 | if ($this.touchRollTime == null || now > ($this.touchRollTime + $this.options.rollOverFrequency)) { 280 | $this.touchRollTime = now; 281 | 282 | triggerEvent(event, $this.element, 'rollover'); 283 | } 284 | } 285 | 286 | // exit if only rollovers wanted! 287 | if (onlyProcessRollover) { 288 | return; 289 | } 290 | 291 | //-------------------------------------------------------------------------------------- 292 | // DRAG 293 | //-------------------------------------------------------------------------------------- 294 | // only trigger `drag` event if cursor actually moved and if we are still dragging this element 295 | if ($this.touchStarted && $this.touchMoved && movedAgain && hasEvent($this, 'drag')) { 296 | 297 | // throttle the `drag` event based on `dragFrequency` 298 | var now = event.timeStamp; 299 | if ($this.touchDragTime == null || now > ($this.touchDragTime + $this.options.dragFrequency)) { 300 | $this.touchDragTime = now; 301 | 302 | triggerEvent(event, $this.element, 'drag'); 303 | } 304 | } 305 | 306 | //-------------------------------------------------------------------------------------- 307 | // ZOOM 308 | //-------------------------------------------------------------------------------------- 309 | // only trigger `zoom` event if cursor actually moved 310 | if ($this.touchStarted && $this.hasZoom) { 311 | 312 | // throttle the `zoom` event based on `zoomFrequency` 313 | var now = event.timeStamp; 314 | if ($this.touchZoomTime == null || now > ($this.touchZoomTime + $this.options.zoomFrequency)) { 315 | $this.touchZoomTime = now; 316 | 317 | checkZoom($this, event); 318 | } 319 | } 320 | } 321 | 322 | function checkZoom($this, event) { 323 | 324 | 325 | // get the list of changed touches from the event 326 | const touches = event.changedTouches; 327 | 328 | // check if exactly two fingers are being used 329 | if (touches.length !== 2) { 330 | // reset dragging state if fewer or more than 2 touches are detected 331 | $this.isZooming = false; 332 | return; 333 | } 334 | 335 | // calculate the distance between the two touch points (euclidean distance) 336 | const newDistance = Math.sqrt( 337 | Math.pow(touches[0].clientX - touches[1].clientX, 2) + // horizontal distance 338 | Math.pow(touches[0].clientY - touches[1].clientY, 2) // vertical distance 339 | ); 340 | 341 | // initialize the gesture if it's not already active 342 | if (!$this.isZooming) { 343 | // mark the gesture as active and store the initial distance 344 | $this.isZooming = true; 345 | $this.initialZoomDistance = newDistance; 346 | return; 347 | } 348 | 349 | // calculate the zoom factor based on the change in distance 350 | const zoomFactor = newDistance / $this.initialZoomDistance; 351 | 352 | 353 | //-------------------------------------------------------------------------------------- 354 | // ZOOM 355 | //-------------------------------------------------------------------------------------- 356 | if (hasEvent($this, 'zoom')) { 357 | 358 | // check if the zoom factor exceeds the threshold for zooming 359 | if (Math.abs(zoomFactor - 1) > ($this.options.zoomDistance / $this.initialZoomDistance)) { 360 | 361 | // trigger the zoom callback with the source and zoom factor 362 | triggerEvent(event, $this.element, 'zoom', zoomFactor); 363 | } 364 | } 365 | 366 | //-------------------------------------------------------------------------------------- 367 | // ZOOM IN/OUT 368 | //-------------------------------------------------------------------------------------- 369 | if (hasEvent($this, 'zoom.in') || hasEvent($this, 'zoom.out')) { 370 | 371 | // check if the distance change is significant enough to count as a zoom gesture 372 | if (Math.abs(newDistance - $this.initialZoomDistance) > $this.options.zoomInOutDistance) { 373 | 374 | // determine zoom direction 375 | if (newDistance > $this.initialZoomDistance) { 376 | // fingers moved apart = zoom in 377 | triggerEvent(event, $this.element, 'zoom.in'); 378 | } else { 379 | // fingers moved closer = zoom out 380 | triggerEvent(event, $this.element, 'zoom.out'); 381 | } 382 | } 383 | } 384 | 385 | 386 | // reset the dragging state after detecting the pinch gesture 387 | $this.isZooming = false; 388 | } 389 | 390 | function touchCancelEvent() { 391 | var $this = this.$$touchObj; 392 | if ($this.touchStarted == true) { 393 | 394 | cancelTouchHoldTimer($this); 395 | removeTouchClass(this); 396 | 397 | $this.touchStarted = $this.touchMoved = false; 398 | $this.startX = $this.startY = 0; 399 | } 400 | } 401 | 402 | /** Fired when the user performs a MOUSE UP on the object (releases the mouse button or finger press) */ 403 | function touchEndEvent(event: MouseEvent | TouchEvent) { 404 | var $this = this.$$touchObj; 405 | if ($this.touchStarted == true) { 406 | 407 | var isTouchEvent = event.type.indexOf('touch') >= 0; 408 | var isMouseEvent = event.type.indexOf('mouse') >= 0; 409 | 410 | if (isTouchEvent) { 411 | $this.lastTouchEndTime = event.timeStamp; 412 | } 413 | 414 | var touchholdEnd = isTouchEvent && !$this.touchHoldTimer; 415 | cancelTouchHoldTimer($this); 416 | 417 | $this.touchStarted = false; 418 | 419 | removeTouchClass(this); 420 | 421 | if (isMouseEvent && $this.lastTouchEndTime && event.timeStamp - $this.lastTouchEndTime < 350) { 422 | return; 423 | } 424 | 425 | //-------------------------------------------------------------------------------------- 426 | // RELEASE 427 | //-------------------------------------------------------------------------------------- 428 | 429 | // trigger `end` event when touch stopped 430 | triggerEvent(event, this, 'release'); 431 | 432 | 433 | //-------------------------------------------------------------------------------------- 434 | // LONGTAP / HOLD / TAP 435 | //-------------------------------------------------------------------------------------- 436 | 437 | if (!$this.touchMoved) { 438 | // detect if this is a longTap event or not 439 | if (hasEvent($this, 'longtap') && event.timeStamp - $this.touchStartTime > $this.options.longTapTimeInterval) { 440 | if (event.cancelable) { 441 | event.preventDefault(); 442 | } 443 | triggerEvent(event, this, 'longtap'); 444 | 445 | } else if (hasEvent($this, 'hold') && touchholdEnd) { 446 | if (event.cancelable) { 447 | event.preventDefault(); 448 | } 449 | return; 450 | } else { 451 | // emit tap event 452 | triggerEvent(event, this, 'tap'); 453 | } 454 | 455 | } 456 | 457 | //-------------------------------------------------------------------------------------- 458 | // SWIPE 459 | //-------------------------------------------------------------------------------------- 460 | // only process swipe events if `swipe.*` event is registered on this element 461 | else if ($this.hasSwipe && !$this.swipeOutBounded) { 462 | var swipeOutBounded = $this.options.swipeTolerance, 463 | direction, 464 | distanceY = Math.abs($this.startY - $this.currentY), 465 | distanceX = Math.abs($this.startX - $this.currentX); 466 | 467 | if (distanceY > swipeOutBounded || distanceX > swipeOutBounded) { 468 | 469 | // Check which swipe direction it is based on the mouse movement 470 | if (distanceX > swipeOutBounded) { 471 | direction = $this.startX > $this.currentX ? 'left' : 'right'; 472 | } else { 473 | direction = $this.startY > $this.currentY ? 'top' : 'bottom'; 474 | } 475 | 476 | // Only emit the specified event when it has modifiers 477 | if (hasEvent($this, 'swipe.' + direction)) { 478 | triggerEvent(event, this, 'swipe.' + direction, direction); 479 | } else { 480 | // Emit a common event when it has no any modifier 481 | triggerEvent(event, this, 'swipe', direction); 482 | } 483 | } 484 | } 485 | } 486 | } 487 | 488 | function mouseEnterEvent() { 489 | addTouchClass(this); 490 | } 491 | 492 | function mouseLeaveEvent() { 493 | removeTouchClass(this); 494 | } 495 | 496 | function hasEvent($this: TouchObject, eventType: string): boolean { 497 | var callbacks = $this.callbacks[eventType]; 498 | return (callbacks != null && callbacks.length > 0); 499 | } 500 | 501 | function triggerEvent(e: Event, $el: HTMLElement, eventType: string, param?: any) { 502 | var $this = $el.$$touchObj as TouchObject; 503 | 504 | // get the subscribers for this event 505 | var callbacks = $this.callbacks[eventType]; 506 | 507 | // exit if no subscribers to this particular event 508 | if (callbacks == null || callbacks.length === 0) { 509 | return null; 510 | } 511 | 512 | // per callback 513 | for (var i = 0; i < callbacks.length; i++) { 514 | var binding = callbacks[i]; 515 | 516 | if (binding.modifiers.stop) { 517 | e.stopPropagation(); 518 | } 519 | 520 | if (binding.modifiers.prevent) { 521 | e.preventDefault(); 522 | } 523 | 524 | // handle `self` modifier` 525 | if (binding.modifiers.self && e.target !== e.currentTarget) { 526 | continue; 527 | } 528 | 529 | if (typeof binding.value === 'function') { 530 | if (param) { 531 | binding.value(param, e); 532 | } else { 533 | binding.value(e); 534 | } 535 | } 536 | } 537 | } 538 | 539 | function addTouchClass($el: HTMLElement) { 540 | var className = ($el.$$touchObj as TouchObject).options.touchClass; 541 | className && $el.classList.add(className); 542 | } 543 | 544 | function removeTouchClass($el: HTMLElement) { 545 | var className = ($el.$$touchObj as TouchObject).options.touchClass; 546 | className && $el.classList.remove(className); 547 | } 548 | 549 | function cancelTouchHoldTimer($this) { 550 | if ($this && $this.touchHoldTimer) { 551 | clearTimeout($this.touchHoldTimer); 552 | $this.touchHoldTimer = null; 553 | } 554 | } 555 | 556 | function buildTouchObj($el: HTMLElement, extraOptions: any) { 557 | var touchObj = ($el.$$touchObj as TouchObject) || { 558 | element: $el, 559 | // an object contains all callbacks registered, 560 | // key is event name, value is an array 561 | callbacks: {}, 562 | // prevent bind twice, set to true when event bound 563 | hasBindTouchEvents: false, 564 | // default options, would be override by v-touch-options 565 | options: globalOptions, 566 | events: {}, 567 | }; 568 | if (extraOptions) { 569 | touchObj.options = Object.assign({}, touchObj.options, extraOptions); 570 | } 571 | $el.$$touchObj = touchObj; 572 | return $el.$$touchObj; 573 | } 574 | 575 | function addEvents(events: any) { 576 | for (const eventName in events) { 577 | if (events.hasOwnProperty(eventName)) { 578 | const [target, handler] = events[eventName]; 579 | target.addEventListener(eventName, handler); 580 | } 581 | } 582 | } 583 | 584 | function removeEvents(events: any) { 585 | for (const eventName in events) { 586 | if (events.hasOwnProperty(eventName)) { 587 | const [target, handler] = events[eventName]; 588 | target.removeEventListener(eventName, handler); 589 | } 590 | } 591 | } 592 | 593 | app.directive(globalOptions.namespace, { 594 | beforeMount: function ($el: HTMLElement, binding: DirectiveBinding) { 595 | // build a touch configuration object 596 | var $this = buildTouchObj($el); 597 | // declare passive option for the event listener. Defaults to { passive: true } if supported 598 | var passiveOpt = isPassiveSupported ? { passive: true } : false; 599 | // register callback 600 | var eventType = binding.arg || 'tap'; 601 | switch (eventType) { 602 | case 'swipe': 603 | var _m = binding.modifiers; 604 | if (_m.left || _m.right || _m.top || _m.bottom) { 605 | for (var i in binding.modifiers) { 606 | if (['left', 'right', 'top', 'bottom'].indexOf(i) >= 0) { 607 | var _e = 'swipe.' + i; 608 | $this.callbacks[_e] = $this.callbacks[_e] || []; 609 | $this.callbacks[_e].push(binding); 610 | } 611 | } 612 | } else { 613 | $this.callbacks.swipe = $this.callbacks.swipe || []; 614 | $this.callbacks.swipe.push(binding); 615 | } 616 | break; 617 | 618 | case 'press': 619 | case 'drag': 620 | if (binding.modifiers.disablePassive) { 621 | // change the passive option for the `drag` event if disablePassive modifier exists 622 | passiveOpt = false; 623 | } 624 | default: 625 | $this.callbacks[eventType] = $this.callbacks[eventType] || []; 626 | $this.callbacks[eventType].push(binding); 627 | } 628 | 629 | // prevent bind twice 630 | if ($this.hasBindTouchEvents) { 631 | return; 632 | } 633 | 634 | // ADD MOBILE EVENTS 635 | if ($this.options.dragOutside) { 636 | $this.events['touchstart'] = [$el, touchStartEvent]; 637 | $this.events['touchmove'] = [window, touchMoveEventWindow.bind($el)]; 638 | $this.events['touchcancel'] = [window, touchCancelEvent.bind($el)]; 639 | $this.events['touchend'] = [window, touchEndEvent.bind($el)]; 640 | } else { 641 | $this.events['touchstart'] = [$el, touchStartEvent]; 642 | $this.events['touchmove'] = [$el, touchMoveEventWindow]; 643 | $this.events['touchcancel'] = [$el, touchCancelEvent]; 644 | $this.events['touchend'] = [$el, touchEndEvent]; 645 | } 646 | 647 | // ADD DESKTOP EVENTS 648 | if (!$this.options.disableClick) { 649 | if ($this.options.dragOutside) { 650 | $this.events['mousedown'] = [$el, touchStartEvent]; 651 | $this.events['mousemove'] = [window, touchMoveEventWindow.bind($el)]; 652 | $this.events['mouseup'] = [window, touchEndEvent.bind($el)]; 653 | $this.events['mouseenter'] = [$el, mouseEnterEvent]; 654 | $this.events['mouseleave'] = [$el, mouseLeaveEvent]; 655 | } else { 656 | $this.events['mousedown'] = [$el, touchStartEvent]; 657 | $this.events['mousemove'] = [$el, touchMoveEvent]; 658 | $this.events['mouseup'] = [$el, touchEndEvent]; 659 | $this.events['mouseenter'] = [$el, mouseEnterEvent]; 660 | $this.events['mouseleave'] = [$el, mouseLeaveEvent]; 661 | } 662 | } 663 | 664 | // register all events 665 | addEvents($this.events); 666 | 667 | // set bind mark to true 668 | $this.hasBindTouchEvents = true; 669 | }, 670 | 671 | unmounted: function ($el: HTMLElement) { 672 | var touchObj = $el.$$touchObj; 673 | 674 | cancelTouchHoldTimer(touchObj); 675 | 676 | // unregister all events 677 | if (touchObj && touchObj.events) { 678 | removeEvents(touchObj.events); 679 | touchObj.events = {}; 680 | } 681 | 682 | // remove vars 683 | delete $el.$$touchObj; 684 | } 685 | }); 686 | 687 | 688 | // Register additional directives for class 689 | app.directive(`${globalOptions.namespace}-class`, { 690 | beforeMount: function ($el, binding) { 691 | buildTouchObj($el, { 692 | touchClass: binding.value 693 | }); 694 | } 695 | }); 696 | 697 | // Register additional directives for options 698 | app.directive(`${globalOptions.namespace}-options`, { 699 | beforeMount: function ($el, binding) { 700 | buildTouchObj($el, binding.value); 701 | } 702 | }); 703 | } 704 | }; 705 | 706 | /* 707 | * Exports 708 | */ 709 | export default Vue3TouchEvents 710 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // We are only using TSC (typescript compiler) to create the `index.d.ts` file. 3 | // The main build JS `index.js` is built using bun. 4 | "compilerOptions": { 5 | "declaration": true, // tell tsc to generate .d.ts files 6 | "declarationDir": "./dist", // output .d.ts files in the dist folder 7 | "outDir": "./dist", // output compiled JS files in dist 8 | "target": "es2016", // specify JS target 9 | "module": "ESNext", // use ESNext module system 10 | "moduleResolution": "node", // resolve modules like Node 11 | "strict": false, // enable all strict type-checking options 12 | "esModuleInterop": true, // ensure compatibility with CommonJS 13 | "skipLibCheck": true, // skip type checking of all declaration files 14 | "allowJs": false // allow JS files to be included 15 | }, 16 | "include": [ 17 | "src/index.ts", 18 | ], 19 | "exclude": [ 20 | "node_modules" 21 | ] 22 | } --------------------------------------------------------------------------------