如何获取routerLinkActive的ElementRef,定位Material Design墨水栏
如何获取routerLinkActive对应的ElementRef来定位Material侧边栏的墨水栏
我之前在自定义Material侧边导航的墨水栏位置时,也遇到过完全一样的需求,下面是几个经过验证的可行方案,你可以根据自己的场景选择:
方案一:通过RouterLinkActive指令实例获取ElementRef
这种方式更符合Angular的组件化思路,能直接拿到指令绑定的元素引用,避免直接操作DOM的潜在问题。
步骤说明:
- 给模板中的
routerLinkActive指令添加模板引用变量,暴露指令实例 - 在组件类中通过
@ViewChildren获取所有该指令的实例,筛选出激活状态的那个,进而拿到对应的ElementRef
代码示例:
模板部分:
<mat-sidenav fxLayout='column' fxLayoutAlign='start center' #sidenav mode="over" [(opened)]="opened" position="end" class="nav-sidenav"> <div class="nav-sidenav-container" fxFlex='1 1 100%'> <div class="ink-bar" #inkBar></div> <!-- 给每个导航项的routerLinkActive添加模板引用变量 --> <a routerLink="/home" routerLinkActive="active-link" #rla="routerLinkActive" class="nav-item">首页</a> <a routerLink="/profile" routerLinkActive="active-link" #rla="routerLinkActive" class="nav-item">个人中心</a> <a routerLink="/settings" routerLinkActive="active-link" #rla="routerLinkActive" class="nav-item">设置</a> </div> </mat-sidenav>
组件类部分:
import { Component, ViewChildren, ElementRef, QueryList, AfterViewInit, Renderer2 } from '@angular/core'; import { RouterLinkActive } from '@angular/router'; @Component({ selector: 'app-side-nav', templateUrl: './side-nav.component.html', styleUrls: ['./side-nav.component.scss'] }) export class SideNavComponent implements AfterViewInit { @ViewChildren('rla') routerLinkActives!: QueryList<RouterLinkActive>; @ViewChildren('inkBar') inkBar!: ElementRef<HTMLElement>; constructor(private renderer: Renderer2) {} ngAfterViewInit(): void { // 监听路由激活状态变化,实时更新墨水栏位置 this.routerLinkActives.changes.subscribe(() => { this.updateInkBarPosition(); }); // 组件初始化时先设置一次初始位置 this.updateInkBarPosition(); } private updateInkBarPosition(): void { // 筛选出当前处于激活状态的路由链接指令 const activeLink = this.routerLinkActives.find(rla => rla.isActive); if (activeLink && this.inkBar) { const activeEl = activeLink.elementRef.nativeElement; const inkBarEl = this.inkBar.nativeElement; const sidenavRect = activeEl.parentElement?.getBoundingClientRect(); if (sidenavRect) { // 计算激活元素相对于侧边栏容器的位置,设置墨水栏样式 const rect = activeEl.getBoundingClientRect(); this.renderer.setStyle(inkBarEl, 'top', `${rect.top - sidenavRect.top}px`); this.renderer.setStyle(inkBarEl, 'left', `${rect.left - sidenavRect.left}px`); this.renderer.setStyle(inkBarEl, 'width', `${rect.width}px`); this.renderer.setStyle(inkBarEl, 'height', `${rect.height}px`); } } } }
方案二:利用routerLinkActive自动添加的类名查询DOM
如果你的场景比较简单,也可以直接通过routerLinkActive默认添加的router-link-active类名(或你自定义的类名)来查询激活元素,实现起来更快捷。
组件类部分关键代码:
import { Component, ElementRef, AfterViewInit, Renderer2 } from '@angular/core'; import { Router, NavigationEnd } from '@angular/router'; import { filter } from 'rxjs'; @Component({ selector: 'app-side-nav', templateUrl: './side-nav.component.html', styleUrls: ['./side-nav.component.scss'] }) export class SideNavComponent implements AfterViewInit { @ViewChildren('inkBar') inkBar!: ElementRef<HTMLElement>; constructor(private router: Router, private renderer: Renderer2) {} ngAfterViewInit(): void { // 监听路由跳转结束事件,更新墨水栏位置 this.router.events.pipe( filter(event => event instanceof NavigationEnd) ).subscribe(() => { this.updateInkBarPosition(); }); // 初始化设置位置 this.updateInkBarPosition(); } private updateInkBarPosition(): void { // 查询带有激活类名的元素(这里用默认的router-link-active,自定义类名的话替换成你的) const activeEl = document.querySelector('.router-link-active'); if (activeEl && this.inkBar) { const inkBarEl = this.inkBar.nativeElement; const rect = activeEl.getBoundingClientRect(); const sidenavRect = activeEl.parentElement?.getBoundingClientRect(); if (sidenavRect) { this.renderer.setStyle(inkBarEl, 'top', `${rect.top - sidenavRect.top}px`); this.renderer.setStyle(inkBarEl, 'left', `${rect.left - sidenavRect.left}px`); this.renderer.setStyle(inkBarEl, 'width', `${rect.width}px`); this.renderer.setStyle(inkBarEl, 'height', `${rect.height}px`); } } } }
额外注意事项
- 如果你给
routerLinkActive自定义了类名,记得把查询的类名改成你设置的(比如模板里的active-link,查询时就用.active-link) - 处理嵌套路由时,需要给
routerLinkActive添加[routerLinkActiveOptions]="{exact: true}"确保精确匹配,避免多个导航项同时被标记为激活 - 墨水栏的基础样式需要设置为绝对定位,示例SCSS:
.nav-sidenav-container { position: relative; .ink-bar { position: absolute; background-color: #1976d2; // Material主题色,可按需修改 border-radius: 4px; transition: all 0.3s ease; // 添加过渡动画,贴合Material风格 pointer-events: none; // 避免遮挡导航项的点击事件 } }
内容的提问来源于stack exchange,提问作者ALGDB
相关产品推荐
相关产品推荐

