I'm trying to implement a drag and drop script and have hit the wall with one problem. When you take an item and start dragging it - the item is directly below your cursor and onmouseover event is very rarely fired on the items below. But i want other items to highlight when i drag something over them. One of the solutions would be not to drag anything at all - that way the mouse events would work, but that would look ugly. Has anyone ever done something like this and know how to overcome this problem?
If you're thinking about suggesting some JQuery plugin or something like that - please don't. I don't need a completed solution, this is educational.
IMO, in order to have the mouseover event to be fired up frequently would be binding the mouseover event to the parent element of all the affected elements, or perhaps to the document itself, since events get bubbled up, they are probably the only elements that can fire the mouseover events.
Then further, write a hit method in your mouseover event and actively check the position of your mouse cursor, see whether it's going under the target element's boundary. Tradeoff in usability and performance. You choose.
My 2cents.
Or perhaps, you can reverse engineer jQuery UI to see how they implement the drag element. Haven't check thou, but I think there should be a wiser way.
Related
So... I am working on an interaction design project and I want to create a sencha-style gesture scroll for content areas. The way I've done it so far, is to attach touchmove/start/end events to the content area, and it translateY's the contents. It works in on desktop with mousemove/up/down events, but it jumps around like crazy with touch. I'm not sure whats wrong.. here is a link to a prototype.
**requires webkit.
http://apps.omorphos.com/gesture-scroll
I think it is an issue with the event response, but I tried and haven't been able to nail it down. Any help is greatly appreciated!
So, I figured this out.
What I was doing was attaching the touch event to the list tag itself.. and, that works fine on desktop with mouse events; however, with touch, the target changes and touchend doesn't fire properly.
So, what I did, and what I believe sencha does, ... and I had originally implemented but went in a different direction... is have an absolutely positioned element with a transparent background color floating above the element that will be manipulated. All of the touch events will be captured by that DIV and then the elements below can be manipulated without losing the event data.
In the new version I used HammerJs ... more info: http://eightmedia.github.io/hammer.js/
but i'm pretty sure you could just use standard events; but the good thing about hammer js, is that it will translate touch events to mouse events for testing in the browser, this also means making the coordinates for touch the same as mouse, so you can access mouse event coords via
e.gesture.touches[0].pageX
e.gesture.touches[0].pageY
which let's you write less code.
Part 2:
Additionally... part of the issue is... how do you click on the content/components(e.g. links) below the screen.
How you do this... is you take the coords from the event handler and pass them through this native Javascript function...
var a = document.elementFromPoint(x, y);
this will return the dom element. and all you have to do is trigger the click/tap event.
Which would be great, except it will pick the element with the highest z-index.. so that is your screen obj(the one that is capturing all of the touch events). So, what you need to do, is hide the screen after a tap is registered, and then execute this function 200ms later, and then bring back the screen to capture whatever events.
You can do this with this function...
$(theScreen).on('tap', function(e){
screen.hide();
var hit = document.elementFromPoint(e.gesture.touches[0].pageX, e.gesture.touches[0].pageY);
$(hit)[0].tagName !=="A" || $(hit).trigger('click');
setTimeout(function(){screen.show()},300);
});
And, that is how I solved it!
My code is not super annotated, but you can find it at the link below:
Updated example:
http://apps.omorphos.com/gesture-scroll/v2/
I was trying to answer an issue with custom drop down, but challenged by an inconsistent behavior in Chrome and Firefox.
DEMO: http://jsfiddle.net/fyeht/ [Added scroll event for more clarity]
See below image, The list items can be navigated using arrow keys.
To Reproduce the issue:
Open console in Chrome (F12)
Click on an item in the list (you would notice some events getting logged in the console)
Use down arrow key to navigate to the next item in the list
Finally, the issue is noticed when you reach the last item in the view and hitting down arrow would scroll. Check the log to see 'scroll', 'mouse enter' and 'mouse move' [check the new demo]
The issue is after reaching the end of items in view, it scrolls. Even though the mouse is untouched, it triggers mouseenter and mousemove events in Chrome. In FF, on scroll it triggers just the mouseenter which make sense.
Question(s):
Why is mousemove triggered when mouse is untouched?
Is this just browser inconsistency? Could not find documentation on events triggered when scrolling? (never knew it did)
Submitted a bug report: https://code.google.com/p/chromium/issues/detail?id=241476
In your example, I see that both Chrome and FF are firing mouseenter DOM events whenever the mouse is left hovering over the <ul> and pressing the key down triggers the browser to scroll in order to bring the selected <li> into view.
However, only Chrome is additionally triggering mousemove events. One obvious difference already in the mouseenter event objects that the two throw is that for Chrome, MouseEvent.offsetX and MouseEvent.offsetY values are included, whereas in FF these properties are undefined. So, when that enter is triggered Chrome has already decided the mouse "has moved".
Since the MouseEvent.screenX and MouseEvent.screenY event context values do not change between scroll-triggered MouseEvent instances, one could perhaps distinguish between an "artificial" mouseenter / mousemove event and an "authentic" one by storing these values from prior events.
DOM Event Specification
The DOM Level 2 Event Specification for mousemove reads:
The mousemove event occurs when the pointing device is moved while it is over an element.
The Level 3 spec (working draft) is essentially the same:
A user agent must dispatch this event when a pointing device is moved while it is over an element.
Seems like it could down to whether one interprets "is moved" relatively or not.
Also, in the section of the Level 3 spec on mouse event order, it states that when a pointer is moved into an element, it triggers mouseover, mouseenter, and mousemove, in that order. Every case that is specified there always has those three together, so perhaps one might interpret it that if you are going to trigger the mouseenter event, you should also be triggering the mousemove event which corresponds to entering the element.
This is a nice demo.
In Chrome mouse movement for elements is definitely relative. In chrome I can get keydown and scroll events only if the mouse pointer is over the scroll bar. I can also get scroll only events if i use the wheel to scroll and leave the mouse over the scroll bar. It is and isn't very odd that scroll by dragging causes "mouse move", and "mouse over" events.
Not only are mouse move and mouse over events produced in profusion by the browser they are not a very good indication of a users intent. Infact these events are a useful entropy source.
To the degree that there are some minor differences this is in the context of how useful these "micro events" are individually. To work with them you must devise a way to filter them for user intention you want to link to higher level actions. Any reasonable method you choose to make sense of these events will probably detect these move - on scroll events as garbage. This is where your point is really worth noting and taking under consternation.
A first stage would be to Filter out events based on the elements and values of coordinates. It might help to create a state machine model. You might register and registered handlers in response to other events. You've identified this case where you'd want to change responsive state if or reaction criteria if a key element has a scroll bar. IF an element or it's parent has a vertical scroll bar throw out mouse moves with relatively high X values.
You might want to also ignore mouse overs if it's fired with a mouse move in that context. It starts to become impractical to handle each of these micro event one at a time even if you are changing state by registering or deregistering handlers. More information can be extracted by creating an event sequence fifo buffer.
Register event handlers to add a new event to the buffer. You might also want to collect information from timer events in this buffer to establish more context. You might create an object that keeps a fifo in an array. This would be like a queue but not in the sense of it being a place where the events are waiting to be processed. Instead your program is waiting to calculate patterns in the buffer and based on the patterns fire higher level events, accept or reject different types of events and extend, contract or save the contents of the buffer. You can then evaluate move events for changes in x and y and also create conditions given the patter of scroll mouse over and mouse move events you've demonstrated.
I really doubt there's a browser inconsistency here. You should create a mousemove event that prints out the x and y coordinate. You'll probably see that the mouse has indeed moved a little bit. If that's the case, try using the plugin hoverIntent to eliminate issues like this.
EDIT:
Using the up and down arrow keys, I'm now able to replicate the issue. Yeah, it sure looks like some kind of bug! I bet the mousemove coordinate delta is tiny. Maybe the cursor moves one or two pixels? I would say, to overcome this, add a check to the mousemove function that compares previous mousemove's x-y coordinates to the current mousemove's x-y coordinates. Determine if it's more than just a few pixels. If so, you know it's a real mousemove. If it's less, you can chalk that up as a chrome bug.
FURTHER EDIT:
It seems like you uncovered a bug where mousemove is being fired in chrome when it probably shouldn't be. There may be workarounds that you could figure out if you hack it enough. But the best solution might be just to avoid using mousemove in this situation. In general, mousemove is one of those expensive events that should be used only when you really need it.
This is not a bug. The mousemove is relative to element that the event is attached to. In your case, you see that your mouse is not moving because you took the browser window as the reference. But for that scrolling list, whenever the list is scrolled, the mouse pointing over some element of the list moved to over different element
Imagine that you as the Earth, a cup of coffee stand still on a table as the mouse, the scrollable list as the Sun : if you (window) don't move, the position of the cup of coffee (mouse) is at the same place for you; but for the Sun (list), it will see that the Earth and the cup of coffee are both moving.
Okay, so, I have a bit of a strange situation which I have encountered multiple times. I'm looking for a simple/the best solution, NOT the overcomplicated solution I used before (and have since forgotten).
You see, I have a slideshow. I also have a couple of buttons that float on top of the slide show (visible here: http://marsxplr.com/view-13378)
Now, I have these buttons appear when the user's mouse goes over the slideshow. However, the minute the user's mouse goes over the buttons, mouseleave is called (even though the mouse is still technically over the slideshow!). This causes the buttons to disappear whenever the mouse goes over them, which is obviously not something I would like to happen.
I solved this by then making each button re-show its self whenever it receives a mouseover.
But now, we have a problem...
The button is on the very edge of the slideshow. That means that when mouseleave is called, one of two things happened:
A:The mouse went off of the button but not off of the slideshow, and mouseEnter got called on the slideshow meaning that no action should be taken because slideshow will eventually take care of hiding the button in its mouseleave.
B: The mouse simply left the slideshow and button altogether meaning that we should HIDE the button!
So my question is, how do I tell the difference between these two possibilities? Yes, yes, one solution would be to just always make the button hide its self -- and rely on the slideshow re-showing the button... But I am worried that the slideshow onmouseenter will be called BEFORE the button onmouseleave on certain browsers! This could cause issues as you can probably tell. In fact, thinking about this, I am already susceptible to this. For instance, if the mouse goes from the slideshow to the button, and the onmouseleave for my slideshow is called AFTER the onmousleave causing the button to still dissapear...
So, long story short, I need to know one of these two things:
Is there a guaranteed order in which these events are called?
Or is there a way to tell where the mouse actually WENT when mouseleave is called?
As you can see in my example above, I am using mootools, so a mootools solution is fine. It would, however, be interesting to see a vanilla-js solution as well.
EDIT:
My buttons are floated and on a separate z-index, so the standard operation for a parent-child DOM relationship does not occur
No such problem with standard DOM events.
As for the order, it bubbles from the deepest elements up to the document root.
There's also an optional (see the false param in the code below) capture phase, when the order is reversed. From the root to each inner element under the cursor (for mouse events).
To check on which inner element of the original element the event actually occurred, you can inspect the target property of the event object:
el.addEventListener('mouseout', function (e) {
var target = e.target;
if (target.classList.contains('whatever')) {
// Do something
}
}, false);
As per Georges Oates Larsen's comment, there's also relatedTarget, designed specifically for events involving several elements.
I have a scenario where I have some text, which should be user-selectable. The problem is, that there's an UI overlay on top of it, which prevents selecting text by default. The logical way to keep the overlay and still be able to select the text, would be to use synthetic events (use document.createEvent), but due to some reason, it doesn't work as expected.
The events seem to be delegated correctly and fire their handlers, but no text is selected. I have an example here, which is a rough simplification of the problem.
A few notes
In Firefox if you start your selection outside of the overlay, you are still able to select the text you want, even if it's under the overlay
When you have a normal selection in the uncovered area and you click on the overlay, it would be expected from the delegated mousedown event to remove the selection, but it doesn't happen
Am I missing an event that should also be delegated (I have mousedown, mousemove and mouseup)? Or is it some kind of a security measure by browsers to disable such behavior (refer to the note nr 2)? Any other suggestions on how to get the desired result? I know I should work around the current overlay solution altogether, but I'm already curious about the problem itself.
I have found two solutions for this problem:
"pointer-events" css property. Requires IE 9.0+ though.
Seems like guys from ExtJS solved it by event forwarding: demo, source, blog post
I would suggest to do the easy trick: put the transparent element with the same content as your text on-top of text itself and overlay. Here is the demonstration.
P.S.: From my experience any solution in the form you suggest will be awful. It will suffer from browser incompatibilities, side-effects of surrounding mark-up and styling etc.
I am simply looking for a way of using drag and drop without jquery or any other library. If a dragged object is dropped on another element the later element should start an event (in FF - better would be browser independent).
I know that drag and drop for JavaScript was discussed sometimes before but the previous postings didn't help me.
Although I found some examples it is not clear to me if there is a "drop" or "dragdrop" events exist but these things don't work:
<p ondrop='alert("It worked");'>Text</p>
<p ondragdrop='alert("It worked");'>Text</p>
How could this be done?
Many thanks in advance.
I agree with the other answers. A library will save you a lot of time and headache. This is coming from someone who just recently created a drag-and-drop control from scratch.
If you insist though this is what you'll need to do:
Bind a onmousedown event to the div you want to drag (div.onmousedown).
Change the div's position style to absolute (div.style.position = 'absolute')
Begin capturing mouse movement (document.onmousemove).
On mouse move update the div's position (div.style.top|left = '[location]px')
On the div's onmouseup event (or the document's) unbind all the handlers and do any other cleanup (null out position changes, etc).
Some problems a library will probably solve:
While dragging you will select text on the page (looks ugly).
Binding to events is different between browsers.
You have to calculate the size of the element being dragged if you want to show placeholders and to make it not "pop" when you begin dragging the control (since changing to absolute positioning will remove the element from flow).
You will probably want your dragged element to move fluidly so you will have to store some mouse offset when selecting the element or automatically center the element to the mouse.
If you want to drag an item in a list you'll have to write a ton more custom code for that list to accept the dragged item.
You'll have to take into consideration dragging when the window is scrolled and possibly dragging inside other elements that are positioned strangely.
I am simply looking for a way of using drag and drop without jquery or any other library.
I'm sorry, but there are no such thing as simply drag and drop without any library. You can write it all yourself, but that will be a lot of JS to make it work in all browsers.
Hmm. It's probably not that simple that you'd want to do it yourself, but I would look at Peter Michaux's FORK Javascript drag and drop library -- unlike JQuery or all those big libraries, FORK's modules are decoupled from each other, and are simple enough that you could probably look at Peter's source code and figure out the bits you need. (edit: I'd just use FORK.Drag as it's really small: 7.6KB total minified)
While I agree that library is the way to go, the answer you want is onmousedown, onmousemove, onmouseup. You have to handle those three events.
In onmousedown you'd find the target (event.target or similar in different browsers) and set draggedObject = event.target. You'd also start handling the onmousemove event.
Whenever the onmousemove event fired, you'd move the dragged element based on the difference in position since last time the onmousemove event fired.
In the onmouseup event, you'd clear your draggedObject variable and stop handling onmousemove.
It's not very crossbrowser, but it's the core of what you'd need to do for dragging and dropping.