Home > Mobile >  content-visibility and sticky headers
content-visibility and sticky headers

Time:09-23

I'm using a sticky header which changes to position:fixed, as soon as you scroll up. For detecting the upwards scroll, I compare the current scrollTop with the old scrolltop – this works perfectly fine!

Now I'm implementing content-visibility for my footer to save some rendering time on the page load.

$(document).ready(function(){
  var lastScrollTop = window.pageOffsetY || 0;
  var isFixed = false;
  $(window).on('scroll', _.throttle(function(){
      var scrollTop = $(window).scrollTop();
      if(scrollTop > 200 && scrollTop < lastScrollTop && !isFixed){
        console.log('stick header');
        $('.header').addClass('header-fixed');
        isFixed = true;
      } else if((scrollTop > lastScrollTop && isFixed) || scrollTop == 0){
        console.log('unstick header');
        $('.header').removeClass('header-fixed');
        isFixed = false;
      }
    lastScrollTop = scrollTop;
  }, 500));
});
.content {
  height: 200vh;
  background-color: grey;
}

.content-visibility {
  content-visibility: auto;
}

.content-inner {
  height: 100px;
  background-color: green;
}

img.content-inner {
  height: auto;
}

.content.content-visibility {
  height: auto;
  background-color: #ddd;
}

.footer {
  background-color: #eee;
}

.header {
  height: 200px;
  background-color: white;
}


.header-fixed {
  position: fixed;
  top: 0;
  width: 100vw;
  left: 0;
  z-index: 10;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

<div class="container">
  <div class="header">
    header
  </div>
  <div class="content">
    content 1
  </div>
  <div class="content-visibility">
     <img loading="lazy" class="content-inner" src="https://via.placeholder.com/301x310">
   <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x303">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/303x340">
        <div style="height:200px"></div>

    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/304x340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/305x340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/306x340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/307x340">
  </div>
  <div class="content">
   content 2
  </div>
  <div class="footer content-visibility">
    footer
   <img loading="lazy" class="content-inner" src="https://via.placeholder.com/301x300">
   <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/303x300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/304x300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/305x300">
    <div style="height:200px"></div>
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/306x300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/307x300">
  </div>
</div>

if you scroll down fast, you can see the log statement "stick header", this is wrong and should only happen on scroll up. The problem now is, that at the moment the browser is rendering content-visibility elements, the javascript detects an upwards scroll, because the document height changes (I guess).

Is there any way to prevent this?

CodePudding user response:

By adding contain-intrinsic-size the browser knows what the size of the element roughly will be. It doesn't need to be accurate since the browser will eventually calculate the actual height, though the closer you are the less jumping happens.

Sidenote: adding the width and height attribute to a lazyloaded image will tell the browser to reserve space for when it loads reducing content jumps even more.

$(document).ready(function(){
  var lastScrollTop = window.pageOffsetY || 0;
  var isFixed = false;
  $(window).on('scroll', _.throttle(function(){
      var scrollTop = $(window).scrollTop();
      if(scrollTop > 200 && scrollTop < lastScrollTop && !isFixed){
        console.log('stick header');
        $('.header').addClass('header-fixed');
        isFixed = true;
      } else if((scrollTop > lastScrollTop && isFixed) || scrollTop == 0){
        console.log('unstick header');
        $('.header').removeClass('header-fixed');
        isFixed = false;
      }
    lastScrollTop = scrollTop;
  }, 500));
});
.content {
  height: 200vh;
  background-color: grey;
}

.content-visibility {
  content-visibility: auto;
  contain-intrinsic-size: 0 5000px; /* width height */
}

.content-inner {
  height: 100px;
  background-color: green;
}

img.content-inner {
  height: auto;
}

.content.content-visibility {
  height: auto;
  background-color: #ddd;
}

.footer {
  background-color: #eee;
}

.header {
  height: 200px;
  background-color: white;
}


.header-fixed {
  position: fixed;
  top: 0;
  width: 100vw;
  left: 0;
  z-index: 10;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

<div class="container">
  <div class="header">
    header
  </div>
  <div class="content">
    content 1
  </div>
  <div class="content-visibility">
     <img loading="lazy" class="content-inner" src="https://via.placeholder.com/301x310" width="301" height="310">
   <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x303" width="302" height="303">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x340" width="302" height="340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/303x340" width="303" height="340">
        <div style="height:200px"></div>

    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/304x340" width="304" height="340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/305x340" width="305" height="340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/306x340" width="306" height="340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/307x340" width="307" height="340">
  </div>
  <div class="content">
   content 2
  </div>
  <div class="footer content-visibility">
    footer
   <img loading="lazy" class="content-inner" src="https://via.placeholder.com/301x300" width="301" height="300">
   <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x300" width="302" height="300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x300" width="302" height="300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/303x300" width="303" height="300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/304x300" width="304" height="300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/305x300" width="305" height="300">
    <div style="height:200px"></div>
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/306x300" width="306" height="300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/307x300" width="307" height="300">
  </div>
</div>

CodePudding user response:

change Event to wheel and everything works fine. The problem on scroll is when your images are loading, your scrollTop variable changes as well ( runs on scroll event). But on Wheel event only runs when you use wheels.

$(document).ready(function(){
  var body = document.body,
    html = document.documentElement;

  var height = Math.max( body.scrollHeight, body.offsetHeight, 
                       html.clientHeight, html.scrollHeight, html.offsetHeight );
  var lastScrollTop = window.pageOffsetY || 0;
  var isFixed = false;
  $(window).on('wheel touchmove', _.throttle(function(){
      var scrollTop = $(window).scrollTop();
      var newheight = Math.max( body.scrollHeight, body.offsetHeight, 
                       html.clientHeight, html.scrollHeight, html.offsetHeight );
    console.log( height, newheight); 
      if( height !== newheight){
        height = newheight; 
        lastScrollTop = scrollTop;
        return;
      }
      if(scrollTop > 200 && scrollTop < lastScrollTop && !isFixed){
        console.log('stick header');
        $('.header').addClass('header-fixed');
        isFixed = true;
      } else if((scrollTop > lastScrollTop && isFixed) || scrollTop == 0){
        console.log('unstick header');
        $('.header').removeClass('header-fixed');
        isFixed = false;
      }
    lastScrollTop = scrollTop;
  }, 500));
});
.content {
  height: 200vh;
  background-color: grey;
}

.content-visibility {
  content-visibility: auto;
}

.content-inner {
  height: 100px;
  background-color: green;
}

img.content-inner {
  height: auto;
}

.content.content-visibility {
  height: auto;
  background-color: #ddd;
}

.footer {
  background-color: #eee;
}

.header {
  height: 200px;
  background-color: white;
}


.header-fixed {
  position: fixed;
  top: 0;
  width: 100vw;
  left: 0;
  z-index: 10;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

<div class="container">
  <div class="header">
    header
  </div>
  <div class="content">
    content 1
  </div>
  <div class="content-visibility">
     <img loading="lazy" class="content-inner" src="https://via.placeholder.com/301x310">
   <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x303">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/303x340">
        <div style="height:200px"></div>

    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/304x340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/305x340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/306x340">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/307x340">
  </div>
  <div class="content">
   content 2
  </div>
  <div class="footer content-visibility">
    footer
   <img loading="lazy" class="content-inner" src="https://via.placeholder.com/301x300">
   <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/302x300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/303x300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/304x300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/305x300">
    <div style="height:200px"></div>
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/306x300">
    <img loading="lazy" class="content-inner" src="https://via.placeholder.com/307x300">
  </div>
</div>

Update: Or! use this new snippet. it checks if the height of the page is changed, if so it updates variables and skips this event from happening.

Add touchmove alonside with wheel to support tuch ( phone ) as well.

  • Related