I'm trying to use mousemove event on document.body to change the cursor (I'm not using CSS because I want to edit the new cursor based on its position).
This part works just fine,
but when I try to detect mouseenter on other elements whitch are body childs, I run into a problem, the mouseenter ( or mouseleave ) doesn't get fired off unless I move the mouse too fast, and also they fire off both together...
Here is my HTML:
<style>
#cursor {
position: absolute;
width: 15px;
height: 15px;
background: #595959;
border-radius: 9999px;
z-index: 9999;
}
#test {
width: 300px;
height: 100px;
border: 1px solid red;
}
</style>
<div id="cursor"></div>
<div id="test"></div>
and my Js:
const cursor = document.querySelector('#cursor');
const test = document.querySelector('#test');
function onBodyMouseMove(e) {
cursor.style.left = e.clientX - 7.5 + 'px';
cursor.style.top = e.clientY - 7.5 + 'px';
};
function enter(e) {
console.log('mouse entered');
}
function leave(e) {
console.log('mouse left');
}
document.body.addEventListener('mousemove', onBodyMouseMove);
test.addEventListener('mouseenter', enter);
test.addEventListener('mouseleave', leave);
you can check out the pen: https://codepen.io/hamid331994/pen/ExgGwqO
Note:
using e.stopPropagation(); doesn't work
adding { passive: false } doesn't work neither
The problem is with CSS. Your mouse cursor is still above element #cursor not above element #test and when you move your cursor faster that means DOM was not updated your #cursor element position.
When you will add pointer-events: none; to #cursor then will help.
The problem is that body dose not really count as much as window in this case.
So you could fix this problem by simple adding this line.
window.addEventListener('mousemove', onBodyMouseMove);
Related
An intersection observer is set up on an element. When the element is scrolled past a certain point, the intersection observer handler is fired as expected. However, if a button is clicked to scroll the element past that same point, the handler is not fired.
Why is that? Is there a way to force the handler to be fired when using scrollTo/scrollIntoView?
const container = document.getElementById("container");
const hello = document.getElementById("hello");
const button = document.getElementById("button");
const options = {
rootMargin: "-100px 0px 0px 0px",
threshold: 1
}
const handleIntersect = entries => {
entries.forEach((entry) => {
console.log("handleIntersect")
});
};
const observer = new IntersectionObserver(handleIntersect, options);
observer.observe(hello);
button.addEventListener("click", () => {
container.scrollTo({
top: 120
});
})
body {
margin: 0;
}
#container {
background-color: #ddd;
height: 400px;
overflow-y: auto;
}
.inner-container {
height: 100px;
border-bottom: 1px solid #bbb;
position: absolute;
top: 0;
left: 0;
right: 0;
text-align: right;
}
#button {
margin: 40px;
font-size: 20px;
}
#hello {
display: inline-block;
padding: 20px 40px;
background-color: blue;
color: white;
margin-top: 150px;
margin-bottom: 500px;
}
<div id="container">
<div class="inner-container">
<button id="button">Scroll</button>
</div>
<div id="hello">Hello</div>
</div>
remove rootMargin from options object and it will intersect, also you can decide percentage of visibility, if callback should be fired if even 50% is visible, you can provide inside options object
{ threshold: 0.5}
and so all...
I don't know if this solves your problem, But What I think is when we have scrollIntoView linked to a button we specify a value to the position of scrollbar if the button is clicked, for example when we click a button which has a scrollTo() function we expect the scrollbar to be at a specific place but that doesn't mean the scrollbar is sliding to the place which looks similar to the action that happens when we scroll the mouse.
In other words the intersection API fires an even when you cross a particular position or a point, however it does not fire an even if you just skip crossing the point and jump directly the desired position which happens when you use scrollIntoView,
In case you wonder that when you use scrollTo() to smooth scroll the webpage, you can visually see the scroll bar sliding to the particular point as if it passes the threshold point, however it is not the case, behind the scene the scrollbar just skip all the content and moves directly the specified position.
One way to counter the problem (not efficient) is to use looping, try looping from the current page offset value to your target value instead of hardcoding the value to the scrollIntoView() , it does gives you the desired output but the scrolling animation will be poor and it loses it's objective.
I have created a very simple example of my problem.
Fiddle Link
In the fiddle, I have created a div named parent containing 2 imgs (i take divs in the example for simplicity but in my project, these are images) and a controller div. I place the images on the top of each other by positioning 2nd image as absolute.
I want to clip the 2nd image using clip-path property whenever, I click and then drag the controller" over the parent div.
But the controller div is causing issue with parent mousemove event whenever cursor goes on controller div, mouseout event is fired on parent div causing glitch in animation.
Adding pointer-events: none property to controller div fix the glitch but it also takes away every kind of mouse interaction from the element and I want click and drag effect.
I want to create similar effect used in this website.
The problem seems to be that the positioning of the controller sometimes (not always) 'interferes' with the reading of offsetX on the parent. And the offset goes down (to 0 or up to about 10 in the given fiddle). Hence you get the flickering as the controller moves back and then up along again.
I cannot at the moment totally explain this, particularly since the controller is an absolutely positioned element.
However, one solution is to move the controller out of the parent.
UPDATE It is though possible to leave the controller in the parent if one ignores any mousemove within the controller (so we don't get readings of 0 to 10 for the offset when the mousemove is within the controller - ignore them and we'll get the event bubbling through to the parent and can then take a reading of offset).
_
<head>
<style>
* {
margin: 0;
padding: 0;
}
#parent {
width: 100%;
position: relative;
}
#img1, #img2 {
display: block;
width: 100%;
height: 200px;
pointer-events: none;
}
#img1 {
background: red;
}
#img2 {
background: green;
position: absolute;
top: 0;
left: 0;
}
#controller {
position: absolute;
top: 0;
left: 10px;
width: 10px;
height: 100%;
height: 200px;
background: black;
z-index: 1;
cursor: ew-resize;
/* pointer-events: none; */
}
</style>
</head>
<body>
<div id="parent">
<div id="img1"></div>
<div id="img2"></div>
<div id="controller"></div>
</div>
<h4>
Click and Drag the controller to clip the front image
</h4>
<!-- img1, img2 are images in my case so i named them as imgs -->
<script>
const parent = document.getElementById('parent'),
img2 = document.getElementById('img2'),
controller = document.getElementById('controller');
let pressed = false;
console.log(pressed)
parent.addEventListener('mousemove', (e) => {
if(!pressed) return;
if (e.target != parent) return;
img2.style.clipPath = `inset(0px 0px 0px ${e.offsetX}px)`;
controller.style.left = `${e.offsetX}px`;
});
// for testing purpose
/* parent.addEventListener('mouseout', (e) => {
console.log('mouse out is called');
}); */
controller.addEventListener('mousedown', (e) => {
pressed = true;
});
controller.addEventListener('mouseup', (e) => {
pressed = false;
});
</script>
</body>
const parent = document.getElementById('parent'),
img2 = document.getElementById('img2'),
controller = document.getElementById('controller');
let pressed = false;
parent.addEventListener('mousemove', (e) => {
if (pressed) {
img2.style.clipPath = `inset(0px 0px 0px ${e.clientX - parent.offsetLeft}px)`;
controller.style.left = `${e.clientX - parent.offsetLeft}px`;
}
});
controller.addEventListener('mousedown', (e) => {
pressed = true;
});
controller.addEventListener('mouseup', (e) => {
pressed = false;
});
#parent {
width: 100%;
position: relative;
}
#img1,
#img2 {
display: block;
width: 100%;
height: 200px;
pointer-events: none;
}
#img1 {
background: red;
}
#img2 {
background: green;
position: absolute;
top: 0;
left: 0;
}
#controller {
position: absolute;
top: 0;
left: 10px;
width: 10px;
height: 100%;
background: black;
z-index: 1;
cursor: ew-resize;
/* pointer-events: none; */
}
<div id="parent">
<div id="img1"></div>
<div id="img2"></div>
<div id="controller"></div>
</div>
<h4>
Click and Drag the controller to clip the front image
</h4>
The problem is, you used offsetX which defines the distance between the top left edge of your controller element. This means the distance is about 5px, your controller jumps to 5px from left, the distance is bigger now, the controller jumps back and so on.
The offsetX read-only property of the MouseEvent interface provides
the offset in the X coordinate of the mouse pointer between that event
and the padding edge of the target node.
So therefore you can use the difference between the mouse x-position and the x-position of parent for positioning your controller:
Instead use clientX which gets the mouse position relative to the window.
img2.style.clipPath = `inset(0px 0px 0px ${e.clientX - parent.offsetLeft}px)`;
controller.style.left = `${e.clientX - parent.offsetLeft}px`;
Top expression has following meaning:
<mouse x-position> - <distance between left screen edge and parent>
I have encountered a situation where certain mouse events stops firing while the user is dragging with the mouse.
Here is a jsFiddle of the issue.
UPDATE: You can find here a much simpler jsFiddle which also displays the issue.
The context
I am creating a tool to crop images. To do this, I create a set of <div> elements over an image, and the user will be able to drag the corners and the edges of the parent <div> by dragging one of the child <div>s.
The details
On mousedown, I add two event listeners to the document body:
a mousemove event listener, to fire constantly
a mouseup event listener, to fire once and remove the mousemove listener when the user stops dragging the mouse
The result
This always works perfectly the first time you drag a corner or an edge. However, the second time you drag the same corner or edge, the mousemove event fires between 1 and 6 times (in my experience) and then stops. The mouseup event is not fired when you release the mouse, but from that moment on, the mousemove events resume, and a subsequent click-and-release will generate a mouseup event.
Investigation
In Chrome Dev Tools, I can see that the event listeners are present by entering getEventListeners(document.body) into the console, or by checking in the Event Listeners pane of the Elements tab, for the body element.
Any help in understanding why this is occurring, and in how to resolve this issue will be greatly appreciated.
HTML
<!DOCTYPE html>
<html lang=en>
<head>
<meta charset="utf-8">
<title>Crop Image</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div id="crop">
<div class="topLeft"></div>
<div class="top"></div>
<div class="topRight"></div>
<div class="right"></div>
<div class="bottomRight"></div>
<div class="bottom"></div>
<div class="bottomLeft"></div>
<div class="left"></div>
</div>
<pre id="feedback"></pre>
<script src="js/crop.js"></script>
</body>
</html>
CSS
div#crop {
position: relative;
width: 320px;
height: 320px;
cursor: pointer;
}
div#crop div {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 20px;
border: 1px solid #000;
box-sizing: border-box;
}
div#crop .top,
div#crop .bottom {
left: 20px;
width: calc(100% - 40px);
}
div#crop .left,
div#crop .right {
top: 20px;
height: calc(100% - 40px);
}
div#crop .topRight,
div#crop .right,
div#crop .bottomRight {
left: auto;
right: 0;
}
div#crop .bottomLeft,
div#crop .bottom,
div#crop .bottomRight {
top: auto;
bottom: 0;
}
pre#feedback {
position: fixed;
top:40px;
left:40px
}
JavaScript
"use strict"
let cropType
, counter
let div = document.getElementById("crop")
let feedback = document.getElementById("feedback")
document.body.onmousemove = log
div.addEventListener("mousedown", startResizeImage, false)
function startResizeImage(event) {
log("startResize")
cropType = event.target.className
counter = 0
document.body.addEventListener("mousemove", resizeImage, false)
document.body.addEventListener("mouseup", stopResizeImage, {once: true})
}
function resizeImage(event) {
log ("drag (" + ++counter + ") " + cropType)
}
function stopResizeImage() {
log("stopResize")
document.body.removeEventListener("mousemove", resizeImage, false)
}
function log(data) {
data = data.type || data
let text = data+": "+event.clientX+", "+event.clientY
let lines = feedback.innerHTML.split("<br>")
if (data === "mousemove") {
text = text + "\n" + lines.pop()
} else {
text = lines.shift() + "\n" + text
}
feedback.innerText = text
}
The reason the second drag operation was failing was because the browser used the first drag-and-release to create a selection, whose anchorNode was the <div> that was clicked. The second drag then tried to move this selection. The clue that revealed this was that the cursor changed to a move closed hand, almost immediately after the second drag began.
The solution is to use CSS to prevent the draggable <div>s from being selected:
div#crop {
-webkit-user-select: none; /* Chrome all / Safari all */
-moz-user-select: none; /* Firefox all */
-ms-user-select: none; /* IE 10+ */
user-select: none; /* Likely future */
}
The irony is that, in the past when I have created a drag feature, I have always disabled selection, for cosmetic reasons. I had not realized that it was a key factor for the success of the operation.
This probably cannot be done, but I have a fixed-position div on top of inline html in the page body. The inline html has clickable elements, and the fixed div has a hover event.
The fixed element is an empty div, so it is invisible.
Currently, the fixed element is blocking click events on the item under it.
Is it possible?
This solution is too complicated
https://stackoverflow.com/a/9616491/209942
Possible solution?
https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events
Thx
The fixed element should not be prevent the clicks from the item under it unless you are stopping the event propagation.
See this fiddle: https://jsfiddle.net/pv0mygz5/
-- it demonstrates that without event.stopPropagation the event should be intercepted by the listener on the span element.
$('#click-me').on('click', function (e) {
console.log('click triggered');
});
$('.box').on('mouseover', function (e) {
//don't stop event from bubbling
console.log('hover triggered');
});
Could you also include a code snippet that demonstrates your problem?
although IE10 doesn't support it you can use
pointer-events: none;
http://jsfiddle.net/leaverou/XxkSC/light/
In this fiddle you can see a drop down being covered with other elements, the other elements has pointer-events: none so you can click on the arrow down button and the click actually goes to the select element itself.
BR,
Saar
You can also try using z-index. Depending on your layout it may not be a solution, but if your front div is invisible, then it shouldn't create unwanted effect. Like this for example:
document.querySelector('#under').addEventListener('click', function(e) {
e.target.style.color = "blue";
});
document.querySelector('#notunder').addEventListener('click', function(e) {
e.target.style.color = "blue";
});
#fix {
width: 60px;
height: 200px;
position: fixed;
z-index: -1;
top: 0px;
border: 1px solid black;
}
#under {
display: inline;
}
#fixnozindex {
width: 100px;
height: 200px;
position: fixed;
left: 75px;
top: 0px;
border: 1px solid black;
}
#notunder {
display: inline;
}
<div id="fix"></div>
<div id="under">Clickable</div>
<div id="fixnozindex"></div>
<div id="notunder">Not clickable</div>
I have a invisible div that on top z-index which I want to act as a clicktag button. But it does not get any mouse event I try to associate with it. Neither it does show the hand pointer specified with css.
This is the example: http://www.miguelrivero.net/test/MS-20_mini/index.html
As you can see I'm using this simple code (just focus on console.log's):
function bgExitHandler(e) {
Enabler.exit("Background Exit");
console.log("click");
}
function onMouseHandler(e) {
console.log("click");
}
document.getElementById("bg-exit").addEventListener("click", bgExitHandler, false);
document.getElementById("bg-exit").addEventListener("onmouseover", onMouseHandler, false);
This is the css:
#bg-exit {
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
cursor: pointer;
opacity: 0;
}
Any light on this, please?
Thanks!
If you are using addEventListener, the name of the event is just mouseover, not onmouseover:
i.e.
document.getElementById("bg-exit").addEventListener("mouseover", onMouseHandler, false);