Search code examples
htmlcssscroll-snapscroll-snap-pointsscroll-snap-type

CSS scroll snapping without restricting parent size


I'm trying to have a web page scroll and snap to beginning of some elements as the user scrolls through a webpage using scroll-snap-type: y mandatory and scroll-snap-align: start. My problem is that it's a large webpage and unlike many samples where the whole page is just a div and children, I've got many divs and children. If I restrict my divs' direct container to have 100vh the scroll snapping works but there is double scrollable content problem: the document scrolls and the inner div's snapping content scrolls independently of the whole document. If I don't restrict the direct parent's height then snapping doesn't work.

Here is an example: https://codepen.io/canp/pen/abGJKXX

Basically the example in the above link does snap but there is the double scroll problem. I want the outer class div the only scrolling component, while still snapping to mid's children page divs without double scrollbars.

How can I make document scrolling to snap to a grand-grandchild's start position?


Solution

  • It is not entirely clear what your detailed objective is, but the code provided below satisfies the following requirements:

    • The outer div is the scroll container.
    • There is only one scroll level, ie, only one scroll bar.
    • The mid div can be at any level you wish, or even absent, or even mulitple and varied mid levels.
    • The pages can be at any level, ie, child, grand child, great grand child, etc, or even a mixture of decendent level if you wish.
    • The parent of a page has no size specified.

    The CSS is straight out of the box from https://www.w3.org/TR/css-scroll-snap/, so should work universally in any browser that supports snapping.

    The thing to do is to have no (other) scrolling defined between the level you have the scroll container at and the elements you want to snap.

    /* Single scroll level with arbitrary decendent depth all snapping to the one scroll level */
    
    .outer {
        overflow-y: auto;
        scroll-snap-type: y mandatory;
        scroll-padding-top: 5vh;
        height: 100vh;
    }
    
    .page {
        height: 100vh;
        scroll-snap-align: start;
    }
    
    /* For this example get rid of typical extra spacing which will impact the size of the available view */
    
    body {
        margin: 0px;
    }
    
    /* Make pages stand out, and advise what generation they are. */
    
    .outer > div.page {
        background: red;
    }
    
    .outer > div.page > p::after {
        content: ': A child of the outer div has a red background.';
    }
    
    .outer > div > div.page {
        background: orange;
    }
    
    .outer > div > div.page > p::after {
        content: ': A grand child of the outer div has an orange background.';
    }
    
    .outer > div > div > div.page  {
        background: yellow;
    }
    
    .outer > div > div > div.page > p::after {
        content: ': A great grand child of the outer div has an yellow background.';
    }
    
    .outer > div > div > div > div.page  {
        background: green;
    }
    
    .outer > div > div > div > div.page > p::after {
        content: ': A great great grand child of the outer div has a green background.';
    }
    .outer > div > div > div > div > div.page  {
        background: blue;
        color: white;
    }
    
    .outer > div > div > div > div > div.page > p::after {
        content: ': A great great great grand child of the outer div has a blue background and white text.';
    
    }
    <div class="outer">
        <div class="page">
            <p>Page 0</p>
        </div>
        <div>
            <div class="page">
                <p>Page 1</p>
            </div>
            <div>
                <div class="page">
                    <p>Page 2</p>
                </div>
            </div>
            <div class="page">
                <p>Page 3</p>
            </div>
            <div>
                <div>
                    <div class="page">
                        <p>Page 4</p>
                    </div>
                </div>
                <div class="page">
                    <p>Page 5</p>
                </div>
            </div>
            <div>
                <div>
                    <div>
                        <div class="page">
                            <p>Page 6</p>
                        </div>
                    </div>
                </div>
                <div class="page">
                    <p>Page 7</p>
                </div>
            </div>
        </div>
        <div class="page">
            <p>Page 8</p>
        </div>
    </div>