Start
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<script type="text/javascript" src="http://static.robotwebtools.org/roslibjs/current/roslib.min.js"></script>
|
||||
<script src="./node_modules/nipplejs/dist/nipplejs.js"></script>
|
||||
<script type="text/javascript" type="text/javascript">
|
||||
var publishImmidiately = true;
|
||||
var host_url = window.location.hostname;
|
||||
console.log(host_url);
|
||||
var watchdog_handle;
|
||||
|
||||
var ros = new ROSLIB.Ros({
|
||||
url: 'ws://' + host_url + ':9090'
|
||||
});
|
||||
|
||||
// Initialize Axes and Buttons arrays
|
||||
var axes = [0, 0, 0, 0, 0, 0];
|
||||
var buttons = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
ros.on('connection', function () {
|
||||
document.getElementById("status").innerHTML = "Connected";
|
||||
});
|
||||
|
||||
ros.on('error', function (error) {
|
||||
document.getElementById("status").innerHTML = "Error";
|
||||
});
|
||||
|
||||
ros.on('close', function () {
|
||||
document.getElementById("status").innerHTML = "Closed";
|
||||
});
|
||||
|
||||
createJoystick = function () {
|
||||
var options = {
|
||||
zone: document.getElementById('joy_container'),
|
||||
threshold: 0.1,
|
||||
position: { left: '50%', top: '50%' },
|
||||
mode: 'static',
|
||||
size: 150,
|
||||
color: 'black'
|
||||
};
|
||||
manager = nipplejs.create(options);
|
||||
|
||||
self.manager.on('start', function (event, nipple) {
|
||||
});
|
||||
|
||||
self.manager.on('move', function (event, nipple) {
|
||||
var max_distance = 75;
|
||||
var x = -Math.cos(nipple.angle.radian) * nipple.distance / max_distance;
|
||||
var y = Math.sin(nipple.angle.radian) * nipple.distance / max_distance;
|
||||
axes = [x, y, 0, 0, 0, 0]
|
||||
|
||||
// nipplejs is triggering events when joystick moves each pixel
|
||||
// we need delay between consecutive messege publications to
|
||||
// prevent system from being flooded by messages
|
||||
// events triggered earlier than 50ms after last publication will be dropped
|
||||
|
||||
if (publishImmidiately) {
|
||||
publishImmidiately = false;
|
||||
joy_event()
|
||||
window.clearInterval(watchdog_handle)
|
||||
watchdog_handle = window.setInterval(joy_event, 50)
|
||||
|
||||
setTimeout(function () {
|
||||
publishImmidiately = true;
|
||||
}, 50);
|
||||
}
|
||||
//joy_event()
|
||||
//window.clearInterval()
|
||||
//window.setInterval(joy_event, 500)
|
||||
});
|
||||
|
||||
self.manager.on('end', function () {
|
||||
window.clearInterval(watchdog_handle)
|
||||
axes = [0, 0, 0, 0, 0, 0]
|
||||
joy_event()
|
||||
console.log('End function called')
|
||||
});
|
||||
}
|
||||
window.onload = function () {
|
||||
createJoystick();
|
||||
// Video output
|
||||
stream_url = '//' + host_url + ':8080/stream?topic=/pi_cam/image_raw';
|
||||
console.log(stream_url);
|
||||
document.getElementById("video_output").src = stream_url;
|
||||
}
|
||||
|
||||
joy_listener = new ROSLIB.Topic({
|
||||
ros: ros,
|
||||
name: "/joy",
|
||||
messageType: 'sensor_msgs/Joy'
|
||||
});
|
||||
|
||||
joy_event = function () {
|
||||
var joy = new ROSLIB.Message({
|
||||
axes: axes,
|
||||
buttons: buttons
|
||||
});
|
||||
joy_listener.publish(joy);
|
||||
console.log(axes)
|
||||
console.log(buttons)
|
||||
}
|
||||
|
||||
button_clicked = function (button_index) {
|
||||
// The desired button index can be found by listening to the /joy ros message
|
||||
// or by checking in the wiki for the gamepad mapping.
|
||||
// Set axes to 0 to prevent driving during mode change.
|
||||
axes = [0, 0, 0, 0, 0, 0];
|
||||
buttons[button_index] = 1;
|
||||
joy_event();
|
||||
// After the command is sent set the index back to 0
|
||||
buttons[button_index] = 0;
|
||||
}
|
||||
|
||||
</script>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<div class="wrapper">
|
||||
<div class="left" id="left">
|
||||
<div class="joy_container" id="joy_container"></div>
|
||||
</div>
|
||||
|
||||
<div class="middle" id="middle">
|
||||
<div class="image_container">
|
||||
<img id="video_output" style="object-fit:cover; width:100%;"></img>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right" id="right">
|
||||
<div class="button_container">
|
||||
<button class="button buttonY" onclick="button_clicked(3)">CRABBING</button>
|
||||
<button class="button buttonX" onclick="button_clicked(0)">SPOT TURN</button>
|
||||
<button class="button buttonA" onclick="button_clicked(1)">ACKERMANN</button>
|
||||
<button class="button buttonStart" onclick="button_clicked(9)">MOTORS</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
</html>
|
||||
+386
@@ -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
@@ -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
@@ -0,0 +1,631 @@
|
||||

|
||||
> A vanilla virtual joystick for touch capable interfaces
|
||||
|
||||
|
||||
[](https://npmjs.org/package/nipplejs)
|
||||
[](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.
|
||||
+1
File diff suppressed because one or more lines are too long
+89
@@ -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
@@ -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
@@ -0,0 +1,9 @@
|
||||
import Manager from './manager';
|
||||
|
||||
const factory = new Manager();
|
||||
export default {
|
||||
create: function (options) {
|
||||
return factory.create(options);
|
||||
},
|
||||
factory: factory
|
||||
};
|
||||
+225
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"nipplejs": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/nipplejs/-/nipplejs-0.8.5.tgz",
|
||||
"integrity": "sha512-2G7warzUh4jIdmzSIDRCeXJUl/QrZyJ0QwFgI0V1lM34HLNsIqEX6uY0NY4V3wBDHNsub3me1cbFb/FJpWE/5g=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
header {
|
||||
background-color: #f1f1f1;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
|
||||
.wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
.button {
|
||||
border: none;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 10px auto;
|
||||
cursor: pointer;
|
||||
width: 90%;
|
||||
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19);
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.buttonY{
|
||||
background-color: orange;
|
||||
grid-row: 1;
|
||||
}
|
||||
.buttonX{
|
||||
background-color: blue;
|
||||
grid-row: 2;
|
||||
}
|
||||
.buttonA{
|
||||
background-color: green;
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
.buttonStart{
|
||||
background-color: red;
|
||||
grid-row: 4;
|
||||
width: 50%;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
|
||||
.button_container {
|
||||
position: absolute;
|
||||
display: grid;
|
||||
left: 25%;
|
||||
top: 25%;
|
||||
height: 50%;
|
||||
width: 50%;
|
||||
}
|
||||
.image_container {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
height: 80%;
|
||||
width: 80%;
|
||||
}
|
||||
.joy_container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.left {
|
||||
position: relative;
|
||||
background: rgba(63, 58, 55, 0.1);
|
||||
grid-column: 1;
|
||||
height: 100%;
|
||||
}
|
||||
.middle {
|
||||
position: relative;
|
||||
background: rgba(45, 48, 45, 0.1);
|
||||
grid-column: 2;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.right {
|
||||
position: relative;
|
||||
background: rgba(63, 58, 55, 0.1);
|
||||
grid-column: 3;
|
||||
}
|
||||
Reference in New Issue
Block a user