Differentiate between mouse wheel scroll or scrollbar scroll? - javascript

I've searched on Stackoverflow but can't seem to find a satisfactory answer to this question. Basically I'd like to know if the scroll was done via mousewheel or the browser scrollbar.

Something like this might work for you but it is not the best solution.
If the a wheel event occurs right before the scroll event, then the scroll is done with the wheel otherwise it is done with using something else then the wheel. There is a slight time difference between both events that are triggered thats why I use a threshold currTime - lastWheelTime > 30.
$('.test').on('scroll wheel DOMMouseScroll mousewheel', function(e) {
var lastWheelTime,
currTime = (new Date()).getTime();
if( e.type === 'scroll' ) {
lastWheelTime = $(this).data().lastWheelTime || 0;
if( currTime - lastWheelTime > 30 ) {
$('.info').text('no wheel');
} else {
$('.info').text('with wheel');
}
} else {
$(this).data().lastWheelTime = (new Date()).getTime();
}
});
.test {
width: 200px;
height: 300px;
border: 1px solid red;
overflow: auto;
}
.inner {
height: 600px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="info"></div>
<div class="test">
<div class="inner"></div>
</div>

Here is my trick to detect scrolling by wheel or not
(Thanks #t.niese for the code snippet, I have made some modification for my demo)
var withWheel = true;
$('.test').on('scroll', function() {
$(".info").text("with wheel: " + withWheel);
})
$('.inner').on('mouseover', function() {
withWheel = true;
}).on('mouseleave', function(e) {
e.stopPropagation();
withWheel = false;
});
.test {
width: 200px;
height: 300px;
border: 1px solid red;
overflow: auto;
}
.info {
position: fixed;
}
.inner {
height: 600px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="info"></div>
<div class="test">
<div class="inner"></div>
</div>

I'd say both the wheel scroll and scrollbar scroll are the same. See the jquery page Here.
The scroll event is sent to an element when the user scrolls to a different place in the element. It applies to window objects
Reading this, it looks like the same event is going to be fired for both.

Another way that might be possible (i haven't tried it yet) is checking whether a mouse button was pressed during the scroll event (one has to click the scrollbar, right?).

Related

How to control the scroll behaviour when there is a long event attached to it

I have a div with a scroll event on it. The scroll event is a sync function that takes some time to be computed. I see that the navigators might react to this situation with 2 different behaviours.
1- The scroll is freezed until the scroll event ends and then it will trigger the next scroll event.
2- I can scroll fluidly, but the scroll event is triggered once the previous event is done.
I would like to know how can I control and decide which one of the two scenarios the user will face.
codepen: https://codepen.io/xmorelll/pen/wvdmbYq
const container = document.getElementById("container");
const text = document.getElementById("text");
container.addEventListener('scroll', (event) => {
//sleep 1Second to simulate that the program is busy.
let a = new Date().getTime();
while(new Date().getTime() < a + 1000){};
text.style.top = container.scrollTop + "px";
});
#container {
height: 300px;
width: 300px;
border: solid 1px black;
overflow: auto;
}
#content {
height: 1000px;
width: 100%;
background-image: linear-gradient(red, yellow);
position: relative;
}
#text {
position: absolute;
left: 0;
}
<div id="container">
<div id="content">
<span id="text">Hola</span>
</div>
</div>

Detect mouse position when or immediately after div scroll

How can I detect where the mouse is positioned whenever I scroll? I can do this using a combination of onmousemove and scroll, but this seems like such a waste. I don't need to know where the mouse is before I scroll, only when I scroll - or perhaps even just right after I scroll. Please refer to my snippet below to see my attempt (event.clientY produces undefined) and please no jquery. Thanks.
var clientY_display_element = document.getElementById("clientY_display");
document.addEventListener('scroll', show_position = function (event) {
clientY_display_element.textContent = "event.clientY = " + event.clientY;
})
#container {
height: 50px;
}
#content {
height: 1000px;
background-color: rgba(120,120,120,1);
}
#clientY_display {
position: fixed;
}
<div id="container">
<div id="content"></div>
</div>
<div id="clientY_display">Scroll to see event.clientY position.. </div>
Is this what you want to achieve? Scroll inside the #container to see the number of pixels scrolled.
clientY is the coordinate of the mouse pointer when a mouse event was triggered. But you are listening for "scroll" event which is triggered when the document view has been scrolled. For scroll events, you can use the scrollTop to get the number of pixels that an element's content is scrolled vertically.
I also changed a bit your css, for this example's purpose.
var clientY_display_element = document.getElementById("clientY_display");
let scroller = document.querySelector("#container")
scroller.addEventListener('scroll', show_position = function (event) {
clientY_display_element.textContent = `scroll position = ${scroller.scrollTop}`;
})
#container {
height: 50px;
overflow: scroll;
}
#content {
height: 1000px;
background-color: rgba(120,120,120,1);
}
#clientY_display {
position: fixed;
}
<div id="container">
<div id="content"></div>
</div>
<div id="clientY_display">Scroll to see scroll position.. </div>

How to scroll MULTIPLE scrollbars at the same time

When I scroll the wrapper div the 2 images should also scroll together with it but it seems only the 2 images sync together but not the wrapper
The 2 images should sync with the body's scrollbar
$(function(){
$('.linked').scroll(function(){
$('.linked').scrollTop($(this).scrollTop());
})
})
#left { width: 300px; height: 400px; overflow: scroll; float: left; }
#right { width: 300px; height: 400px; overflow: scroll; float: left; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<div id="left" class="linked">
<img src="http://upload.wikimedia.org/wikipedia/commons/5/51/Eiffel_Tower_(72_names).jpg">
</div>
<div id="right" class="linked">
<img src="http://upload.wikimedia.org/wikipedia/commons/5/51/Eiffel_Tower_(72_names).jpg">
</div>
This might help
let l = document.getElementById(".left");
let r = document.getElementById(".right");
l.addEventListener("scroll", function () {
r.scrollTop = l.scrollTop;
});
l.addEventListener("scroll", function () {
r.scrollTop = l.scrollTop;
});
If I understood correctly you're question is how to sync a set of scroll bars including the window scroll bar? This is possible and I will demonstrate for education purposes only however this is not something I would personally recommend.
Manipulating the browser window scroll bar can create a poor user experience as it interferes with the expected behavior of the scroll bar. Additionally, scrollbar manipulation can cause unexpected behavior and may not work consistently across different browsers and devices, leading to a fragmented user experience.
This example does not take into consideration the dimensions of the browser window and elements with scroll bars so be warned that it might not work as expected.
// Pseudo code
// 1. Query the scroll bars you want to sync
// 2. Add event listener to each of them
// 3. In the event listener, set the scroll position of the other scroll bar(s) to the same value
// Step 1
const scrollBarContainers = [
...document.querySelectorAll('.scrollable'),
document // Include document for window scrolling
];
// Step 2
scrollBarContainers.forEach(container => {
container.addEventListener('scroll', syncScrolls);
});
// Step 3
function syncScrolls(e) {
scrollBarContainers.forEach(container => {
if (container !== e.target) {
// The document element doesn't directly have a scrollLeft property.
// It's on the scrollingElement. The other elements have it directly.
if (container.scrollingElement) {
container.scrollingElement.scrollLeft = e.target.scrollLeft;
} else if (e.target.scrollingElement) {
container.scrollLeft = e.target.scrollingElement.scrollLeft;
} else {
container.scrollLeft = e.target.scrollLeft;
}
}
});
}
.container {
height: 100px;
border: 1px solid gray;
width: 900px;
margin-bottom: 1em;
overflow: auto;
}
span {
display: inline-block;
width: 1200px;
height: 50px;
background-color: red;
}
<div class="container scrollable"><span></span></div>
<div class="container scrollable"><span></span></div>

Why does Firefox fire mousemove outside an overflow:hidden element while dragging, but not with overflow:visible?

I just found this and can't explain myself why it behaves like this. But this is exactly the behavior I want to implement. Try this example:
const redBar = document.querySelector("#red");
const greenBar = document.querySelector("#green");
redBar.onmousemove = moveHandle;
redBar.onclick = moveHandle;
greenBar.onmousemove = moveHandle;
greenBar.onclick = moveHandle;
function moveHandle(event) {
if (event.buttons === 1 || event.type === "click") {
let y = (event.y - this.offsetTop) - 7.5;
if (y < -7.5) {
y = -7.5;
} else if (y > this.offsetHeight - 7.5) {
y = this.offsetHeight - 7.5;
}
this.querySelector(".handle").style.top = y + "px";
}
}
body {
display: flex;
width: 100vw;
height: 100vh;
justify-content: center;
align-items: center;
margin: 0;
}
.container {
display: flex;
flex-direction: row;
}
.bar {
position: relative;
background: red;
overflow: visible;
margin: 30px;
width: 30px;
height: 200px;
}
.no-overflow {
background: green;
overflow: hidden;
}
.handle {
position: relative;
width: 60px;
height: 15px;
top: -7px;
left: -15px;
background: #222d;
}
<div class="container">
<div id="red" class="bar">
<div class="handle"></div>
</div>
<div id="green" class="no-overflow bar">
<div class="handle"></div>
</div>
</div>
Try moving the handle of the red bar and of the green bar. On the green bar you can move the handle once you had the mouse down on the bar and keep holding down the button, even if you are not above the bar.
The only difference is that on the green one (except the color) the overflow
is hidden.
Is this a bug or did I forget something? (I'm on Firefox btw)
And whats the best way to implement a behavior like this without the overflow being hidden because when i try to do this, it always get pretty messy. (I know there are sliders but maybe in case I want something like this with 2 dimensions)
Thanks!
UPDATE
By further testing, I noticed that this stops working when user-select is none. Then it behaves like the element without the hidden overflow.
The mousemove misfiring in Firefox due to overflow:hidden looks like bug 620513 / bug 1352061.
As for achieving this effect without relying on the bug, Teemu correctly notes that:
Usually this is done by listening mousedown on the element, and when that fires, stop listening mousedown and start to listen mouseup and mousemove on document. When mouseup fires, stop listening mouseup and mousemove, and start listening mousedown on the element again.
Pointer Events API (.setPointerCapture()) is the modern way to achieve this behavior (needs a polyfill for Safari and older browsers).

Why touchmove event is not fired after DOM changes?

While moving my application from mouse to touch events I noticed some strange behaviour. Basically, touchmove stops working after DOM changes. Mouse events work fine in the same situation. I tested it with chrome developer tools as well as firefox's. They seem to agree on results. Is it a bug or am I missing something?
I created very simple code example to demonstrate that the problem is not connected to any frameworks or libs I use. I also found seemingly related question which unfortunately contains no solution.
Touch demo:
window.addEventListener("touchmove", onTouchMove, {passive: false})
document.addEventListener('DOMContentLoaded', function(){
var elem = document.getElementById("nice");
console.log(elem)
elem.addEventListener("touchstart", onTouchStart)
})
function onTouchMove(event) {
console.log("touch move")
}
function onTouchStart(event) {
console.log("touch start")
var elem = document.getElementById("nice")
elem.remove()
}
<!DOCTYPE html>
<html>
<body style="width: 100%; height: 100%; background-color: yellow">
<div style="position: absolute; width: 100px; height: 100px; background-color: red; left: 100px; top: 100px" id="nice"></div>
</body>
</html>
Mouse demo:
window.addEventListener("mousemove", onMouseMove, {passive: false})
document.addEventListener('DOMContentLoaded', function(){
var elem = document.getElementById("nice");
console.log(elem)
elem.addEventListener("mousedown", onMouseDown)
})
function onMouseMove(event) {
console.log("mouse move")
}
function onMouseDown(event) {
console.log("mouse start")
var elem = document.getElementById("nice")
elem.remove()
}
<!DOCTYPE html>
<html>
<body style="width: 100%; height: 100%; background-color: yellow">
<div style="position: absolute; width: 100px; height: 100px; background-color: red; left: 100px; top: 100px" id="nice"></div>
</body>
</html>
One continuous drag gesture starting from red square should cause 1) 'start' message in the log, 2) disappiaring of that square, which is the DOM change in this case 3) sequence of 'move' messages in the log. It is so in mouse demo, but in touch demo there are no 'move' events after square disappears.
This is an intended behaviour if your element is deleted.
According to the docs, if you delete an element, the events will still be targeted at it, and hence won't necessarily bubble up to the window or document anymore.
So there are two solutions if you want to delete the element. You can modify the "remove" method so that it would only hide the element until the touch process ends, or you can attach events to the target itself.
Here is an example, you can see the window touchmove events do not appear, while the element touchmove events appear even after the element's removal.
window.addEventListener("touchmove", onTouchMoveWindow, {passive: false})
document.addEventListener('DOMContentLoaded', function(){
var elem = document.getElementById("nice");
console.log(elem)
elem.addEventListener("touchstart", onTouchStart)
elem.addEventListener("touchmove", onTouchMoveElement)
})
function onTouchMoveWindow(event) {
console.log("touch move window")
}
function onTouchMoveElement(event) {
console.log("touch move element")
}
function onTouchStart(event) {
console.log("touch start")
var elem = document.getElementById("nice")
elem.remove()
}
<!DOCTYPE html>
<html>
<body style="width: 100%; height: 100%; background-color: yellow">
<div style="position: absolute; width: 100px; height: 100px; background-color: red; left: 100px; top: 100px" id="nice"></div>
</body>
</html>
Related questions:
Touchmove event stops triggering after any element is removed from dom
Touch Move event don't fire after Touch Start target is removed
touchmove events stop after replacing innerHTML

Categories