Angular @HostListener监听滚动事件无法获取正确滚动值问题
问题复现代码
在app.component.html中编写如下全局侧边栏布局结构:
<app-header *ngIf="navbarTop"></app-header> <mat-drawer-container id="drawer-container"> <mat-drawer mode="over" id="side-drawer" [opened]="isOpen"> <div> <!-- Sidenav content --> </div> </mat-drawer> <!-- Main content of the site --> <mat-drawer-content id="scroller" (scroll)="scrollHandler($event)"> <main class="container"> <router-outlet></router-outlet> </main> </mat-drawer-content> </mat-drawer-container> <app-header *ngIf="!navbarTop"></app-header>
该结构通过全局mat-drawer实现侧边导航,确保所有页面都可访问侧边菜单,站点主内容区域的mat-drawer-content中放置router-outlet承载路由页面。
尝试过将(scroll)事件绑定在mat-drawer、main标签以及router-outlet上,确认绑定在mat-drawer-content上是最优选择。但.ts文件中编写如下滚动监听逻辑时出现异常:
@HostListener('window:scroll', ['$event']) scrollHandler(e) { console.log("scrolled"); console.log(window.scrollY); console.log(document.body.scrollTop); }
运行时滚动事件可以正常触发,但window.scrollY、document.body.scrollTop的打印结果始终为0,无法获取正确的scrollY/offsetY/scrollTop等滚动位置数值。
问题原因
mat-drawer组件默认会为根节点<html>、<body>添加height: 100%; overflow: hidden样式,禁用页面本身的滚动条,所有主内容区的滚动行为全部由内部的mat-drawer-content元素承接。此时滚动的容器不是window也不是body,因此读取window.scrollY、document.body.scrollTop永远返回0。
同时模板上已经给mat-drawer-content绑定了(scroll)事件,额外用@HostListener监听window滚动属于冗余逻辑,还可能导致重复触发。
解决方案
方法1:直接从滚动事件对象获取(推荐)
删除@HostListener('window:scroll')装饰器,直接从滚动事件的目标元素上读取scrollTop值即可:
scrollHandler(e: Event) { const scrollContainer = e.target as HTMLElement; // 实际滚动位置 const scrollTop = scrollContainer.scrollTop; console.log("滚动位置:", scrollTop); // 其他滚动相关属性可按需获取 // scrollContainer.scrollHeight 内容总高度 // scrollContainer.clientHeight 可视区域高度 }
方法2:通过组件引用获取(非事件场景使用)
如果需要在滚动事件之外的逻辑里获取滚动位置,可以通过模板变量+ViewChild拿到mat-drawer-content的实例再读取属性:
- 首先给模板里的
mat-drawer-content添加模板引用变量:
<mat-drawer-content #scroller id="scroller" (scroll)="scrollHandler($event)">
- 在组件类中通过ViewChild获取组件实例,需要时读取原生元素的滚动属性:
import { Component, ViewChild } from '@angular/core'; import { MatDrawerContent } from '@angular/material/sidenav'; @Component({ // 组件元配置 }) export class AppComponent { @ViewChild('scroller') private scroller: MatDrawerContent; // 任意需要获取滚动位置的方法中调用 getCurrentScrollTop() { const scrollContainer = this.scroller.getElementRef().nativeElement; return scrollContainer.scrollTop; } }
内容的提问来源于stack exchange,提问作者hego64

