Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scroll Snap only when scrolling down

I am struggling with scroll-snap in CSS.

Is there a way to use scroll-snap only in one vertical direction?

I want to make it snap on every section when scrolling down, but not to snap when scrolling back up again. Is it possible inside CSS or do I need some JavaScript?

html {
    scroll-snap-type: y mandatory;
}

body {
    margin: 0;
    scroll-snap-type: y mandatory;
  }
  
  section {
    font-family: sans-serif;
    font-size: 12ch;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    scroll-snap-align: start; 
  }
 
  <head>

    <!-- MAIN STYLE -->
    <link rel="stylesheet" href="style.css">
    

  </head>

    <main>
     <section>
        <h1>Hello!</h1>
     </section>

     <section>
        <h1>Toodles~</h1>
     </section>

     <section>
        <h1>Boodles~</h1>
     </section>
            
    </main>

</html> 
like image 431
kailias Avatar asked Jun 23 '26 09:06

kailias


2 Answers

Unfortunately, you'll need JavaScript to change scrolling behavior when the scrolling direction changes.

🤷 I'm Not 100% Sure Why It Works...

I immediately thought that:

  • registering the "scroll" event to window
  • assigning scroll-snap-type: y mandatory to <body>✣
  • and getting a distance from window.scrollYšŸ—” to compare with.

This and many other combinations derived from here and here eluded me. Upon further digging, the CSS property, scroll-behavior will not propigate from the <body> to the viewport (aka window) -- instead it's the <html> tag that'll actually be detected when a "scroll" event is triggered. Although scroll-behavior is for scrolling from links, it's the only clue that lead me in the right direction, and I can't find a real good explination as to why it appears that CSS scrolling depends solely on the root. I believe that since the content takes up the whole viewport, window is the best choice to bind the event handler to and that CSS scrolling doesn't bubble the standard way.

✣<html>
šŸ—”document.body.getBoundingClientRect().top


Details are commented in exaple below

Note: The iframe in which Stack Snippets reside within are not at all a close proximity to what the real dimaensions are. To get the true dimensions (like viewport) and associated behavior (like scrolling), review it in Full page

                                 fullpage

<!DOCTYPE html>
<html lang="en">

<head>
  <title></title>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <style>
     :root {
      font-family: 'Georgia';
      font-size: 62.5%;
      line-height: 1;
      scroll-behavior: smooth;
    }
    
    html,
    body {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
    
    section {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      font-size: 1.6rem;
      outline: 5px dashed tomato;
      scroll-snap-align: start;
    }
    
    section:nth-of-type(odd) {
      color: gold;
      background: navy;
    }
    
    section:nth-of-type(even) {
      color: lime;
      background: black;
    }
    
    h1 {
      font-size: 6em;
      margin: 0;
    }
  </style>
</head>

<body>
  <section><h1>I</h1></section>
  <section><h1>II</h1></section>
  <section><h1>III</h1></section>
  <section><h1>IV</h1></section>
  <section><h1>V</h1></section>
  <section><h1>VI</h1></section>
  <section><h1>VII</h1></section>
  <section><h1>VIII</h1></section>
  <section><h1>IX</h1></section>
  <section><h1>X</h1></section>

  <script>
/*
Declare an initial value outside of event handler so that it is retained
after the function is done. 
*/
 let start = 0;
 // Bind window to the scroll event
 window.addEventListener('scroll', function() {
   // Reference the BCR of <body>
   const bcr = document.body.getBoundingClientRect();
   /*
   Get the distance from the top of viewport and the top of the body
   */
   const stop = bcr.top;
   /*
   If that distance greater than start, disable scroll-snap-type on <html>
   */
   if (stop > start) {
     document.scrollingElement.style.setProperty('scroll-snap-type', 'none');
   //console.log('up')
   } else {
   // Otherwise, enable scroll-snap-type
     document.scrollingElement.style.setProperty('scroll-snap-type', 'y mandatory');
   //console.log('down');
   }
   // Set start as stop for the next scroll
   start = stop;
 });
 </script>
</body>

</html>
like image 134
zer00ne Avatar answered Jun 24 '26 22:06

zer00ne


You could listen the the scroll event and set the scroll-snap-type property to the html element according to the scroll direction.

//save initial scrollY position
var previousScrollY = window.scrollY;
//listen to scroll event
window.addEventListener("scroll", function(e) {
  //is the user scrolling down ?
  let down = window.scrollY > previousScrollY;
  //set scroll-snap-type according to scroll direction
  document.documentElement.style.scrollSnapType = down ? "y mandatory" : "";
  //save current scrollY position
  previousScrollY = window.scrollY;
});
body {
  margin: 0;
}
  
section {
  font-family: sans-serif;
  font-size: 12ch;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  scroll-snap-align: start; 
}
<section>
    <h1>Hello!</h1>
 </section>

 <section>
    <h1>Toodles~</h1>
 </section>

 <section>
    <h1>Boodles~</h1>
 </section>
like image 21
Tom Avatar answered Jun 24 '26 23:06

Tom



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!