javascriptnodelistintersection-observer

How to get IntersectionObserver to work with a nodeList of div IDs or array of div IDs?


I’m creating a site that has multiple “chapters” on the same HTML page.

At the top of each chapter are two skip buttons. The left skip button jumps to the previous chapter. The right skip button jumps to the next chapter.

I want the SkipButtons to get 0.5 or 1.0 opacity only when they are near the top of the viewport. (Otherwise they have opacities of 0.0.)

I figured out how to create an IntersectionObserver for each skipButtonId; but the code is super ugly and redundant.

Each element that an observer observes is the same element for which I want to change the className.

How can I rewrite the code so that there is only one observer?

Ideally, I would like to work from a NodeList of all skipButtonIds that have the className trailingArrowsClass.

Or, if there is a reason to do so, work from that NodeList converted to an array.

Or, if neither of those choices work, work from a manually created array of div IDs.

Nothing I've tried worked. Your expertise please!

Code for entire HTML page is pasted below.

A working demo page is at https://jerrymarlow.com/intersectionobserver.

If you view the code or demo page in a browser, the console will log: o NodeList o NodeList converted to array o Manually created array of skipButtonIds

Thank you! Jerry

<!doctype html>
<html>

<head>
    <meta charset="UTF-8">
    <title>How to get IntersectionObserver to work with a nodeList of div IDs or array of div IDs?</title>

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <style>
        /* This section of CSS is for styling only. It does not affect IntersectionObserver. */
        .containerDivClass {
            width: auto;
            margin: auto;
            background-color: lightgray;
        }

        .chapterContainerClass {
            background-color: white;
            margin: auto;
            max-width: 400px;
            padding: 0px;
            width: 100%;
            max-width: 400px;
            border-bottom: solid 3px rgba(230, 230, 230, 1.00);
            margin-bottom: 20px;
        }

        .chapterHeaderClass {
            box-sizing: border-box;
            background-color: #F8F8F8;
            background-color: white;
            margin: 0px;
            padding: 14px;
            border-top: thick;
            border-top-color: currentcolor;
            border-bottom: 5px #F0E008;
            border-bottom-color: rgb(240, 224, 8);
            border-bottom-style: none;
            border-bottom-style: solid;
            border-color: #990101;
        }

        h2 {
            color: #990101;
        }

        .storyTellerClass {
            padding: 14px;
        }

        .skipButtonsContainerClass {
            box-sizing: border-box;
            width: 100%;
            height: 35px;
            background-color: #990101;
        }


        .skipLeftButtonClass,
        .skipRightButtonClass {
            width: 105px;
            height: 35px;
            margin-top: 0px;
            margin-bottom: 0px;
            background-size: 105px 35px;
        }

        .skipLeftButtonClass,
        .skipLeftButtonClass:hover {
            float: left;
            margin-left: 0px;
        }

        .skipLeftButtonClass {
            background-image: url(https://jerrymarlow.com/images/skip_left.jpg)
        }

        .skipLeftButtonClass:hover {
            background-image: url(https://jerrymarlow.com/images/skip_left_hover.jpg)
        }

        .skipRightButtonClass,
        .skipRightButtonClass:hover {
            float: right;
            margin-right: 0px;
        }

        .skipRightButtonClass {
            background-image: url(https://jerrymarlow.com/images/skip_right.jpg)
        }

        .skipRightButtonClass:hover {
            background-image: url(https://jerrymarlow.com/images/skip_right_hover.jpg)
        }

        .spacerClass {
            height: 70px;
        }
    </style>
    <style>
        /* IntersectionObserver uses this section of CSS to change the opacity of the skip buttons.  */

        .skipButtonOpacity0 {
            opacity: 0;
            pointer-events: none;
        }

        .skipButtonOpacity50 {
            opacity: 0.5;
            pointer-events: auto;
        }

        .skipButtonOpacity100 {
            opacity: 1;
            pointer-events: auto;
        }
    </style>
</head>

<body>
    <main>
        <div id="containerDivID" class="containerDivClass">
            <div class="spacerClass"> </div>

            <div class="chapterContainerClass" id="AlohaChapterID">
                <div class="singleColumnContainerClass">

                    <div class="skipButtonsContainerClass">
                        <a href="#DingoChapterID">
                            <div id="AlohaLeftskipButtonId" class="skipLeftButtonClass trailingArrowClass skipButtonOpacity0">
                            </div>
                        </a>
                        <a href="#BravoChapterID">
                            <div id="AlohaRightskipButtonId" class="skipRightButtonClass trailingArrowClass skipButtonOpacity0">
                            </div>
                        </a>
                    </div>

                    <div class="chapterHeaderClass">
                        <h2>
                            Aloha Chapter
                        </h2>
                    </div>
                    <div class="storyTellerClass">
                        <p> Aloha, aloha, aloha. </p>
                        <p> Aloha, aloha, aloha. </p>
                        <p> Aloha, aloha, aloha. </p>
                        <p> Aloha, aloha, aloha. </p>
                        <p> Aloha, aloha, aloha. </p>
                        <p> Aloha, aloha, aloha. </p>
                        <p> Aloha, aloha, aloha. </p>
                        <p> Aloha, aloha, aloha. </p>
                    </div>
                </div>
            </div>

            <div class="chapterContainerClass" id="Bravo_left_you_upset_rattled_or_traumatized_think">
                <div class="singleColumnContainerClass">

                    <div class="skipButtonsContainerClass">
                        <a href="#AlohaChapterID">
                            <div id="BravoLeftskipButtonId" class="skipLeftButtonClass trailingArrowClass skipButtonOpacity0">
                            </div>
                        </a>
                        <a href="#CandyChapterID">
                            <div id="BravoRightskipButtonId" class="skipRightButtonClass trailingArrowClass skipButtonOpacity0">
                            </div>
                        </a>
                    </div>

                    <div class="chapterHeaderClass">
                        <h2>Bravo Chapter
                        </h2>
                    </div>
                    <div class="storyTellerClass">
                        <p> Bravo, bravo, bravo. </p>
                        <p> Bravo, bravo, bravo. </p>
                        <p> Bravo, bravo, bravo. </p>
                        <p> Bravo, bravo, bravo. </p>
                        <p> Bravo, bravo, bravo. </p>
                    </div>
                </div>
            </div>


            <div class="chapterContainerClass" id="CandyChapterID">
                <div class="singleColumnContainerClass">
                    <div class="skipButtonsContainerClass">
                        <a href="#Bravo_left_you_upset_rattled_or_traumatized_think">
                            <div id="CandyLeftskipButtonId" class="skipLeftButtonClass trailingArrowClass skipButtonOpacity0">
                            </div>
                        </a>
                        <a href="#DingoChapterID">
                            <div id="CandyRightskipButtonId" class="skipRightButtonClass trailingArrowClass skipButtonOpacity0">
                            </div>
                        </a>
                    </div>
                    <div class="chapterHeaderClass">
                        <h2> Candy Chapter </h2>
                    </div>
                    <div class="storyTellerClass">
                        <p> Candy, candy, candy. </p>
                        <p> Candy, candy, candy. </p>
                        <p> Candy, candy, candy. </p>
                        <p> Candy, candy, candy. </p>
                        <p> Candy, candy, candy. </p>
                        <p> Candy, candy, candy. </p>
                        <p> Candy, candy, candy. </p>
                    </div>
                </div>
            </div>

            <div class="chapterContainerClass" id="DingoChapterID">
                <div class="singleColumnContainerClass">
                    <div class="skipButtonsContainerClass">
                        <a href="#CandyChapterID">
                            <div id="DingoLeftskipButtonId" class="skipLeftButtonClass trailingArrowClass skipButtonOpacity0">
                            </div>
                        </a>
                        <a href="#AlohaChapterID">
                            <div id="DingoRightskipButtonId" class="skipRightButtonClass trailingArrowClass skipButtonOpacity0">
                            </div>
                        </a>
                    </div>
                    <div class="chapterHeaderClass">
                        <h2> Dingo Chapter</h2>
                    </div>
                    <div class="storyTellerClass">
                        <p> Dingo, dingo, dingo. </p>
                        <p> Dingo, dingo, dingo. </p>
                        <p> Dingo, dingo, dingo. </p>
                        <p> Dingo, dingo, dingo. </p>
                        <p> Dingo, dingo, dingo. </p>
                        <p> Dingo, dingo, dingo. </p>
                    </div>
                </div>
            </div>
            <div class="spacerClass"> </div>
            <div class="spacerClass"> </div>
            <div class="spacerClass"> </div>
            <div class="spacerClass"> </div>
            <div class="spacerClass"> </div>
            <div class="spacerClass"> </div>
            <div class="spacerClass"> </div>
            <div class="spacerClass"> </div>
        </div>
    </main>

    <script>
        var AlohaLeftskipButtonId;
        var AlohaRightskipButtonId;
        var BravoLeftskipButtonId;
        var BravoRightskipButtonId;
        var CandyLeftskipButtonId;
        var CandyRightskipButtonId;
        var DingoLeftskipButtonId;
        var DingoRightskipButtonId;


        var trailingArrowsNodeList;
        var trailingIDsArray;
        var skipButtonsIDsArray;

        window.addEventListener("load", function() {

            // This demo web page has four "chapters."
            // At the top of each chapter is a LeftSkipButton that jumps the reader back one chapter
            // and a RightSkipButton that jumps the reader ahead one chapter. 

            AlohaLeftskipButtonId = document.getElementById("AlohaLeftskipButtonId");
            AlohaRightskipButtonId = document.getElementById("AlohaRightskipButtonId");
            BravoLeftskipButtonId = document.getElementById("BravoLeftskipButtonId");
            BravoRightskipButtonId = document.getElementById("BravoRightskipButtonId");
            CandyLeftskipButtonId = document.getElementById("CandyLeftskipButtonId");
            CandyRightskipButtonId = document.getElementById("CandyRightskipButtonId");
            DingoLeftskipButtonId = document.getElementById("DingoLeftskipButtonId");
            DingoRightskipButtonId = document.getElementById("DingoRightskipButtonId");

            // I want the SkipButtons to get 0.5 or 1.0 opacity only when they are near the top of the viewport. 
            // (Otherwise they have opacities of 0.0.)


            // I figured out how to create an IntersectionObserver for each skipButtonId. 
            // But the code is super ugly and redundant. 
            // Each element that an observer observes is the same element for which I want to change the className.
            // How can I rewrite the code so that there is only one observer?

            const AlohaLeftskipButtonObserver = new IntersectionObserver(function(entries) {
                if (entries[0].intersectionRatio < 0.5) {
                    AlohaLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity0";
                }
                if (entries[0].intersectionRatio >= 0.5 &&
                    entries[0].intersectionRatio < 1.0) {
                    AlohaLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity50";
                }
                if (entries[0].intersectionRatio >= 1.0) {
                    AlohaLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity100";
                }
            }, {
                "threshold": [0, 0.5, 1.0],
                "rootMargin": "-50px 0px -67% 0px"
            });
            AlohaLeftskipButtonObserver.observe(AlohaLeftskipButtonId);


            const AlohaRightskipButtonObserver = new IntersectionObserver(function(entries) {
                if (entries[0].intersectionRatio < 0.5) {
                    AlohaRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity0";
                }
                if (entries[0].intersectionRatio >= 0.5 &&
                    entries[0].intersectionRatio < 1.0) {
                    AlohaRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity50";
                }
                if (entries[0].intersectionRatio >= 1.0) {
                    AlohaRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity100";
                }
            }, {
                "threshold": [0, 0.5, 1.0],
                "rootMargin": "-50px 0px -67% 0px"
            });
            AlohaRightskipButtonObserver.observe(AlohaRightskipButtonId);



            const BravoLeftskipButtonObserver = new IntersectionObserver(function(entries) {
                if (entries[0].intersectionRatio < 0.5) {
                    BravoLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity0";
                }
                if (entries[0].intersectionRatio >= 0.5 &&
                    entries[0].intersectionRatio < 1.0) {
                    BravoLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity50";
                }
                if (entries[0].intersectionRatio >= 1.0) {
                    BravoLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity100";
                }
            }, {
                "threshold": [0, 0.5, 1.0],
                "rootMargin": "-50px 0px -67% 0px"
            });
            BravoLeftskipButtonObserver.observe(BravoLeftskipButtonId);


            const BravoRightskipButtonObserver = new IntersectionObserver(function(entries) {
                if (entries[0].intersectionRatio < 0.5) {
                    BravoRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity0";
                }
                if (entries[0].intersectionRatio >= 0.5 &&
                    entries[0].intersectionRatio < 1.0) {
                    BravoRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity50";
                }
                if (entries[0].intersectionRatio >= 1.0) {
                    BravoRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity100";
                }
            }, {
                "threshold": [0, 0.5, 1.0],
                "rootMargin": "-50px 0px -67% 0px"
            });
            BravoRightskipButtonObserver.observe(BravoRightskipButtonId);



            const CandyLeftskipButtonObserver = new IntersectionObserver(function(entries) {
                if (entries[0].intersectionRatio < 0.5) {
                    CandyLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity0";
                }
                if (entries[0].intersectionRatio >= 0.5 &&
                    entries[0].intersectionRatio < 1.0) {
                    CandyLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity50";
                }
                if (entries[0].intersectionRatio >= 1.0) {
                    CandyLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity100";
                }
            }, {
                "threshold": [0, 0.5, 1.0],
                "rootMargin": "-50px 0px -67% 0px"
            });
            CandyLeftskipButtonObserver.observe(CandyLeftskipButtonId);


            const CandyRightskipButtonObserver = new IntersectionObserver(function(entries) {
                if (entries[0].intersectionRatio < 0.5) {
                    CandyRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity0";
                }
                if (entries[0].intersectionRatio >= 0.5 &&
                    entries[0].intersectionRatio < 1.0) {
                    CandyRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity50";
                }
                if (entries[0].intersectionRatio >= 1.0) {
                    CandyRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity100";
                }
            }, {
                "threshold": [0, 0.5, 1.0],
                "rootMargin": "-50px 0px -67% 0px"
            });
            CandyRightskipButtonObserver.observe(CandyRightskipButtonId);


            const DingoLeftskipButtonObserver = new IntersectionObserver(function(entries) {
                if (entries[0].intersectionRatio < 0.5) {
                    DingoLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity0";
                }
                if (entries[0].intersectionRatio >= 0.5 &&
                    entries[0].intersectionRatio < 1.0) {
                    DingoLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity50";
                }
                if (entries[0].intersectionRatio >= 1.0) {
                    DingoLeftskipButtonId.className = "skipLeftButtonClass skipButtonOpacity100";
                }
            }, {
                "threshold": [0, 0.5, 1.0],
                "rootMargin": "-50px 0px -67% 0px"
            });
            DingoLeftskipButtonObserver.observe(DingoLeftskipButtonId);


            const DingoRightskipButtonObserver = new IntersectionObserver(function(entries) {
                if (entries[0].intersectionRatio < 0.5) {
                    DingoRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity0";
                }
                if (entries[0].intersectionRatio >= 0.5 &&
                    entries[0].intersectionRatio < 1.0) {
                    DingoRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity50";
                }
                if (entries[0].intersectionRatio >= 1.0) {
                    DingoRightskipButtonId.className = "skipRightButtonClass skipButtonOpacity100";
                }
            }, {
                "threshold": [0, 0.5, 1.0],
                "rootMargin": "-50px 0px -67% 0px"
            });
            DingoRightskipButtonObserver.observe(DingoRightskipButtonId);


            
            // Ideally, I would like to work from a trailingArrowsNodeList:
            trailingArrowsNodeList = document.querySelectorAll(".trailingArrowClass");
            console.log("trailingArrowsNodeList " + trailingArrowsNodeList);

            for (var entry of trailingArrowsNodeList.entries()) {
                console.log(entry);
            }

            // Or, if there is a reason to do so, work from that NodeList converted to an array: 
            var trailingArrowsArray = Array.from(trailingArrowsNodeList);
            console.log("trailingArrowsArray " + trailingArrowsArray);

            for (var entry of trailingArrowsArray.entries()) {
                console.log(entry);
            }


            // Or, if neither of those choices work, work from a manually created array of div IDs: 
            const skipButtonsIDsArray = [AlohaLeftskipButtonId, AlohaRightskipButtonId, BravoLeftskipButtonId, BravoRightskipButtonId, CandyLeftskipButtonId, CandyRightskipButtonId, DingoLeftskipButtonId, DingoRightskipButtonId];
            console.log("skipButtonsIDsArray " + skipButtonsIDsArray);

            for (var entry of skipButtonsIDsArray.entries()) {
                console.log(entry);
            }
            
            // Nothing I've tried worked. Your expertise please!
            // Thank you. 
            // Jerry 
        });
    </script>
</body>
</html>

Solution

  • /* E[foo$="bar"]    
     an E element whose "foo" attribute value ends exactly with the string "bar" 
    Attribute selectors     3
    div[id$="skipButtonId"] */
    mybuttons = document.querySelectorAll("div[id$='skipButtonId']");
    const mybuttonObserver = new IntersectionObserver(function(entries) {
      console.log("Num entries= " + entries.length);
      entries.forEach((n) => {
        /* I want the SkipButtons to get 0.5 or 1.0 opacity 
           only when they are near the top of the viewport. 
           (Otherwise they have opacities of 0.0.) */
        if (n.intersectionRatio < 0.5) {
          n.target.classList.remove("skipButtonOpacity50", "skipButtonOpacity100");
          n.target.classList.add("skipButtonOpacity0");
          console.log("io = <0.5 - ", n.target.classList);
        }
        if (n.intersectionRatio >= 0.5 && n.intersectionRatio < 1.0) {
          n.target.classList.remove("skipButtonOpacity0", "skipButtonOpacity100");
          n.target.classList.add("skipButtonOpacity50");
          console.log("io = >=0.5  and <1.0", n.target.classList);
        }
        if (n.intersectionRatio >= 1.0) {
          n.target.classList.remove("skipButtonOpacity0", "skipButtonOpacity50");
          n.target.classList.add("skipButtonOpacity100");
          console.log("io = >=1 ", n.target.classList);
        }
      });
    }, {
      threshold: [0, 0.5, 1],
      rootMargin: "-50px 0px -67% 0px"
    });
    for (var e of mybuttons) {
      mybuttonObserver.observe(e);
    }