Show header when at top of page - Scroll event re-firing issue


Tags: javascript,jquery,html,css,knockout.js

Problem :

I have a set up where, there is a header, which will only be shown when the scrollTop of the scrollable area is at 0 (meaning they are at the top of the page). This seems to work fine in many cases, but there is a case where it fails in a rather annoying way.

The Bug

If the content in the scrollable area is just slightly big enough to make a scroll bar, then when you scroll down, the header will disappear and the scrollable area will now grow to fill that empty space, which will now allow the content to appear without having a scroll bar. The transition from having a scroll bar to not having a scroll bar apparently triggers another scroll event to be fired in all the browsers I have tested I do not know why that is or how to solve it.

One possible solution

I can set my content to have a min-height of 101% so that there will always be a tiny amount of scroll bar no matter what the actual content height would otherwise be. This isn't my favorite solution however and I am looking for something better.

html

<div class="container">
<div class="header">Header</div>
<div class="content" data-bind="css: {'show-header': showHeader}">
    <div class="sub-header">Sub Header</div>
    <div class="scrollable" data-bind="event: {'scroll':test}">
        <div class="stuff">asdf</div>       
    </div>
</div>

CSS

    .container {
    position: absolute;
    top: 0;
    left: 0;
    width: 320px;
    height: 352px;
    border: 1px solid black;
}

.header {
    height: 48px;
    background: grey;
}

.content {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    transition: top 0.3s;
}

.show-header {
 top: 48px;   
}


.sub-header {
    height: 48px;
    background: lightgrey;
}

.scrollable {
    position: absolute;
    top: 48px;
    left: 0;
    right: 0;
    bottom: 0;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    overflow-x: hidden;
}

.stuff {
    /*min-height: 101%;*/
    height: 260px;
    /*height: 2000px;*/
}

JS

    var appViewModel = {};
var didScroll = false;
var scrollElem = $('.scrollable');

appViewModel.showHeader = ko.observable(true);

appViewModel.test = function() {
    didScroll = true;
};


setInterval(function() {
    if (didScroll) {
        didScroll = false;
        appViewModel.hasScrolled();
    }
}, 250);


appViewModel.hasScrolled = function() {
    var st = scrollElem.scrollTop();

    if ( st > 0 && appViewModel.showHeader() ) {
        appViewModel.showHeader(false);
    }
    else if ( st <= 0 && !appViewModel.showHeader() ) {
        appViewModel.showHeader(true);
    }
};


ko.applyBindings(appViewModel);

JS Fiddle JS Fiddle Example



Solution :

I did a bit of rewriting to make this more Knockout-y and (hopefully) a bit more straightforward. I brought the header height into the code as a variable, since I needed to use it.

scrollTop is a throttled observable, so it will not announce updates more than every 250 ms.

I use an observable and an if binding to control whether the header is rendered. When the scrollTop changes, a subscribed function determines whether the header should be shown. The calculation depends on whether the header is currently being shown.

scrollableHeight controls the height of the scrollable region, expanding it if the header is hidden.

The only way I know of to avoid the problem of having the scrollbar go away when the header does is to only remove the header when scrolling beyond the header height.

var appViewModel = {};
var didScroll = false;

appViewModel.headerHeight = 48;
appViewModel.scrollTop = ko.observable(0).extend({rateLimit:250});
appViewModel.showHeader = ko.observable(true);
appViewModel.scrollTop.subscribe(function (newTop) {
    console.debug("New top");
    if (appViewModel.showHeader()) {
        appViewModel.showHeader(newTop < appViewModel.headerHeight);
    }
    else {
        appViewModel.showHeader(newTop <= 0);
    }
});
appViewModel.scrollableHeight = ko.computed(function () {
    return (appViewModel.showHeader()) ? '256px' : (256 + appViewModel.headerHeight) + 'px';
});

appViewModel.test = function (data, event) {
    var scrollElem = $(event.target);
    appViewModel.scrollTop(scrollElem.scrollTop());
};

ko.applyBindings(appViewModel);
.container {
  position: absolute;
  top: 0;
  left: 0;
  width: 320px;
  height: 352px;
  border: 1px solid black;
}
.header {
  background: grey;
}
.sub-header {
  height: 48px;
  background: lightgrey;
}
.scrollable {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overflow-x: hidden;
  height: 256px;
}
.stuff {
  background-color: #fee;
  height: 320px;
  /*height: 2000px;*/
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="container">
  <!-- ko if:showHeader -->
  <div class="header" data-bind="style:{height:headerHeight+'px'}">Header</div>
  <!-- /ko -->
  <div class="sub-header">Sub Header</div>
  <div class="scrollable" data-bind="style:{height:scrollableHeight}, event: {'scroll':test}">
    <div class="stuff">asdf</div>
  </div>
</div>


    CSS Howto..

    How can I make a fluid width header with text-overflow ellipsis without wrapping?

    How remove space on around content (css)?

    how to bring two blocks closer

    How to include library files (css/js) in angular-fullstack based app

    How to change html td background colour through Javascript?

    How to create an overlay to be used under a pop up

    How to make SVG clip-path work correctly in Google Chrome?

    how to define minimum height for tbody in css

    In a text based browser game, how would I make a typewriter effect in the dialogue?

    CSS How to set div height 100% minus nPx

    CSS: How to select elements by referencing the parent, not the child

    How to apply @media for css attribute that are set via jquery

    How to make button look like a link?

    How to add a custom css selector to just a span??

    How to resize the images to have the same height in group while keeping aspect ratio?

    How to display row of images in a repeater control vertically instead of horizontally

    transition ease-out, right to left, how?

    How to add CSS class to password field using jquery in MVC?

    How do I absolute position a div from left to the right using media queries?

    How to get the table (datatable) left aligned?

    How to make a bullet list align with text in css?

    How to set CSS of one element depending on another element having a specific class?

    CSS: how do I position my slideshow counter up into the slideshow

    How do I modify the bootstrap carousel javascript file so that it can trigger this function?

    CSS: How do I make text indent for a label with long text?

    How to stop main scrollbar working when popup modal is activated, which needs also a scrollbar

    HOW TO check if an external (cross-domain) CSS file is loaded using Javascript

    How can I center the column vertically in bootstrap mobile mode?

    How do I add a dropdown menu to my css menu?

    how to change the CSS Class of the asp label dynamically based on its value