I have an iframe that takes up the entire window (100% wide, 100% high), and I need the main window to be able to detect when the mouse has been moved.
Already tried an onMouseMove attribute on the iframe and it obviously didn't work. Also tried wrapping the iframe in a div like so:
<div onmousemove="alert('justfortesting');"><iframe src="foo.bar"></iframe></div>
.. and it didn't work. Any suggestions?
If your target isn't Opera 9 or lower and IE 9 or lower you can use css attribute pointer-events: none.
I found it the best way just to ignore iframe. I add class with this attribute to iframe in onMouseDown event and remove in onMouseUp event.
Works perfect for me.
Iframes capture mouse events, but you can transfer the events to the parent scope if the cross-domain policy is satisfied. Here's how:
// This example assumes execution from the parent of the the iframe
function bubbleIframeMouseMove(iframe){
// Save any previous onmousemove handler
var existingOnMouseMove = iframe.contentWindow.onmousemove;
// Attach a new onmousemove listener
iframe.contentWindow.onmousemove = function(e){
// Fire any existing onmousemove listener
if(existingOnMouseMove) existingOnMouseMove(e);
// Create a new event for the this window
var evt = document.createEvent("MouseEvents");
// We'll need this to offset the mouse move appropriately
var boundingClientRect = iframe.getBoundingClientRect();
// Initialize the event, copying exiting event values
// for the most part
evt.initMouseEvent(
"mousemove",
true, // bubbles
false, // not cancelable
window,
e.detail,
e.screenX,
e.screenY,
e.clientX + boundingClientRect.left,
e.clientY + boundingClientRect.top,
e.ctrlKey,
e.altKey,
e.shiftKey,
e.metaKey,
e.button,
null // no related element
);
// Dispatch the mousemove event on the iframe element
iframe.dispatchEvent(evt);
};
}
// Get the iframe element we want to track mouse movements on
var myIframe = document.getElementById("myIframe");
// Run it through the function to setup bubbling
bubbleIframeMouseMove(myIframe);
You can now listen for mousemove on the iframe element or any of its parent elements -- the event will bubble up as you would expect.
This is compatible with modern browsers. If you need it to work with IE8 and below, you'll need to use the IE-specific replacements of createEvent, initMouseEvent, and dispatchEvent.
Another way to solve this that work well for me is to disable mouse move events on the iframe(s) with something like on the mouse down:
$('iframe').css('pointer-events', 'none');
and then, re-enable mouse move events on the iframe(s) on the mouse up:
$('iframe').css('pointer-events', 'auto');
I tried some of the other approaches above and they work, but this seems to be the simplest approach.
Credit to: https://www.gyrocode.com/articles/how-to-detect-mousemove-event-over-iframe-element/
MouseEvent.initMouseEvent() is now deprecated, so #Ozan's answer is a bit dated. As an alternative to what's provided in his answer, I'm now doing it like this:
var bubbleIframeMouseMove = function( iframe ){
iframe.contentWindow.addEventListener('mousemove', function( event ) {
var boundingClientRect = iframe.getBoundingClientRect();
var evt = new CustomEvent( 'mousemove', {bubbles: true, cancelable: false})
evt.clientX = event.clientX + boundingClientRect.left;
evt.clientY = event.clientY + boundingClientRect.top;
iframe.dispatchEvent( evt );
});
};
Where I'm setting clientX & clientY you'll want to pass any info from the content window's event to the event we'll be dispatching (i.e., if you need to pass something like screenX/screenY, do it there).
The page inside your iframe is a complete document. It will consume all events and have no immediate connection to it's parent document.
You will need to catch the mouse events from javascript inside the child document and then pass this somehow to the parent.
I have been faced with a similar issue, where I had div's that I wanted to drag around over an iFrame. Problem was that if the mouse pointer moved outside the div, onto the iFrame, the mousemove events were lost and the div stopped dragging. If this is the sort of thing you want to do (as opposed to just detecting the user waving the mouse over the iFrame), I found a suggestion in another question thread which seems to work well when I tried it.
In the page that contains the and the things you want to drag, also include a like this:
<div class="dragSurface" id="dragSurface">
<!-- to capture mouse-moves over the iframe-->
</div>
Set it's initial style to be something like this:
.dragSurface
{
background-image: url('../Images/AlmostTransparent.png');
position: absolute;
z-index: 98;
width: 100%;
visibility: hidden;
}
The z-index of '98' is because I set the div's I want to drag around to be z-index:99, and the iFrame at z-index:0.
When you detect the mousedown in the to-be-dragged object (not the dragSurface div), call the following function as part of your event handler:
function activateDragSurface ( surfaceId )
{
var surface = document.getElementById( surfaceId );
if ( surface == null ) return;
if ( typeof window.innerWidth != 'undefined' )
{ viewportheight = window.innerHeight; }
else
{ viewportheight = document.documentElement.clientHeight; }
if ( ( viewportheight > document.body.parentNode.scrollHeight ) && ( viewportheight > document.body.parentNode.clientHeight ) )
{ surface_height = viewportheight; }
else
{
if ( document.body.parentNode.clientHeight > document.body.parentNode.scrollHeight )
{ surface_height = document.body.parentNode.clientHeight; }
else
{ surface_height = document.body.parentNode.scrollHeight; }
}
var surface = document.getElementById( surfaceId );
surface.style.height = surface_height + 'px';
surface.style.visibility = "visible";
}
Note: I cribbed most of that from somebody else's code I found on the internet! The majority of the logic is just there to set the size of the dragSurface to fill the frame.
So, for example, my onmousedown handler looks like this:
function dragBegin(elt)
{
if ( document.body.onmousemove == null )
{
dragOffX = ( event.pageX - elt.offsetLeft );
dragOffY = ( event.pageY - elt.offsetTop );
document.body.onmousemove = function () { dragTrack( elt ) };
activateDragSurface( "dragSurface" ); // capture mousemoves over the iframe.
}
}
When dragging stops, your onmouseup handler should include a call to this code:
function deactivateDragSurface( surfaceId )
{
var surface = document.getElementById( surfaceId );
if ( surface != null ) surface.style.visibility = "hidden";
}
Finally, you create the background image (AlmostTransparent.png in my example above), and make it anything except completely transparent. I made an 8x8 image with alpha=2.
I have only tested this in Chrome so far. I need to get it working in IE as well, and will try and update this answer with what I discover there!
I found a relatively simple solution to this for a similar issue I was having where I wanted to resize the iframe and a long with a div sitting next to it. Once the mouse went over the iframe jquery would stop detecting the mouse.
To fix this I had a div sitting with the iframe both filling the same area with the same styles except for the z-index of the div (set to 0) and the iframe (set to 1). This allowed for the iframe to be used normally when not resizing.
<div id="frameOverlay"></div>
<iframe></iframe>
When resizing, the div z-index gets set to 2 and then back to 0 afterwards. This meant the iframe was still visible but the overlay was blocking it, allowing for the mouse to be detected.
On your "parent" frame, select your "child" iframe and detect the event you are interested, in your case mousemove
This an example of code to be used in your "parent" frame
document.getElementById('yourIFrameHere').contentDocument.addEventListener('mousemove', function (event) {
console.log(, event.pageX, event.pageY, event.target.id);
}.bind(this));
<script>
// dispatch events to the iframe from its container
$("body").on('click mouseup mousedown touchend touchstart touchmove mousewheel', function(e) {
var doc = $("#targetFrame")[0].contentWindow.document,
ev = doc.createEvent('Event');
ev.initEvent(e.originalEvent.type, true, false);
for (var key in e.originalEvent) {
// we dont wanna clone target and we are not able to access "private members" of the cloned event.
if (key[0] == key[0].toLowerCase() && $.inArray(key, ['__proto__', 'srcElement', 'target', 'toElement']) == -1) {
ev[key] = e.originalEvent[key];
}
}
doc.dispatchEvent(ev);
});
</script>
<body>
<iframe id="targetFrame" src="eventlistener.html"></iframe>
</body>
Here is my solution with jQuery. You can detect multiple events as I do below. Putting the .on() event handler inside the .on('load') event handler is necessary, because it would stop detecting the iframe content events when the iframe navigates to a new page otherwise. Additionally, I believe this only works if the iframe content is on the same domain as the parent page due to security, and there is no way around that.
$(document).ready(function() {
$('#iframe_id').on('load', function() {
$('#iframe_id').contents().on('click mousemove scroll', handlerFunction);
});
});
handlerFunction() {
//do stuff here triggered by events in the iframe
}
Basic way in document
var IE = document.all?true:false;
// If NS -- that is, !IE -- then set up for mouse capture
if (!IE) document.captureEvents(Event.MOUSEMOVE);
// Set-up to use getMouseXY function onMouseMove
document.onmousemove = getMouseXY;
// Temporary variables to hold mouse x-y pos.s
var tempX = 0;
var tempY = 0;
// Main function to retrieve mouse x-y pos.s
function getMouseXY(e) {
if (IE) { // grab the x-y pos.s if browser is IE
tempX = event.clientX + document.body.scrollLeft;
tempY = event.clientY + document.body.scrollTop;
} else { // grab the x-y pos.s if browser is NS
tempX = e.pageX;
tempY = e.pageY;
}
// catch possible negative values in NS4
if (tempX < 0){tempX = 0}
if (tempY < 0){tempY = 0}
// show the position values in the form named Show
// in the text fields named MouseX and MouseY
console.log(tempX, tempY);
return true;
}
Detect Mouse Move over Iframe
window.frames['showTime'].document.onmousemove = getMouseXY;
Detect Mouse Move over in Child Iframe under parent's Iframe
window.frames['showTime'].document.getElementById("myFrame").contentWindow.onmousemove = getMouseXY;
Trigger an IDLE detect function when no mouse move in Browser.
you can add a overlay on top of iframe when drag begin ( onmousedown event ) , and remove it when drag end ( on mouserup event ).
the jquery UI.layout plugin use this method to solve this problem.
Related
I need to manually construct/fire a mousedown event in a way that can be handled by any relevant event handlers (either in straight JS, jQuery, or interact.js), just as a natural mousedown event would. However, the event does not seem to trigger anything the way I expect it to.
I am trying to make some irregularly shaped images draggable using the interact.js library. The images are simply rectangular img elements with transparent portions. On each element, I have defined 2 interact.js event listeners:
Checking if the click was inside the image area, disabling drag if not (fires on "down" event)
Handling the drag (fires on "drag" event)
However, if the img elements are overlapping, and the user clicks in the transparent area of the top element but on the filled area of the lower element, the lower element should be the target of the drag. After trying several things (see below), I have settled on the solution of: re-ordering the z-indexes of the elements at the same time that I disable drag in step 1, then re-firing the mousedown event on all lower elements. I'm using the "native" event type (not jQuery or interact.js) in hopes that it will literally just replicate the original mousedown event.
// code to re-assign "zIndex"s
function demote(element, interactible, event){
// block dragging on element
interactible.draggable(false);
// get all images lower than the target
var z = element.css("zIndex");
var images = $("img").filter(function() {
return Number($(this).css("zIndex")) < z;
});
// push the target to the back
element.css("zIndex",1);
// re-process all lower events
$(images).each( function () {
// move element up
$(this).css("zIndex",Number($(this).css("zIndex"))+1);
// re-fire event if element began below current target
elem = document.getElementById($(this).attr('id'));
// Create the event.
e = new MouseEvent("mousedown", {clientX: event.pageX, clientY: event.pageY});
var cancelled = !elem.dispatchEvent(e);
});
}
Unfortunately, this does not work, as the mousedown event does not register with any of the handlers. Why?
I have all the (relevant) code at this JSFiddle: https://jsfiddle.net/tfollo/xr27938a/10/
Note that this JSFiddle does not seem to work as smoothly as it does in a normal browser window, but I think it demonstrates the intended functionality.
Other things I have tried:
Lots of people have proposed different schemes to handle similar problems (i.e. forwarding events to lower layers, using pointer-events: none, etc), but none seem to work to trigger the interact.js handler and start a drag interaction on the right element. I also tried using interaction.start (provided by interact.js) but it seems buggy--there is at least one open issue on the topic and when I tried to start a new drag interaction on a target of my choice I got lots of errors from within the library's code.
I'm not against revisiting any of these solutions per se, but I would also really like to know why manually firing a mousedown event won't work.
The idea is to listen down event on parent element and to choose manually drag target. Also I didn't use z-index for choosing which image to drag, because z-index doesn't work with position:static. Instead of that I just gave priorities to images, it's all up to you. https://jsfiddle.net/yevt/6wb5oxx3/3/
var dragTarget;
function dragMoveListener (event) {
var target = event.target,
// keep the dragged position in the data-x/data-y attributes
x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
// translate the element
target.style.webkitTransform =
target.style.transform =
'translate(' + x + 'px, ' + y + 'px)';
// update the posiion attributes
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
}
function setDragTarget(event) {
//choose element to drag
dragTarget = $('#parent').find('img')
.filter(function(i, el) {
var clickCandicateRect = el.getBoundingClientRect();
return insideBoundingRect(event.x, event.y, clickCandicateRect);
}).sort(byPriority).get(0);
}
function insideBoundingRect(x, y, rect) {
return (rect.left <= x) && (rect.top <= y) && (rect.right >= x) && (rect.bottom >= y);
}
function byPriority (a, b) {
return $(b).data('priority') - $(a).data('priority');
}
function startDrag(event) {
var interaction = event.interaction;
var target = dragTarget;
if (!interaction.interacting()) {
interaction.start({ name: 'drag' }, event.interactable, target);
}
}
//Give priority as you wish
$('#face1').data('priority', 2);
$('#face2').data('priority', 1);
interact('#parent').draggable({
//use manualStart to determine which element to drag
manualStart: true,
onmove: dragMoveListener,
restrict: {
restriction: "parent",
endOnly: true,
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
},
})
.on('down', setDragTarget)
.on('move', startDrag);
Have you tried to set the css property pointer-events: none on the higher levels (it could also be set via javascript)?
Started using RxJs. Can't find a way around this problem. I have a draggable control:
startDrag = rx.Observable.fromEvent(myElem,'mousedown')
now, because the control is too small mousemove and mouseup events should be at document level (otherwise it won't stop dragging unless cursor exactly on the element)
endDrag = rx.Observable.fromEvent document,'mouseup'
position = startDrag.flatMap ->
rx.Observable.fromEvent document,'mousemove'
.map (x)-> x.clientX
.takeUntil endDrag
now how do I "catch" the right moment when is not being dragged anymore (mouseup).
you see the problem with subscribing to endDrag? It will fire every time clicked anywhere, and not just myElem
How do I check all 3 properties at once? It should take only those document.mouseups that happened exactly after startDrag and position
Upd: I mean the problem is not with moving the element. That part is easy - subscribe to position, change element's css.
My problem is - I need to detect the moment of mouseup and know the exact element that's been dragged (there are multiple elements on the page). How to do that I have no idea.
I have adapted the drag and drop example provided at the RxJS repo to behave as you need.
Notable changes:
mouseUp listens at document.
The targeted element is added to the return from select.
Drag movements are handled inside of map and map returns the element that was targeted in the mouseDown event.
Call last after takeUntil(mouseUp) so subscribe will only be reached when the drag process ends (once per drag).
Working example:
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 mousedrag = mousedown.selectMany(function(md) {
// calculate offsets when mouse down
var startX = md.offsetX;
var startY = md.offsetY;
// Calculate delta with mousemove until mouseup
return mousemove.select(function(mm) {
if (mm.preventDefault) mm.preventDefault();
else event.returnValue = false;
return {
// Include the targeted element
elem: mm.target,
pos: {
left: mm.clientX - startX,
top: mm.clientY - startY
}
};
})
.map(function(data) {
// Update position
dragTarget.style.top = data.pos.top + 'px';
dragTarget.style.left = data.pos.left + 'px';
// Just return the element
return data.elem;
})
.takeUntil(mouseup)
.last();
});
// Here we receive the element when the drag is finished
subscription = mousedrag.subscribe(function(elem) {
alert('Drag ended on #' + elem.id);
});
}
main();
#dragTarget {
position: absolute;
width: 20px;
height: 20px;
background: #0f0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.min.js"></script>
<div id="dragTarget"></div>
I am using html5 'draggable' attribute to drag 2 elements within a container and svg line to connect the two.
Once connected, Dragging the first Div should redraw the connecting svg line (i do on dragover event by calling 'handleDragOver' function). But If you drag the first div faster, drop event is not triggered and the div maintains it's original place while the line gets drawn.
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
//Some code doing DOM computation and manipulation
}
return false;
//e.dataTransfer.dropEffect = 'move';
}
How can I ensure that drop event gets triggered every time.
Please note:
I can't use any framework, just plain javascript
I can't not redraw line while dragging.
drag functionality works fine when I'm not doing any computation/redrawing in 'handleDragOver'
Here is the code: http://jsfiddle.net/bhuwanbhasker/3yx9ds4m/1/
Please take a look on the updated fiddle: http://jsfiddle.net/sejoker/d4vs9fgs/1/
following changes:
line manipulation moved from dragover's handler to handleDrop, redraw line once during drag'n'drop operation
calling moveLine in setTimeout seems to improve usability (optional)
minor changes in moveLine function to connect draggable elements properly (optional)
function handleDrop(e) {
console.log('enter Drop');
if (e.stopPropagation) {
e.stopPropagation(); // stops the browser from redirecting.
}
var offset = e.dataTransfer.getData('Text').split(',');
dragSrcEl.style.left = (e.clientX + parseInt(offset[0], 10)) + 'px';
dragSrcEl.style.top = (e.clientY + parseInt(offset[1], 10)) + 'px';
setTimeout(function(){
var elem = svgLine.aLine;
if (elem !== null) {
var x = e.clientX - svgLine.left,
y = e.clientY - svgLine.top;
moveLine(x, y, elem, offset[2]);
}
}, 50)
I am translating ( via jQuery / CSS3 ) a #wrapper div, by updating Y axis.
I've attached mouseenter / mouseleave events to child elements of #wrapper.
When #wrapper translates, its child comes under mouse one by one ( even if mouse does not move ). And this does not fire the mouseenter , mouseleave events.
However, events are fired when element has scrollbar and scrolled by mousewheel ( instead of translating ).
Is this a default behavior ? If yes, any workaround ?
Demo
Try scrolling with mousewheel, without moving mouse. I expect to change the background of .block to red color, but it's not happening.
Example:
document.elementFromPoint(x, y);
Definition from Here:
Returns the element from the document whose elementFromPoint method is
being called which is the topmost element which lies under the given
point. To get an element, specify the point via coordinates, in CSS
pixels, relative to the upper-left-most point in the window or frame
containing the document.
Browser support from Here:
Internet Explorer 5.5+
Mozilla FireFox 3+
Safari Win 5+
Google Chrome 4+
Opera 10.53+
Solution 1 Working Example*:
var currentMousePos = { x: -1, y: -1 };
$(document).mousemove(function(event) {
currentMousePos.x = event.pageX;
currentMousePos.y = event.pageY;
});
$(containerElement).mousewheel(function(event) {
$(elementClass).trigger('mouseleave');
var element = document.elementFromPoint(currentMousePos.x, currentMousePos.y);
$(element).trigger('mouseenter');
});
* there are some bugs, but you get the idea
Solution 2:
Use debounce from lodash or underscore libraries, to reduce load on client.
var currentMousePos = { x: -1, y: -1 };
$(document).mousemove(function (event) {
currentMousePos.x = event.pageX;
currentMousePos.y = event.pageY;
});
var debounced = _.debounce(function () {
$(elementClass).trigger('mouseleave');
var element = document.elementFromPoint(currentMousePos.x, currentMousePos.y);
$(element).trigger('mouseenter');
}, 150);
$(containerElement).mousewheel(function (event) {
debounced();
});
I have a series of buttons of fixed height and width, those need to be draggable and droppable anywhere inside the parent div.
As per client request, I cannot use any external libraries, meh... I could have done this with jQuery in seconds but, I guess this is one of its drawbacks: you don't get to learn more basic stuff...
How would I go about doing it? A problem with this is that those buttons are inside a div that is also draggable so I need to be careful about positioning, I can only probably use relative.
Any ideas how I can go about doing it? Thanks in advance.
Peter-Paul Koch wrote an excellent how-to on drag and drop. I only remembered this 3/4 of the way through writing my own, so I wrapped that up in a fiddle.
function makeDraggable(draggable, container){
// In case you don't want to have a container
var container = container || window;
// So we know what to do on mouseup:
// At this point we're not sure the user wants to drag
var dragging = false;
// The movement listener and position modifier
function dragHandler(moveEvent){
moveEvent.preventDefault();
dragging = true;
// Ascertain where the mouse is
var coordinates = [
moveEvent.clientX,
moveEvent.clientY
];
// Style properties we need to apply to the element
var styleValues = {
position : 'absolute',
left : coordinates[0] + 'px',
top : coordinates[1] + 'px'
};
// Apply said styles
for(property in styleValues){
if(styleValues.hasOwnProperty(property)){
draggable.style[property] = styleValues[property];
}
}
}
function dropHandler(upEvent){
// Only interfere if we've had a drag event
if(dragging === true){
// We don't want the button click event to fire!
upEvent.preventDefault();
// We don't want to listen for drag and drop until this is clicked again
container.removeEventListener('mousemove', dragHandler, false);
draggable.removeEventListener('mouseup', dropHandler, false);
dragging = false;
}
}
// Where all the fun happens
draggable.addEventListener('mousedown', function dragListener(downEvent){
downEvent.preventDefault();
// The drag event
container.addEventListener('mousemove', dragHandler, false);
// The end of drag, if dragging occurred
draggable.addEventListener('mouseup', dropHandler, false);
}, false);
}