Return smoothed drag end velocity with event
This commit is contained in:
parent
691dc21a4d
commit
c7d1fe0c0c
7 changed files with 177 additions and 12 deletions
|
@ -32,6 +32,7 @@ Emitted with the following info:
|
|||
Emitted with the following info:
|
||||
|
||||
- `offset: {x, y, z}` - The offset from entity center to drag position.
|
||||
- `velocity: {x, y, z}` - The smoothed velocity of the entity at dragend time.
|
||||
- `depth` - the perpendicular distance from the screen to align the entity while
|
||||
dragging
|
||||
- `clientX` - the final mouse event's `clientX` value
|
||||
|
|
|
@ -12,22 +12,33 @@
|
|||
h1 {
|
||||
font-weight: 300;
|
||||
}
|
||||
a {
|
||||
color: #FAFAFA;
|
||||
a.demo-link {
|
||||
display: block;
|
||||
padding: 15px 0;
|
||||
}
|
||||
a {
|
||||
color: #FAFAFA;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>A-Frame Click & Drag Component</h1>
|
||||
<a href="basic/">Basic Demo</a>
|
||||
<a class="demo-link" href="basic/">Basic Demo</a>
|
||||
<p>Click + Drag entities on the screen. Note the plane cannot be dragged (it does not have the "click-drag" attribute).</p>
|
||||
<p>Try the WASD keys to move around while dragging an entity!</p>
|
||||
|
||||
<hr />
|
||||
<a href="events/">Events Demo</a>
|
||||
|
||||
<a class="demo-link" href="events/">Events Demo</a>
|
||||
<p>Events are fired for beginning to drag, ending a drag, and for each drag event in etween.</p>
|
||||
<p>This example shows how those events can be used to "ghost" a dragged entity</p>
|
||||
<p>This example shows how those events can be used to "ghost" a dragged entity.</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<a class="demo-link" href="physics/">Physics Demo</a>
|
||||
<p>Calculating the velocity at the time of drag end.</p>
|
||||
<p>Combined with a physics library (for example; <a href="https://github.com/donmccurdy/aframe-extras/tree/master/src/physics">aframe-extras physics</a>), we get some very nice interactions.</p>
|
||||
<p>Try gently tossing the ball around / throwing it at the ground.</p>
|
||||
|
||||
<!-- GitHub Corner. -->
|
||||
<a href="https://github.com/jesstelford/aframe-click-drag-component" class="github-corner">
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import aframe from 'aframe';
|
||||
import extras from 'aframe-extras';
|
||||
import clickDragComponent from '../src/index';
|
||||
|
||||
extras.physics.registerAll(aframe);
|
||||
clickDragComponent(aframe);
|
||||
|
||||
|
|
42
examples/physics/index.html
Normal file
42
examples/physics/index.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>A-Frame Click & Drag Component - Events</title>
|
||||
<script src="../build.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a-scene physics="debug: true">
|
||||
|
||||
<a-sphere
|
||||
click-drag
|
||||
dynamic-body="mass: 20"
|
||||
position="0 3 -1"
|
||||
radius="1.25"
|
||||
color="#EF2D5E"
|
||||
>
|
||||
</a-sphere>
|
||||
|
||||
<a-plane static-body rotation="-90 0 0" width="200" height="200" color="#7BC8A4"></a-plane>
|
||||
|
||||
<a-sky color="#ECECEC"></a-sky>
|
||||
|
||||
<a-entity position="0 0 3.8">
|
||||
<a-camera look-controls-enabled="false"></a-camera>
|
||||
</a-entity>
|
||||
<script>
|
||||
var draggable = document.querySelector('[click-drag]');
|
||||
draggable.addEventListener('dragstart', function(dragInfo) {
|
||||
draggable.pause();
|
||||
});
|
||||
draggable.addEventListener('dragend', function(dragInfo) {
|
||||
var x = dragInfo.detail.velocity.x;
|
||||
var y = dragInfo.detail.velocity.y;
|
||||
var z = dragInfo.detail.velocity.z;
|
||||
|
||||
draggable.play();
|
||||
draggable.body.velocity.set(x, y, z);
|
||||
console.log('drag end', dragInfo.detail.velocity);
|
||||
});
|
||||
</script>
|
||||
</a-scene>
|
||||
</body>
|
||||
</html>
|
12
package.json
12
package.json
|
@ -5,11 +5,11 @@
|
|||
"main": "lib/index.js",
|
||||
"browser": "dist/aframe-click-drag-component.min.js",
|
||||
"scripts": {
|
||||
"build-example": "browserify examples/main.js --debug --verbose -t babelify -t [envify --NODE_ENV development ] -o examples/build.js",
|
||||
"build-example": "browserify examples/main.js --debug --verbose -g uglifyify -t [ rollupify --config rollup.config.js ] -t babelify -t [envify --NODE_ENV development ] -o examples/build.js",
|
||||
"build-lib": "mkdir -p lib && babel src/index.js -o lib/build.js",
|
||||
"dist": "browserify src/index.js --verbose --debug --standalone registerAframeClickDragComponent -g uglifyify -t rollupify -t babelify -t [envify --NODE_ENV production ] | exorcist dist/out.map > dist/out.js && uglifyjs dist/out.js --screw-ie8 -c -m --in-source-map dist/out.map --source-map dist/aframe-click-drag-component.min.js.map --source-map-url aframe-click-drag-component.min.js.map > dist/aframe-click-drag-component.min.js && rm dist/out*",
|
||||
"dist": "browserify src/index.js --verbose --debug --standalone registerAframeClickDragComponent -g uglifyify -t [ rollupify --config rollup.config.js ] -t babelify -t [envify --NODE_ENV production ] | exorcist dist/out.map > dist/out.js && uglifyjs dist/out.js --screw-ie8 -c -m --in-source-map dist/out.map --source-map dist/aframe-click-drag-component.min.js.map --source-map-url aframe-click-drag-component.min.js.map > dist/aframe-click-drag-component.min.js && rm dist/out*",
|
||||
"test": "npm run test:lint",
|
||||
"test:lint": "eslint .",
|
||||
"test:lint": "eslint ./src",
|
||||
"start": "budo examples/main.js:../build.js --serve build.js --dir examples --port 8000 --live --open -- --debug --verbose -t babelify -t [envify --NODE_ENV development ]",
|
||||
"prepublish": "in-publish && npm run dist && npm run build-lib || not-in-publish",
|
||||
"preghpages": "npm run build-example && rm -rf gh-pages && mkdir gh-pages && cp -r examples/* gh-pages",
|
||||
|
@ -38,6 +38,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"aframe": "^0.3.0",
|
||||
"aframe-extras": "^2.5.3",
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.8.0",
|
||||
"babel-preset-es2015": "^6.9.0",
|
||||
|
@ -55,12 +56,15 @@
|
|||
"exorcist": "^0.4.0",
|
||||
"ghpages": "^0.0.8",
|
||||
"in-publish": "^2.0.0",
|
||||
"rollup-plugin-commonjs": "^5.0.4",
|
||||
"rollup-plugin-node-resolve": "^2.0.0",
|
||||
"rollupify": "^0.3.4",
|
||||
"uglify-js": "^2.7.3",
|
||||
"uglifyify": "^3.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"deep-equal": "^1.0.1"
|
||||
"deep-equal": "^1.0.1",
|
||||
"simple-statistics": "^2.1.0"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
|
|
12
rollup.config.js
Normal file
12
rollup.config.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
require('rollup-plugin-node-resolve')({
|
||||
jsnext: true,
|
||||
main: true,
|
||||
browser: true,
|
||||
}),
|
||||
require('rollup-plugin-commonjs')({
|
||||
include: 'node_modules/**',
|
||||
}),
|
||||
],
|
||||
};
|
97
src/index.js
97
src/index.js
|
@ -1,10 +1,14 @@
|
|||
import deepEqual from 'deep-equal';
|
||||
import linearRegression from 'simple-statistics/src/linear_regression';
|
||||
import linearRegressionLine from 'simple-statistics/src/linear_regression_line';
|
||||
|
||||
const COMPONENT_NAME = 'click-drag';
|
||||
const DRAG_START_EVENT = 'dragstart';
|
||||
const DRAG_MOVE_EVENT = 'dragmove';
|
||||
const DRAG_END_EVENT = 'dragend';
|
||||
|
||||
const TIME_TO_KEEP_LOG = 300;
|
||||
|
||||
function cameraPositionToVec3(camera, vec3) {
|
||||
|
||||
let element = camera;
|
||||
|
@ -339,6 +343,24 @@ const {initialize, tearDown} = (function closeOverInitAndTearDown() {
|
|||
let removeDragListeners;
|
||||
let draggedElement;
|
||||
let dragInfo;
|
||||
const positionLog = [];
|
||||
|
||||
function cleanUpPositionLog() {
|
||||
const now = performance.now();
|
||||
while (positionLog.length && now - positionLog[0].time > TIME_TO_KEEP_LOG) {
|
||||
// remove the first element;
|
||||
positionLog.shift();
|
||||
}
|
||||
}
|
||||
|
||||
function onDragged({detail: {nextPosition}}) {
|
||||
// Continuously clean up so we don't get huge logs built up
|
||||
cleanUpPositionLog();
|
||||
positionLog.push({
|
||||
position: Object.assign({}, nextPosition),
|
||||
time: performance.now(),
|
||||
});
|
||||
}
|
||||
|
||||
function onMouseDown({clientX, clientY}) {
|
||||
|
||||
|
@ -347,7 +369,17 @@ const {initialize, tearDown} = (function closeOverInitAndTearDown() {
|
|||
if (element) {
|
||||
// Can only drag one item at a time, so no need to check if any
|
||||
// listener is already set up
|
||||
removeDragListeners = dragItem(THREE, element, offset, camera, depth, {clientX, clientY});
|
||||
let removeDragItemListeners = dragItem(
|
||||
THREE,
|
||||
element,
|
||||
offset,
|
||||
camera,
|
||||
depth,
|
||||
{
|
||||
clientX,
|
||||
clientY,
|
||||
}
|
||||
);
|
||||
|
||||
draggedElement = element;
|
||||
|
||||
|
@ -359,12 +391,71 @@ const {initialize, tearDown} = (function closeOverInitAndTearDown() {
|
|||
};
|
||||
|
||||
element.emit(DRAG_START_EVENT, dragInfo);
|
||||
|
||||
element.addEventListener(DRAG_MOVE_EVENT, onDragged);
|
||||
|
||||
removeDragListeners = _ => {
|
||||
element.removeEventListener(DRAG_MOVE_EVENT, onDragged);
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
removeDragItemListeners && removeDragItemListeners();
|
||||
// in case this removal function gets called more than once
|
||||
removeDragItemListeners = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function fitLineToVelocity(dimension) {
|
||||
|
||||
if (positionLog.length < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const velocities = positionLog
|
||||
|
||||
// Pull out just the x, y, or z values
|
||||
.map(log => ({time: log.time, value: log.position[dimension]}))
|
||||
|
||||
// Then convert that into an array of array pairs [time, value]
|
||||
.reduce((memo, log, index, collection) => {
|
||||
|
||||
// skip the first item (we're looking for pairs)
|
||||
if (index === 0) {
|
||||
return memo;
|
||||
}
|
||||
|
||||
const deltaPosition = log.value - collection[index - 1].value;
|
||||
const deltaTime = (log.time - collection[index - 1].time) / 1000;
|
||||
|
||||
// The new value is the change in position
|
||||
memo.push([log.time, deltaPosition / deltaTime]);
|
||||
|
||||
return memo;
|
||||
|
||||
}, []);
|
||||
|
||||
// Calculate the line function
|
||||
const lineFunction = linearRegressionLine(linearRegression(velocities));
|
||||
|
||||
// Calculate what the point was at the end of the line
|
||||
// ie; the velocity at the time the drag stopped
|
||||
return lineFunction(positionLog[positionLog.length - 1].time);
|
||||
}
|
||||
|
||||
function onMouseUp({clientX, clientY}) {
|
||||
|
||||
draggedElement.emit(DRAG_END_EVENT, Object.assign({}, dragInfo, {clientX, clientY}));
|
||||
cleanUpPositionLog();
|
||||
|
||||
const velocity = {
|
||||
x: fitLineToVelocity('x'),
|
||||
y: fitLineToVelocity('y'),
|
||||
z: fitLineToVelocity('z'),
|
||||
};
|
||||
|
||||
draggedElement.emit(
|
||||
DRAG_END_EVENT,
|
||||
Object.assign({}, dragInfo, {clientX, clientY, velocity})
|
||||
);
|
||||
|
||||
removeDragListeners && removeDragListeners(); // eslint-disable-line no-unused-expressions
|
||||
removeDragListeners = undefined;
|
||||
}
|
||||
|
@ -487,4 +578,4 @@ export default function aframeDraggableComponent(aframe, componentName = COMPONE
|
|||
didMount(this, THREE, componentName);
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue