最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

CSS Position Sticky: Jittery Transition When Scrolling to Partially Hide Banner - Stack Overflow

programmeradmin0浏览0评论

Here is the stripped down version of what I've been working on via CodePen:

I am trying to implement a two column layout according to this image:

The left column is the menu sidebar; the right column contains the main content. The main content block has a horizontal banner that would represent the branding for the department I work for. The banner consists of two SVGs; one is the horizontally striped background and the other is the logo that is positioned on the background SVG. If the user scrolls down, the banner will hide itself partially - Only the bottom portion is shown and the logo disappears. If the user scrolls back up, it will show the banner in full.

Issue: When scrolling back up to the top of the page, it is not a smooth motion - it does it in two movements as opposed to one smooth motion.

The two-step movement has been driving me nuts for the last while and it's not clear to me what's exactly causing it. My implementation is otherwise satisfactory if it were not for this.

Relevant code...

HTML:

<div class="sidebar nav navbar flex-shrink-0" style="width: 280px">
  <!-- sidebar... -->
</div>

<!-- This is for inserting the contents of the page -->
<main id="main-content-block" style="margin-top: -10px;">
  <div id="banner-wrapper" class="banner-wrapper">
    <div id="org-banner" class="banner">
      <svg width="3000" height="130" xmlns=";>
          <rect x="0" y="0" width="3000" height="80" fill="red" />
          <rect x="0" y="80" width="3000" height="25" fill="orange" />
          <rect x="0" y="105" width="3000" height="25" fill="coral" />
      </svg>
    </div>

    <div id="crest-container">
          <div class="crestbg">
            <svg width="125" height="125" xmlns=";>
                <rect x="0" y="0" width="125" height="125" fill="maroon" />
            </svg>
          </div>
    </div>

  </div>

  <!-- Content -->
  <hr>

</main>

CSS:

/* Note: I also use bootstrap.css styling, v5.3.3 */

body {
    font-family: "Roboto";
    display: flex;
    flex-direction: row;
    height: 100vh;
}

main {
    height: 100vh;
    flex-grow: 1;
    overflow-y: auto;
    padding: 10px 10px 10px 10px;
}

/* For banner */

#banner-wrapper {
    position: sticky;
    top: 0;
    z-index: 10;
    margin: -8px -10px 30px -10px;
    height: 130px;
    overflow: hidden;
    transition: all 0.5s ease-out;
}

.banner svg {
    height: 130px;
    top: 0px;
}

/* Positions the crest within the banner BG. */ 
.crestbg svg {
    height: 130px;
    position: absolute;
    top: 0px;
    right: 40px;
}

.banner a {
    text-decoration: none;
}

/* CSS for hiding banner partially when scrolling */

header {
    height: 80px;
    transition: all 0.5s ease-out;
}

header.scrolled {
    height: 50px;
}
#banner-wrapper.scrolled {
    top: -80px;
}
#crest-container.scrolled {
    display: none;
}

JS:

 // For banner scrolling.
    const mainContentEle = document.getElementById('main-content-block')

    mainContentEle.addEventListener('scroll', () => {
      // Dependent on the SG banner dimensions and how it's designed. Change this as needed.
      const scrollPixelCutoffValue = 80;

      const headerElement = document.querySelector('header')
      const svgBannerContainer = document.getElementById('banner-wrapper')
      const crestContainer = document.getElementById('crest-container')
      if (mainContentEle.scrollTop > scrollPixelCutoffValue) { // Adjust this value as needed
        svgBannerContainer.classList.add('scrolled');
        headerElement.classList.add('scrolled');
        crestContainer.classList.add('scrolled');
      } else {
        svgBannerContainer.classList.remove('scrolled');
        headerElement.classList.remove('scrolled');
        crestContainer.classList.remove('scrolled');
      }
      return;
    });

Edit: I've added my own CodePen for reference, should the JSFiddle in the chosen answer disappear in the future:

Here is the stripped down version of what I've been working on via CodePen: https://codepen.io/moosearch/pen/EaxYjEP

I am trying to implement a two column layout according to this image: https://imgur.com/a/bj1tWCK

The left column is the menu sidebar; the right column contains the main content. The main content block has a horizontal banner that would represent the branding for the department I work for. The banner consists of two SVGs; one is the horizontally striped background and the other is the logo that is positioned on the background SVG. If the user scrolls down, the banner will hide itself partially - Only the bottom portion is shown and the logo disappears. If the user scrolls back up, it will show the banner in full.

Issue: When scrolling back up to the top of the page, it is not a smooth motion - it does it in two movements as opposed to one smooth motion.

The two-step movement has been driving me nuts for the last while and it's not clear to me what's exactly causing it. My implementation is otherwise satisfactory if it were not for this.

Relevant code...

HTML:

<div class="sidebar nav navbar flex-shrink-0" style="width: 280px">
  <!-- sidebar... -->
</div>

<!-- This is for inserting the contents of the page -->
<main id="main-content-block" style="margin-top: -10px;">
  <div id="banner-wrapper" class="banner-wrapper">
    <div id="org-banner" class="banner">
      <svg width="3000" height="130" xmlns="http://www.w3.org/2000/svg">
          <rect x="0" y="0" width="3000" height="80" fill="red" />
          <rect x="0" y="80" width="3000" height="25" fill="orange" />
          <rect x="0" y="105" width="3000" height="25" fill="coral" />
      </svg>
    </div>

    <div id="crest-container">
          <div class="crestbg">
            <svg width="125" height="125" xmlns="http://www.w3.org/2000/svg">
                <rect x="0" y="0" width="125" height="125" fill="maroon" />
            </svg>
          </div>
    </div>

  </div>

  <!-- Content -->
  <hr>

</main>

CSS:

/* Note: I also use bootstrap.css styling, v5.3.3 */

body {
    font-family: "Roboto";
    display: flex;
    flex-direction: row;
    height: 100vh;
}

main {
    height: 100vh;
    flex-grow: 1;
    overflow-y: auto;
    padding: 10px 10px 10px 10px;
}

/* For banner */

#banner-wrapper {
    position: sticky;
    top: 0;
    z-index: 10;
    margin: -8px -10px 30px -10px;
    height: 130px;
    overflow: hidden;
    transition: all 0.5s ease-out;
}

.banner svg {
    height: 130px;
    top: 0px;
}

/* Positions the crest within the banner BG. */ 
.crestbg svg {
    height: 130px;
    position: absolute;
    top: 0px;
    right: 40px;
}

.banner a {
    text-decoration: none;
}

/* CSS for hiding banner partially when scrolling */

header {
    height: 80px;
    transition: all 0.5s ease-out;
}

header.scrolled {
    height: 50px;
}
#banner-wrapper.scrolled {
    top: -80px;
}
#crest-container.scrolled {
    display: none;
}

JS:

 // For banner scrolling.
    const mainContentEle = document.getElementById('main-content-block')

    mainContentEle.addEventListener('scroll', () => {
      // Dependent on the SG banner dimensions and how it's designed. Change this as needed.
      const scrollPixelCutoffValue = 80;

      const headerElement = document.querySelector('header')
      const svgBannerContainer = document.getElementById('banner-wrapper')
      const crestContainer = document.getElementById('crest-container')
      if (mainContentEle.scrollTop > scrollPixelCutoffValue) { // Adjust this value as needed
        svgBannerContainer.classList.add('scrolled');
        headerElement.classList.add('scrolled');
        crestContainer.classList.add('scrolled');
      } else {
        svgBannerContainer.classList.remove('scrolled');
        headerElement.classList.remove('scrolled');
        crestContainer.classList.remove('scrolled');
      }
      return;
    });

Edit: I've added my own CodePen for reference, should the JSFiddle in the chosen answer disappear in the future: https://codepen.io/moosearch/pen/wBvwyxx

Share Improve this question edited 1 hour ago moosearch asked yesterday moosearchmoosearch 3062 silver badges11 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 2

This is a know issue in Chrome. Position sticky elements cause bouncing effect when trying to adjust/animate the height of the element. As suggested in the answer here, one solution is to have a non moving parent element.

Below I've implemented a solution which involves a combination of above technique and absolute positioning. Check this JsFiddle demo.

Full code:

document.addEventListener('DOMContentLoaded', () => {});

// For banner scrolling.
const mainContentEle = document.getElementById('main-content-block')

mainContentEle.addEventListener('scroll', () => {
  // Dependent on the SG banner dimensions and how it's designed. Change this as needed.
  const scrollPixelCutoffValue = 80;

  const headerElement = document.querySelector('header')
  const svgBannerContainer = document.getElementById('org-banner')
  const crestContainer = document.getElementById('crest-container')
  if (mainContentEle.scrollTop > scrollPixelCutoffValue) { // Adjust this value as needed
    svgBannerContainer.classList.add('scrolled');
    headerElement.classList.add('scrolled');
    crestContainer.classList.add('scrolled');
  } else {
    svgBannerContainer.classList.remove('scrolled');
    headerElement.classList.remove('scrolled');
    crestContainer.classList.remove('scrolled');
  }
  return;
});
body {
  font-family: "Roboto";
  display: flex;
  flex-direction: row;
  height: 100vh;
  overflow: hidden;
  margin-right: 0;
}

.sidebar {
  max-width: 30%;
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  justify-content: flex-start;
  align-items: stretch;
  border-right: 2px solid var(--bs-border-color);
  padding: 0 10px 10px 10px;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-gutter: auto;

  --bs-nav-link-color: var(--bs-sidebar-link-color);
  --bs-nav-link-hover-color: var(--bs-sidebar-link-hover-color);
  --bs-nav-link-disabled-color: var(--bs-sidebar-link-disabled-color);
}

main {
  height: 100vh;
  flex-grow: 1;
  overflow-y: auto;
  padding: 10px 10px 10px 10px;
}

/* For banner */

#non-moving {
  margin-bottom: 150px;
  position: sticky;
  top: 0;
}

#org-banner {
  margin: -8px -20px 30px 0px;
  height: 130px;
  overflow: hidden;
  transition: all 0.5s ease-out;
  position: absolute;
}

.banner svg {
  width: 100%;
  height: 130px;
  top: 0px;
}

/* Positions the crest within the banner BG. */
.crestbg svg {
  height: 120px;
  position: absolute;
  top: -10px;
  right: 40px;
  transition: all 0.5s ease-out;
}

.banner a {
  text-decoration: none;
}

/* CSS for hiding banner partially when scrolling */

header {
  height: 80px;
  transition: all 0.5s ease-out;
}

header.scrolled {
  height: 50px;
}

#org-banner.scrolled {
  margin-top: -90px;
}

#crest-container.scrolled svg {
  height: 0;
}
<div class="sidebar nav navbar flex-shrink-0" style="width: 280px">
  <header>
    <span class="fs-5 fw-semibold">App</span>
  </header>
  <hr style="padding: 0; margin: 0;">
  <div class="nav-pills" style="height: 48px; display: flex; align-items: center; justify-content: flex-start;">
    <span>Name</span>
  </div>
  <hr style="padding-top:0;margin-top:0;">

</div>

<!-- This is for inserting the contents of the page -->
<main id="main-content-block" style="margin-top: -10px;">
  <div id="non-moving">
    <div id="banner-wrapper" class="banner-wrapper">
      <div id="org-banner" class="banner">
        <svg width="3000" height="130" xmlns="http://www.w3.org/2000/svg">
        <rect x="0" y="0" width="3000" height="80" fill="red" />
        <rect x="0" y="80" width="3000" height="25" fill="orange" />
        <rect x="0" y="105" width="3000" height="25" fill="coral" />
    </svg>

      </div>
      <div id="crest-container">
        <div class="crestbg">
          <svg width="125" height="125" xmlns="http://www.w3.org/2000/svg">
                  <rect x="0" y="0" width="125" height="125" fill="maroon" />
              </svg>
        </div>
      </div>
    </div>
  </div>

  <h1>Template Bootstrap</h1>

  <p> This page is intended to view the components and how they render within the application with the customized
    bootstrap theme. </p>

  <p style="height: 500px">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>

  <p style="height: 500px">Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla..</p>

  <p style="height: 500px">Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum.</p>
  <hr>

</main>

I have played around with this quite a bit. I believe I have solved the "smooth motion" while scrolling.

However, I am aware this causes two regressions in your code, but I am thinking you can pick it up from here.

#banner-wrapper {
  position: absolute; //position: sticky;
  top: 0;
  z-index: 10;
  margin: 0 -10px 30px -10px; //margin: -8px -10px 30px -10px;
  height: 130px;
  overflow: hidden;
  transition: all 0.5s ease-out;
}

Changing the banner to be absolute positioning creates the desired effect. The negative top margin is no longer needed.

Regressions:

  • The crest image is no longer visible on the right side of the banner.
  • The text on the page needs a margin because it is hidden underneath the banner.
发布评论

评论列表(0)

  1. 暂无评论