Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sticky Header - Scroll with Tabs

I am having problems with the behavior of my sticky header.

Desired behavior:

a) Scrolling to where the bottom of the .nav hits the top of a section adds the active class to the tab and it remains active until the .nav reaches the top of the next section.

b) Clicking a corresponding section's .tab always navigates you to the top of that section and adds the active class to the tab.

So either by scrolling or clicking, the active state of the tabs is always kept until the .nav crosses into the next section, in which case the active state goes to that section's tab, etc.

Test the issues:

1) Half-way scrolling down Option Two the active state of that .tab is lost.

2) The use of scrollTop is scrolling to the top of the .container instead of the top of the selected section.

class StickyNavigation {
  constructor() {
    this.currentId = null;
    this.currentTab = null;
    let self = this;
    $(".tab").click(function() {
      self.onTabClick(event, $(this));
    });
    $(".container").scroll(() => {
      this.onScroll();
    });
    $(".container").resize(() => {
      this.onResize();
    });
  }
  /*Scrolls down to Tab selection*/
  onTabClick(event, element) {
    event.preventDefault();
    let scrollTop = $(element.attr("href")).offset().top;
    if (!$(".nav").hasClass("nav--top")) {
      scrollTop = scrollTop;
    }
    $(".container").animate({
      scrollTop: scrollTop
    }, 600);
  }
  onScroll() {
    this.navPosition();
    this.tabAnimation();
  }
  navPosition() {
    let offset = $(".sticky").offset().top + $(".sticky").height();
    if ($(".container").scrollTop() > offset) {
      $(".nav").addClass("nav--top");
    } else {
      $(".nav").removeClass("nav--top");
    }
  }
  tabAnimation() {
    $("section").each(function() {
      var actual = $(this),
        actualHeight = actual.height(),
        actualAnchor = $(".sticky").find('a[href="#' + actual.attr("id") + '"]');
      if (
        actual.offset().top <= $(".container").scrollTop() &&
        actual.offset().top + actualHeight > $(".container").scrollTop()
      ) {
        actualAnchor.addClass("active");
      } else {
        actualAnchor.removeClass("active");
      }
    });
  }
}
new StickyNavigation();
body {
  position: fixed;
  display: flex;
  flex-direction: column;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  overflow: hidden;
}

section {
  height: 600px;
  border: 2px solid white;
  background: blue;
}

section:nth-child(2) {
  background: red;
}

.container {
  flex: 1;
  display: flex;
  position: relative;
  flex-direction: column;
  overflow: auto;
}

.long {
  height: 1200px;
}

.header {
  height: 75px;
  background: green;
}

.hero {
  background: silver;
  flex: 0;
  border: 1px solid;
}

.nav {
  background: white;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
}

.nav--top {
  position: fixed;
  top: 75px;
}

.sticky {
  background: white;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  position: relative;
}

.tab {
  padding: 30px 45px;
  position: relative;
}

.tab.active {
  background: #6567c5;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="header">
  <h1>Header</h1>
</div>
<div class="container">
  <div class="hero">
    <h1>Hero</h1>
  </div>
  <div class="sticky">
    <nav role="navigation" class="nav">
      <a class="tab" href="#One">Option One</a>
      <a class="tab" href="#Two">Option Two</a>
    </nav>
  </div>
  <div class="main">
    <section id="One">
    </section>
    <section class="long" id="Two">
    </section>
  </div>
</div>
</div>
like image 302
Kyle Underhill Avatar asked Mar 06 '19 07:03

Kyle Underhill


People also ask

How do I keep my table header fixed while scrolling in HTML?

To freeze the row/column we can use a simple HTML table and CSS. HTML: In HTML we can define the header row by <th> tag or we can use <td> tag also. Below example is using the <th> tag. We also put the table in DIV element to see the horizontal and vertical scrollbar by setting the overflow property of the DIV element.

How do you make a scrolling header sticky in CSS?

above css code, is the class which makes navigation sticky using " position :fixed " property, using this property we make sure, the content will remain positioned as fixed during scroll in HTML page. " top:0; left:0 " gives the position from top of browser, where content should be placed.


Video Answer


1 Answers

The reason your section scrolls to the top is the change of positions due to adding and removal sticky element. Use a wrapper in place of sticky to always occupy the height of the sticky element whether it is there or not.

class StickyNavigation {
  constructor() {
    this.currentId = null;
    this.currentTab = null;
    this.setup();
    this.onResize();
    let self = this;
    $(".tab").click(function(event) {
      self.onTabClick(event, $(this));
    });
    $(".container").scroll(() => {
      this.onScroll();
    });
    $(window).resize(() => {
      this.onResize();
    });
  }
  setup() {
    this.$sticky = $('.sticky');
    window.stk = this.$sticky;
    this.$stickyWrap = $('<div>').insertAfter(this.$sticky);
    this.$sticky.appendTo(this.$stickyWrap);
  }
  /*Scrolls down to Tab selection*/
  onTabClick(event, element) {
    event.preventDefault();
    let $targetElement = $(element.attr("href"));
    let positionTop = $targetElement.position().top;
    let scrollTop = $('.container').scrollTop();
    $(".container").animate({
      scrollTop: scrollTop - this.stickyOuterHeight + positionTop
    }, 600);
  }
  onScroll() {
    this.navPosition();
    this.tabAnimation();
  }
  onResize() {
    this.stickyOuterHeight = this.$sticky.outerHeight();
    this.$sticky.width(this.$stickyWrap.width());
    this.$stickyWrap.css('minHeight', this.stickyOuterHeight);
  }
  navPosition() {
    if (this.$stickyWrap.position().top < 0) {
      this.$sticky.addClass("fixed");
    } else {
      this.$sticky.removeClass("fixed");
    }
  }
  tabAnimation() {
    let desiredSpace = this.stickyOuterHeight + 10;
    $("section").each(function() {
      let actual = $(this),
        actualHeight = actual.height(),
        actualAnchor = $(".sticky").find('a[href="#' + actual.attr("id") + '"]');
      let actualTop = actual.position().top;
      let actualBottom = actualTop + actualHeight;
      if (actualTop < desiredSpace && actualBottom > desiredSpace) {
        actualAnchor.addClass("active");
      } else {
        actualAnchor.removeClass("active");
      }
    });
  }
}
$(function() {
  new StickyNavigation();
});
body {
  position: fixed;
  display: flex;
  flex-direction: column;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  overflow: hidden;
}

section {
  height: 600px;
  border: 2px solid white;
  background: blue;
}

section:nth-child(2) {
  background: red;
}

.container {
  flex: 1;
  display: flex;
  position: relative;
  flex-direction: column;
  overflow: auto;
}

.long {
  height: 1200px;
}

.header {
  height: 75px;
  background: green;
}

.hero {
  background: silver;
  flex: 0;
  border: 1px solid;
}

.nav {
  background: white;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
}

.sticky {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}

.sticky.fixed {
  position: fixed;
  top: 83px;
}

.tab {
  padding: 30px 45px;
  position: relative;
}

.tab.active {
  background: #6567c5;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="header">
  <h1>Header</h1>
</div>
<div class="container">
  <div class="hero">
    <h1>Hero</h1>
  </div>
  <div class="sticky">
    <nav role="navigation" class="nav">
      <a class="tab" href="#One">Option One</a>
      <a class="tab" href="#Two">Option Two</a>
    </nav>
  </div>
  <div class="main">
    <section id="One">
      Content One
    </section>
    <section class="long" id="Two">
      Content Two
    </section>
  </div>
</div>
like image 179
Munim Munna Avatar answered Oct 06 '22 16:10

Munim Munna