CSS Partial Scroll Snapping
Asked Answered
S

2

6

Been playing around with the scroll snapping, looks like it saves a lot of head scratching with writing the functionality in JS.

Here's a challenge, has anyone out there found a way to selectively choose which children to snap and which children to freely scroll?

I think this will be useful for content rich pages that contain parts that wouldn't benefit from scroll snapping.

Here's an example of the problem: https://codepen.io/nodelondon/pen/YzxWqLG

html {
  background: #f2f2f2;
}

.scroll-container,
.scroll-area-none,
.scroll-area {
  max-width: 850px;
  height: 600px;
  font-size: 60px;
}

.scroll-area-none {
  scroll-snap-align: none;
  background-color: black;
}

.scroll-container {
  overflow: auto;
  scroll-snap-type: y mandatory;
}

.scroll-area {
  scroll-snap-align: start;
}

.scroll-container,
.scroll-area-none,
.scroll-area {
  margin: 0 auto;
}

.scroll-area-none,
.scroll-area {
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
}

.scroll-area:nth-of-type(4n+1) {
  background: #49b293;
}

.scroll-area:nth-of-type(4n+2) {
  background: #c94e4b;
}

.scroll-area:nth-of-type(4n+3) {
  background: #4cc1be;
}

.scroll-area:nth-of-type(4n+4) {
  background: #8360A6;
}
<div class="support-scrollsnap"></div>

<div class="scroll-container">
  <div class="scroll-area-none">-1</div>
  <div class="scroll-area-none">0</div>
  <div class="scroll-area">1</div>
  <div class="scroll-area">2</div>
  <div class="scroll-area">3</div>
  <div class="scroll-area">4</div>
  <div class="scroll-area-none">5</div>
  <div class="scroll-area-none">6</div>
</div>

Ideally, the boxes with -1,0,5 and 6 should be able to freely scroll but the mandatory boxes in between keep pulling you back.

If you're thinking of suggesting changing it to proximity, this is a good suggestion, but, with IOS Safari (On OSX Safari for me as well), unfortunately it still forces scroll snapping on boxes that have scroll-snap-align set to Start no matter where you are on the page.

Stefaniastefanie answered 19/10, 2021 at 9:26 Comment(0)
V
0

Found that changing scroll-snap-type on documentElement causes the scroll to jump to the nearest snap-align element, which seems to look awful.

Looks like the simplest fix is working fine:

let t = window.scrollY;
requestAnimationFrame(() => window.scroll(0, t));
Venus answered 31/3, 2022 at 6:29 Comment(0)
G
-1

I propose the following logic:

  1. Define which children block is at the top border of the scroll container. I am using the Element.getBoundingClientRect() method to compare positions of the scroll container and its children.
  2. Check which scroll-snap-align property has this child block.
  3. Set the scroll-snap-type property of the container as y proximity or y mandatory.
  4. Handle the scroll event on desktop, On mobile this event works at the end of the scroll, so we need something else for mobile (may be jQuery Mobile).

Here is a working draft solution. But it requires optimizations and improvements such as scroll event throttling.

https://codepen.io/glebkema/pen/zYdPqeY

let scrollContainers = document.getElementsByClassName('scroll-container');
for (let sc of scrollContainers) {
  sc.addEventListener('scroll', updateSnapType);
  sc.addEventListener('touchstart', updateSnapType);
}

function updateSnapType(event) {
  let parent = event.currentTarget;
  let parentRect = parent.getBoundingClientRect();
  for (let child of parent.children) {
    let childRect = child.getBoundingClientRect();
    if (childRect.top <= parentRect.top && parentRect.top < childRect.bottom) {
      let childStyle = window.getComputedStyle(child);
      let scrollSnapAlign = childStyle.getPropertyValue('scroll-snap-align');
      console.log(child.innerText, scrollSnapAlign);
      parent.style.scrollSnapType = "none" === scrollSnapAlign ? 'y proximity' : 'y mandatory';
      break;
    }
  }
}
html {
  background: #f2f2f2;
}

.scroll-container,
.scroll-area-none,
.scroll-area {
  height: 100px;
  font-size: 60px;
}

.scroll-container {
  max-width: 850px;
  margin: 15px auto;
  overflow: auto;
  scroll-snap-type: y mandatory;
}

.scroll-area {
  scroll-snap-align: start;
}

.scroll-area-none {
  scroll-snap-align: none;
  background-color: black;
}

.scroll-area-none,
.scroll-area {
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
}

.scroll-area:nth-of-type(4n+1) {
  background: #49b293;
}

.scroll-area:nth-of-type(4n+2) {
  background: #c94e4b;
}

.scroll-area:nth-of-type(4n+3) {
  background: #4cc1be;
}

.scroll-area:nth-of-type(4n+4) {
  background: #8360A6;
}
<div class="scroll-container">
  <div class="scroll-area-none">1. -1</div>
  <div class="scroll-area-none">1. 0</div>
  <div class="scroll-area">1. 1</div>
  <div class="scroll-area">1. 2</div>
  <div class="scroll-area">1. 3</div>
  <div class="scroll-area">1. 4</div>
  <div class="scroll-area-none">1. 5</div>
  <div class="scroll-area-none">1. 6</div>
</div>

<div class="scroll-container">
  <div class="scroll-area-none">2. -1</div>
  <div class="scroll-area-none">2. 0</div>
  <div class="scroll-area">2. 1</div>
  <div class="scroll-area">2. 2</div>
  <div class="scroll-area">2. 3</div>
  <div class="scroll-area">2. 4</div>
  <div class="scroll-area-none">2. 5</div>
  <div class="scroll-area-none">2. 6</div>
</div>
Giroux answered 1/11, 2021 at 22:38 Comment(10)
Section 1.5 is unreachable for me using this solution (on Chrome)Humphreys
@Humphreys Sections 5 and 6 are set to be unreachable by the scroll-are-none class as well as sections -1 and 0Giroux
Is it really the intention to make these sections unreachable? I thought the point was to allow free scrolling in these sections (avoid snapping), not to make them unreachable… Wouldn't display: none be a better way to make something unreachable?Humphreys
@Humphreys It seems I misunderstood your first comment. Block 5 becomes free scrollable when block 4 is fully scrolled. As long as at least part of block 4 is visible on the screen, scrolling tends to return to its beginning. The same happens for blocks 1, 2 and 3. This achieves the difference requested in the question between free-scrolling blocks and blocks with jumps at the beginning of the blocks.Giroux
Doesn't work, sorry.Brynnbrynna
@Brynnbrynna Please describe in more detail what is not working. On my computer, the above code works as expected.Giroux
On Firefox in the code snippet above I cannot scroll down past 1.4 and 2.4. If I try to scroll using the mouse wheel, the scroll action is ignored and if I use the scrollbars the scroll jumps back to 1.4 and 2.4 when releasing the scrollbar.Brynnbrynna
@Brynnbrynna 1) In order for the mouse wheel to overcome the barrier created by the scroll-snap-type property, an additional script or other trick is needed. The current script toggles this property on scrollbar scrolling only. 2) Thanks for pointing out the problem on FF. It seemed to me that with the increase in the height of the blocks, this problem almost disappears. The code on codepen.io/glebkema/pen/zYdPqeY uses blocks with height of 250px, and the effect of toggling the scroll-snap-type property becomes noticeable when scrolling with the scrollbar on FF too. Do you see the same?Giroux
With the new changes it seems to work in FF too, but it doesn't work on mobile nor does it work when scrolling with the wheel. I think the touch-drag on mobile has similar mechanics to wheel-scroll. Any idea how this could be made into a cross-browser solution?Brynnbrynna
@Brynnbrynna For mobile I only have an idea from the 4th point in the answer. This code draft is for desktop use only. Thanks again for pointing out the code behavior on FFGiroux

© 2022 - 2024 — McMap. All rights reserved.