Search code examples
javascripthtmlcsscss-positionsticky

How can I scroll to an element with sticky positioning?


I have the following code and I want to use ids to switch between slides, but with an id it doesn't work and als with JS scrollTo, scrollIntoView and other variations. It works on the way down but not up.

I try to make a website which overlaps with itself and creates a nice progressive flow for surveys. It should also be able to move to a previous slide, so you can enter or change some answer in such a survey.

I hope you can help me and maybe see something which i didn't see.

// set color for each slide
window.onload = function() {
    let slides = [...document.getElementsByClassName("slide")];
    for(let n in slides) {
        let slide = slides[n];
        slide.style.backgroundColor = "hsl("+((360 / slides.length) * n)+", 100%, 25%)";
    }
}
body {
    font-size: 3vw;
    margin: 0;
    padding: 0;
    position: relative;
}

#ref {
    background-color: black;
    position: fixed;
    top: 0;
    z-index: 1000;
}

#ref a {
    color: white;
    text-decoration: none;
}

.slide {
    background-color: #404050;
    border: 1px solid white;
    color: white;
    height: calc(100vh - 2px);
    left: 0;
    position: sticky;
    top: 0;
    width: calc(100vw - 2px);
}
<!DOCTYPE html>
<html>
    <head>
        <title>Page Title</title>
    </head>
    <body>
        <div id="ref">
            <a href="#s1">S1</a>
            <a href="#s2">S2</a>
            <a href="#s3">S3</a>
            <a href="#s4">S4</a>
            <a href="#s5">S5</a>
            <a href="#s6">S6</a>
            <a href="#s7">S7</a>
            <a href="#s8">S8</a>
            <a href="#s9">S9</a>
        </div>
        <div id="s1" class="slide">
            <h1>Slide 1</h1>
        </div>
        <div id="s2" class="slide">
            <h1>Slide 2</h1>
        </div>
        <div id="s3" class="slide">
            <h1>Slide 3</h1>
        </div>
        <div id="s4" class="slide">
            <h1>Slide 4</h1>
        </div>
        <div id="s5" class="slide">
            <h1>Slide 5</h1>
        </div>
        <div id="s6" class="slide">
            <h1>Slide 6</h1>
        </div>
        <div id="s7" class="slide">
            <h1>Slide 7</h1>
        </div>
        <div id="s8" class="slide">
            <h1>Slide 8</h1>
        </div>
        <div id="s9" class="slide">
            <h1>Slide 9</h1>
        </div>
    </body>
</html>


Solution

  • After trying a long time I found a solution but it is very specific.

    You need to have a parent element which only includes the slides (in this case it is main), then you need to give the parent element a height and an overflow auto.

    After those steps add the javascript, which gets the selected element and its parent (here again main), then calculates the average height per element, after that it gets the index of the elem relative to the parent and multiplies it with the height to get the scrollOffset, which is in the last step set for the parent.

    // set color for each slide
    window.onload = function() {
        let slides = [...document.getElementsByClassName("slide")];
        for(let n in slides) {
            let slide = slides[n];
            slide.style.backgroundColor = "hsl("+((360 / slides.length) * n)+", 100%, 25%)";
        }
    }
    
    window.onhashchange = function() {
        let hash = document.body.querySelector(location.hash);
        let parent = hash.parentElement;
        let scrollOffset = parent.scrollHeight / parent.childElementCount;
        scrollOffset *= Array.prototype.indexOf.call(parent.children, hash);
        parent.scrollTop = scrollOffset;
    }
    body {
        font-size: 3vw;
        margin: 0;
        padding: 0;
        position: relative;
    }
    
    #ref {
        background-color: black;
        position: fixed;
        top: 0;
        z-index: 1000;
    }
    
    #ref a {
        color: white;
        text-decoration: none;
    }
    
    main {
        height: 100vh;
        overflow: auto;
    }
    
    .slide {
        background-color: #404050;
        border: 1px solid white;
        color: white;
        height: calc(100vh - 2px);
        left: 0;
        position: sticky;
        top: 0;
        width: calc(100vw - 2px);
    }
    <!DOCTYPE html>
    <html>
        <head>
            <title>Page Title</title>
        </head>
        <body>
            <div id="ref">
                <a href="#s1">S1</a>
                <a href="#s2">S2</a>
                <a href="#s3">S3</a>
                <a href="#s4">S4</a>
                <a href="#s5">S5</a>
                <a href="#s6">S6</a>
                <a href="#s7">S7</a>
                <a href="#s8">S8</a>
                <a href="#s9">S9</a>
            </div>
            <main>
                <div id="s1" class="slide">
                    <h1>Slide 1</h1>
                </div>
                <div id="s2" class="slide">
                    <h1>Slide 2</h1>
                </div>
                <div id="s3" class="slide">
                    <h1>Slide 3</h1>
                </div>
                <div id="s4" class="slide">
                    <h1>Slide 4</h1>
                </div>
                <div id="s5" class="slide">
                    <h1>Slide 5</h1>
                </div>
                <div id="s6" class="slide">
                    <h1>Slide 6</h1>
                </div>
                <div id="s7" class="slide">
                    <h1>Slide 7</h1>
                </div>
                <div id="s8" class="slide">
                    <h1>Slide 8</h1>
                </div>
                <div id="s9" class="slide">
                    <h1>Slide 9</h1>
                </div>
            </main>
        </body>
    </html>