You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否通过代码动态设置Angular Material组件的位置与高度?

动态设置Angular Material Sidenav组件高度的解决方案

问题背景

开发响应式Angular应用时,通过CSS设置<mat-sidenav>和<mat-sidenav-content>的高度与位置未达预期,希望通过代码动态设置样式,而非依赖CSS硬编码。

实现方案

通过Angular的ViewChild获取组件实例,结合生命周期钩子和事件监听,动态计算并设置元素样式:

1. 组件TS代码修改

import { Component, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import { MediaMatcher } from '@angular/cdk/layout';

@Component({
  selector: 'app-layout',
  templateUrl: './layout.component.html',
  styleUrls: ['./layout.component.css']
})
export class LayoutComponent implements AfterViewInit, OnDestroy {
  @ViewChild('snav') sidenav!: any;
  @ViewChild('matSidenavContent') sidenavContent!: any;
  mobileQuery: MediaQueryList;
  private _mobileQueryListener: () => void;

  fillerNav = ['导航项1', '导航项2', '导航项3', '导航项4'];
  fillerContent = Array.from({length: 10}, () => '示例内容文本,用于填充页面展示滚动效果');

  constructor(private media: MediaMatcher) {
    this.mobileQuery = media.matchMedia('(max-width: 767.98px)');
    this._mobileQueryListener = () => this.adjustLayout();
    this.mobileQuery.addListener(this._mobileQueryListener);
  }

  ngAfterViewInit(): void {
    // 初始加载时调整布局
    this.adjustLayout();
    // 监听窗口大小变化
    window.addEventListener('resize', () => this.adjustLayout());
  }

  adjustLayout(): void {
    const headerEl = document.querySelector('header');
    const footerEl = document.querySelector('footer');
    const headerHeight = headerEl ? headerEl.offsetHeight : 0;
    const footerHeight = footerEl ? footerEl.offsetHeight : 0;
    const viewportHeight = window.innerHeight;

    if (!this.mobileQuery.matches) {
      // 桌面端布局设置
      if (this.sidenav?.nativeElement) {
        this.sidenav.nativeElement.style.height = `${viewportHeight - headerHeight - footerHeight}px`;
        this.sidenav.nativeElement.style.top = `${headerHeight}px`;
        this.sidenav.nativeElement.style.bottom = `${footerHeight}px`;
        this.sidenav.nativeElement.style.marginTop = '0';
        this.sidenav.nativeElement.style.marginBottom = '0';
      }

      if (this.sidenavContent?.nativeElement) {
        this.sidenavContent.nativeElement.style.height = `${viewportHeight - headerHeight - footerHeight}px`;
        this.sidenavContent.nativeElement.style.marginTop = `${headerHeight}px`;
        this.sidenavContent.nativeElement.style.marginBottom = `${footerHeight}px`;
        this.sidenavContent.nativeElement.style.overflow = 'auto';
      }
    } else {
      // 移动端重置样式,依赖组件自身的fixedInViewport属性
      if (this.sidenav?.nativeElement) {
        this.sidenav.nativeElement.style.height = '';
        this.sidenav.nativeElement.style.top = '';
        this.sidenav.nativeElement.style.bottom = '';
        this.sidenav.nativeElement.style.marginTop = '0';
        this.sidenav.nativeElement.style.marginBottom = '0';
      }

      if (this.sidenavContent?.nativeElement) {
        this.sidenavContent.nativeElement.style.height = '';
        this.sidenavContent.nativeElement.style.marginTop = '0';
        this.sidenavContent.nativeElement.style.marginBottom = '0';
      }
    }
  }

  ngOnDestroy(): void {
    // 清理监听事件,避免内存泄漏
    this.mobileQuery.removeListener(this._mobileQueryListener);
    window.removeEventListener('resize', () => this.adjustLayout());
  }
}

2. 模板代码(保留原有引用变量)

<header>
    <mat-toolbar color="primary">
        <button (click)="snav.toggle()" mat-icon-button>
            <mat-icon>menu</mat-icon>
        </button>
        <span>My App</span>
        <span class="mat-toolbar-spacer"></span>
        <button mat-icon-button [matMenuTriggerFor]="menu">
            <mat-icon>more_vert</mat-icon>
        </button>
        <mat-menu #menu="matMenu">
            <button mat-menu-item>
                <mat-icon>dialpad</mat-icon>
                <span>重拨</span>
            </button>
            <button mat-menu-item disabled>
                <mat-icon>voicemail</mat-icon>
                <span>查看语音信箱</span>
            </button>
            <button mat-menu-item>
                <mat-icon>notifications_off</mat-icon>
                <span>禁用提醒</span>
            </button>
        </mat-menu>
    </mat-toolbar>
</header>

<mat-sidenav-container class="mat-sidenav-container" [style.marginTop.px]="mobileQuery.matches ? 56 : 0">
    <mat-sidenav #snav [opened] = "!mobileQuery.matches" [mode]="mobileQuery.matches ? 'over' : 'side'" [fixedInViewport]="mobileQuery.matches"
        fixedTopGap="56">
        <mat-nav-list>
            <a mat-list-item routerLink="." *ngFor="let nav of fillerNav">{{nav}}</a>
        </mat-nav-list>
    </mat-sidenav>

    <mat-sidenav-content #matSidenavContent >
        <p *ngFor="let content of fillerContent">{{content}}</p>
    </mat-sidenav-content>
</mat-sidenav-container>

<footer>
    <span>页脚内容</span>
</footer>

3. 优化后CSS代码(移除硬编码高度)

.mat-toolbar-spacer {
    flex: 1 1 auto;
}

header {
    width: 100%;
    position: fixed;
    top: 0;
    z-index: 100;
    display: flex;
}

footer {
    height: auto;
    background-color: #F5F5F5;
    text-align: center;
    padding: 20px 0px 20px 0px;
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
}

mat-sidenav-container {
    background-color: #FFFFFF;
    height: auto;
    overflow: auto;
}

mat-sidenav{
    position: fixed;
}

mat-sidenav-content{
    padding: 20px;
    overflow: auto;
}

mat-nav-list{
    width: 250px;
    padding: 5px;
}

mat-nav-list mat-list-item{
    width: 100%;
    padding-right: 100px;
}

@media (max-width: 767.98px) {
    mat-nav-list{
        margin-top: 0px;
        width: auto;
    }
    
    footer {
        position:initial;
        left:initial;
        bottom: initial;
    }
}

关键说明

  • 利用ngAfterViewInit确保DOM渲染完成后再执行样式设置
  • 通过计算视口高度、头部和底部的高度,动态分配侧边栏和内容区的可用空间
  • 监听窗口resize和移动端媒体查询变化,自动适配不同屏幕尺寸
  • 移动端重置样式,配合fixedInViewport属性实现侧边栏悬浮效果

内容的提问来源于stack exchange,提问作者Pamba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 03:02:01