After Edge joined the valiant ranks of Chromium browsers, customization of scrollbars via CSS is absent only in Firefox. This is great, but apart from Firefox, the CSS solution has a ton of limitations. See what black magic you have to use to fade out. To get full control over the appearance, you still need to resort to JavaScript. Let's see how to do it through the Angular component in a good way.

CSS magic
, , . -, . . scrollbar-width
, Firefox. Edge IE -ms-overflow-style
. IE , position: sticky
. Chrome Safari ::-webkit-scrollbar
.
-, , . , - position: fixed
. : .
position: sticky
display: flex
. :
<div class="bars">...</div>
<div class="content"><ng-content></ng-content></div>
. , - z-index: 1000, 1001, 9999
. , position: relative, z-index: 0
. - .
z-index: 1
, , 100%. :

, . . margin-right: -100%
, «» .
, , float
, 100%, (max-height, flex: 1
).
Angular
Angular, , . . . , . :
<div class="bars">
<div *ngIf="hasVerticalBar" class="bar">
<div
class="thumb"
[class.thumb_active]="verticalThumbActive"
[style.height.%]="verticalView"
[style.top.%]="verticalThumb"
></div>
</div>
</div>
:
//
get verticalScrolled(): number {
const {
scrollTop,
scrollHeight,
clientHeight
} = this.elementRef.nativeElement;
return scrollTop / (scrollHeight - clientHeight);
}
//
get verticalSize(): number {
const { clientHeight, scrollHeight } = this.elementRef.nativeElement;
return Math.ceil(clientHeight / scrollHeight * 100);
}
//
get verticalPosition(): number {
return this.verticalScrolled * (100 - this.verticalSize);
}
// ,
get hasVerticalBar(): boolean {
return this.verticalSize < 100;
}
, , β . . , .
mousedown
, mousemove
mouseup
.
:
<div
#vertical
class="thumb"
[class.thumb_active]="verticalThumbActive"
[style.height.%]="verticalSize"
[style.top.%]="verticalPosition"
(mousedown)="onVerticalStart($event)"
(document:mousemove)="onVerticalMove($event, vertical)"
></div>
mouseup
:
@HostListener('document:mouseup)
onDragEnd() {
this.verticalThumbActive = false;
}
onVerticalStart(event: MouseEvent) {
event.preventDefault();
const { target, clientY } = event;
const { top, height } = target.getBoundingClientRect();
this.verticalThumbDragOffset = (clientY - top) / height;
this.verticalThumbActive = true;
}
onVerticalMove(
{ clientY }: MouseEvent,
{ offsetHeight }: HTMLElement
) {
if (!this.verticalThumbActive) {
return;
}
const { nativeElement } = this.elementRef;
const { top, height } = nativeElement.getBoundingClientRect();
const maxScrollTop = nativeElement.scrollHeight - height;
const scrolled =
(clientY - top - offsetHeight * this.verticalThumbDragOffset) /
(height - offsetHeight);
nativeElement.scrollTop = maxScrollTop * scrolled;
}
.
Angular
, . mousemove
. . , Angular , . @tinkoff/ng-event-plugins, . .silent
@shouldCall
:
(document:mousemove.silent)="onVerticalMove($event, vertical)"
@shouldCall(isActive)
@HostListener('init.end', ['$event'])
@HostListener('document:mouseup.silent')
onDragEnd() {
this.verticalThumbActive = false;
}
@shouldCall(isActive)
@HostListener('init.move', ['$event'])
onVerticalMove(
{ clientY }: MouseEvent,
{ offsetHeight }: HTMLElement
) {
const { nativeElement } = this.elementRef;
const { top, height } = nativeElement.getBoundingClientRect();
const maxScrollTop = nativeElement.scrollHeight - height;
const scrolled =
(clientY - top - offsetHeight * this.verticalThumbDragOffset) /
(height - offsetHeight);
nativeElement.scrollTop = maxScrollTop * scrolled;
}
: Angular 10 markDirty(this)
@shouldCall
@HostListener(βinit.xxxβ, [β$eventβ])
, , β .
, . . , . ResizeObserver
@ng-web-apis/resize-observer, . Stackblitz.
Edit:
, , . . Output fromEvent, :
@Output()
dragged = fromEvent(
this.elementRef.nativeElement,
'mousedown'
).pipe(
switchMap(event => {
event.preventDefault();
const clientRect = event.target.getBoundingClientRect();
const offsetVertical = getOffsetVertical(event, clientRect);
const offsetHorizontal = getOffsetHorizontal(event, clientRect);
return fromEvent(this.documentRef, 'mousemove').pipe(
map(event => this.getScrolled(event, offsetVertical, offsetHorizontal)),
takeUntil(fromEvent(this.documentRef, 'mouseup'))
);
})
);
. , .