This is a classic. You have a parent and a child element. Child element is absolutely positioned and you want the user to scroll through its content. However, when you reach the bottom of the child element, the parent element (which also is allowed to have scroll bars) begins to scroll. That's undesirable. The basic behavior I want to reproduce is that of the comment section of the New York Times. For
The body is allowed to scroll down, but when you are at the bottom of the comment section, scrolling down doesn't do anything. I think the main difference in this case, is that I want to let the user scroll down when the cursor is positioned over the body element. Other approaches require to add a class to the body to prevent any scroll event in the body. I thought I would be able to do this with a bit of Javascript in Angular 2, but this is my failed attempt so far:
I have a custom directive in my child component:
<child-element scroller ></child-element>
and this directive is supposed to stop the propagation of the scroll event to the body element:
import {Component} from 'angular2/core'
import {Directive, ElementRef, Renderer} from 'angular2/core';
@Directive({
selector: '[scroller]',
})
export class ScrollerDirective {
constructor(private elRef: ElementRef, private renderer: Renderer) {
renderer.listen(elRef.nativeElement, 'scroll', (event) => {
console.log('event!');
event.stopPropagation();
event.preventDefault();
})
}
}
It actually listens to the event but it doesn't stop the propagation.
Demo: Scroll down through the list of numbers and when you reach the bottom, its parent element starts to scroll down. That's the problem.
If you have another approach to accomplish this, please let me know.
UPDATE: Based on the answer provided by Günter Zöchbauer, I'm trying to prevent the wheel event when the user reaches the bottom. This is basically what I have so far in this updated demo:
renderer.listen(elRef.nativeElement, 'wheel', (e) => {
console.log('event', e);
console.log('scrollTop', elRef.nativeElement.scrollTop);
console.log('lastScrollTop', lastScrollTop);
if (elRef.nativeElement.scrollTop == lastScrollTop && e.deltaY > 0) {
e = e || window.event;
if (e.preventDefault)
e.preventDefault();
e.returnValue = false;
}
else if (elRef.nativeElement.scrollTop == 0) {
lastScrollTop = -1;
}
else {
lastScrollTop = elRef.nativeElement.scrollTop;
}
}, false)
However, the logic is ugly and doesn't work great. For example, when the user reaches the bottom, scrolls up a little and scrolls down again, the parent component moves slightly. Does anyone know how to deal with this? A (much) better implementation?
UPDATE 2:
This is much better, but it is late now, so I will check again tomorrow.
CodePudding user response:
I would suggest something more straightforward in my opinion: add an arbitrary class to an arbitrary parent, and prevent scrolling via CSS overflow: hidden
.
In this example, I wrote the directive to prevent the parent from scrolling while the element exists at all, as this was my desired behavior. Instead of OnDestroy and AfterViewInit, for your use case you should bind to mouseenter
and mouseleave
HTML:
<div add- to="body">Some Content Here</div>
CSS:
.noscroll { overflow: hidden; }
TS:
import {Directive, AfterViewInit, OnDestroy, Input} from "@angular/core";
@Directive({
selector: '[add-class]'
})
export class AddClassDirective implements AfterViewInit, OnDestroy {
@Input('add-class')
className: string;
@Input('to')
selector: string;
ngOnDestroy(): void {
document.querySelector(this.selector).classList.remove(this.className);
}
ngAfterViewInit(): void {
document.querySelector(this.selector).classList.add(this.className);
}
}
CodePudding user response:
This is my best attempt so far.
renderer.listen(elRef.nativeElement, 'wheel', (e) => {
let el = elRef.nativeElement;
let conditions = ((el.scrollTop el.offsetHeight > el.scrollHeight)) && e.deltaY > 0 || el.scrollTop === 0 & e.deltaY < 0;
if (conditions) {
e = e || window.event;
if (e.preventDefault)
e.preventDefault();
e.returnValue = false;
}
}, false)
It works great in Firefox, Chrome stable and beta, but for some reason, Chrome dev behaves a bit differently. If the user scrolls down really hard near the bottom (or equivalently, scrolls up near the top), the parent element moves a little. Unfortunately, I noticed that the comment section of the New York Times also has the same annoyance. If you have suggestions, let me know.
I reported the issue occurring in Chrome dev and this has been the response so far.
CodePudding user response:
I had the similar problem. I wanted to disable scrolling while my modal component was opened.
Here is how I've solved it:
import { Component, HostListener } from '@angular/core';
@Component({
selector : 'app-modal',
templateUrl: './modal.component.html',
styleUrls : ['./modal.component.scss'],
})
export class ModalComponent {
@HostListener('wheel', ['$event'])
handleWheelEvent(event) {
event.preventDefault();
}
...
Hope you find it useful.
CodePudding user response:
Robert' answer was really helpful, but it was outdated and didn't work for me first. I improved it, to make it work for me, and therefore I wanted to share it with you:
import { Component, Directive, Renderer2, ElementRef } from '@angular/core';
@Directive({
selector: '[scroller-directive]',
})
export class ScrollerDirective {
constructor(elRef: ElementRef, renderer: Renderer2) {
renderer.listen(elRef.nativeElement, 'wheel', (event) => {
const el = elRef.nativeElement;
const conditions = ((el.scrollTop el.offsetHeight >= el.scrollHeight)) && event.deltaY > 0 || el.scrollTop === 0 && event.deltaY < 0;
if (conditions === true) {
event.preventDefault();
event.returnValue = false;
}
});
}
}
Used in html like:
<div scroller-directive>
// Code
</div>
CodePudding user response:
You can't prevent or stop-propagate the scroll
event. You need to prevent the events that cause the scroll
event.
-
- How to disable scrolling temporarily? (also monitors some keys)
CodePudding user response:
overflow: hidden
hides vertical scrollbar, which is something I don't want.
AngularX solution:
HTML:
<div (wheel)="onWheel($event)">
</div>
TS:
onWheel($event)
{
$event.preventDefault();
$event.stopPropagation();
}