When the mouse starts hovering over an element because of scrolling (either by wheel, or by keyboard scrolling), it does not trigger a mouseover event on the elements it is hovering (Chrome 6 on OSX). What would be an elegant way to trigger the mouseover event for the correct elements when scrolling?
Honestly, this is gonna be a pain. You'll have to
determine the size and position of every element that should get a mouseover handler.
add a scroll listener to the window.
In the handler, get the mouse cursor position and pageOffset.
Find out which element(s) the cursor is in.
manually call the actual mouseover handler
(Find out which elements the cursor has left, if you want some mouseout behaviour too)
You may need to re-calculate the elements' positions and sizes if they are dynamic. (move 1. beneath 3.)
While this should work fine with block-level elements, I have absolutely no idea on a solution for inline elements.
This is much more simple in the modern day web using document.elementsFromPoint:
Add a scroll listener to the window.
In the handler, call document.elementsFromPoint.
Manually call the actual pointerover handler for those elements. Keep track of these elements.
(optionally) Manually call the actual pointermove handler for those elements.
Check the list of elements from the previous time around. Manually call the actual pointerleave handler for elements no longer being hovered.
Here's some psuedo-code:
let prevHoveredEls = [];
document.addEventListener("scroll", (e) => {
let hoveredEls = document.elementsFromPoint(e.pageX, e.pageY);
hoveredEls = hoveredEls.filter(
(el) => el.classList.contains("elements-cared-about")
);
const notHoveredEls = prevHoveredEls.filter(
(el) => !prevHoveredEls.includes(el)
);
hoveredEls.forEach((el) => {
const bcr = el.getBoundingClientRect();
el.handlePointerEnter({
layerX: e.pageX - bcr.left,
layerY: e.pageY - bcr.top,
});
});
notHoveredEls.forEach((el) => {
const bcr = el.getBoundingClientRect();
el.handlePointerLeave({
layerX: e.pageX - bcr.left,
layerY: e.pageY - bcr.top,
});
});
prevHoveredEls = hoveredEls;
});
Try some hack like myDiv.style.opacity = 1+Math.random(); on scroll ;)
Related
I have tried almost every solution that is available online but nothing worked out, below are few solutions I tried
My actual scenario is to show a specific div element when the other div element goes off from the screen when the screen is scrolled down.
I'm able to do this on desktops/laptops but not on mobiles where the scroll event doesn't fire at all.
my Desktop / Laptop scroll event listener I'm using is
#HostListener('window:scroll', ['$event']) getScrollHeight(event) {
console.log('from desk' , window.pageYOffset, event);
}
While for the mobile as my scroll event doesn't fire so I used below
#HostListener('window:touchStart', ['$event']) checkTouchEnd(event) {
this.start = event.changedTouches[0];
});
#HostListener('window:touchend', ['$event']) checkTouchEnd(event) {
this.end = event.changedTouches[0];
let scrollpositionValue;
if((this.end.screenY - this.start.screenY) > 0) {
console.log('scrolling up');
console.log('[scroll-up]', this.end.screenY - this.start.screenY);
scrollpositionValue = this.end.screenY - this.start.screenY;
} else if(this.end.screenY - this.start.screenY < 0) {
console.log('scrolling down');
console.log('[scroll-down]', this.end.screenY - this.start.screenY);
scrollpositionValue = this.end.screenY - this.start.screenY;
}
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
console.log('[scroll]', scrollPosition);
});
Can somebody help me with a solution, All I want is to get the scrollY position on a mobile browser scrolled.
thanks in advance!
My actual scenario is to show a specific div element when the other div element goes off from the screen when the screen is scrolled down.
Don't use scroll event listener for this, use Intersection Observer (IO) for this.
this was designed for such problems. With IO you can react whenever an HTML element intersects with another one (or with the viewport)
Check this page, it shows you how to animate an element once it comes into viewport (scroll all the way down)
Short recap on what you have to do:
First you have to create a new observer:
var options = {
rootMargin: '0px',
threshold: 1.0
}
var observer = new IntersectionObserver(callback, options);
Here we define that once your target Element is 100% visible in the viewport (threshold of 1) your callback Function is getting executed. Here you can define another percentage, 0.5 would mean that the function would be executed once your element is 50% visible.
Then you have to define which elements to watch
var target = document.querySelector('.div-to-watch');
observer.observe(target);
Last you need to specify what should happen once the element is visible in your viewport by defining the callback function:
var callback = function(entries, observer) {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// here you animate another element and do whatever you like
});
};
If you need to support older browsers, use the official polyfill from w3c.
If you don't want to trigger the animation again when the elements are scrolled again into view a second time then you can also unobserve an element once it's animated.
One can determine the element below the mouse cursor (i.e. the top-most hovered element) with the following techniques:
Listen for the mousemove event. The target is
event.target or
document.elementFromPoint(event.clientX, event.clientY).
This does not work when scrolling while not moving the mouse. Then, the mouse technically doesn’t move; thus, no mouse event will fire.
Unfortunately, both techniques from above are no longer applicable when listening for the scroll event. event.target will be whichever element is scrolled (or document). Also, the mouse cursor position is not exposed on the event object.
As described in this answer to “Determine which element the mouse pointer is on top of in Javascript”, one possible solution is querying the hovered element via the CSS :hover pseudo-class.
document.addEventListener('scroll', () => {
const hoverTarget = document.querySelector('.element:hover');
if (hoverTarget) {
hover(hoverTarget);
}
});
However, this is not usable because it is very inefficient and inaccurate. The scroll event is one of the rapidly firing events and needs to be slowed down when performing anything mildly costly (e.g. querying the DOM).
Also, the hovered element lags behind when scrolling. You can observe this on any kind of website with a lot of links: Hover over one of them and scroll to another link without moving the mouse. It updates only after a few milliseconds.
Is there any way, this can be implemented nicely and efficient? Basically, I want the inverse of mouseenter: Instead of knowing when the mouse enters and element, I want to know when an element intersects with the mouse (e.g. when the mouse is not moved but the element [i.e. when scrolling]).
One approach of tackling this is storing the mouse cursor location with the mousemove event and in the scroll event use document.elementFromPoint(x, y) to figure out the element that should be hovered.
Keep in mind that this is still pretty inefficient due to the scroll event being fired with such a high frequency. The event handler should be debounced to limit execution of the function to once per delay. David Walsh explains how to do this in JavaScript Debounce Function.
let hoveredElement;
let mouseX = 0, mouseY = 0;
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('mousemove', event => {
mouseX = event.clientX;
mouseY = event.clientY;
hover(event.target);
});
document.addEventListener('scroll', () => {
const hoverTarget = document.elementFromPoint(mouseX, mouseY);
if (hoverTarget) {
hover(hoverTarget);
}
});
});
function hover(targetElement) {
// If the target and stored element are the same, return early
// because setting it again is unnecessary.
if (hoveredElement === targetElement) {
return;
}
// On first run, `hoveredElement` is undefined.
if (hoveredElement) {
hoveredElement.classList.remove('hover');
}
hoveredElement = targetElement;
hoveredElement.classList.add('hover');
}
.element {
height: 200px;
border: 2px solid tomato;
}
.element.hover {
background-color: lavender;
}
<div class="container">
<div class="element element-1">1</div>
<div class="element element-2">2</div>
<div class="element element-3">3</div>
<div class="element element-4">4</div>
<div class="element element-5">5</div>
</div>
Currently, the solution will hover the top-most element under the mouse both when moving the mouse and when scrolling. It might be more suitable for your needs to attach the mousemove listener to a set of specific elements and then always hover event.currentTarget (i.e. the element the event listener was attached to). As for the scroll part, you can use hoverTarget.closest to find the suitable element up in the DOM tree.
I've built custom scrolling on my page through wheel event on window. However it shoots even when I want to scroll the element with basic scrollbar. How to disable wheel event on this element, but be able to scroll?
You can check in the event handler if the element is scrollable
window.addEventListener('wheel', function(event) {
if ( event.target.scrollHeight < event.target.clientHeight ) {
// scollheight less than clientheight, means it doesn't have scrollbars ...
// do stuff
}
})
I have a div with several img-Objects with position:absolute like so:
<div>
<img>
<img>
<img>
<img>
</div>
Now when one of the image's mousedown handler is called, the event will only bubble down, ignoring the other images, even when they might be behind each other.
$('img').mousedown((event) -> if(something) event.stopPropagation());
$('div').mousedown(-> alert('event came through'));
I tried to nest them to work around this issue, but that didn't work either:
<div>
<img>
<div>
<img>
<div>
<img>
...
</div>
</div>
</div>
is there any way I can get this to work without manually running a hit-test on every image?
is there any way I can get this to work without manually running a hit-test on every image?
I believe it's correct that you have to run a hit-test yourself. mousedown only occurs on the front-most element under the mouse pointer, not all elements at those (x, y) coordinates.
In practice, this isn't so hard. Here's a working example: http://jsfiddle.net/TrevorBurnham/GBuZz/
In that example, mousedown events are captured on the container element and handled like so:
$('#container').on 'mousedown', (e) ->
{pageX, pageY} = e
$(#).children().each (i) ->
{top, left} = $(#).offset()
if top <= pageY <= top + $(#).outerHeight() &&
left <= pageX <= left + $(#).outerWidth()
console.log 'collision with box ' + i
I'm using the following code to allow users to resize a DIV element vertically, but when the click and drag the handle, text and other elements on the page get selected as if the user was just clicking and highlighting everything on the page. Is there a way to prevent this from happening as this looks very bad? This is being used with Prototype.js.
function DragCorner(container, handle) {
var container = $(container);
var handle = $(handle);
// Add property to container to store position variables
container.moveposition = {y:0};
function moveListener(event) {
// Calculate how far the mouse moved
var moved = { y:(container.moveposition.y - event.pointerY()) };
// Reset container's x/y utility property
container.moveposition = {y:event.pointerY()};
// Update container's size
var size = container.getDimensions();
container.setStyle({height: size.height + moved.y + 'px'});
}
// Listen for 'mouse down' on handle to start the move listener
handle.observe('mousedown', function(event) {
// Set starting x/y
container.moveposition = {y:event.pointerY()};
// Start listening for mouse move on body
Event.observe(document.body,'mousemove',moveListener);
});
// Listen for 'mouse up' to cancel 'move' listener
Event.observe(document.body,'mouseup', function(event) {
Event.stopObserving(document.body,'mousemove',moveListener);
console.log('stop listening');
});
}
Following up your earlier question and answer, add a small handle element and make it as the only element that can be selected and draggable. Also I guess you made that div's position as relative and handle position as absolute. Right??
Adding the following to the DIV fixed this:
onselectstart="return false;"