Reputation: 8681
I am trying to apply a fixed header on of my pages in my angular application and having trouble to get it working completely. So currently if the user expands the accordians on the page and scrolls down, the headers get fixed but when the page is collapsed or scrolled up, the headers should be normal. So my code logic written works at one point of time but fails when all the accordians are opened. I feel there is an issue with this.stickyTabsOffset value not being set correctly.
So when the user scrolls down the tabs headers and div (black section below the tabs) should get fixed when they reach top of the window. In the ngAfterViewInit method I am getting calling the getelementById and of the two elements that is strategyTabs (div id of the tabs) and stragegyDetails (div id of the black section) and then setting this.stickyTabsOffset = this.stickyTabs.offsetTop; I have also tried the following
this.stickyTabsOffset = this.stickyTabs.offsetHeight + this.strategyDetails.offsetHeight;
this.stickyTabsOffset = this.stickyTabs.offsetTop + this.strategyDetails.offsetTop;
Screenshot when the accordians are in the closed state.
import { Component, OnInit , AfterViewInit, HostListener } from '@angular/core';
export class ResultsComponent extends Base.ReactiveComponent implements OnInit, AfterViewInit {
@HostListener('window:scroll', [])
onWindowScroll() {
if (window.pageYOffset >= this.stickyTabsOffset) {
this.stickyTabs.classList.add('sticky');
this.strategyDetails.classList.add('fix-Container');
} else {
this.stickyTabs.classList.remove('sticky');
this.strategyDetails.classList.remove('fix-Container');
}
}
ngAfterViewInit() {
const interval = setInterval(() => {
this.stickyTabs = document.getElementById('strategyTabs') as any;
this.strategyDetails = document.getElementById('strategyDetails') as any;
if (this.stickyTabs && this.stickyTabs !== 'undefined' ) {
this.stickyTabsOffset = this.stickyTabs.offsetTop;
clearInterval(interval);
}
}, 500);
}
}
css
.sticky {
position: fixed;
top: 0;
width: 100%;
z-index: 999999;
}
.fix-Container {
position: fixed;
top: 75px;
z-index: 1;
}
Upvotes: 12
Views: 11706
Reputation: 255
You should be getting your element positioning within the scroll event, not on an interval. There is no guarantee that the interval will have gotten the current element positioning when the scroll event fires, meaning the element positioning will be wrong.
The line
if (window.pageYOffset >= this.stickyTabsOffset)
should be
if (window.pageYOffset > this.stickyTabsOffset)
Because if you fix tabs, their offset becomes 0, and if you then scroll to the top of the page, the pageYOffset also becomes zero, meaning the else
can never fire to reset your element's positioning.
I grabbed your code and dumped it into some simple, vanilla JS and HTML.
<body>
<div style="position: static; top:30px; height: 300px; width: 300px; background-color:green"></div>
<div id="test" style="position: static; top:0px; height: 300px; width: 300px; background-color:red"></div>
<div style="position: static; top:30px; height: 300px; width: 300px; background-color:purple"></div>
<div style="position: static; top:30px; height: 300px; width: 300px; background-color:yellow"></div>
<div style="position: static; top:30px; height: 300px; width: 300px; background-color:gray"></div>
<div style="position: static; top:30px; height: 300px; width: 300px; background-color:blue"></div>
</body>
And here is the JS. I copied as much of your code as possible.
<script>
window.onscroll = function() {
if ( window.pageYOffset > document.getElementById("test").offsetTop ) {
document.getElementById("test").style.position = "fixed";
} else {
document.getElementById("test").style.position = "static";
}
}
</script>
You should not be fixing two elements. Wrap both elements into a single element then fix that element. Also — you may be taking care of this elsewhere but I'm mentioning it for the edification of anyone who wanders by — when you rip an element out of the document flow with position:fixed
, its previous space is no longer taken up, meaning that the page's content will experience a jerky reflow. Place the element that you are fixing in a parent element with a set height that does not become fixed. That prevents the page from reflowing.
Again, I'm not sure if you are doing this elsewhere, but I think it worth mentioning that it's bad practice to directly access the document. You should instead be injecting the Angular DOCUMENT token.
Upvotes: 0
Reputation:
Some time ago, I decided to stop relying on fixed positions and created my own. This is very useful in Angular actually.
The trick is to make a container with a hidden overflow that takes all your page, and create other containers in it that can overflow.
You can use either a flex layout, or a calculated layout. Here is how :
/* LAYOUT STYLE (SIDE BY SIDE) */
.main-container {
display: flex;
flex-direction: row;
align-items: stretch;
width: 100%;
height: 100%;
}
/* FLEX LAYOUT (LEFT SIDE) */
.flex-layout {
background: bisque;
flex: 0 0 50%;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
}
.flex-layout header {
flex: 0 0 50px;
}
.flex-layout content {
flex: 1 0 auto;
height: 0px;
}
.flex-layout footer {
flex: 0 0 50px;
}
/* OLD LAYOUT (RIGHT SIDE) */
.old-layout {
background: aliceblue;
flex: 0 0 50%;
position: relative;
}
.old-layout header {
height: 50px;
position: absolute;
top: 0;
width: 100%;
}
.old-layout content {
position: absolute;
top: 50px;
bottom: 50px;
width: 100%;
}
.old-layout footer {
height: 50px;
position: absolute;
bottom: 0;
width: 100%;
}
/* COMMON STYLING CODE */
html, body {
height: 100%;
margin: 0;
overflow: hidden;
}
* { box-sizing: border-box; font-family: Helvetica; font-weight: bold; text-transform: uppercase; }
header {
display: flex;
justify-content: center;
align-items: center;
border-bottom: 1px solid #ddd;
}
content {
background: white;
opacity: 0.75;
border-bottom: 1px solid #ddd;
overflow-y: auto;
}
footer {
background: black;
opacity: 0.20;
}
.content {
height: 300%;
}
<div class="main-container">
<div class="flex-layout">
<header>Flex layout</header>
<content><div class="content"></div></content>
<footer></footer>
</div>
<div class="old-layout">
<header>Old layout</header>
<content><div class="content"></div></content>
<footer></footer>
</div>
</div>
As you can see in this (very small) snippet, the fixed position is simulated, but still behaves like it should.
(Scrollbars are set on auto, if the content doesn't take all the space, they will be hidden)
Upvotes: 0
Reputation: 151
Put the header component in app.component after that add router-outlet
<div>
<div>
<app-header></app-header>
</div>
<div>
<router-outlet></router-outlet>
</div>
</div>
Hope this will help you.
Upvotes: 1
Reputation: 538
If you don't need to support IE you can just use position: sticky
in your CSS
Here is an example : https://www.w3schools.com/cssref/tryit.asp?filename=trycss_position_sticky
Upvotes: 0
Reputation: 2817
For such kind of stuff I use something like this:
// component.ts
navFixed: boolean = false;
private scrollOffset: number = 70;
@HostListener('window:scroll')
onWindowScroll() {
this.navFixed = (window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop || 0
) > this.scrollOffset;
}
<!-- component.html -->
<header [class.fixed]="navFixed">
<!-- Header content -->
</header>
<main>
<router-outlet></router-outlet>
</main>
// component.scss
header {
position: fixed;
width: 100%;
left: 0;
top: 0;
z-index: 1001;
&.fixed {
// Styles for a fixed header
}
}
main {
padding-top: 170px; // Your header height + some slack
}
Hope this helps a little :)
Upvotes: 16