I am using Angular 6 trying to create an HTML element (table headers) that is positioned in the middle of the page but will stick to the top of the page when the user scrolls past that point. I am trying to grab that element's position using @ViewChild but am unable to retrieve it. What might be missing?
Currently, the 'sticky' class is always applied but I want to only apply the sticky class when the element's position is <= to window.pageYOffset.
I am trying to achieve a similar effect to THIS using THIS tutorial.
My HTML:
<table id="tabletop" class="table" >
<thead >
<tr #stickyMenu [class.sticky] = "stickyMenu" >
<th scope="col" id="th0">Monitoring Point</th>
<th scope="col" id="th1">Network Health<br><small id="more_detail">(Click icon to see more detail)</small></th>
<th scope="col" id="th2">Active Alerts</th>
<th scope="col" id="th3">Velocity (ft/s)</th>
<th scope="col" id="th4">Water Temperature (°F)</th>
<th scope="col" id="th5">Depth Readings (m)</th>
</tr>
</thead>
</table>
The css:
.sticky{
position: fixed;
top: 0;
/* overflow: hidden; */
z-index: 10;
background-color: #fff;
width:100%;
padding-right:20px!important;
}
My component:
import { Component, OnInit, ViewChild , ViewEncapsulation, HostListener, ElementRef, AfterViewInit} from '@angular/core';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { AuthService } from '../../services/auth.service';
import { SiteService } from '../../services/site.service';
import { SiteModel } from '../../models/site-model';
import { BaseComponent } from '../base.component';
import { DomSanitizer } from '@angular/platform-browser';
import { DateTime } from 'luxon';
@Component({
selector: 'app-site',
templateUrl: './site.component.html',
styleUrls: ['./site.component.css','../../../vizuly/lib/styles/vizuly.css']
})
export class SiteComponent extends BaseComponent implements OnInit, AfterViewInit {
siteHid;
scute_data;
window_narrow;
scute_data_ready: Boolean;
sticky: boolean = false;
elementPosition: any;
constructor(public sanitizer: DomSanitizer, private route: ActivatedRoute, protected router: Router, private authService: AuthService, private siteService: SiteService) {
super();
}
@ViewChild("stickyMenu", {read: ElementRef}) tr: ElementRef;
ngOnInit() {
this.route.paramMap.subscribe((params: ParamMap) => {
this.siteHid = params.get('siteHid');
this.loadSite(this.siteHid);
});
}
ngAfterViewInit(){
console.log(this.tr)
this.elementPosition = this.tr.nativeElement
// this.elementPosition = this.stickyMenu.nativeElement.offsetTop;
console.log(this.elementPosition) //undefined
}
@HostListener('window:scroll', ['$event'])
handleScroll() {
console.log(this.sticky, window.pageYOffset, this.elementPosition)
this.sticky = (window.pageYOffset >= this.elementPosition) ? true : false;
console.log(this.sticky)
}
loadSite(siteHid){
}
}
All the other answers have pieces of the puzzle, but one thing is missing IMO, grabbing this.elementPosition
from this.tr.nativeElement.offsetTop
is returning a value that's too small.
Looking at MDN - HTMLElement.offsetTop
The HTMLElement.offsetTop read-only property returns the distance of the current element relative to the top of the offsetParent node.
but the offsetParent in this case is <thead>
, not the top of the window which is what needs to be compared to window.scrollTop
in handleScroll()
.
(Giovanni Chiodi has his sticky element close to the window top, so offsetTop
works ok for him).
This SO question How to get an element's top position relative to the browser's viewport has a better way to get the element position,
var viewportOffset = el.getBoundingClientRect();
// these are relative to the viewport, i.e. the window
var top = viewportOffset.top;
So I would change your code to:
template
<table id="tabletop" class="table" >
<thead >
<tr #stickyMenu [class.sticky] = "sticky">
...
component
@ViewChild("stickyMenu", { read: ElementRef }) tr: ElementRef;
ngAfterViewInit() {
this.elementPosition = this.tr.nativeElement.getBoundingClientRect().top;
}
@HostListener('window:scroll', ['$event'])
handleScroll() {
this.sticky = (window.pageYOffset >= this.elementPosition);
}