简体   繁体   中英

How to stay mat-toolbar and sidenav on top in side mode (angular material)?

I want to place mat-sidenav in side mode. but also I want to have mat-toolbar on top.

The problem is when I scroll to bottom the mat-menu and the sidenav go up, and I want them to stay in place (always on top).

I try to set position:fixed;width:100% to mat-toolbar but the div is grow, so it hide the home button. so this is not work.

the position: sticky;top: 0; also not working.

any idea how to do it correctly?

The full code in stackblitz

You shoud add position:fixed to the upper element which is in your case mat-sidenav and mat-toolbar or you can simply add a CSS class as follows:

.fixedElement{
position: fixed;
}

and assign it to your Elements mat-sidenav and mat-toolbar

or you can simply add inline CSS to your element as follows:

<mat-sidenav style="position:fixed" mode="side" [(opened)]="opened" (opened)="events.push('open!')"
    (closed)="events.push('close!')">
    Sidenav content
</mat-sidenav>

Working StackBlitz example

Updated

Header was hiding behind content when position is set to fixed, However Adding z-index:1000 to the #header css, and add padding-top to the body css which should be a bit more than header's height. For example, if the header's height is 40px, put the padding-top: 50px to the body css and it should work.

I was dealing with almost the same issue with fixed header with <mat-sidenav mode="side"> .

sidenav could be easily fixed with css rule position: fixed

There were 2 problems with the header :

  • the main one is that the header with position:fixed is not adjusting width when we open the sidenav (couldn't solve it with sticky positioning)
  • the second one is that for a header with dynamic height some content would be hidden under the header (with fixed height we only need to set top-margin of content element to a fixed value to mitigate this issue)

The solution for the main issue is to track the width of the <mat-sidenav-content> and adjust the width of the header accordingly.

Solution for the second issue is analogic to the first one - tracking the height of header and adjusting the top margin of the element under the header.

In both cases it is possible to set a css variable and use that for the element style instead of setting the height of elements directly. My example showcases both ways.

Let's see how it works.

We need a directive to track resize events.


import {
  Directive,
  ElementRef,
  OnDestroy,
  OnInit,
  Output,
} from '@angular/core';
import { Subject } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';


export interface Size {
  width: number;
  height: number;
}

@Directive({
  selector: '[myResize]',
})
export class ResizeDirective implements OnInit, OnDestroy {
  private resizeSubject = new Subject<Size>();
  private resizeObserver: ResizeObserver;

  @Output() myResize = this.resizeSubject.pipe(
    distinctUntilChanged(
      (a: Size, b: Size) => a.width === b.width && a.height == b.height
    )
  );

  constructor(private el: ElementRef) {
    this.resizeObserver = new ResizeObserver((entries) => {
      const size = {
        width: Math.trunc(entries[0].contentRect.width),
        height: Math.trunc(entries[0].contentRect.height),
      };
      this.resizeSubject.next(size);
    });
  }

  ngOnInit() {
    this.resizeObserver.observe(this.el.nativeElement);
  }

  ngOnDestroy() {
    this.resizeObserver.unobserve(this.el.nativeElement);
  }
}


Then we apply our directive to header and sidenav-content elements.


<mat-sidenav-container class="container" autosize>
  <mat-sidenav class="sidenav" mode="side">
    Some navigation that expands from the side and thus shrinks the  &lt;mat-sidenav-content&gt;
    because of mode=side
  </mat-sidenav>

  <mat-sidenav-content #content class="sidenav-content" (myResize)="onContentResized($event)">
    <my-header class="header" (myResize)="onHeaderResized($event)"></my-header>
    <div class="content">
      <router-outlet></router-outlet>
    </div>
  </mat-sidenav-content>
</mat-sidenav-container>


The component code.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {

  @ViewChild('content') content!: MatSidenavContent;

  constructor(
    private renderer: Renderer2
  ) {}

  onContentResized(event: any) {
    console.log('on content resized event', event);

    //I am creating a css variable on <mat-sidenav-content> where my header and the main content element are located
    // thus the variable will be available to all descendants 
    this.renderer.setStyle(
      this.content.getElementRef().nativeElement,
      '--content-width',
      event.width + 'px',
      RendererStyleFlags2.DashCase
    );
  }

  onHeaderResized(event: any) {
    console.log('on header resized event', event);

    //I am setting the margin on content element directly
    this.renderer.setStyle(
      this.content.getElementRef().nativeElement,
      'margin-top',
      event.height + 'px',
      RendererStyleFlags2.DashCase
    );
  }
}



And for the stylesheet.


.sidenav {
  position: fixed;
}

.header {
  position: fixed;
  top: 0;
  z-index: 100;
  width: var(--content-width, 100%);
}

Note: I only included critical parts of code to explain the solution.

If somebody has a pure css solution for this problem, please don't hesitate to share it with us. I've done some research but the only viable solution seems to be via coding.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM