Dispatch mouse wheel event on another DOM element - javascript

I have a web page with scrollable div on it.
On top of scrollable div I have absolutely positioned div that overlaps half of scrollable div.
When I put mouse cursor over scrollable div I can scroll it with mouse wheel. But when I move cursor over overlapping div then mouse wheel stops scroll that div (and this is correct behavior because absolute positioned div is not inside scrollable div).
Question: how to pass or dispatch scroll event received by absolute positioned div to this underlying scrollable div to make this absolute positioned div 'transparent' for mouse wheel events.
I could get it work in Chrome, but not in IE and Firefox. How to rewrite this code to get it work in IE and Firefox?
if ($.browser.webkit) {
$(".overlap-container").bind("mousewheel", function (e) {
var origEvent = e.originalEvent;
var evt = document.createEvent("WheelEvent");
evt.initWebKitWheelEvent(
origEvent.wheelDeltaX,
origEvent.wheelDeltaY,
window,
0,
0,
0,
0,
origEvent.ctrlKey,
origEvent.altKey,
origEvent.shiftKey,
origEvent.metaKey);
$(".scroll-container").get(0).dispatchEvent(evt);
});
}
See example here:
http://jsfiddle.net/HAc4K/5
EDITED: This issue originally is from jqGrid - frozen columns don't react mouse wheel scrolling.
In Chrome and Firefox awesome CSS property is supported: pointer-events:none

Looks like a known issue with jQuery: OriginalEvent not supported in IE

The short answer: you use wrong parameters of initWheelEvent in the demo in case of usage Internet Explorer. The method should have 16 parameters described in the documentation. Yo use currently only 11 parameters the same which have initWebKitWheelEvent, but the meaning of all parameters of initWheelEvent is absolutely another. You have to fix the parameters of initWheelEvent.

Use RetargetMouseScroll(overlap container, scroll container).

I realize this isn't exactly what you're looking for, but at least in Chrome, IE7+, and Firefox 3.5+ this does what you ask - scroll the underlying div when the div overlaying it receives scroll events:
http://jsfiddle.net/b9chris/yM3qs/
It's doing so simply because the overlapping div is a child of the div it overlaps - no jquery passing on any mousewheel events (although it is listening to scroll to ensure the overlapping div stays where it needs to be).
Implementing that kind of workaround in jqGrid might require upending a fair bit of code there, however.
HTML:
<div id=under>
(content goes here)
<div id=over></div>
</div>
CSS:
#under {
position: absolute;
left: 0; top: 0;
width: 220px; height: 200px;
overflow-y: scroll;
}
#over {
position: absolute;
left: 1px; top: 100px;
width: 200px; height: 100px;
z-index: 2;
}
JS:
(function() {
$('#under').on('scroll', function() {
$('#over').css('top', $(this).scrollTop() + 100);
});
})();

Here's the 2019 solution for overlapped container to get wheel event:
document.querySelector('.overlap').on('wheel', (e) => {
const overlap = e.target
overlap.style.pointerEvents = 'none'
setTimeout(() => {overlap.style.pointerEvents = 'auto'}, 0)
})
This way the overlapped element gets wheel event from the overlapping element called .overlap.

Related

Don't allow horizontal scroll when scrolling vertically (and vice versa)

I have a list, with the overflow-x and overflow-y set to auto. In addition, I've set up momentum scroll, so the touch scrolling works nice in mobile, using webkit-overflow-scrolling: true.
The issue, however, is that I cannot figure out how to disable the horizontal scroll when scrolling vertically. It leads to really bad user experience, as the swiping towards the top left or top right will cause the table to scroll diagonally. When the user is scrolling vertically, I absolutely do NOT want any scrolling horizontally until the user has stopped scrolling vertically.
I've tried the following:
JS:
offsetX: number;
offsetY: number;
isScrollingHorizontally = false;
isScrollingVertically = false;
//Detect the scrolling events
ngOnInit() {
this.scrollListener = this.renderer.listen(
this.taskRows.nativeElement,
'scroll',
evt => {
this.didScroll();
}
);
fromEvent(this.taskRows.nativeElement, 'scroll')
.pipe(
debounceTime(100),
takeUntil(this.destroy$)
)
.subscribe(() => {
this.endScroll();
});
}
didScroll() {
if ((this.taskRows.nativeElement.scrollLeft != this.offsetX) && (!this.isScrollingHorizontally)){
console.log("Scrolling horizontally")
this.isScrollingHorizontally = true;
this.isScrollingVertically = false;
this.changeDetectorRef.markForCheck();
}else if ((this.taskRows.nativeElement.scrollTop != this.offsetY) && (!this.isScrollingVertically)) {
console.log("Scrolling Vertically")
this.isScrollingHorizontally = false;
this.isScrollingVertically = true;
this.changeDetectorRef.markForCheck();
}
}
endScroll() {
console.log("Ended scroll")
this.isScrollingVertically = false;
this.isScrollingHorizontally = false;
this.changeDetectorRef.markForCheck();
}
HTML:
<div
class="cu-dashboard-table__scroll"
[class.cu-dashboard-table__scroll_disable-x]="isScrollingVertically"
[class.cu-dashboard-table__scroll_disable-y]="isScrollingHorizontally"
>
CSS:
&__scroll {
display: flex;
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: auto;
will-change: transform;
-webkit-overflow-scrolling: touch;
&_disable-x {
overflow-x: hidden;
}
&_disable-y {
overflow-y: hidden;
}
}
But the everytime I set overflow-x or overflow-y to hidden when its been scrolled, scrolling will glitch and jump back to the top. I've also noticed that webkit-overflow-scrolling: true is the reason why this occurs, when I remove it, the behavior seems to stop, but I absolutely need this for momentum scrolling in mobile devices.
How do I disable horizontal scroll when scrolling vertically?
It's generally a bad practice for your design to need multiaxis scrolling on mobile, unless maybe you're showing big tables of data. That being said, why do you want to prevent it? If a user wants to scroll diagonally, that doesn't seem like the end of the world to me. Some browsers, like Chrome on OSX, already do what you're describing by default.
If you must have single-axis scrolling, a possible solution might be to keep track of the scroll position yourself via touchstart and touchmove events. If you set your drag threshold lower than the browser's, you may be able to do your css stuff before it starts scrolling, avoiding the perceived glitch. Also, even if it does still glitch, you have the touch start and the touch's current location. From these, if you record your div's starting scroll position, you can manually scroll the div to the correct place to counteract it jumping to the top if you have to. A possible algorithm might look like this:
// Touchstart handler
if (scrollState === ScrollState.Default) {
// Record position and id of touch
touchStart = touch.location
touchStartId = touch.id.
scrollState = ScrollState.Touching
// If you have to manually scroll the div, first store its starting scroll position:
containerTop = $('.myContainer').scrollTop();
containerLeft = $('.myContainer').scrollLeft();
}
// Touchmove handler - If the user has dragged beyond a distance threshold,
// do the css classes swap.
if (touch.id === touchStartId && distance(touchStart, touch.location > threshold) {
scrollState = ScrollState.Scrolling;
swapCssClasses();
// If you have to manually scroll the div to prevent jump:
$('.myContainer').scrollTop(containerTop + (touch.location.y - touchStart.y));
// Do the same for horizontal scroll.
}
// Then, listen for debounced scroll events, like you're already doing,
// to reset your state back to default.
Second idea: in your scroll handler, instead of changing the css classes, set the scroll position of the div directly for the axis you want locked. IE, if you're scrolling horizontally, always set the scrollTop to its starting value. This might also cancel scrolling, not sure. You'd have to try it to see if it works.
Try this
HTML
<div
class="cu-dashboard-table__scroll-vertical"
>
<div
class="cu-dashboard-table__scroll-horizontal"
>
<!-- Scroll content here -->
</div>
</div>
CSS
&__scroll {
&-horizontal {
overflow-x: auto;
width: 100%;
-webkit-overflow-scrolling: touch;
}
&-vertical {
overflow-y: auto;
height: 100%;
-webkit-overflow-scrolling: touch;
}
}
Instead of using one div for scrolling, why dont you use two? Have one for X and one for Y
Need to use three containers.
In the first container I enable vertical scrolling and disallow horizontal.
In the second, vice versa, I enable horizontal scrolling and disallow vertical.
Be sure to use overflow-x: hidden; and overflow-y: hidden;, otherwise child containers may go beyond the current container.
Also need to use min-width: 100%; min-height: 100%;.
For the third container we need to use display: inline-block; and then the internal content will stretch this container and the corresponding scroll bars will appear on the two parent blocks.
HTML
<div class="scroll-y">
<div class="scroll-x">
<div class="content-container">
<!-- scrollable content here -->
</div>
</div>
</div>
CSS
.scroll-y {
position: absolute;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
height: 100%;
min-width: 100%;
min-height: 100%;
}
.scroll-x {
overflow-y: hidden;
overflow-x: auto;
width: auto;
min-width: 100%;
min-height: 100%;
}
.content-container {
min-width: 100%;
min-height: 100%;
display: inline-block;
}
You can test it here in Safari on iPhone.
Good luck!! 😉
There are numerous ways to try to solve this. Some good ideas are offered here.
However, as others have alluded, relying on (or trying to avoid) multi-dimensional scrolling is a bad UX smell - indicative of the UX being a problem. I don't think this is a legitimate dev issue. It would be better to take a step back and reevaluate what you're trying to accomplish. One of the issues might be that in an effort to make the UI more usable, you'll be confusing users. The usability described here would likely cause confusion.
Why can't I scroll horizontally?
Why when I stop scrolling vertically, only then can I scroll horizontally?
These are some questions that may be asked (inaudible).
If you're trying to allow for additional information of the vertical data list to be browsable only when the user has selected the row, it would likely be much better to simply have a flat list by default only scrollable vertically, and only toggle the vertical information when the "row" has been selected/activated. Collapsing the details would get you back to the flat vertical list.
If you're having to jump through hoops to solve such a fringe technical challenge, it's a good indication that the user experience has not been designed well to begin with.
this looks like fun ;)
I won't argue about if it's reasonable.
I tried it with RxJS:
ngAfterViewInit() {
const table = document.getElementById("table");
const touchstart$ = fromEvent(table, "touchstart");
const touchmove$ = fromEvent(table, "touchmove");
const touchend$ = fromEvent(table, "touchend");
touchstart$
.pipe(
switchMapTo(
touchmove$.pipe(
// tap(console.log),
map((e: TouchEvent) => ({
x: e.touches[0].clientX,
y: e.touches[0].clientY
})),
bufferCount(4),
map(calculateDirection),
tap(direction => this.setScrollingStyles(direction)),
takeUntil(touchend$)
)
)
)
.subscribe();
}
We buffer every 4th touchemove event and then make some highly sophisticated calculation with the coordinates of the four events (map(calculateDirection)) which outputs RIGHT, LEFT, UP or DOWN and based on that I try to disable vertical or horicontal scrolling. On my android phone in chrome it kind of works ;)
I'v created a little playground, which could be enhanced, rewritten, whatsoever ...
Cheers Chris

strange results differing between left and right positioning

This is a strange bug i'm facing, i don't really understand the problem so forgive me for the obscure title.
The problem is I'm developing a SPA style site and i want the content to slide in from the right (when the buttons at the bottom are clicked)
I thought this would be easy, but for some reason it is easy to achieve from the left, using the example below
.page {
right: 100%;}
.page.active {
right: 0; }
https://jsfiddle.net/pphfstos/3/
and less ideally to slide the full width across like this
.page {
left: -100%;}
.page.active {
left: 0; }
https://jsfiddle.net/pphfstos/4/
But when i try to create the same effect as the first example but from the right it not only doesn't work but totally seems to destroy the page
.page {
right: -100%;}
.page.active {
right: 0; }
https://jsfiddle.net/pphfstos/5/
There is other code involved as you can see in the fiddle, but these are the only things that are different between the 3 examples
Can anyone explain what is happening and how to fix it?
Content you position outside of the viewport to the left is actually hidden, and can’t be reached via scrolling.
Content you position outside of the viewport to the right however “extends” the page in that direction, and can be scrolled to.
Remove the overflow-x: hidden from html/body in your first and third fiddle, and you see what I mean – in the first one, the content positioned to the left is hidden, and no scrollbar appears; in your third fiddle however you do get a scrollbar, and the content positioned to the right can be reached via scrolling, moving the part of your page that is initially visible to the left while you’re doing so.
Now, setting overflow-x: hidden removes the ability to scroll using the mouse; but the viewport can still be “shifted” to display that content, for example by navigating to an anchor – and that is what your links are doing. (But because this is an “instant jump” and not smooth scrolling, you don’t see your initially visible content move away, it is just gone instantly.)
So you simply need to suppress the default action of your anchor links in your click event handler:
mainNavButton.click(function (e) {
e.preventDefault(); // prevent event default action
// … rest of your code
– and the effect of the page ”jumping” to the anchor position is gone.
https://jsfiddle.net/pphfstos/6/

How to scroll window without overflow

I need to scroll in a window but my window height is too small to scroll. Is it possible to scroll when the height of container is too small to see the scrollbar.
Here is my code to scroll:
setTimeout(function(){
$(window).scrollTop($(window).scrollTop()+1);
$(window).scrollTop($(window).scrollTop()-1);
}, 800);
I need to scroll window or body even if height of it is less than 100px.
I believe you want to set a min-height of 110% on html in your CSS. I would do:
html {
min-height: 110%;
}
Here's a demo: http://jsbin.com/sebago/1/edit?html,css,output
If you define a fixed height in your element, then you can use overflow:scroll to enable scroll.
You need first hide the scroll bar to do not take space (because you don't have too much space in the element), you can make that with the next css:
#elementId{
overflow: hidden;
}
Then you need to bind the mousewheel event over the 'small' element and trigger a function to manually scroll your element, you can do that with the next jQuery code:
$('#elementId').bind('DOMMouseScroll mousewheel', function(e) {
$('#elementId').scrollTop($('#elementId').scrollTop()+1);
});
This example is simplified just to bind the mousewheel event in general, to know if it is up or down you can use the jQuery Mouse Wheel Plugin that you can get here.
To see scrollbar, just use CSS property overflow:scroll; on your container.

Fixing third div under first div after scrolling down

I'm trying to fix #fixed_header_bottom div right under #fixed_header_top div after scrolling 100px down but failed to do so. #fixed_header_middle div obviously will appear and disappear when scrolling up and down and only #fixed_header_top, #fixed_header_bottom and #body_block will be visible when (for example scrolling down come to an end).
JSFIDDLE is here
In the second image, #fixed_header_middle disappeared completely but will start appearing when scrolling up.
Thanks
First, you need to set a top style for your headers so they are in the correct position, you should think about a more robust way to do this.
Another way to consider doing this is to make a hidden clone of your bottom header.
Then, simply hide/show it when the scroll position is correct. This method avoids any funny business with the scroll bar changing size and/or position as the element is taken in and out of the scrollable portion of the page (because the original is still there):
JSFiddle Demo
An even better way is to simply make a placeholder that you show/hide as the bottom header is fixed/un-fixed:
<div id="fixed_header_top">fixed_header_top</div>
<div id="fixed_header_middle">fixed_header_middle</div>
<div id="fixed_header_bottom">fixed_header_bottom</div>
<div id="fixed_placeholder">Shouldn't ever see me</div>
JS:
$(document).ready(function () {
$(window).bind("scroll", function (e) {
if(!$('#fixed_header_bottom').hasClass('fixed')) {
if ($(document).scrollTop() >= 100) {
$('#fixed_placeholder').show();
$('#fixed_header_bottom').addClass('fixed');
}
} else {
if ($(document).scrollTop() < 100) {
$('#fixed_placeholder').hide();
$('#fixed_header_bottom').removeClass('fixed');
}
}
});
});
CSS:
#fixed_header_bottom, #fixed_placeholder {
display: block;
width: 100%;
height: 50px;
background-color: #11DD55;
}
#fixed_placeholder {
display: none;
}
.fixed {
position: fixed;
top: 50px;
}
JSFiddle Demo
This code works as it should.
Your problem is that you didn't set any top property, so it stays on its original top position.
Same Fiddle with jQuery 1.8.3 (as 1.10 don't handle scrollTop() method for IE) ;): http://jsfiddle.net/h8H6N/4/
I added top: 0; to the header top, and top: 50px; to bottom header, assuming that's the render you're looking for.

How can I disable a browser or element scrollbar, but still allow scrolling with wheel or arrow keys?

I want to hide any scrollbars from my div elements and my whole body, but still let the user scroll with the mouse wheel or arrow keys. How can this be achieved with raw JavaScript or jQuery? Any ideas?
Like the previous answers, you would use overflow:hidden to disable the scrollbars on the body/div.
Then you'd bind the mousewheel event to a function that would change the scrollTop of the div to emulate scrolling.
For arrow keys, you would bind the keydown event to recognize an arrow key, and then change scrollTop and scrollLeft of the div as appropriate to emulate scrolling.
(Note: you use keydown instead of keypress since IE doesn't recognize keypress for arrow keys.)
Edit: I couldn't get FF/Chrome to recognize keydown on a div, but it works in IE8. Depending on what you needed this for, you can set a keydown listener on the document to scroll the div. (Check out the keyCode reference as an example.)
For example, scrolling with the mouse wheel (using jQuery and a mousewheel plugin):
<div id="example" style="width:300px;height:200px;overflow:hidden">
insert enough text to overflow div here
</div>
<script>
$("#example").bind("mousewheel",function(ev, delta) {
var scrollTop = $(this).scrollTop();
$(this).scrollTop(scrollTop-Math.round(delta));
});
</script>
(This is a quick mockup, you'd have to adjust the numbers since for me, this scrolls a bit slowly.)
keyCode reference
mousewheel plugin
keydown, keypress # quirksmode
Update 12/19/2012:
The updated location of the mousewheel plugin is at: https://github.com/brandonaaron/jquery-mousewheel
What about a purely CSS solution?
Solution 1 (cross browser but more hacky)
#div {
position: fixed;
right: -20px;
left: 20px;
background-color: black;
color: white;
height: 5em;
overflow-y: scroll;
overflow-x: hidden;
}
<html>
<body>
<div id="div">
Scrolling div with hidden scrollbars!<br/>
On overflow, this div will scroll with the mousewheel but scrollbars won't be visible.<br/>
Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>
</div>
</body>
</html>
Solution 2 (uses experimental features, may not support some browsers)
Just add the nobars class to any element you want to hide the scrollbars on.
.nobars {
/* Firefox: https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-width */
scrollbar-width: none;
/* IE: https://msdn.microsoft.com/en-us/library/hh771902(v=vs.85).aspx */
-ms-overflow-style: none;
}
.nobars::-webkit-scrollbar {
/* Chrome/Edge/Opera/Safari: https://css-tricks.com/custom-scrollbars-in-webkit/ */
display: none;
}
Solution 3 (cross browser javascript)
Perfect Scrollbar doesn't require jQuery (although it can utilise jQuery if installed) and has a demo available here. The components can be styled with css such as in the following example:
.ps__rail-y {
display: none !important;
}
Here is a complete example including the implementation of Perfect Scrollbar:
<link rel="stylesheet" href="css/perfect-scrollbar.css">
<style>
#container {
position: relative; /* can be absolute or fixed if required */
height: 200px; /* any value will do */
overflow: auto;
}
.ps__rail-y {
display: none !important;
}
</style>
<script src='dist/perfect-scrollbar.min.js'></script>
<div id="container">
Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>Scrollable<br>
</div>
<script>
// on dom ready...
var container = document.getElementById("container");
var ps = new PerfectScrollbar(container);
//ps.update(container);
//ps.destroy(container);
</script>
You dont have to use jquery or js to make this. Its more performant with simple webkit.
Just add the code below to your css file.
::-webkit-scrollbar {
display: none;
}
Caution !
This will disable all the scrollbar so be sure to put it in a specific class or id if you just want one to be hidden.
I much prefer SamGoody's answer provided to a duplicate of this question. It leaves native scrolling effects intact, instead of trying to manually re-implement for a few particular input devices:
A better solution is to set the target div to overflow:scroll, and wrap it inside a second element that is 8px narrower, who's overflow:hidden.
See the original comment for a fleshed-out example. You may want to use JavaScript to determine the actual size of scrollbars rather than assuming they are always 8px wide as his example does.
To get this working for me, I used this CSS:
html { overflow-y: hidden; }
But I had problems using $(this).scrollTop(), so I bound to my #id, but adjusted the scrollTop of window. Also, my smooth scrolling mouse would fire lots of 1 or -1 deltas, so I multiplied that by 20.
$("#example").bind("mousewheel", function (ev, delta) {
var scrollTop = $(window).scrollTop();
$(window).scrollTop(scrollTop - Math.round(delta * 20));
});
As BaldrĂĄni said above
::-webkit-scrollbar { display: none; }
Or you can do
::-webkit-scrollbar{ width: 0px; }
(posted for other people that stumble on this from google search!)
Well, perhaps not the most intuitive in my opinion, but I can imagine you being able to make it a decent experience, give this a try.
overflow:hidden;
make sure the parent object has a height and width, and displays as block

Categories