This commit is contained in:
2026-05-18 19:49:40 +02:00
commit 27fc2d2757
170 changed files with 6571 additions and 0 deletions
+386
View File
@@ -0,0 +1,386 @@
<a name="0.8.5"></a>
## [0.8.5](https://github.com/yoannmoinet/nipplejs/compare/v0.8.4...v0.8.5) (2020-01-27)
<a name="0.8.4"></a>
## [0.8.4](https://github.com/yoannmoinet/nipplejs/compare/v0.7.3...v0.8.4) (2019-11-28)
### Bug Fixes
* allow older typescript ([c164597](https://github.com/yoannmoinet/nipplejs/commit/c164597))
* better lockX and lockY mecanism ([8355fb8](https://github.com/yoannmoinet/nipplejs/commit/8355fb8)), closes [#111](https://github.com/yoannmoinet/nipplejs/issues/111)
* joystick in 'dynamic' mode gets stuck on iOS ([94bb784](https://github.com/yoannmoinet/nipplejs/commit/94bb784)), closes [#94](https://github.com/yoannmoinet/nipplejs/issues/94)
* released outside will be considered inside ([9a8a7b4](https://github.com/yoannmoinet/nipplejs/commit/9a8a7b4)), closes [#109](https://github.com/yoannmoinet/nipplejs/issues/109)
* reset direction when below the threshold ([6f41b38](https://github.com/yoannmoinet/nipplejs/commit/6f41b38)), closes [#110](https://github.com/yoannmoinet/nipplejs/issues/110)
### Features
* add an options to use with dynamic pages ([8258501](https://github.com/yoannmoinet/nipplejs/commit/8258501))
* add raw data on move for data-only usage ([c818fb2](https://github.com/yoannmoinet/nipplejs/commit/c818fb2)), closes [#54](https://github.com/yoannmoinet/nipplejs/issues/54)
* add unit vector representation to move event output ([a4496fe](https://github.com/yoannmoinet/nipplejs/commit/a4496fe))
* upgrade dev experience with webpack and es6 ([cc1e824](https://github.com/yoannmoinet/nipplejs/commit/cc1e824))
<a name="0.7.3"></a>
## [0.7.3](https://github.com/yoannmoinet/nipplejs/compare/v0.7.2...v0.7.3) (2018-12-11)
<a name="0.7.2"></a>
## [0.7.2](https://github.com/yoannmoinet/nipplejs/compare/v0.7.1...v0.7.2) (2018-12-11)
### Features
* Add TypeScript Definitions ([#85](https://github.com/yoannmoinet/nipplejs/issues/85)) ([0b1f4ae](https://github.com/yoannmoinet/nipplejs/commit/0b1f4ae)), closes [#21](https://github.com/yoannmoinet/nipplejs/issues/21)
<a name="0.7.1"></a>
## [0.7.1](https://github.com/yoannmoinet/nipplejs/compare/v0.7.0...v0.7.1) (2018-06-06)
<a name="0.7.0"></a>
# [0.7.0](https://github.com/yoannmoinet/nipplejs/compare/v0.6.8...v0.7.0) (2018-06-06)
### Bug Fixes
* register touchcancel and pointercancel events ([2c541a6](https://github.com/yoannmoinet/nipplejs/commit/2c541a6))
### Features
* **joystick:** add lockX and lockY options ([6baba97](https://github.com/yoannmoinet/nipplejs/commit/6baba97))
<a name="0.6.8"></a>
## [0.6.8](https://github.com/yoannmoinet/nipplejs/compare/v0.6.7...v0.6.8) (2017-12-19)
### Bug Fixes
* add touchcancel handling as touchend for events coming from outside ([ebc501e](https://github.com/yoannmoinet/nipplejs/commit/ebc501e)), closes [#61](https://github.com/yoannmoinet/nipplejs/issues/61) [#57](https://github.com/yoannmoinet/nipplejs/issues/57) [#33](https://github.com/yoannmoinet/nipplejs/issues/33) [#31](https://github.com/yoannmoinet/nipplejs/issues/31) [#30](https://github.com/yoannmoinet/nipplejs/issues/30)
* update manager with changing identifier ([45ec1d7](https://github.com/yoannmoinet/nipplejs/commit/45ec1d7)), closes [#64](https://github.com/yoannmoinet/nipplejs/issues/64)
### Features
* add restJoystick option ([55b6482](https://github.com/yoannmoinet/nipplejs/commit/55b6482))
<a name="0.6.7"></a>
## [0.6.7](https://github.com/yoannmoinet/nipplejs/compare/v0.6.6...v0.6.7) (2016-10-27)
### Bug Fixes
* **events:** trigger events with the correct id and not identifier ([17f0fe6](https://github.com/yoannmoinet/nipplejs/commit/17f0fe6))
* **identifier:** better identifier management ([ff44916](https://github.com/yoannmoinet/nipplejs/commit/ff44916))
* update dependencies ([73bcda9](https://github.com/yoannmoinet/nipplejs/commit/73bcda9))
<a name="0.6.6"></a>
## [0.6.6](https://github.com/yoannmoinet/nipplejs/compare/v0.6.5...v0.6.6) (2016-08-23)
### Bug Fixes
* **bin:** adapt slashes to windows if needed ([7dffd7c](https://github.com/yoannmoinet/nipplejs/commit/7dffd7c))
* **bin:** add missing space ([48f6a1f](https://github.com/yoannmoinet/nipplejs/commit/48f6a1f))
* **bin:** make the copyToGhPages script windows compatible ([11da89a](https://github.com/yoannmoinet/nipplejs/commit/11da89a))
* **dataOnly:** conserve chainability even with dataOnly ([d1eddd0](https://github.com/yoannmoinet/nipplejs/commit/d1eddd0))
<a name="0.6.5"></a>
## [0.6.5](https://github.com/yoannmoinet/nipplejs/compare/v0.6.4...v0.6.5) (2016-08-15)
### Bug Fixes
* **collection:** avoid any native touch action on the `options.zone` ([bea2c42](https://github.com/yoannmoinet/nipplejs/commit/bea2c42))
<a name="0.6.4"></a>
## [0.6.4](https://github.com/yoannmoinet/nipplejs/compare/v0.6.3...v0.6.4) (2016-07-20)
### Bug Fixes
* **nipple:** remove size of container and apply on children only ([0033342](https://github.com/yoannmoinet/nipplejs/commit/0033342)), closes [#32](https://github.com/yoannmoinet/nipplejs/issues/32)
<a name="0.6.3"></a>
## [0.6.3](https://github.com/yoannmoinet/nipplejs/compare/v0.6.2...v0.6.3) (2016-05-29)
### Bug Fixes
* **manager:** add missing closing bracket ([9b99637](https://github.com/yoannmoinet/nipplejs/commit/9b99637))
* **resize:** listen to resize via event instead of global handler ([14c28f1](https://github.com/yoannmoinet/nipplejs/commit/14c28f1))
<a name="0.6.2"></a>
## [0.6.2](https://github.com/yoannmoinet/nipplejs/compare/v0.6.1...v0.6.2) (2015-12-31)
<a name="0.6.1"></a>
## [0.6.1](https://github.com/yoannmoinet/nipplejs/compare/v0.6.0...v0.6.1) (2015-12-26)
### Bug Fixes
* **collection:** trigger `start` event before any potential direction ([c5441fd](https://github.com/yoannmoinet/nipplejs/commit/c5441fd))
* **nipple:** fragment control of previous directions ([790fa26](https://github.com/yoannmoinet/nipplejs/commit/790fa26)), closes [#25](https://github.com/yoannmoinet/nipplejs/issues/25)
### Features
* **nipple:** add `resetDirection()` to cancel previous directions ([66f6e3b](https://github.com/yoannmoinet/nipplejs/commit/66f6e3b))
<a name="0.6.0"></a>
# [0.6.0](https://github.com/yoannmoinet/nipplejs/compare/v0.5.6...v0.6.0) (2015-11-29)
### Bug Fixes
* clean identifier if not found in any collection ([3496395](https://github.com/yoannmoinet/nipplejs/commit/3496395))
* handle scrolling offset by resetting zone's box ([6ccd0b5](https://github.com/yoannmoinet/nipplejs/commit/6ccd0b5))
* remove useless injections ([a0b1d7e](https://github.com/yoannmoinet/nipplejs/commit/a0b1d7e))
* return the found nipple in `get()` ([439aa69](https://github.com/yoannmoinet/nipplejs/commit/439aa69))
* unbind document when collection is manually destroyed ([6e66b62](https://github.com/yoannmoinet/nipplejs/commit/6e66b62))
* **nipple:** call off() after triggering ([bf8e5c3](https://github.com/yoannmoinet/nipplejs/commit/bf8e5c3))
* **nipple:** only trigger itself, not its manager ([6b39786](https://github.com/yoannmoinet/nipplejs/commit/6b39786))
* **super:** move internal declarations where needed ([a4c79c2](https://github.com/yoannmoinet/nipplejs/commit/a4c79c2))
* **utils:** return object on u.extend ([9197c00](https://github.com/yoannmoinet/nipplejs/commit/9197c00))
### Features
* **nipple:** add an incremental id to absolutely differentiate each ([598a5eb](https://github.com/yoannmoinet/nipplejs/commit/598a5eb))
* **utils:** add u.map to execute fn in array or single element ([515aa5d](https://github.com/yoannmoinet/nipplejs/commit/515aa5d))
* **utils:** add u.safeExtend to only replace existent attributes ([5212ffe](https://github.com/yoannmoinet/nipplejs/commit/5212ffe))
### Performance Improvements
* safety clean if nipple isn't found in collection ([ca20985](https://github.com/yoannmoinet/nipplejs/commit/ca20985))
<a name="0.5.6"></a>
## [0.5.6](https://github.com/yoannmoinet/nipplejs/compare/v0.5.5...v0.5.6) (2015-11-09)
<a name="0.5.5"></a>
## [0.5.5](https://github.com/yoannmoinet/nipplejs/compare/v0.5.4...v0.5.5) (2015-11-09)
### Bug Fixes
* **manager:** allow multitouch to be more than 1 ([7569536](https://github.com/yoannmoinet/nipplejs/commit/7569536))
* **manager:** clean the start process, no duplicate ([7e30551](https://github.com/yoannmoinet/nipplejs/commit/7e30551))
* **manager:** remove handler only if last touch ([1f03064](https://github.com/yoannmoinet/nipplejs/commit/1f03064))
<a name="0.5.4"></a>
## [0.5.4](https://github.com/yoannmoinet/nipplejs/compare/v0.5.3...v0.5.4) (2015-11-08)
### Bug Fixes
* **manager:** better handle identifiers ([54b3cff](https://github.com/yoannmoinet/nipplejs/commit/54b3cff)), closes [#16](https://github.com/yoannmoinet/nipplejs/issues/16)
* **nipple:** use `document.body.contains` for IE11 doesn't shortcut ([6e5b80a](https://github.com/yoannmoinet/nipplejs/commit/6e5b80a)), closes [#17](https://github.com/yoannmoinet/nipplejs/issues/17)
<a name="0.5.3"></a>
## [0.5.3](https://github.com/yoannmoinet/nipplejs/compare/v0.5.2...v0.5.3) (2015-10-04)
### Bug Fixes
* **manager:** add back pressure variable... oups ([a46982f](https://github.com/yoannmoinet/nipplejs/commit/a46982f))
<a name="0.5.2"></a>
## [0.5.2](https://github.com/yoannmoinet/nipplejs/compare/v0.5.1...v0.5.2) (2015-10-04)
### Bug Fixes
* **manager:** correctly unbind all events when destroying manager ([e3f96ef](https://github.com/yoannmoinet/nipplejs/commit/e3f96ef))
* **utils:** handle touch based on their event's type ([406a7b2](https://github.com/yoannmoinet/nipplejs/commit/406a7b2))
### Features
* **manager:** better pressure management cross-plateform ([d867812](https://github.com/yoannmoinet/nipplejs/commit/d867812))
<a name="0.5.1"></a>
## [0.5.1](https://github.com/yoannmoinet/nipplejs/compare/v0.5.0...v0.5.1) (2015-10-03)
### Bug Fixes
* **nipple:** remove useless backPosition ([4c76adc](https://github.com/yoannmoinet/nipplejs/commit/4c76adc))
* **super:** better inheritance ([4a3a8dc](https://github.com/yoannmoinet/nipplejs/commit/4a3a8dc))
<a name="0.5.0"></a>
# [0.5.0](https://github.com/yoannmoinet/nipplejs/compare/v0.4.2...v0.5.0) (2015-09-29)
### Bug Fixes
* **manager:** add default pressure at 0 ([1cb31d0](https://github.com/yoannmoinet/nipplejs/commit/1cb31d0))
* **nipple:** control dom presence before add or remove ([7cc1d8c](https://github.com/yoannmoinet/nipplejs/commit/7cc1d8c))
* **nipple:** return the manager's known object in event triggers ([2deb42c](https://github.com/yoannmoinet/nipplejs/commit/2deb42c))
* remove useless rimraf ([26db70f](https://github.com/yoannmoinet/nipplejs/commit/26db70f))
* **npm:** add rimraf as a dev dependency ([deb7acc](https://github.com/yoannmoinet/nipplejs/commit/deb7acc)), closes [#12](https://github.com/yoannmoinet/nipplejs/issues/12)
### Features
* **manager:** handle resize and reinit positions ([261fb59](https://github.com/yoannmoinet/nipplejs/commit/261fb59))
* **nipple:** remove position offset ([be59bc4](https://github.com/yoannmoinet/nipplejs/commit/be59bc4))
* add dataOnly option ([2f527b1](https://github.com/yoannmoinet/nipplejs/commit/2f527b1))
* add two modes, static and semi ([347feaa](https://github.com/yoannmoinet/nipplejs/commit/347feaa))
* **manager:** add destroy method ([295654f](https://github.com/yoannmoinet/nipplejs/commit/295654f))
* **manager:** expose nippleOptions ([968103c](https://github.com/yoannmoinet/nipplejs/commit/968103c))
* **nipple:** add destroy method ([713e505](https://github.com/yoannmoinet/nipplejs/commit/713e505))
* **off:** allow unsubscribing on all events at once ([c30bfad](https://github.com/yoannmoinet/nipplejs/commit/c30bfad))
<a name="0.4.2"></a>
## [0.4.2](https://github.com/yoannmoinet/nipplejs/compare/v0.4.1...v0.4.2) (2015-09-15)
### Bug Fixes
* correctly return directions ([871d782](https://github.com/yoannmoinet/nipplejs/commit/871d782))
<a name="0.4.1"></a>
## [0.4.1](https://github.com/yoannmoinet/nipplejs/compare/v0.4.0...v0.4.1) (2015-09-15)
### Bug Fixes
* return the computed direction ([b3d80b3](https://github.com/yoannmoinet/nipplejs/commit/b3d80b3))
<a name="0.4.0"></a>
# [0.4.0](https://github.com/yoannmoinet/nipplejs/compare/v0.3.1...v0.4.0) (2015-09-15)
### Bug Fixes
* **manager:** streamline events triggered ([76941ec](https://github.com/yoannmoinet/nipplejs/commit/76941ec))
### Features
* **nipple:** add a callback after show and hide ([5b24124](https://github.com/yoannmoinet/nipplejs/commit/5b24124))
* **nipple:** add the positions to the instance ([fac981b](https://github.com/yoannmoinet/nipplejs/commit/fac981b))
* **off:** allow unsubscribing of a type at once ([cbbefca](https://github.com/yoannmoinet/nipplejs/commit/cbbefca))
* **on:** pass the target in the event ([bc0630d](https://github.com/yoannmoinet/nipplejs/commit/bc0630d))
* add more events for both nipple and manager ([f7966b9](https://github.com/yoannmoinet/nipplejs/commit/f7966b9))
* add support for both mouse and touch at the same time ([e8fcba2](https://github.com/yoannmoinet/nipplejs/commit/e8fcba2))
* add support for pressure ([80e7770](https://github.com/yoannmoinet/nipplejs/commit/80e7770)), closes [#10](https://github.com/yoannmoinet/nipplejs/issues/10)
* attach values to each instance of nipple ([c5bf9ab](https://github.com/yoannmoinet/nipplejs/commit/c5bf9ab))
* limit the number of simultaneous nipples ([801e91d](https://github.com/yoannmoinet/nipplejs/commit/801e91d))
* support multitouch correctly ([33bbe23](https://github.com/yoannmoinet/nipplejs/commit/33bbe23)), closes [#2](https://github.com/yoannmoinet/nipplejs/issues/2) [#6](https://github.com/yoannmoinet/nipplejs/issues/6)
<a name="0.3.1"></a>
## [0.3.1](https://github.com/yoannmoinet/nipplejs/compare/v0.3.0...v0.3.1) (2015-09-07)
### Bug Fixes
* **on:** allows multiple spaces in event string ([b89bd0e](https://github.com/yoannmoinet/nipplejs/commit/b89bd0e))
* **on:** removes variable name clash. ([3d70a3c](https://github.com/yoannmoinet/nipplejs/commit/3d70a3c))
<a name="0.3.0"></a>
# [0.3.0](https://github.com/yoannmoinet/nipplejs/compare/v0.2.1...v0.3.0) (2015-09-06)
### Features
* offset angle by 180 for units circle ([3002014](https://github.com/yoannmoinet/nipplejs/commit/3002014))
* support IE PointerEvents ([ed65d1e](https://github.com/yoannmoinet/nipplejs/commit/ed65d1e)), closes [#1](https://github.com/yoannmoinet/nipplejs/issues/1)
<a name="0.2.1"></a>
## [0.2.1](https://github.com/yoannmoinet/nipplejs/compare/v0.2.0...v0.2.1) (2015-09-06)
### Bug Fixes
* correct chrome's clientBoundingBox ([b94bc43](https://github.com/yoannmoinet/nipplejs/commit/b94bc43))
<a name="0.2.0"></a>
# [0.2.0](https://github.com/yoannmoinet/nipplejs/compare/v0.1.1...v0.2.0) (2015-09-06)
### Bug Fixes
* **on:** return self to allow chain ([f068cc9](https://github.com/yoannmoinet/nipplejs/commit/f068cc9))
### Features
* **on:** let listen to multiple events ([d19ad97](https://github.com/yoannmoinet/nipplejs/commit/d19ad97))
* **on, off:** allow to chain calls ([c82c580](https://github.com/yoannmoinet/nipplejs/commit/c82c580))
* **trigger:** trigger only if different direction ([9e334ba](https://github.com/yoannmoinet/nipplejs/commit/9e334ba))
<a name="0.1.1"></a>
## [0.1.1](https://github.com/yoannmoinet/nipplejs/compare/v0.1.0...v0.1.1) (2015-09-06)
### Bug Fixes
* add scroll offset ([7b36fb6](https://github.com/yoannmoinet/nipplejs/commit/7b36fb6))
* remove useless log ([b958cbf](https://github.com/yoannmoinet/nipplejs/commit/b958cbf))
* send data to start event ([c1f1688](https://github.com/yoannmoinet/nipplejs/commit/c1f1688))
<a name="0.1.0"></a>
# 0.1.0 (2015-09-05)
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 Yoann Moinet <yoann.moinet@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+631
View File
@@ -0,0 +1,631 @@
![alt tag](./assets/nipplejs.png)
> A vanilla virtual joystick for touch capable interfaces
[![npm](https://img.shields.io/npm/v/nipplejs.svg)](https://npmjs.org/package/nipplejs)
[![npm](https://img.shields.io/npm/dm/nipplejs.svg)](https://npmjs.org/package/nipplejs)
# Table Of Contents
<details>
<!-- toc -->
- [Install](#install)
- [Demo](#demo)
- [Usage](#usage)
- [Options](#options)
* [`options.zone` defaults to 'body'](#optionszone-defaults-to-body)
* [`options.color` defaults to 'white'](#optionscolor-defaults-to-white)
* [`options.size` defaults to 100](#optionssize-defaults-to-100)
* [`options.threshold` defaults to 0.1](#optionsthreshold-defaults-to-01)
* [`options.fadeTime` defaults to 250](#optionsfadetime-defaults-to-250)
* [`options.multitouch` defaults to false](#optionsmultitouch-defaults-to-false)
* [`options.maxNumberOfNipples` defaults to 1](#optionsmaxnumberofnipples-defaults-to-1)
* [`options.dataOnly` defaults to false](#optionsdataonly-defaults-to-false)
* [`options.position` defaults to `{top: 0, left: 0}`](#optionsposition-defaults-to-top-0-left-0)
* [`options.mode` defaults to 'dynamic'.](#optionsmode-defaults-to-dynamic)
+ [`'dynamic'`](#dynamic)
+ [`'semi'`](#semi)
+ [`'static'`](#static)
* [`options.restJoystick` defaults to true](#optionsrestjoystick-defaults-to-true)
* [`options.restOpacity` defaults to 0.5](#optionsrestopacity-defaults-to-05)
* [`options.catchDistance` defaults to 200](#optionscatchdistance-defaults-to-200)
- [API](#api)
* [NippleJS instance (manager)](#nipplejs-instance-manager)
+ [`manager.on(type, handler)`](#managerontype-handler)
+ [`manager.off([type, handler])`](#managerofftype-handler)
+ [`manager.get(identifier)`](#managergetidentifier)
+ [`manager.destroy()`](#managerdestroy)
+ [`manager.ids`](#managerids)
+ [`manager.id`](#managerid)
* [nipple instance (joystick)](#nipple-instance-joystick)
* [`joystick.on`, `joystick.off`](#joystickon-joystickoff)
* [`joystick.el`](#joystickel)
* [`joystick.show([cb])`](#joystickshowcb)
* [`joystick.hide([cb])`](#joystickhidecb)
* [`joystick.add()`](#joystickadd)
* [`joystick.remove()`](#joystickremove)
* [`joystick.destroy()`](#joystickdestroy)
* [`joystick.setPosition(cb, { x, y })`](#joysticksetpositioncb--x-y-)
* [`joystick.identifier`](#joystickidentifier)
* [`joystick.trigger(type [, data])`](#joysticktriggertype--data)
* [`joystick.position`](#joystickposition)
* [`joystick.frontPosition`](#joystickfrontposition)
* [`joystick.ui`](#joystickui)
- [Events](#events)
* [manager only](#manager-only)
+ [`added`](#added)
+ [`removed`](#removed)
* [manager and joysticks](#manager-and-joysticks)
+ [`start`](#start)
+ [`end`](#end)
+ [`move`](#move)
+ [`dir`](#dir)
+ [`plain`](#plain)
+ [`shown`](#shown)
+ [`hidden`](#hidden)
+ [`destroyed`](#destroyed)
+ [`pressure`](#pressure)
- [Contributing](#contributing)
+ [Commits](#commits)
+ [Style](#style)
+ [Workflow](#workflow)
+ [Build](#build)
<!-- tocstop -->
</details>
## Install
```bash
npm install nipplejs --save
```
----
## Demo
Check out the [demo here](http://yoannmoinet.github.io/nipplejs/#demo).
----
## Usage
Import it the way you want into your project :
```javascript
// CommonJS
var manager = require('nipplejs').create(options);
```
```javascript
// AMD
define(['nipplejs'], function (nipplejs) {
var manager = nipplejs.create(options);
});
```
```javascript
// Module
import nipplejs from 'nipplejs';
```
```html
<!-- Global -->
<script src="./nipplejs.js"></script>
<script>
var manager = nipplejs.create(options);
</script>
```
**:warning: NB :warning:** Your joystick's container **has** to have its CSS `position` property set, either `absolute`, `relative`, `static`, ....
----
## Options
You can configure your joystick in different ways :
```javascript
var options = {
zone: Element, // active zone
color: String,
size: Integer,
threshold: Float, // before triggering a directional event
fadeTime: Integer, // transition time
multitouch: Boolean,
maxNumberOfNipples: Number, // when multitouch, what is too many?
dataOnly: Boolean, // no dom element whatsoever
position: Object, // preset position for 'static' mode
mode: String, // 'dynamic', 'static' or 'semi'
restJoystick: Boolean,
restOpacity: Number, // opacity when not 'dynamic' and rested
lockX: Boolean, // only move on the X axis
lockY: Boolean, // only move on the Y axis
catchDistance: Number, // distance to recycle previous joystick in
// 'semi' mode
dynamicPage: Boolean, // Enable if the page has dynamically visible elements
};
```
All options are optional :sunglasses:.
### `options.zone` defaults to 'body'
The dom element in which all your joysticks will be injected.
```html
<div id="zone_joystick"></div>
<script type="text/javascript" src="./nipplejs.js"></script>
<script type="text/javascript">
var options = {
zone: document.getElementById('zone_joystick'),
};
var manager = nipplejs.create(options);
</script>
```
This zone also serve as the mouse/touch events handler.
It represents the zone where all your joysticks will be active.
### `options.color` defaults to 'white'
The background color of your joystick's elements.
Can be any valid CSS color.
### `options.size` defaults to 100
The size in pixel of the outer circle.
The inner circle is 50% of this size.
### `options.threshold` defaults to 0.1
This is the strength needed to trigger a directional event.
Basically, the center is 0 and the outer is 1.
You need to at least go to 0.1 to trigger a directional event.
### `options.fadeTime` defaults to 250
The time it takes for joystick to fade-out and fade-in when activated or de-activated.
### `options.multitouch` defaults to false
Enable the multitouch capabilities.
If, for reasons, you need to have multiple nipples in the same zone.
Otherwise, it will only get one, and all new touches won't do a thing.
Please note that multitouch is off when in `static` or `semi` modes.
### `options.maxNumberOfNipples` defaults to 1
If you need to, you can also control the maximum number of instances that could be created.
Obviously in a multitouch configuration.
### `options.dataOnly` defaults to false
The library won't draw anything in the DOM and will only trigger events with data.
### `options.position` defaults to `{top: 0, left: 0}`
An object that will determine the position of a `static` mode.
You can pass any of the four `top`, `right`, `bottom` and `left`.
They will be applied as any css property.
Ex :
- `{top: '50px', left: '50px'}`
- `{left: '10%', bottom: '10%'}`
### `options.mode` defaults to 'dynamic'.
Three modes are possible :
#### `'dynamic'`
- a new joystick is created at each new touch.
- the joystick gets destroyed when released.
- **can** be multitouch.
#### `'semi'`
- new joystick is created at each new touch farther than `options.catchDistance` of any previously created joystick.
- the joystick is faded-out when released but not destroyed.
- when touch is made **inside** the `options.catchDistance` a new direction is triggered immediately.
- when touch is made **outside** the `options.catchDistance` the previous joystick is destroyed and a new one is created.
- **cannot** be multitouch.
#### `'static'`
- a joystick is positioned immediately at `options.position`.
- one joystick per zone.
- each new touch triggers a new direction.
- **cannot** be multitouch.
### `options.restJoystick` defaults to true
Reset the joystick's position when it enters the rest state.
### `options.restOpacity` defaults to 0.5
The opacity to apply when the joystick is in a rest position.
### `options.catchDistance` defaults to 200
This is only useful in the `semi` mode, and determine at which distance we recycle the previous joystick.
At 200 (px), if you press the zone into a rayon of 200px around the previously displayed joystick,
it will act as a `static` one.
### `options.lockX` defaults to false
Locks joystick's movement to the x (horizontal) axis
### `options.lockY` defaults to false
Locks joystick's movement to the y (vertical) axis
### `options.dynamicPage` defaults to true
Enable if the page has dynamically visible elements such as for Vue, React, Angular or simply some CSS hiding or showing some DOM.
----
## API
### NippleJS instance (manager)
Your manager has the following signature :
```javascript
{
on: Function, // handle internal event
off: Function, // un-handle internal event
get: Function, // get a specific joystick
destroy: Function, // destroy everything
ids: Array // array of assigned ids
id: Number // id of the manager
options: {
zone: Element, // reactive zone
multitouch: Boolean,
maxNumberOfNipples: Number,
mode: String,
position: Object,
catchDistance: Number,
size: Number,
threshold: Number,
color: String,
fadeTime: Number,
dataOnly: Boolean,
restJoystick: Boolean,
restOpacity: Number
}
}
```
#### `manager.on(type, handler)`
If you wish to listen to internal events like :
```javascript
manager.on('event#1 event#2', function (evt, data) {
// Do something.
});
```
Note that you can listen to multiple events at once by separating
them either with a space or a comma (or both, I don't care).
#### `manager.off([type, handler])`
To remove an event handler :
```javascript
manager.off('event', handler);
```
If you call off without arguments, all handlers will be removed.
If you don't specify the handler but just a type, all handlers for that type will be removed.
#### `manager.get(identifier)`
A helper to get an instance via its identifier.
```javascript
// Will return the nipple instantiated by the touch identified by 0
manager.get(0);
```
#### `manager.destroy()`
Gently remove all nipples from the DOM and unbind all events.
```javascript
manager.destroy();
```
#### `manager.ids`
The array of nipples' ids under this manager.
#### `manager.id`
The incremented id of this manager.
### nipple instance (joystick)
Each joystick has the following signature :
```javascript
{
on: Function,
off: Function,
el: Element,
show: Function, // fade-in
hide: Function, // fade-out
add: Function, // inject into dom
remove: Function, // remove from dom
destroy: Function,
setPosition: Function,
identifier: Number,
trigger: Function,
position: { // position of the center
x: Number,
y: Number
},
frontPosition: { // position of the front part
x: Number,
y: Number
},
ui: {
el: Element,
front: Element,
back: Element
},
options: {
color: String,
size: Number,
threshold: Number,
fadeTime: Number
}
}
```
### `joystick.on`, `joystick.off`
The same as the manager.
### `joystick.el`
Dom element in which the joystick gets created.
```html
<div class="nipple">
<div class="front"></div>
<div class="back"></div>
</div>
```
### `joystick.show([cb])`
Will show the joystick at the last known place.
You can pass a callback that will be executed at the end of the fade-in animation.
### `joystick.hide([cb])`
Will fade-out the joystick.
You can pass a callback that will be executed at the end of the fade-out animation.
### `joystick.add()`
Add the joystick's element to the dom.
### `joystick.remove()`
Remove the joystick's element from the dom.
### `joystick.destroy()`
Gently remove this nipple from the DOM and unbind all related events.
### `joystick.setPosition(cb, { x, y })`
Set the joystick to the specified position, where x and y are distances away from the center in pixels. This does not trigger joystick events.
### `joystick.identifier`
Returns the unique identifier of the joystick.
Tied to its touch's identifier.
### `joystick.trigger(type [, data])`
Trigger an internal event from the joystick.
The same as `on` you can trigger multiple events at the same time.
### `joystick.position`
The absolute position of the center of the joystick.
### `joystick.frontPosition`
The absolute position of the back part of the joystick's ui.
### `joystick.ui`
The object that store its ui elements
```html
{
el: <div class="nipple"></div>
back: <div class="back"></div>
front: <div class="front"></div>
}
```
----
## Events
You can listen events both on the manager and all the joysticks.
But some of them are specific to its instance.
If you need to listen to each joystick, for example, you can :
```javascript
manager.on('added', function (evt, nipple) {
nipple.on('start move end dir plain', function (evt) {
// DO EVERYTHING
});
}).on('removed', function (evt, nipple) {
nipple.off('start move end dir plain');
});
```
### manager only
#### `added`
A joystick just got added.
Will pass the instance alongside the event.
#### `removed`
A joystick just got removed.
Fired at the end of the fade-out animation.
Will pass the instance alongside the event.
Won't be trigger in a `dataOnly` configuration.
### manager and joysticks
Other events are available on both the manager and joysticks.
When listening on the manager,
you can also target **a joystick in particular** by prefixing
the event with its identifier, **`0:start`** for example.
Else you'll get all events from all the joysticks.
#### `start`
A joystick is activated. (the user pressed on the active zone)
Will pass the instance alongside the event.
#### `end`
A joystick is de-activated. (the user released the active zone)
Will pass the instance alongside the event.
#### `move`
A joystick is moved.
Comes with data :
```javascript
{
identifier: 0, // the identifier of the touch/mouse that triggered it
position: { // absolute position of the center in pixels
x: 125,
y: 95
},
force: 0.2, // strength in %
distance: 25.4, // distance from center in pixels
pressure: 0.1, // the pressure applied by the touch
angle: {
radian: 1.5707963268, // angle in radian
degree: 90
},
vector: { // force unit vector
x: 0.508,
y: 3.110602869834277e-17
},
raw: { // note: angle is the same, beyond the 50 pixel limit
distance: 25.4, // distance which continues beyond the 50 pixel limit
position: { // position of the finger/mouse in pixels, beyond joystick limits
x: 125,
y: 95
}
},
instance: Nipple // the nipple instance that triggered the event
}
```
#### `dir`
When a direction is reached after the threshold.
Direction are split with a 45° angle.
```javascript
// \ UP /
// \ /
// LEFT RIGHT
// / \
// /DOWN \
```
You can also listen to specific direction like :
- `dir:up`
- `dir:down`
- `dir:right`
- `dir:left`
In this configuration only one direction is triggered at a time.
#### `plain`
When a plain direction is reached after the threshold.
Plain directions are split with a 90° angle.
```javascript
// UP |
// ------ LEFT | RIGHT
// DOWN |
```
You can also listen to specific plain direction like :
- `plain:up`
- `plain:down`
- `plain:right`
- `plain:left`
In this configuration two directions can be triggered at a time,
because the user could be both `up` and `left` for example.
#### `shown`
Is triggered at the end of the fade-in animation.
Will pass the instance alongside the event.
Won't be trigger in a `dataOnly` configuration.
#### `hidden`
Is triggered at the end of the fade-out animation.
Will pass the instance alongside the event.
Won't be trigger in a `dataOnly` configuration.
#### `destroyed`
Is triggered at the end of destroy.
Will pass the instance alongside the event.
#### `pressure`
> MBP's [**Force Touch**](http://www.apple.com/macbook-pro/features-retina/#interact), iOS's [**3D Touch**](http://www.apple.com/iphone-6s/3d-touch/), Microsoft's [**pressure**](https://msdn.microsoft.com/en-us/library/hh772360%28v=vs.85%29.aspx) or MDN's [**force**](https://developer.mozilla.org/en-US/docs/Web/API/Touch/force)
Is triggered when the pressure on the joystick is changed.
The value, between 0 and 1, is sent back alongside the event.
----
## Contributing
You can follow [this document](./CONTRIBUTING.md) to help you get started.
File diff suppressed because one or more lines are too long
+89
View File
@@ -0,0 +1,89 @@
{
"_from": "nipplejs",
"_id": "nipplejs@0.8.5",
"_inBundle": false,
"_integrity": "sha512-2G7warzUh4jIdmzSIDRCeXJUl/QrZyJ0QwFgI0V1lM34HLNsIqEX6uY0NY4V3wBDHNsub3me1cbFb/FJpWE/5g==",
"_location": "/nipplejs",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "nipplejs",
"name": "nipplejs",
"escapedName": "nipplejs",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/nipplejs/-/nipplejs-0.8.5.tgz",
"_shasum": "9b16bd1fc70afadc35b09355b86dad65d863bafa",
"_spec": "nipplejs",
"_where": "/root/catkin_ws/src/robot_gui_bridge/gui",
"author": {
"name": "Yoann Moinet",
"email": "yo@nnmoi.net",
"url": "https://yoannmoi.net"
},
"browser": "./dist/nipplejs.js",
"bugs": {
"url": "https://github.com/yoannmoinet/nipplejs/issues"
},
"bundleDependencies": false,
"dependencies": {},
"deprecated": false,
"description": "A virtual joystick for touch capable interfaces",
"devDependencies": {
"@babel/core": "7.5.5",
"@babel/preset-env": "7.5.5",
"babel-loader": "8.0.6",
"babel-plugin-add-module-exports": "1.0.2",
"casperjs": "1.1.4",
"concat-cli": "4.0.0",
"conventional-changelog": "1.1.0",
"eslint": "5.10.0",
"eslint-loader": "2.2.1",
"expect.js": "0.3.1",
"markdown-toc": "1.2.0",
"mocha": "5.2.0",
"phantomjs-prebuilt": "2.1.16",
"webpack": "4.39.2",
"webpack-cli": "3.3.7",
"webpack-dev-server": "3.8.0"
},
"directories": {
"lib": "src",
"test": "test",
"example": "example"
},
"files": [
"dist/*",
"src/*",
"types/*"
],
"homepage": "https://github.com/yoannmoinet/nipplejs",
"license": "MIT",
"main": "./dist/nipplejs.js",
"module": "./src/index.js",
"name": "nipplejs",
"repository": {
"type": "git",
"url": "git+https://github.com/yoannmoinet/nipplejs.git"
},
"scripts": {
"build": "webpack --config webpack.config.js",
"build:dev": "webpack-dev-server --config webpack.config.js",
"changelog": "node ./bin/changelog.js",
"copyGh": "node ./bin/copyToGhPages.js",
"postversion": "npm run changelog",
"prepare": "NODE_ENV=production npm run build",
"start": "npm run build:dev -- --progress --watch",
"test": "casperjs test ./test/nipplejs.casper.js",
"toc": "markdown-toc -i README.md"
},
"types": "./types/index.d.ts",
"version": "0.8.5"
}
+579
View File
@@ -0,0 +1,579 @@
import Nipple from './nipple';
import Super from './super';
import * as u from './utils';
///////////////////////////
/// THE COLLECTION ///
///////////////////////////
function Collection (manager, options) {
var self = this;
self.nipples = [];
self.idles = [];
self.actives = [];
self.ids = [];
self.pressureIntervals = {};
self.manager = manager;
self.id = Collection.id;
Collection.id += 1;
// Defaults
self.defaults = {
zone: document.body,
multitouch: false,
maxNumberOfNipples: 10,
mode: 'dynamic',
position: {top: 0, left: 0},
catchDistance: 200,
size: 100,
threshold: 0.1,
color: 'white',
fadeTime: 250,
dataOnly: false,
restJoystick: true,
restOpacity: 0.5,
lockX: false,
lockY: false,
dynamicPage: false
};
self.config(options);
// Overwrites
if (self.options.mode === 'static' || self.options.mode === 'semi') {
self.options.multitouch = false;
}
if (!self.options.multitouch) {
self.options.maxNumberOfNipples = 1;
}
self.updateBox();
self.prepareNipples();
self.bindings();
self.begin();
return self.nipples;
}
Collection.prototype = new Super();
Collection.constructor = Collection;
Collection.id = 0;
Collection.prototype.prepareNipples = function () {
var self = this;
var nips = self.nipples;
// Public API Preparation.
nips.on = self.on.bind(self);
nips.off = self.off.bind(self);
nips.options = self.options;
nips.destroy = self.destroy.bind(self);
nips.ids = self.ids;
nips.id = self.id;
nips.processOnMove = self.processOnMove.bind(self);
nips.processOnEnd = self.processOnEnd.bind(self);
nips.get = function (id) {
if (id === undefined) {
return nips[0];
}
for (var i = 0, max = nips.length; i < max; i += 1) {
if (nips[i].identifier === id) {
return nips[i];
}
}
return false;
};
};
Collection.prototype.bindings = function () {
var self = this;
// Touch start event.
self.bindEvt(self.options.zone, 'start');
// Avoid native touch actions (scroll, zoom etc...) on the zone.
self.options.zone.style.touchAction = 'none';
self.options.zone.style.msTouchAction = 'none';
};
Collection.prototype.begin = function () {
var self = this;
var opts = self.options;
// We place our static nipple
// if needed.
if (opts.mode === 'static') {
var nipple = self.createNipple(
opts.position,
self.manager.getIdentifier()
);
// Add it to the dom.
nipple.add();
// Store it in idles.
self.idles.push(nipple);
}
};
// Nipple Factory
Collection.prototype.createNipple = function (position, identifier) {
var self = this;
var scroll = u.getScroll();
var toPutOn = {};
var opts = self.options;
if (position.x && position.y) {
toPutOn = {
x: position.x -
(scroll.x + self.box.left),
y: position.y -
(scroll.y + self.box.top)
};
} else if (
position.top ||
position.right ||
position.bottom ||
position.left
) {
// We need to compute the position X / Y of the joystick.
var dumb = document.createElement('DIV');
dumb.style.display = 'hidden';
dumb.style.top = position.top;
dumb.style.right = position.right;
dumb.style.bottom = position.bottom;
dumb.style.left = position.left;
dumb.style.position = 'absolute';
opts.zone.appendChild(dumb);
var dumbBox = dumb.getBoundingClientRect();
opts.zone.removeChild(dumb);
toPutOn = position;
position = {
x: dumbBox.left + scroll.x,
y: dumbBox.top + scroll.y
};
}
var nipple = new Nipple(self, {
color: opts.color,
size: opts.size,
threshold: opts.threshold,
fadeTime: opts.fadeTime,
dataOnly: opts.dataOnly,
restJoystick: opts.restJoystick,
restOpacity: opts.restOpacity,
mode: opts.mode,
identifier: identifier,
position: position,
zone: opts.zone,
frontPosition: {
x: 0,
y: 0
}
});
if (!opts.dataOnly) {
u.applyPosition(nipple.ui.el, toPutOn);
u.applyPosition(nipple.ui.front, nipple.frontPosition);
}
self.nipples.push(nipple);
self.trigger('added ' + nipple.identifier + ':added', nipple);
self.manager.trigger('added ' + nipple.identifier + ':added', nipple);
self.bindNipple(nipple);
return nipple;
};
Collection.prototype.updateBox = function () {
var self = this;
self.box = self.options.zone.getBoundingClientRect();
};
Collection.prototype.bindNipple = function (nipple) {
var self = this;
var type;
// Bubble up identified events.
var handler = function (evt, data) {
// Identify the event type with the nipple's id.
type = evt.type + ' ' + data.id + ':' + evt.type;
self.trigger(type, data);
};
// When it gets destroyed.
nipple.on('destroyed', self.onDestroyed.bind(self));
// Other events that will get bubbled up.
nipple.on('shown hidden rested dir plain', handler);
nipple.on('dir:up dir:right dir:down dir:left', handler);
nipple.on('plain:up plain:right plain:down plain:left', handler);
};
Collection.prototype.pressureFn = function (touch, nipple, identifier) {
var self = this;
var previousPressure = 0;
clearInterval(self.pressureIntervals[identifier]);
// Create an interval that will read the pressure every 100ms
self.pressureIntervals[identifier] = setInterval(function () {
var pressure = touch.force || touch.pressure ||
touch.webkitForce || 0;
if (pressure !== previousPressure) {
nipple.trigger('pressure', pressure);
self.trigger('pressure ' +
nipple.identifier + ':pressure', pressure);
previousPressure = pressure;
}
}.bind(self), 100);
};
Collection.prototype.onstart = function (evt) {
var self = this;
var opts = self.options;
var origEvt = evt;
evt = u.prepareEvent(evt);
// Update the box position
self.updateBox();
var process = function (touch) {
// If we can create new nipples
// meaning we don't have more active nipples than we should.
if (self.actives.length < opts.maxNumberOfNipples) {
self.processOnStart(touch);
}
else if(origEvt.type.match(/^touch/)){
// zombies occur when end event is not received on Safari
// first touch removed before second touch, we need to catch up...
// so remove where touches in manager that no longer exist
Object.keys(self.manager.ids).forEach(function(k){
if(Object.values(origEvt.touches).findIndex(function(t){return t.identifier===k;}) < 0){
// manager has id that doesn't exist in touches
var e = [evt[0]];
e.identifier = k;
self.processOnEnd(e);
}
});
if(self.actives.length < opts.maxNumberOfNipples){
self.processOnStart(touch);
}
}
};
u.map(evt, process);
// We ask upstream to bind the document
// on 'move' and 'end'
self.manager.bindDocument();
return false;
};
Collection.prototype.processOnStart = function (evt) {
var self = this;
var opts = self.options;
var indexInIdles;
var identifier = self.manager.getIdentifier(evt);
var pressure = evt.force || evt.pressure || evt.webkitForce || 0;
var position = {
x: evt.pageX,
y: evt.pageY
};
var nipple = self.getOrCreate(identifier, position);
// Update its touch identifier
if (nipple.identifier !== identifier) {
self.manager.removeIdentifier(nipple.identifier);
}
nipple.identifier = identifier;
var process = function (nip) {
// Trigger the start.
nip.trigger('start', nip);
self.trigger('start ' + nip.id + ':start', nip);
nip.show();
if (pressure > 0) {
self.pressureFn(evt, nip, nip.identifier);
}
// Trigger the first move event.
self.processOnMove(evt);
};
// Transfer it from idles to actives.
if ((indexInIdles = self.idles.indexOf(nipple)) >= 0) {
self.idles.splice(indexInIdles, 1);
}
// Store the nipple in the actives array
self.actives.push(nipple);
self.ids.push(nipple.identifier);
if (opts.mode !== 'semi') {
process(nipple);
} else {
// In semi we check the distance of the touch
// to decide if we have to reset the nipple
var distance = u.distance(position, nipple.position);
if (distance <= opts.catchDistance) {
process(nipple);
} else {
nipple.destroy();
self.processOnStart(evt);
return;
}
}
return nipple;
};
Collection.prototype.getOrCreate = function (identifier, position) {
var self = this;
var opts = self.options;
var nipple;
// If we're in static or semi, we might already have an active.
if (/(semi|static)/.test(opts.mode)) {
// Get the active one.
// TODO: Multi-touche for semi and static will start here.
// Return the nearest one.
nipple = self.idles[0];
if (nipple) {
self.idles.splice(0, 1);
return nipple;
}
if (opts.mode === 'semi') {
// If we're in semi mode, we need to create one.
return self.createNipple(position, identifier);
}
// eslint-disable-next-line no-console
console.warn('Coudln\'t find the needed nipple.');
return false;
}
// In dynamic, we create a new one.
nipple = self.createNipple(position, identifier);
return nipple;
};
Collection.prototype.processOnMove = function (evt) {
var self = this;
var opts = self.options;
var identifier = self.manager.getIdentifier(evt);
var nipple = self.nipples.get(identifier);
// If we're moving without pressing
// it's that we went out the active zone
if (!u.isPressed(evt)) {
this.processOnEnd(evt);
return;
}
if (!nipple) {
// This is here just for safety.
// It shouldn't happen.
// eslint-disable-next-line no-console
console.error('Found zombie joystick with ID ' + identifier);
self.manager.removeIdentifier(identifier);
return;
}
if (opts.dynamicPage) {
var scroll = u.getScroll();
pos = nipple.el.getBoundingClientRect();
nipple.position = {
x: scroll.x + pos.left,
y: scroll.y + pos.top
};
}
nipple.identifier = identifier;
var size = nipple.options.size / 2;
var pos = {
x: evt.pageX,
y: evt.pageY
};
if (opts.lockX){
pos.y = nipple.position.y;
}
if (opts.lockY) {
pos.x = nipple.position.x;
}
var dist = u.distance(pos, nipple.position);
var angle = u.angle(pos, nipple.position);
var rAngle = u.radians(angle);
var force = dist / size;
var raw = {
distance: dist,
position: pos
};
// If distance is bigger than nipple's size
// we clamp the position.
if (dist > size) {
dist = size;
pos = u.findCoord(nipple.position, dist, angle);
}
var xPosition = pos.x - nipple.position.x;
var yPosition = pos.y - nipple.position.y;
nipple.frontPosition = {
x: xPosition,
y: yPosition
};
if (!opts.dataOnly) {
u.applyPosition(nipple.ui.front, nipple.frontPosition);
}
// Prepare event's datas.
var toSend = {
identifier: nipple.identifier,
position: pos,
force: force,
pressure: evt.force || evt.pressure || evt.webkitForce || 0,
distance: dist,
angle: {
radian: rAngle,
degree: angle
},
vector: {
x: xPosition / size,
y: - yPosition / size
},
raw: raw,
instance: nipple,
lockX: opts.lockX,
lockY: opts.lockY
};
// Compute the direction's datas.
toSend = nipple.computeDirection(toSend);
// Offset angles to follow units circle.
toSend.angle = {
radian: u.radians(180 - angle),
degree: 180 - angle
};
// Send everything to everyone.
nipple.trigger('move', toSend);
self.trigger('move ' + nipple.id + ':move', toSend);
};
Collection.prototype.processOnEnd = function (evt) {
var self = this;
var opts = self.options;
var identifier = self.manager.getIdentifier(evt);
var nipple = self.nipples.get(identifier);
var removedIdentifier = self.manager.removeIdentifier(nipple.identifier);
if (!nipple) {
return;
}
if (!opts.dataOnly) {
nipple.hide(function () {
if (opts.mode === 'dynamic') {
nipple.trigger('removed', nipple);
self.trigger('removed ' + nipple.id + ':removed', nipple);
self.manager
.trigger('removed ' + nipple.id + ':removed', nipple);
nipple.destroy();
}
});
}
// Clear the pressure interval reader
clearInterval(self.pressureIntervals[nipple.identifier]);
// Reset the direciton of the nipple, to be able to trigger a new direction
// on start.
nipple.resetDirection();
nipple.trigger('end', nipple);
self.trigger('end ' + nipple.id + ':end', nipple);
// Remove identifier from our bank.
if (self.ids.indexOf(nipple.identifier) >= 0) {
self.ids.splice(self.ids.indexOf(nipple.identifier), 1);
}
// Clean our actives array.
if (self.actives.indexOf(nipple) >= 0) {
self.actives.splice(self.actives.indexOf(nipple), 1);
}
if (/(semi|static)/.test(opts.mode)) {
// Transfer nipple from actives to idles
// if we're in semi or static mode.
self.idles.push(nipple);
} else if (self.nipples.indexOf(nipple) >= 0) {
// Only if we're not in semi or static mode
// we can remove the instance.
self.nipples.splice(self.nipples.indexOf(nipple), 1);
}
// We unbind move and end.
self.manager.unbindDocument();
// We add back the identifier of the idle nipple;
if (/(semi|static)/.test(opts.mode)) {
self.manager.ids[removedIdentifier.id] = removedIdentifier.identifier;
}
};
// Remove destroyed nipple from the lists
Collection.prototype.onDestroyed = function(evt, nipple) {
var self = this;
if (self.nipples.indexOf(nipple) >= 0) {
self.nipples.splice(self.nipples.indexOf(nipple), 1);
}
if (self.actives.indexOf(nipple) >= 0) {
self.actives.splice(self.actives.indexOf(nipple), 1);
}
if (self.idles.indexOf(nipple) >= 0) {
self.idles.splice(self.idles.indexOf(nipple), 1);
}
if (self.ids.indexOf(nipple.identifier) >= 0) {
self.ids.splice(self.ids.indexOf(nipple.identifier), 1);
}
// Remove the identifier from our bank
self.manager.removeIdentifier(nipple.identifier);
// We unbind move and end.
self.manager.unbindDocument();
};
// Cleanly destroy the manager
Collection.prototype.destroy = function () {
var self = this;
self.unbindEvt(self.options.zone, 'start');
// Destroy nipples.
self.nipples.forEach(function(nipple) {
nipple.destroy();
});
// Clean 3DTouch intervals.
for (var i in self.pressureIntervals) {
if (self.pressureIntervals.hasOwnProperty(i)) {
clearInterval(self.pressureIntervals[i]);
}
}
// Notify the manager passing the instance
self.trigger('destroyed', self.nipples);
// We unbind move and end.
self.manager.unbindDocument();
// Unbind everything.
self.off();
};
export default Collection;
+9
View File
@@ -0,0 +1,9 @@
import Manager from './manager';
const factory = new Manager();
export default {
create: function (options) {
return factory.create(options);
},
factory: factory
};
+225
View File
@@ -0,0 +1,225 @@
import Collection from './collection';
import Super from './super';
import * as u from './utils';
///////////////////////
/// MANAGER ///
///////////////////////
function Manager (options) {
var self = this;
self.ids = {};
self.index = 0;
self.collections = [];
self.config(options);
self.prepareCollections();
// Listen for resize, to reposition every joysticks
var resizeTimer;
u.bindEvt(window, 'resize', function (evt) {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
var pos;
var scroll = u.getScroll();
self.collections.forEach(function (collection) {
collection.forEach(function (nipple) {
pos = nipple.el.getBoundingClientRect();
nipple.position = {
x: scroll.x + pos.left,
y: scroll.y + pos.top
};
});
});
}, 100);
});
return self.collections;
}
Manager.prototype = new Super();
Manager.constructor = Manager;
Manager.prototype.prepareCollections = function () {
var self = this;
// Public API Preparation.
self.collections.create = self.create.bind(self);
// Listen to anything
self.collections.on = self.on.bind(self);
// Unbind general events
self.collections.off = self.off.bind(self);
// Destroy everything
self.collections.destroy = self.destroy.bind(self);
// Get any nipple
self.collections.get = function (id) {
var nipple;
// Use .every() to break the loop as soon as found.
self.collections.every(function (collection) {
nipple = collection.get(id);
return nipple ? false : true;
});
return nipple;
};
};
Manager.prototype.create = function (options) {
return this.createCollection(options);
};
// Collection Factory
Manager.prototype.createCollection = function (options) {
var self = this;
var collection = new Collection(self, options);
self.bindCollection(collection);
self.collections.push(collection);
return collection;
};
Manager.prototype.bindCollection = function (collection) {
var self = this;
var type;
// Bubble up identified events.
var handler = function (evt, data) {
// Identify the event type with the nipple's identifier.
type = evt.type + ' ' + data.id + ':' + evt.type;
self.trigger(type, data);
};
// When it gets destroyed we clean.
collection.on('destroyed', self.onDestroyed.bind(self));
// Other events that will get bubbled up.
collection.on('shown hidden rested dir plain', handler);
collection.on('dir:up dir:right dir:down dir:left', handler);
collection.on('plain:up plain:right plain:down plain:left', handler);
};
Manager.prototype.bindDocument = function () {
var self = this;
// Bind only if not already binded
if (!self.binded) {
self.bindEvt(document, 'move')
.bindEvt(document, 'end');
self.binded = true;
}
};
Manager.prototype.unbindDocument = function (force) {
var self = this;
// If there are no touch left
// unbind the document.
if (!Object.keys(self.ids).length || force === true) {
self.unbindEvt(document, 'move')
.unbindEvt(document, 'end');
self.binded = false;
}
};
Manager.prototype.getIdentifier = function (evt) {
var id;
// If no event, simple increment
if (!evt) {
id = this.index;
} else {
// Extract identifier from event object.
// Unavailable in mouse events so replaced by latest increment.
id = evt.identifier === undefined ? evt.pointerId : evt.identifier;
if (id === undefined) {
id = this.latest || 0;
}
}
if (this.ids[id] === undefined) {
this.ids[id] = this.index;
this.index += 1;
}
// Keep the latest id used in case we're using an unidentified mouseEvent
this.latest = id;
return this.ids[id];
};
Manager.prototype.removeIdentifier = function (identifier) {
var removed = {};
for (var id in this.ids) {
if (this.ids[id] === identifier) {
removed.id = id;
removed.identifier = this.ids[id];
delete this.ids[id];
break;
}
}
return removed;
};
Manager.prototype.onmove = function (evt) {
var self = this;
self.onAny('move', evt);
return false;
};
Manager.prototype.onend = function (evt) {
var self = this;
self.onAny('end', evt);
return false;
};
Manager.prototype.oncancel = function (evt) {
var self = this;
self.onAny('end', evt);
return false;
};
Manager.prototype.onAny = function (which, evt) {
var self = this;
var id;
var processFn = 'processOn' + which.charAt(0).toUpperCase() +
which.slice(1);
evt = u.prepareEvent(evt);
var processColl = function (e, id, coll) {
if (coll.ids.indexOf(id) >= 0) {
coll[processFn](e);
// Mark the event to avoid cleaning it later.
e._found_ = true;
}
};
var processEvt = function (e) {
id = self.getIdentifier(e);
u.map(self.collections, processColl.bind(null, e, id));
// If the event isn't handled by any collection,
// we need to clean its identifier.
if (!e._found_) {
self.removeIdentifier(id);
}
};
u.map(evt, processEvt);
return false;
};
// Cleanly destroy the manager
Manager.prototype.destroy = function () {
var self = this;
self.unbindDocument(true);
self.ids = {};
self.index = 0;
self.collections.forEach(function(collection) {
collection.destroy();
});
self.off();
};
// When a collection gets destroyed
// we clean behind.
Manager.prototype.onDestroyed = function (evt, coll) {
var self = this;
if (self.collections.indexOf(coll) < 0) {
return false;
}
self.collections.splice(self.collections.indexOf(coll), 1);
};
export default Manager;
+405
View File
@@ -0,0 +1,405 @@
import Super from './super';
import * as u from './utils';
///////////////////////
/// THE NIPPLE ///
///////////////////////
function Nipple (collection, options) {
this.identifier = options.identifier;
this.position = options.position;
this.frontPosition = options.frontPosition;
this.collection = collection;
// Defaults
this.defaults = {
size: 100,
threshold: 0.1,
color: 'white',
fadeTime: 250,
dataOnly: false,
restJoystick: true,
restOpacity: 0.5,
mode: 'dynamic',
zone: document.body,
lockX: false,
lockY: false
};
this.config(options);
// Overwrites
if (this.options.mode === 'dynamic') {
this.options.restOpacity = 0;
}
this.id = Nipple.id;
Nipple.id += 1;
this.buildEl()
.stylize();
// Nipple's API.
this.instance = {
el: this.ui.el,
on: this.on.bind(this),
off: this.off.bind(this),
show: this.show.bind(this),
hide: this.hide.bind(this),
add: this.addToDom.bind(this),
remove: this.removeFromDom.bind(this),
destroy: this.destroy.bind(this),
setPosition:this.setPosition.bind(this),
resetDirection: this.resetDirection.bind(this),
computeDirection: this.computeDirection.bind(this),
trigger: this.trigger.bind(this),
position: this.position,
frontPosition: this.frontPosition,
ui: this.ui,
identifier: this.identifier,
id: this.id,
options: this.options
};
return this.instance;
}
Nipple.prototype = new Super();
Nipple.constructor = Nipple;
Nipple.id = 0;
// Build the dom element of the Nipple instance.
Nipple.prototype.buildEl = function (options) {
this.ui = {};
if (this.options.dataOnly) {
return this;
}
this.ui.el = document.createElement('div');
this.ui.back = document.createElement('div');
this.ui.front = document.createElement('div');
this.ui.el.className = 'nipple collection_' + this.collection.id;
this.ui.back.className = 'back';
this.ui.front.className = 'front';
this.ui.el.setAttribute('id', 'nipple_' + this.collection.id +
'_' + this.id);
this.ui.el.appendChild(this.ui.back);
this.ui.el.appendChild(this.ui.front);
return this;
};
// Apply CSS to the Nipple instance.
Nipple.prototype.stylize = function () {
if (this.options.dataOnly) {
return this;
}
var animTime = this.options.fadeTime + 'ms';
var borderStyle = u.getVendorStyle('borderRadius', '50%');
var transitStyle = u.getTransitionStyle('transition', 'opacity', animTime);
var styles = {};
styles.el = {
position: 'absolute',
opacity: this.options.restOpacity,
display: 'block',
'zIndex': 999
};
styles.back = {
position: 'absolute',
display: 'block',
width: this.options.size + 'px',
height: this.options.size + 'px',
marginLeft: -this.options.size / 2 + 'px',
marginTop: -this.options.size / 2 + 'px',
background: this.options.color,
'opacity': '.5'
};
styles.front = {
width: this.options.size / 2 + 'px',
height: this.options.size / 2 + 'px',
position: 'absolute',
display: 'block',
marginLeft: -this.options.size / 4 + 'px',
marginTop: -this.options.size / 4 + 'px',
background: this.options.color,
'opacity': '.5'
};
u.extend(styles.el, transitStyle);
u.extend(styles.back, borderStyle);
u.extend(styles.front, borderStyle);
this.applyStyles(styles);
return this;
};
Nipple.prototype.applyStyles = function (styles) {
// Apply styles
for (var i in this.ui) {
if (this.ui.hasOwnProperty(i)) {
for (var j in styles[i]) {
this.ui[i].style[j] = styles[i][j];
}
}
}
return this;
};
// Inject the Nipple instance into DOM.
Nipple.prototype.addToDom = function () {
// We're not adding it if we're dataOnly or already in dom.
if (this.options.dataOnly || document.body.contains(this.ui.el)) {
return this;
}
this.options.zone.appendChild(this.ui.el);
return this;
};
// Remove the Nipple instance from DOM.
Nipple.prototype.removeFromDom = function () {
if (this.options.dataOnly || !document.body.contains(this.ui.el)) {
return this;
}
this.options.zone.removeChild(this.ui.el);
return this;
};
// Entirely destroy this nipple
Nipple.prototype.destroy = function () {
clearTimeout(this.removeTimeout);
clearTimeout(this.showTimeout);
clearTimeout(this.restTimeout);
this.trigger('destroyed', this.instance);
this.removeFromDom();
this.off();
};
// Fade in the Nipple instance.
Nipple.prototype.show = function (cb) {
var self = this;
if (self.options.dataOnly) {
return self;
}
clearTimeout(self.removeTimeout);
clearTimeout(self.showTimeout);
clearTimeout(self.restTimeout);
self.addToDom();
self.restCallback();
setTimeout(function () {
self.ui.el.style.opacity = 1;
}, 0);
self.showTimeout = setTimeout(function () {
self.trigger('shown', self.instance);
if (typeof cb === 'function') {
cb.call(this);
}
}, self.options.fadeTime);
return self;
};
// Fade out the Nipple instance.
Nipple.prototype.hide = function (cb) {
var self = this;
if (self.options.dataOnly) {
return self;
}
self.ui.el.style.opacity = self.options.restOpacity;
clearTimeout(self.removeTimeout);
clearTimeout(self.showTimeout);
clearTimeout(self.restTimeout);
self.removeTimeout = setTimeout(
function () {
var display = self.options.mode === 'dynamic' ? 'none' : 'block';
self.ui.el.style.display = display;
if (typeof cb === 'function') {
cb.call(self);
}
self.trigger('hidden', self.instance);
},
self.options.fadeTime
);
if (self.options.restJoystick) {
self.setPosition(cb, { x: 0, y: 0 });
}
return self;
};
// Set the nipple to the specified position
Nipple.prototype.setPosition = function (cb, position) {
var self = this;
self.frontPosition = {
x: position.x,
y: position.y
};
var animTime = self.options.fadeTime + 'ms';
var transitStyle = {};
transitStyle.front = u.getTransitionStyle('transition',
['top', 'left'], animTime);
var styles = {front: {}};
styles.front = {
left: self.frontPosition.x + 'px',
top: self.frontPosition.y + 'px'
};
self.applyStyles(transitStyle);
self.applyStyles(styles);
self.restTimeout = setTimeout(
function () {
if (typeof cb === 'function') {
cb.call(self);
}
self.restCallback();
},
self.options.fadeTime
);
};
Nipple.prototype.restCallback = function () {
var self = this;
var transitStyle = {};
transitStyle.front = u.getTransitionStyle('transition', 'none', '');
self.applyStyles(transitStyle);
self.trigger('rested', self.instance);
};
Nipple.prototype.resetDirection = function () {
// Fully rebuild the object to let the iteration possible.
this.direction = {
x: false,
y: false,
angle: false
};
};
Nipple.prototype.computeDirection = function (obj) {
var rAngle = obj.angle.radian;
var angle45 = Math.PI / 4;
var angle90 = Math.PI / 2;
var direction, directionX, directionY;
// Angular direction
// \ UP /
// \ /
// LEFT RIGHT
// / \
// /DOWN \
//
if (
rAngle > angle45 &&
rAngle < (angle45 * 3) &&
!obj.lockX
) {
direction = 'up';
} else if (
rAngle > -angle45 &&
rAngle <= angle45 &&
!obj.lockY
) {
direction = 'left';
} else if (
rAngle > (-angle45 * 3) &&
rAngle <= -angle45 &&
!obj.lockX
) {
direction = 'down';
} else if (!obj.lockY) {
direction = 'right';
}
// Plain direction
// UP |
// _______ | RIGHT
// LEFT |
// DOWN |
if (!obj.lockY) {
if (rAngle > -angle90 && rAngle < angle90) {
directionX = 'left';
} else {
directionX = 'right';
}
}
if (!obj.lockX) {
if (rAngle > 0) {
directionY = 'up';
} else {
directionY = 'down';
}
}
if (obj.force > this.options.threshold) {
var oldDirection = {};
var i;
for (i in this.direction) {
if (this.direction.hasOwnProperty(i)) {
oldDirection[i] = this.direction[i];
}
}
var same = {};
this.direction = {
x: directionX,
y: directionY,
angle: direction
};
obj.direction = this.direction;
for (i in oldDirection) {
if (oldDirection[i] === this.direction[i]) {
same[i] = true;
}
}
// If all 3 directions are the same, we don't trigger anything.
if (same.x && same.y && same.angle) {
return obj;
}
if (!same.x || !same.y) {
this.trigger('plain', obj);
}
if (!same.x) {
this.trigger('plain:' + directionX, obj);
}
if (!same.y) {
this.trigger('plain:' + directionY, obj);
}
if (!same.angle) {
this.trigger('dir dir:' + direction, obj);
}
} else {
this.resetDirection();
}
return obj;
};
export default Nipple;
+147
View File
@@ -0,0 +1,147 @@
///////////////////////
/// SUPER CLASS ///
///////////////////////
import * as u from './utils';
// Constants
var isTouch = !!('ontouchstart' in window);
var isPointer = window.PointerEvent ? true : false;
var isMSPointer = window.MSPointerEvent ? true : false;
var events = {
touch: {
start: 'touchstart',
move: 'touchmove',
end: 'touchend, touchcancel'
},
mouse: {
start: 'mousedown',
move: 'mousemove',
end: 'mouseup'
},
pointer: {
start: 'pointerdown',
move: 'pointermove',
end: 'pointerup, pointercancel'
},
MSPointer: {
start: 'MSPointerDown',
move: 'MSPointerMove',
end: 'MSPointerUp'
}
};
var toBind;
var secondBind = {};
if (isPointer) {
toBind = events.pointer;
} else if (isMSPointer) {
toBind = events.MSPointer;
} else if (isTouch) {
toBind = events.touch;
secondBind = events.mouse;
} else {
toBind = events.mouse;
}
function Super () {}
// Basic event system.
Super.prototype.on = function (arg, cb) {
var self = this;
var types = arg.split(/[ ,]+/g);
var type;
self._handlers_ = self._handlers_ || {};
for (var i = 0; i < types.length; i += 1) {
type = types[i];
self._handlers_[type] = self._handlers_[type] || [];
self._handlers_[type].push(cb);
}
return self;
};
Super.prototype.off = function (type, cb) {
var self = this;
self._handlers_ = self._handlers_ || {};
if (type === undefined) {
self._handlers_ = {};
} else if (cb === undefined) {
self._handlers_[type] = null;
} else if (self._handlers_[type] &&
self._handlers_[type].indexOf(cb) >= 0) {
self._handlers_[type].splice(self._handlers_[type].indexOf(cb), 1);
}
return self;
};
Super.prototype.trigger = function (arg, data) {
var self = this;
var types = arg.split(/[ ,]+/g);
var type;
self._handlers_ = self._handlers_ || {};
for (var i = 0; i < types.length; i += 1) {
type = types[i];
if (self._handlers_[type] && self._handlers_[type].length) {
self._handlers_[type].forEach(function (handler) {
handler.call(self, {
type: type,
target: self
}, data);
});
}
}
};
// Configuration
Super.prototype.config = function (options) {
var self = this;
self.options = self.defaults || {};
if (options) {
self.options = u.safeExtend(self.options, options);
}
};
// Bind internal events.
Super.prototype.bindEvt = function (el, type) {
var self = this;
self._domHandlers_ = self._domHandlers_ || {};
self._domHandlers_[type] = function () {
if (typeof self['on' + type] === 'function') {
self['on' + type].apply(self, arguments);
} else {
// eslint-disable-next-line no-console
console.warn('[WARNING] : Missing "on' + type + '" handler.');
}
};
u.bindEvt(el, toBind[type], self._domHandlers_[type]);
if (secondBind[type]) {
// Support for both touch and mouse at the same time.
u.bindEvt(el, secondBind[type], self._domHandlers_[type]);
}
return self;
};
// Unbind dom events.
Super.prototype.unbindEvt = function (el, type) {
var self = this;
self._domHandlers_ = self._domHandlers_ || {};
u.unbindEvt(el, toBind[type], self._domHandlers_[type]);
if (secondBind[type]) {
// Support for both touch and mouse at the same time.
u.unbindEvt(el, secondBind[type], self._domHandlers_[type]);
}
delete self._domHandlers_[type];
return this;
};
export default Super;
+175
View File
@@ -0,0 +1,175 @@
///////////////////////
/// UTILS ///
///////////////////////
export const distance = (p1, p2) => {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
return Math.sqrt((dx * dx) + (dy * dy));
};
export const angle = (p1, p2) => {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
return degrees(Math.atan2(dy, dx));
};
export const findCoord = (p, d, a) => {
const b = {x: 0, y: 0};
a = radians(a);
b.x = p.x - d * Math.cos(a);
b.y = p.y - d * Math.sin(a);
return b;
};
export const radians = (a) => {
return a * (Math.PI / 180);
};
export const degrees = (a) => {
return a * (180 / Math.PI);
};
export const isPressed = (evt) => {
if (isNaN(evt.buttons)) {
return evt.pressure !== 0;
}
return evt.buttons !== 0;
};
export const bindEvt = (el, arg, handler) => {
const types = arg.split(/[ ,]+/g);
let type;
for (let i = 0; i < types.length; i += 1) {
type = types[i];
if (el.addEventListener) {
el.addEventListener(type, handler, false);
} else if (el.attachEvent) {
el.attachEvent(type, handler);
}
}
};
export const unbindEvt = (el, arg, handler) => {
const types = arg.split(/[ ,]+/g);
let type;
for (let i = 0; i < types.length; i += 1) {
type = types[i];
if (el.removeEventListener) {
el.removeEventListener(type, handler);
} else if (el.detachEvent) {
el.detachEvent(type, handler);
}
}
};
export const trigger = (el, type, data) => {
const evt = new CustomEvent(type, data);
el.dispatchEvent(evt);
};
export const prepareEvent = (evt) => {
evt.preventDefault();
return evt.type.match(/^touch/) ? evt.changedTouches : evt;
};
export const getScroll = () => {
const x = (window.pageXOffset !== undefined) ?
window.pageXOffset :
(document.documentElement || document.body.parentNode || document.body)
.scrollLeft;
const y = (window.pageYOffset !== undefined) ?
window.pageYOffset :
(document.documentElement || document.body.parentNode || document.body)
.scrollTop;
return {
x: x,
y: y
};
};
export const applyPosition = (el, pos) => {
if (pos.top || pos.right || pos.bottom || pos.left) {
el.style.top = pos.top;
el.style.right = pos.right;
el.style.bottom = pos.bottom;
el.style.left = pos.left;
} else {
el.style.left = pos.x + 'px';
el.style.top = pos.y + 'px';
}
};
export const getTransitionStyle = (property, values, time) => {
const obj = configStylePropertyObject(property);
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
if (typeof values === 'string') {
obj[i] = values + ' ' + time;
} else {
let st = '';
for (let j = 0, max = values.length; j < max; j += 1) {
st += values[j] + ' ' + time + ', ';
}
obj[i] = st.slice(0, -2);
}
}
}
return obj;
};
export const getVendorStyle = (property, value) => {
const obj = configStylePropertyObject(property);
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
obj[i] = value;
}
}
return obj;
};
export const configStylePropertyObject = (prop) => {
const obj = {};
obj[prop] = '';
const vendors = ['webkit', 'Moz', 'o'];
vendors.forEach(function (vendor) {
obj[vendor + prop.charAt(0).toUpperCase() + prop.slice(1)] = '';
});
return obj;
};
export const extend = (objA, objB) => {
for (let i in objB) {
if (objB.hasOwnProperty(i)) {
objA[i] = objB[i];
}
}
return objA;
};
// Overwrite only what's already present
export const safeExtend = (objA, objB) => {
const obj = {};
for (let i in objA) {
if (objA.hasOwnProperty(i) && objB.hasOwnProperty(i)) {
obj[i] = objB[i];
} else if (objA.hasOwnProperty(i)) {
obj[i] = objA[i];
}
}
return obj;
};
// Map for array or unique item.
export const map = (ar, fn) => {
if (ar.length) {
for (let i = 0, max = ar.length; i < max; i += 1) {
fn(ar[i]);
}
} else {
fn(ar);
}
};
+348
View File
@@ -0,0 +1,348 @@
export interface JoystickManagerOptions {
/**
* Defaults to `'body'`
* The dom element in which all your joysticks will be injected.
*
* This zone also serve as the mouse/touch events handler.
*
* It represents the zone where all your joysticks will be active.
*/
zone?: HTMLElement;
/**
* Defaults to `'white'`
* The background color of your joystick’s elements.
*
* Can be any valid CSS color.
*/
color?: string;
/**
* Defaults to `100`
* The size in pixel of the outer circle.
*
* The inner circle is 50% of this size.
*/
size?: number;
/**
* This is the strength needed to trigger a directional event.
*
* Basically, the center is 0 and the outer is 1.
*
* You need to at least go to 0.1 to trigger a directional event.
*/
threshold?: number;
/**
* Defaults to `250`
*
* The time it takes for joystick to fade-out and fade-in when activated or de-activated.
*/
fadeTime?: number;
/**
* Defaults to `false`
*
* Enable the multitouch capabilities.
*
* If, for reasons, you need to have multiple nipples into the same zone.
*
* Otherwise it will only get one, and all new touches won’t do a thing.
*
* Please note that multitouch is off when in static or semi modes.
*/
multitouch?: boolean;
/**
* Defaults to `1`
*
* If you need to, you can also control the maximum number of instance that could be created.
*
* Obviously in a multitouch configuration.
*/
maxNumberOfNipples?: number;
/**
* Defaults to `false`
*
* The library won’t draw anything in the DOM and will only trigger events with data.
*/
dataOnly?: boolean;
/**
* Defaults to `{top: 0, left: 0}`
*
* An object that will determine the position of a static mode.
*
* You can pass any of the four top, right, bottom and left.
*
* They will be applied as any css property.
*/
position?: {
top?: string;
right?: string;
bottom?: string;
left?: string;
};
/**
* Behavioral mode for the joystick
*
* ### 'dynamic':
* a new joystick is created at each new touch.
* the joystick gets destroyed when released.
* can be multitouch.
*
* ### 'semi':
* new joystick is created at each new touch farther than options.catchDistance of any previously created joystick.
* the joystick is faded-out when released but not destroyed.
* when touch is made inside the options.catchDistance a new direction is triggered immediately.
* when touch is made outside the options.catchDistance the previous joystick is destroyed and a new one is created.
* cannot be multitouch.
*
* ### 'static':
* a joystick is positioned immediately at options.position.
* one joystick per zone.
* each new touch triggers a new direction.
* cannot be multitouch.
*/
mode?: 'dynamic' | 'semi' | 'static';
/**
* Defaults to `true`
*
* Reset the joystick’s position when it enters the rest state.
*/
restJoystick?: boolean;
/**
* Defaults to `0.5`
* The opacity to apply when the joystick is in a rest position.
*/
restOpacity?: number;
/**
* Defaults to `200`
*
* This is only useful in the semi mode, and determine at which distance we recycle the previous joystick.
*/
catchDistance?: number;
/**
* Defaults to `false`
*
* Locks joystick’s movement to the x (horizontal) axis
*/
lockX?: boolean;
/**
* Defaults to `false`
*
* Locks joystick’s movement to the y (vertical) axis
*/
lockY?: boolean;
}
export interface Position {
x: number;
y: number;
}
export interface JoystickOutputData {
angle: {
degree: number;
radian: number;
};
direction: {
angle: string;
x: string;
y: string;
};
vector: {
x: number;
y: number;
};
raw: {
distance: number;
position: Position;
};
distance: number;
force: number;
identifier: number;
instance: Joystick;
position: Position;
pressure: number;
}
export type JoystickEventTypes =
/**
* A joystick is activated. (the user pressed on the active zone)
* Will pass the instance alongside the event.
*/
| 'start'
/** A joystick is de-activated. (the user released the active zone)
* Will pass the instance alongside the event.
*/
| 'end'
/**
* A joystick is moved
*/
| 'move'
/**
* When a direction is reached after the threshold.
*
* Direction are split with a 45° angle.
*/
| 'dir'
| 'dir:up'
| 'dir:down'
| 'dir:right'
| 'dir:left'
/**
* When a plain direction is reached after the threshold.
* Plain directions are split with a 90° angle.
*/
| 'plain'
// plain variations
| 'plain:up'
| 'plain:down'
| 'plain:right'
| 'plain:left'
/**
* Is triggered at the end of the fade-in animation.
* Will pass the instance alongside the event.
*
* Won’t be trigger in a dataOnly configuration.
*/
| 'shown'
/**
* Is triggered at the end of the fade-out animation.
*
* Will pass the instance alongside the event.
*
* Won’t be trigger in a dataOnly configuration.
*/
| 'hidden'
/**
* Is triggered at the end of destroy.
*
* Will pass the instance alongside the event.
*/
| 'destroyed'
/**
* MBP’s Force Touch, iOS’s 3D Touch, Microsoft’s pressure or MDN’s force
*
*Is triggered when the pressure on the joystick is changed.
*
*The value, between 0 and 1, is sent back alongside the event.
*/
| 'pressure';
export type ManagerOnlyEventTypes =
/**
* A joystick just got added.
*
* Will pass the instance alongside the event.
*/
| 'added'
/**
* A joystick just got removed.
*
* Fired at the end of the fade-out animation.
*
* Will pass the instance alongside the event.
*
* Won’t be trigger in a dataOnly configuration.
*/
| 'removed';
export type JoystickManagerEventTypes = JoystickEventTypes | ManagerOnlyEventTypes;
export interface EventData {
type: JoystickEventTypes | ManagerOnlyEventTypes;
target: Collection;
}
export class JoystickManager {
create(options?: JoystickManagerOptions): JoystickManager;
on(
type: JoystickManagerEventTypes | JoystickManagerEventTypes[],
handler: (evt: EventData, data: JoystickOutputData) => void
): void;
off(
type: JoystickManagerEventTypes | JoystickManagerEventTypes[],
handler: (evt: EventData, data: JoystickOutputData) => void
): void;
get(identifier: number): Joystick;
destroy(): void;
}
export interface Collection {
nipples: Joystick[];
idles: Joystick[];
actives: Joystick[];
ids: number[];
pressureIntervals: {};
manager: JoystickManager;
id: number;
defaults: JoystickManagerOptions;
}
export interface Joystick {
on(
type: JoystickEventTypes | JoystickEventTypes[],
handler: (evt: EventData, data: JoystickOutputData) => void
): void;
off(
type: JoystickEventTypes | JoystickEventTypes[],
handler: (evt: EventData, data: JoystickOutputData) => void
): void;
el: HTMLElement;
show(cb?: () => void): void;
hide(cb?: () => void): void;
add(): void;
remove(): void;
destroy(): void;
identifier: number;
trigger(
type: JoystickEventTypes | JoystickEventTypes[],
handler: (evt: EventData, data: any) => void
): void;
position: Position;
frontPosition: Position;
ui: {
el: HTMLElement;
front: HTMLElement;
back: HTMLElement;
};
options: JoystickManagerOptions;
}
/**
* A JavaScript library for creating vanillaJS virtual joysticks, for touch capable interfaces.
*/
declare module 'nipplejs' {
/**
* Create a Joystick manager
* @param options for creating a manager instance
* @return manager instance
*/
function create(options: JoystickManagerOptions): JoystickManager;
/**
* Library's root manger instance.
*/
const factory: JoystickManager;
}