Search code examples
javascriptajaxscroll

Generate content with Ajax - scroll to Id doesn't work


I am generating the page content depending on the data I get via ajax. The problem I am having, is that when I would like to scroll to an ID, the scroll either doesn't happen or it scrolls to the wrong position.

I've been looking at SO q&a, but I haven't found any good solutions. A lot of answers are for Angular or React, but nothing really solid for plain js.

As an example, lets say the user clicks a link About us -> Project, where about us is the page and projects is in id at the end of the page with some content.

The problem occurs when I click the link from a different page.

Lets say we are on the home page, an we click a link for the about us page, section projects (id projects). This is when the scroll doesn't work as it is supposed to. If we click on the link project, when we are on the about us page, this problem doesn't happen.

As the page data in being rendered with javascript, I can't use event listeners DOMContentLoaded or load, as they trigger before the content is being generated (I think).

Below is my non working solution. It is supposed to check if the id is in viewport, and if not, it should scroll.

I don't want to use setTimeout as it might not always work (slow internet, more content (images) etc.)

Any help would be much appreciated.


function generate(data){
   // code

    let idx = 0 //just in case, so we don't have an infinite loop
    if (window.location.href.indexOf("#") !== -1) {
        const id = window.location.href.split("#")[1];

        const element = document.getElementById(id);
        document.addEventListener('DOMContentLoaded', function () {
            console.log("loaded");
            if (element) {
                while (!isInViewport(id)) {
                    idx = idx + 1;
                    console.log(isInViewport(id))
                    scrollToElement(id);
                    if (idx === 10000){
                        break;
                    }
                };
            }
        });
    }
}

generateContent();


function scrollToElement(id) {
    const element = document.getElementById(id);
    if (element) {
        element.scrollIntoView({
            behavior: 'smooth'
        });
    }
}

function isInViewport(id) {
    const element = document.getElementById(id);
    if (element) {
        const rect = element.getBoundingClientRect();
        return (
            rect.top >= 0 &&
            rect.left >= 0 &&
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
            rect.right <= (window.innerWidth || document.documentElement.clientWidth)
        );
    }
}

If I can provide any additional data, please let me know.


Solution

  • while it is not the most elegant solution it is working so far.

    If anyone can figure out something better, please let me know.

    Edit: Changed the code a little bit, removing the problem with programmatic scrolling even after user scrolls.

    function generateContent(data){
       // code
    
        if (window.location.href.indexOf("#") !== -1) {
            const id = window.location.href.split("#")[1];
            const element = document.getElementById(id);
            if (element) {
                scrollToLoop(id);
            }
        }
    }
    generateContent();
    
    let isUserScrolling = false;
    window.addEventListener("wheel", function () {
        isUserScrolling = true;
    });
    window.addEventListener("touchmove", function () {
        isUserScrolling = true;
    });
    function scrollToLoop(id, scrl) {
        let scroll = scrl;
        const element = document.getElementById(id);
    
        if (element) {
            if (!isInViewport(id) && scroll && !isUserScrolling) {
                scrollToElement(id);
                setTimeout(() => {
                    if (!isInViewport(id)) {
                        scrollToLoop(id, true);
                    }
                }, 500);
            } else {
                setTimeout(() => {
                    if (!isInViewport(id) && scroll && !isUserScrolling) {
                        scrollToLoop(id, true);
                    }
                }, 500);
            }
        }
    }