I am trying to set up dynamic draggable element which will move its anchor point according to the drag delta. However I cannot seem to get the delta using event.movementX or event.movementY.
My simple drag event code:
mainMenuDiv.addEventListener("drag", (event)=>{
console.log(event.movementX, event.movementY);
});
// The console print out is simply:
//>0 0
// This prints out a lot of times as i drag the mouse but doesn't change the properties.
I think i am missing something about the way the movement property works with drag events.
I couldn't get it to work either, so I just did the following:
var start = null, delta = null
el.addEventListener('dragstart', e => {
start = {x: e.clientX, y: e.clientY}
})
el.addEventListener('drag', e => {
delta = {x: e.clientX - start.x, y: e.clientY - start.y}
})
el.addEventListener('dragend', e => start = delta = null)
You can use e.nativeEvent.offsetX and e.nativeEvent.offsetY, too.
I don't know the browser support though.
Related
I have a button and an image.
When I click the button, I want it to go to a "wait mode" of sorts.
Waiting for two separate clicks, that both return the x, y value of the mouse click events.
I got the mouse xy part no problem but at loss for what RxJS operator to use next
const elem = document.getElementById("MyImage");
const root = fromEvent(elem, "click");
const xy = root.pipe(map(evt => xyCartoPos(elem, evt)));
xy.subscribe(coords => console.log("xy:", coords));
function xyCartoPos(elem, e) {
const elemRect = elem.getBoundingClientRect();
return {
x: e.clientX - elemRect.x - elemRect.width / 2,
y: flipNum(e.clientY - elemRect.y - elemRect.height / 2)
};
}
You can use bufferCount to emit a fixed number of clicks at once (in one array).
const xy = root.pipe(
map(evt => xyCartoPos(elem, evt)),
bufferCount(2),
//take(1) // use take(1) if you only want to emit one pair of clicks and then complete
);
You could use scan to collect the events as an array, then use filter to verify the length of the array is 2:
const xy = root.pipe(
map(evt => xyCartoPos(elem, evt)),
scan((acc, evt) => {
acc.push(evt);
return acc;
}, []),
filter(events => events.length == 2),
);
This will cause only an array with the two mouse events, after two clicks, to be published to the subscriber.
I want to rotate an object3D with hammerjs gestures.
Basically the rotation work, but with two issues I can't figure out.
The direction of the rotation changes randomly. It turns left. I stop and than move my fingers in the same diction again and suddenly instead of continuing to rotate left its going right. Happens sometimes but not always.
Once the rotation is started it only rotates to the same direction, despite me moving my fingers to different directions.
Here is how I handle the rotation:
public rotateObject3D (e: HammerInput): void {
if (rotationEnabled) {
const translation = new THREE.Vector3();
const rotation = new THREE.Quaternion();
const scale = new THREE.Vector3();
const rotateMatrix = new THREE.Matrix4();
const deltaRotationQuaternion = new THREE.Quaternion();
this._myObject.matrix.decompose(translation, rotation, scale);
this._deltaRot = (e.rotation * 0.01);
deltaRotationQuaternion.setFromEuler(new THREE.Euler(
0,
this._deltaRot * (Math.PI / 180),
0,
'XYZ'
));
deltaRotationQuaternion.multiplyQuaternions(deltaRotationQuaternion, rotation);
this._myObject.matrix = rotateMatrix.compose(translation, deltaRotationQuaternion, scale);
}
}
And this is the call of it:
this._hammerManager.on('rotate', (e) => {
this._arTools.rotateObject3D(e);
});
Is there anything I am missing?
It looks like you're using the absolute rotation value, instead of the change in rotation. For instance, consider the following scenario:
Rotation1: 12°
Rotation2: 10°
Rotation3: 5°
By multiplying your quaternions, your object is being rotated to 12°, then 22°, then finally 27°, even though you were turning back towards 0. This is because you're adding the new rotation to the last rotation on each event.
What you should do is save the previous rotation value, and subtract it from the new one, to get the rotation delta:
var previousRot = 0;
var newRot = 0;
rotateObject3D(e) {
newRot = e.rotation - previousRot;
// You could use newRot for your quaternion calculations
// but modifying .rotation.y is simpler
myObject.rotation.y += newRot
// Save value to be used on next event
previousRot = newRot;
}
With this method, the scenario above will give you the change in rotation. Your object will first rotate by +12°, then -2°, then -5°, for a more natural behavior.
Just make sure to reset previousRot = 0 on HammerJS' rotateend event so you don't use the value from previous gestures when a new one begins.
I'm trying to make a simple canvas-drawing app using Bacon.js. It should allow to draw a line by selecting start and end point using mouse clicks like this.
points = paper.asEventStream('click')
.map((e) -> x: e.pageX, y: e.pageY)
.bufferWithCount(2)
So far so good. Now it should allow to cancel starting point selection by clicking Esc.
cancel = doc.asEventStream('keypress')
.map((e) -> e.keyCode)
.filter((key) -> key is 27)
.map(true)
What is the baconian way of restarting buffering?
UPD: The implementation I've got so far looks awful.
points
.merge(cancel)
.buffer(undefined, (buffer) ->
values = buffer.values
if values.some((val) -> val is true) then values.length = 0
else if values.length is 2 then buffer.flush())
You can start the buffering from cancel:
var filteredPairs = cancel.startWith(true).flatMapLatest(function() {
return points.bufferWithCount(2)
})
See jsFiddle
I asynchronously receive new positions of element. Each time I receive the position I calculate the offset and move the element (set), like this:
asynchReceiveData(id,function(newposition){
var offset = {};
var oldposition = getOldPosition(markerArray[id]); //returns object with old x and y
offset.x = (newposition.x - oldposition.x);
offset.y = (newposition.y - oldposition.y);
markerArray[id].entireSet.stop()
.animate({ transform:"...T"+offset.x+","+offset.y }, 400);
//i also tried without .stop()
});
For example:
with each update the set should move 50px to the right, after 10 slow updates (lets say one update per 2 seconds), the set is 500px to the right, everything is OK.
Problem is, when I receive too many new positions too fast:
(e.g. one update per 200ms),then the set is 300, or 350 or 400 or 450 instead of 500px to the right.
I think the problem is that the animation does not have enough time to finish before new animation is triggered. I tried lowering the animation time from 400ms to 200, but with little success, it still sometimes happened.
Everything works fine, when I do not use animation and do just this:
markerArray[id].entireSet.transform("...T"+offset.x+","+offset.y);
But I would like to have this animations. Do you have any suggestion how to fix this?
So.. after few tries I came to a solution:
After the end of every animation I check the real position of element (with getBBox()) and compare it with expected position. If it differs I move the element by the difference; In code:
asynchReceiveData(id,function(newposition){
var offset = {};
var oldposition = getOldPosition(markerArray[id]); //returns object with old x and y
offset.x = (newposition.x - oldposition.x);
offset.y = (newposition.y - oldposition.y);
markerArray[id].entireSet.stop().animate({ transform:"...T"+offset.x+","+offset.y}, 500,
function () {
var o = {};
o.x = markerArray[id].x - markerArray[id].circleObj.getBBox().cx;
o.y = markerArray[id].y - markerArray[id].circleObj.getBBox().cy;
markerArray[id].entireSet.transform("...T"+o.x+","+o.y);
});
});
Sometimes it's not very smooth (a bit laggy), but anyway, it solves the problem.
I am new to Reactive Extensions and JavaScript. Can someone help me unscrew the following code? It's from Matthew Podwysocki's Introduction to the Reactive Extensions to JavaScript.
<html>
<head>
<title>Learning ReactiveExtensions</title>
<!--scripts-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<script src="rx.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
var mouseDragMe = $("#mouseDragMe").context;
var mouseMove = Rx.Observable.FromHtmlEvent(mouseDragMe, "mousemove");
var mouseUp = Rx.Observable.FromHtmlEvent(mouseDragMe, "mouseup");
var mouseDown = Rx.Observable.FromHtmlEvent(mouseDragMe, "mousedown");
var mouseMoves = mouseMove
.Skip(1)
.Zip(mouseMove, function(left, right) {
return { x1 : left.clientX,
y1 : left.clientY,
x2 : right.clientX,
y2 : right.clientY };
});
var mouseDrags = mouseDown.SelectMany(function(md) {
return mouseMoves.TakeUntil(mouseUp);
mouseDrags.Subscribe(function(mouseEvents) {
$("#results").html(
"Old (X: " + mouseEvents.x1 + " Y: " + mouseEvents.y1 + ") " +
"New (X: " + mouseEvents.x2 + " Y: " + mouseEvents.y2 + ")");
});
});
});
</script>
</head>
<body>
<div id="mouseDragMe" style="border:solid 1px red;">
i am a rx newbie
</div>
</body>
</html>
There are descriptions of this out there. I would recommend looking at this video about Writing your first Rx Application. The code's in C# but the concepts are exactly the same regardless of the programming language.
Essentially you want to understand 3 things
Conceptualizing of a series of events as a Sequence. Just like an
array is a sequence of data in space, events can be thought of as a
sequence of data in time (or in motion).
The operators (i.e. Skip, Zip, SelectMany and TakeUntil)
Subscription semantics
In this scenario we have 3 source sequences; mouseMove, mouseUp and mouseDown.
The mouseMove sequence will push a value of the mouse coordinates each time the mouse is moved. For example if the mouse started at the top left corner of the screen and moved diagonally down then straight across you may see values like {0,0}, {10,10}, {20,10} published on the sequence.
The values that mouseUp and mouseDown publish are not interesting, just the point in time that they are published is interesting.
The actual problem of "dragging" requires us to know when the mouse button is pressed and the delta of where the mouse was when it was pressed, and where it was when the button was released. The way we can get the delta of these positions is to take values of the final position and minus the values of the original position. Even better is if we can get all of the intermediate delta values so we can animate the movement. If we take our sequence above, to get a delta of movements we want to have the original sequence and an off by one sequence
Original { 0, 0}, {10,10}, {20,10}
offby1 {10,10}, {20,10}
This allows us to calculate the deltas to figure out the movement (not just the location).
Original { 0, 0}, {10,10}, {20,10}
offby1 {10,10}, {20,10}
delta {10,10}, {10, 0}
The way we can achieve this with Rx is first to use Skip(1) to skip one value. This creates our offby1 sequence. Next we want to combine values in pairs. The Zip function gives us this (more in my blog post about Combining sequences with Zip).
We could rewrite the code you have above
var mouseMoves = mouseMove
.Skip(1)
.Zip(mouseMove, function(left, right) {
return { x1 : left.clientX,
y1 : left.clientY,
x2 : right.clientX,
y2 : right.clientY };
});
To be
var offby1 = mouseMove.Skip(1);
var mouseMoves = offby1.Zip(mouseMove, function(left, right) {
return { x1 : left.clientX,
y1 : left.clientY,
x2 : right.clientX,
y2 : right.clientY };
});
Once we have the pairs, we need to apply the simple math of newValue-OldValue=delta.
This gives us our delta sequence which is effectively our Movement sequence.
var offby1 = mouseMove.Skip(1);
var mouseMoves = offby1.Zip(mouseMove, function(current, last) {
return { x : current.clientX-last.clientX,
y : current.clientY-last.clientY };
});
Now we only want to get values when the mouse is down. To do this we use the SelectMany operator. This says for each value from the source, get 0 or more values from this source. In our case each time a mouseDown event happens we want to get all the delta events (as mouseMoves).
var mouseDrags = mouseDown.SelectMany(function(md) { return mouseMoves;});
However we only want to keep receiving them until a corresponding mouseUp event happens. To do this we TakeUntil the mouseUp event produces a value.
var mouseDrags = mouseDown.SelectMany(function(md) {
return mouseMoves.TakeUntil(mouseUp);
});
Now that we have all this plumbing done, we usually apply those movements to the Position/Margin/offset of a UI element. It appears in the example, we just print the value though.