Search code examples
javascriptsetintervalclearintervalintersection-observer

How to clearInterval() when element isNotIntersecting?


To help you understand my code, I have several <div class="sheet">, and each of them is 100% the size of the viewport. In each of these divs, there is a paragraph and each letter has a .char class. With my code, I can reproduce a typewriter effect on the text of a <div class="sheet"> as soon as it enters the viewport. However, this animation doesn't stop even when it leaves the viewport.

const storyChars = new SplitType('.storyline', { types: 'chars' });

const sheets = document.querySelectorAll('.sheet');

function lettersReveal(entries, observer) {

    entries.forEach((entry) => {
        let autoTyper;
        
        const sheetDiv = entry.target;
        const letters = sheetDiv.querySelectorAll('.char');

        let i = 0;

        console.log(entry.isIntersecting);

        function autoTyping() {
            if (i < letters.length) {
                letters[i].classList.add('fade')
                i++;
            }
        }

        if (entry.isIntersecting) {
            autoTyper = setInterval(autoTyping, 100);

        } else {
            clearInterval(autoTyper);
        }
    });
}

const options = {
    root: null,
    threshold: 0.25
};

const observer = new IntersectionObserver(lettersReveal, options);

sheets.forEach((sheet) => {
    observer.observe(sheet);
});

I want to stop the animation on the text of a <div class="sheet"> as soon as it is no longer visible in the viewport and restart the animation when the <div class="sheet"> is visible again.


Solution

  • Looks like a scope problem with the autoTyper variable.

    Try to declare it outside of the function instead.

    That means you will need to write your "sheets" loop in another way, so that there is an "autoTyper" variable for each loop instance.

    const storyChars = new SplitType('.storyline', { types: 'chars' });
    
    const sheets = document.querySelectorAll('.sheet');
    
    sheets.forEach((sheet) => {
    
        let autoTyper;
    
        const letters = sheet.querySelectorAll('.char');
    
        let i = 0;
    
        function autoTyping() {
            if (i < letters.length) {
                letters[i].classList.add('fade')
                i++;
            }
        }
    
        function lettersReveal(entries, observer) {
            entries.forEach((entry) => {
                if (entry.isIntersecting) {
                    autoTyper = setInterval(autoTyping, 100);
    
                } else {
                    if (autoTyper) clearInterval(autoTyper);
                    i = 0;
                }
            });
        }
    
        const options = {
            root: null,
            threshold: 0.25
        };
    
        const observer = new IntersectionObserver(lettersReveal, options);
    
        observer.observe(sheet);
    });
    

    I also moved out the autoTyping function, there was no need to have it created fresh each time the lettersReveal function ran.

    I haven't tested the code above. Hopefully it works as you wish now.