I have an svg rectangle with 4 resizers on its 4 edges, both the rectangle and the resizers have their onmousedown/onmousemove/onmouseup event listeners.
When I resize the rectangle from the resizers the onmousemove of the resizer doesn't stop when I stop resizing the element or maybe the onmouseup is not triggered.
Here is my code:
The rectangle events used for drag and drop:
onMouseDown = (e) => {
if ( this.state.isDraggable ) {
document.addEventListener('mousemove', this.onMouseMove);
this.coords = {
x: e.clientX,
y: e.clientY
}
}
}
onMouseUp = (e) => {
// this.props.updateStateDragging(this.props.id, false);
document.removeEventListener('mousemove', this.onMouseMove);
this.coords = {};
}
onMouseMove = (e) => {
const xDiff = this.coords.x - e.clientX;
const yDiff = this.coords.y - e.clientY;
this.coords.x = e.clientX;
this.coords.y = e.clientY;
this.setState({
x: this.state.x - xDiff,
y: this.state.y - yDiff,
});
}
The resizer events used to resize the rectangle:
onMouseDown = (e) => {
document.addEventListener('mousemove', this.onMouseMove);
this.props.updateStateResizing(this.props.id, true);
this.props.updateStateDragging(this.props.id, false);
}
onMouseMove = (e) => {
if ( this.props.isResizing ){
this.props.nodeResizer(this.props.id, e.target, e.clientX, e.clientY);
}
}
onMouseUp = (e) => {
document.removeEventListener('mousemove', this.onMouseMove.bind(this));
if ( this.props.isResizing ){
this.props.updateStateResizing(this.props.id, false);
}
}
What am I doing wrong? How can it fix it?
The call to remove the "mousemove" listener must supply the same function object as supplied when adding the listener. However
this.onMouseMove
is not the same function object as
this.onMouseMove.bind(this)
Try removing .bind(this) in the onMouseUp code to start with.
Technical Note
Rebinding an arrow function does not change the this value seen within the arrow function.
Arrow functions always use the lexical this value that was in effect at the time they were defined. Syntactically you can call bind on an arrow function - they are function objcts and inherit from Function.prototype - but the arrow function never uses the this value supplied to bind.
Try using settimeout wherever you are using document.removeEventListener like:
setTimeout(() => {
document.removeEventListener('mousemove', this.onMouseMove.bind(this));
}, 500);
onmousedown = (e) => {
console.log("D>>>>>", e);
}
onmousemove = (e) => {
console.log("M>>>>>", e);
}
onmouseup = (e) => {
console.log("U>>>>>", e);
}
example
Related
I want to make a border like Leetcode where i can adjust left and right containers. I have googled but I could find much about my problem.
I have the following code
const divider = document.getElementById('divider');
const [divPosition, setDivPosition] = useState(window.innerWidth - 700);
useEffect(() => {
function resize(e: MouseEvent) {
console.log(e.x);
const dx = window.innerWidth - e.x;
console.log(dx);
setDivPosition(e.x);
// if (divider) divider.style.width = dx + 'px';
}
if (divider)
divider?.addEventListener(
'mousedown',
(e) => {
if (e.offsetX < 6) {
logger(e.x, String(e.offsetX));
// setDivPosition(e.offsetX);
document.addEventListener('mousemove', resize, false);
}
},
false
);
document.addEventListener(
'mouseup',
function () {
document.removeEventListener('mousemove', resize, false);
},
false
);
return () => {
if (divider)
divider.removeEventListener('mousedown', (e) => {
if (e.offsetX < 6) {
logger(e.offsetX, 'offset is');
// logger(e.x, String(e.offsetX));
// setDivPosition(e.offsetX);
// document.addEventListener('mousemove', resize, false);
}
});
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divider]);
But it has delays and more I resize slower it gets and border gets behind mouse. Moreover, when I click and start to resize, cursor changes to normal cursor.
How can I make it exactly like leetcode's? and any resources?
I have a found a great answer for y problem: Split.js
So I am currently trying to create a notebook styled module for a web application. Currently it is working perfectly fine with its mouse event but not when using touch events.
//TOUCH LISTENER
theCanvas.addEventListener("touchstart", startT, false);
theCanvas.addEventListener("touchend", endT, false);
theCanvas.addEventListener("touchmove", moveT, false);
//MOUSE LISTENER
theCanvas.addEventListener("mousedown", start);
theCanvas.addEventListener("mouseup", end);
theCanvas.addEventListener("mousemove", move, false);
//MOUSE EVENTS
function start(event) {
if (event.button === MAIN_MOUSE_BUTTON) {
shouldDraw = true;
setLineProperties(theContext);
theContext.beginPath();
let elementRect = event.target.getBoundingClientRect();
theContext.moveTo(event.clientX - elementRect.left, event.clientY - elementRect.top);
console.log(PenType(mode));
}
}
function end(event) {
if (event.button === MAIN_MOUSE_BUTTON) {
shouldDraw = false;
}
}
function move(event) {
if (shouldDraw) {
let elementRect = event.target.getBoundingClientRect();
theContext.lineTo(event.clientX - elementRect.left, event.clientY - elementRect.top);
theContext.stroke()
}
}
//TOUCH EVENTS
function startT(event) {
event.preventDefault();
shouldDraw = true;
setLineProperties(theContext);
theContext.beginPath();
let elementRect = event.target.getBoundingClientRect();
theContext.moveTo(event.clientX - elementRect.left, event.clientY - elementRect.top);
console.log(PenType(mode));
}
function endT(event) {
if (event.touches.length == 1) {
event.preventDefault();
shouldDraw = false;
}
}
function moveT(event) {
if (shouldDraw) {
event.preventDefault();
let elementRect = event.target.getBoundingClientRect();
theContext.lineTo(event.clientX - elementRect.left, event.clientY - elementRect.top);
theContext.stroke()
}
}
While the mouse event is fully functional and allow drawing on the canvas. The touch events has only the a small space near the left and top border of the canvas that can be drawn on, and it is only when u tap on the border, just a dot. It should have worked like the mouse event
Since touch event can handle multiple touch points, the x and y of the fingers are not described by event.clientX and event.clientY like for mouse. Instead you got event.touches which is an array of coordinates. So in your code, everytime you want to handle touch event, just replace event.clientX by event.touches[0].clientX and event.clientY by event.touches[0].clientY (considering you only want to handle one finger of course)
I have a function that activates the dragControls, while the "m" key is pressed. For some reason it is not deactivated when the "m" is unpressed. How can I disable the dragControls?
I have tried to encapsulate the dragControls to be activated if the statement is true, else dragControls = null. But whenever the first activation happens, it is not deactivated, even when the statement is false.
While and do while loops are just freezing the browser.
init() {
// EVENT LISTENERS:
map.addEventListener('mousedown', this.movePoi, false);
document.addEventListener('keydown', this.onDocumentKeyDown, false);
document.addEventListener('keyup', this.onDocumentKeyUp, false);
},
// HELPER FUNCTIONS:
onDocumentKeyDown(event) {
let keycode = event.which;
if (keycode === 77) {
this.moveIt = true;
this.controls.enabled = false;
console.log("Key is pressed");
console.log(this.moveIt);
}
},
onDocumentKeyUp(event){
let keycode = event.which;
console.log(keycode);
if (keycode === 77) {
this.moveIt = false;
this.controls.enabled = true;
console.log("Key is unpressed");
console.log(this.moveIt);
}
},
mouseOverScene (event) {
event.preventDefault();
let rect = event.target.getBoundingClientRect();
let x = event.clientX - rect.left;
let y = event.clientY - rect.top;
this.mouse.x = ( x / this.mapWidth) * 2 - 1;
this.mouse.y = - ( y / this.mapHeight ) * 2 + 1;
this.rayCaster.setFromCamera(this.mouse, this.camera);
},
//POI movement around the scene:
movePoi (event) {
event.preventDefault();
let controlsDrag;
if (this.moveIt) {
controlsDrag = new DragControls(this.spheres, this.camera, this.renderer.domElement);
} else {
controlsDrag = null;
}
}
EXPECTED: the objects should be dragged around by the left-click mouse, while the "m" key is pressed (the orbitControls are also disabled when this happens. This part works fine). When the "m" is not pressed, they should return the undraggable state and the orbitControls are enabled again.
ACTUAL: All of the above happens, BUT the objects are still draggable after the "m" is unpressed. The orbitControls are obviously enabled, which brings the whole next level of haywireness happening on the screen.
Not tested but you should try calling the movePoi function at the end of onDocumentKeyUp. At a glance it seems like the verification of whether "m" is pressed or not is only made when the mouse left button is clicked and not when the "m" key is unpresssed. Hope that helps.
So the solution goes as following:
init() {
this.controlsDrag = new DragControls(this.spheres, this.camera, this.renderer.domElement);
this.controlsDrag.deactivate();
// EVENT LISTENERS:
map.addEventListener('mousedown', this.startMovePoi, false);
this.controlsDrag.addEventListener('mouseup', this.stopMovePoi,false);
},
//POI movement around the scene:
startMovePoi () {
let controls = this.controls;
this.controlsDrag.activate();
this.controlsDrag.addEventListener('dragstart', function () {
controls.enabled = false;
});
this.controlsDrag.addEventListener('dragend', function () {
controls.enabled = true;
});
},
stopMovePoi(){
this.controlsDrag.deactivate();
}
Needed some code refactoring before.
Given: the reactive extensions drag and drop example , how would you subscribe to just a drop event?
I have modified the code to subscribe to a 'completed' callback, but it does not complete.
(function (global) {
function main () {
var dragTarget = document.getElementById('dragTarget');
var $dragTarget = $(dragTarget);
// Get the three major events
var mouseup = Rx.Observable.fromEvent(document, 'mouseup');
var mousemove = Rx.Observable.fromEvent(document, 'mousemove');
var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown');
var mousedrag = mousedown
.filter(function(md){
//console.log(md.offsetX + ", " + md.offsetY);
return md.offsetX <= 100
||
md.offsetY <= 100;
})
.flatMap(function (md) {
// calculate offsets when mouse down
var startX = md.offsetX, startY = md.offsetY;
// Calculate delta with mousemove until mouseup
return mousemove.map(function (mm) {
mm.preventDefault();
return {
left: mm.clientX - startX,
top: mm.clientY - startY
};
}).takeUntil(mouseup);
});
// Update position
var subscription = mousedrag.subscribe(
function (pos) {
dragTarget.style.top = pos.top + 'px';
dragTarget.style.left = pos.left + 'px';
},
function(errorToIgnore) {},
function() { alert('drop');});
}
main();
}(window));
I have read that hot observables, such as those that have been created from mouse events, never 'complete'. Is this correct? How can I otherwise get a callback on 'drop'?
Something like this should do the trick.
(function (global) {
function main () {
var dragTarget = document.getElementById('dragTarget');
// Get the three major events
var mouseup = Rx.Observable.fromEvent(document, 'mouseup');
var mousemove = Rx.Observable.fromEvent(document, 'mousemove');
var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown');
var drop = mousedown
.selectMany(
Rx.Observable
.concat(
[
mousemove.take(1).ignoreElements(),
mouseup.take(1)
]
)
);
}
main();
}(window));
Edit:
If you think of an observable as an asynchronous function which yields multiple values, and then possibly completes or errors, you'll immediately recognize that there can only be one completion event.
When you start composing multiple function, the outer-most function still only completes once, even if that function contains multiple functions inside of it. So even though the total number of "completions" is 3, the outer-most function still only completes once.
Basically, that means if the outer-most function is suppose to return a value each time a drag completes, you need a way of actually doing that. You need to translate the drag completion into an "onNext" event for the outer-most observable.
ANY which way you can do that is going to get you what you want. Maybe that's the only kind of events that the outer-most function returns, or maybe it also returns drag starts and moves, but so long as it returns the drag completions, you'll end up with what you need (even if you have to filter it later).
The example I've given above is just one way to return the drag drops in the outer-most observable.
OP's link to the official example was down, it's here:
https://github.com/Reactive-Extensions/RxJS/blob/master/examples/dragndrop/dragndrop.js
There are two solutions to the original question, using either the primitive mouse events expanding upon the official example, or the native HTML5 drag and drop events.
Composing from 'mouseup', 'mousedown' and 'mousemove'
First we use mousedown1.switchMapTo(mousemove.takeUntil(mouseup).take(1)) to get the 'drag start' stream. switchMapTo (see doc) uses a combination of flattening (mapping 'mousedown1' to each of the first emit of 'mouse moving until mouse up', aka 'dragging') and switch (see doc), giving us the latest mousedown1 emit followed by dragging, i.e. not just any time you click your mouse on the box.
Another 'mousedown' stream mousedown2 is used to compose a mousedrag stream so we can constantly render the box while dragging. mousedown1 above is prioritised over mousedown2. See https://stackoverflow.com/a/35964479/232288 for more information.
Once we have our 'drag start' stream we can get the 'drag stop' stream via: mousedragstart.mergeMapTo(mouseup.take(1)). mergeMapTo (see doc) maps 'mousedragstart' to each of the first emit of 'mouseup', giving us the latest 'mousedragstart' followed by 'mouseup', which is essentially 'drag stop'.
The demo below works with RxJS v5:
const { fromEvent } = Rx.Observable;
const target = document.querySelector('.box');
const events = document.querySelector('#events');
const mouseup = fromEvent(target, 'mouseup');
const mousemove = fromEvent(document, 'mousemove');
const [mousedown1, mousedown2] = prioritisingClone(fromEvent(target, 'mousedown'));
const mousedrag = mousedown2.mergeMap((e) => {
const startX = e.clientX + window.scrollX;
const startY = e.clientY + window.scrollY;
const startLeft = parseInt(e.target.style.left, 10) || 0;
const startTop = parseInt(e.target.style.top, 10) || 0;
return mousemove.map((e2) => {
e2.preventDefault();
return {
left: startLeft + e2.clientX - startX,
top: startTop + e2.clientY - startY
};
}).takeUntil(mouseup);
});
// map the latest mouse down emit to the first mouse drag emit, i.e. emits after pressing down and
// then dragging.
const mousedragstart = mousedown1.switchMapTo(mousemove.takeUntil(mouseup).take(1));
// map the mouse drag start stream to first emit of a mouse up stream, i.e. emits after dragging and
// then releasing mouse button.
const mousedragstop = mousedragstart.mergeMapTo(mouseup.take(1));
mousedrag.subscribe((pos) => {
target.style.top = pos.top + 'px';
target.style.left = pos.left + 'px';
});
mousedragstart.subscribe(() => {
console.log('Dragging started');
events.innerText = 'Dragging started';
});
mousedragstop.subscribe(() => {
console.log('Dragging stopped');
events.innerText = 'Dragging stopped';
});
function prioritisingClone(stream$) {
const first = new Rx.Subject();
const second = stream$.do(x => first.next(x)).share();
return [
Rx.Observable.using(
() => second.subscribe(() => {}),
() => first
),
second,
];
}
.box {
position: relative;
width: 150px;
height: 150px;
background: seagreen;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.3/Rx.js"></script>
<div class="box"></div>
<h3 id="events"></h3>
Composing from HTML5 native 'dragstart', 'dragover' and 'drop'
The native events make things a little easier. Just make sure the dragover observer calls e.preventDefault() so the body element can become a valid drop zone. See more info.
We use switchMap (see doc) in a similar fashion as how switchMapTo is used above: flatten and map the latest 'dragstart' to the latest 'drop' to get our 'drag then drop' stream.
The difference is only when the user drops the div do we update the position.
const { fromEvent } = Rx.Observable;
const target = document.querySelector('.box');
const events = document.querySelector('#events');
const dragstart = fromEvent(target, 'dragstart');
const dragover = fromEvent(document.body, 'dragover');
const drop = fromEvent(document.body, 'drop');
const dragthendrop = dragstart.switchMap((e) => {
const startX = e.clientX + window.scrollX;
const startY = e.clientY + window.scrollY;
const startLeft = parseInt(e.target.style.left, 10) || 0;
const startTop = parseInt(e.target.style.top, 10) || 0;
// set dataTransfer for Firefox
e.dataTransfer.setData('text/html', null);
console.log('Dragging started');
events.innerText = 'Dragging started';
return drop
.take(1)
.map((e2) => {
return {
left: startLeft + e2.clientX - startX,
top: startTop + e2.clientY - startY
};
});
});
dragover.subscribe((e) => {
// make it accepting drop events
e.preventDefault();
});
dragthendrop.subscribe((pos) => {
target.style.top = `${pos.top}px`;
target.style.left = `${pos.left}px`;
console.log('Dragging stopped');
events.innerText = 'Dragging stopped';
});
.box {
position: relative;
width: 150px;
height: 150px;
background: seagreen;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.3/Rx.js"></script>
<div class="box" draggable="true"></div>
<h3 id="events"></h3>
I'm trying to have a selection wheel appear when the user holds down the Shift key.
The wheel should be centred on the mouse's position.
However when I test this, pageX and clientX are both undefined on the event object.
Is it possible to get the mouse coordinates on a keyboard event?
No, simply track mousemove events and continuously save the current position in case you get a keyboard event.
Cache mouse position in a global variable in every mousemove event and use it when a key event fires:
var mousePosition = {x:0, y:0};
$(document).bind('mousemove',function(mouseMoveEvent){
mousePosition.x = mouseMoveEvent.pageX;
mousePosition.y = mouseMoveEvent.pageY;
});
$(document).bind('keyup', function(keyUpEvent){
$('body').append($('<p/>').text('x:' + mousePosition.x + ' * y: ' + mousePosition.y));
});
JSBIN: http://jsbin.com/uxecuj/4
JavaScript without jQuery:
var mousePosition = {x:0, y:0};
document.addEventListener('mousemove', function(mouseMoveEvent){
mousePosition.x = mouseMoveEvent.pageX;
mousePosition.y = mouseMoveEvent.pageY;
}, false);
document.addEventListener('keyup', function(keyUpEvent){
var divLog = document.querySelector('#log'),
log = 'x:' + mousePosition.x + ' * y: ' + mousePosition.y,
p = document.createElement('p').innerHTM = log;
divLog.appendChild(p);
}, false);
Here's the POJS equivalent of other answers that is cross browser back to IE 6 (and probably IE 5 but I don't have it to test any more). No global variables even:
function addEvent(el, evt, fn) {
if (el.addEventListener) {
el.addEventListener(evt, fn, false);
} else if (el.attachEvent) {
el.attachEvent('on' + evt, fn);
}
}
(function () {
var x, y;
window.onload = function() {
addEvent(document.body, 'mousemove', function(e) {
// Support IE event model
e = e || window.event;
x = e.pageX || e.clientX;
y = e.pageY || e.clientY;
});
// Show coords, assume element with id "d0" exists
addEvent(document.body, 'keypress', function() {
document.getElementById('d0').innerHTML = x + ',' + y;
});
}
}());
But there are bigger issues. Key events are only dispatched if an element that can receive keyboard input is focused (input, textarea, and so on). Also, if the user scrolls the screen without moving the mouse, the coordinates will probably be wrong.
An alternative solution is to use CSS to replace the cursor with a custom animation.
If you're using jQuery, you can do the following (assuming you have an image with id="wheelImage" and whose position is set to absolute), write the following inside your keydown event. Here we use the global properties pageX and pageY that are passed to any handler. You can also use jQuery's shiftKey property to check if the shift key has been pressed.
$().keydown(function(e) {
if (e.shiftKey) {
e.preventDefault();
$('#wheelImage').css('left',e.pageX ).css('top', e.pageY);
}
});
Cache the mouse position.
var x = 0, y = 0;
document.addEventListener('mousemove', function(e){
x = e.pageX
y = e.pageY;
}, false);
document.addEventListener('keyup', function(e){
console.log(x + ' ' + y);
}, false);
Or with JS Ninja Library.
var x = 0, y = 0;
$(document).mousemove(function(e){
x = e.pageX
y = e.pageY;
});
$(document).keypressed(function() {
console.log(x + ' ' + y);
});