Search code examples
javascriptvimeovimeo-apivimeo-player

Multiple Vimeo video in multiple modals


I have several popup modals each with it's own video in autoplay.

Each popups work and close correctly and video autoplays but when I click on the second one, the first one autoplays in the background and the second one doesn't autoplay.

Not sure what I am doing wrong. I have been working on it at:Codepen example

// Get the button that opens the modal
var btn = document.querySelectorAll("a.modal-button");

// All page modals
var modals = document.querySelectorAll(".modal");

// Get the <span> element that closes the modal
var spans = document.querySelectorAll(".close");


// When the user clicks the button, open the modal
let iframe = document.querySelector('iframe');
let player = new Vimeo.Player(iframe);

for (var i = 0; i < btn.length; i++) {
  btn[i].onclick = function (e) {
    e.preventDefault();
    modal = document.querySelector(e.target.getAttribute("href"));
    modal.style.display = "block";
        player.play();
  };
}

// When the user clicks on <span> (x), close the modal
for (var i = 0; i < spans.length; i++) {
  spans[i].onclick = function () {
    for (var index in modals) {
      if (typeof modals[index].style !== "undefined")
        modals[index].style.display = "none";
        player.pause();     
    }
  };
}

// When the user clicks anywhere outside of the modal, close it
window.onclick = function (event) {
  if (event.target.classList.contains("modal")) {
    for (var index in modals) {
      if (typeof modals[index].style !== "undefined")
        modals[index].style.display = "none";
                player.pause();
    }
  }
};
.modal {
    display: none;
    padding-top: 100px;
    overflow: auto;
    background-color: rgb(0, 0, 0);
    background-color: rgba(0, 0, 0, 0.4);
    margin-bottom: auto;
    padding: 40px;
    border-radius: 8px;
    position: fixed;
    z-index: 9999999;
}
.modal-content {
    background-color: #fff;
    padding: 1rem;
    border: 1px solid #333;
    padding: 40px;
    border-radius: 8px;
    display: flex;
        align-items: center;
        justify-content: center;
        margin: auto;
}

.close {
    position: absolute;
    right: 0;
    top: 0;
    transform: rotate(-45deg);
    font-size: 28px;
    font-weight: bold;
}

iframe {
      width: 100%;
    height: 100%;
}
<script src="https://player.vimeo.com/api/player.js"></script>

<div class="modal" id="modal1">
    <div class="modal-content">
  <iframe src="https://player.vimeo.com/video/1053959997?title=0&amp;byline=0&amp;portrait=0&amp;badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" width="1080" height="1620" frameborder="0" allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media" title="long - dynamic content"></iframe>
  </div>
</div>

<div class="modal" id="modal2">
  <div class="modal-content">
  <iframe src="https://player.vimeo.com/video/1054046699?title=0&amp;byline=0&amp;portrait=0&amp;badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" width="1080" height="1620" frameborder="0" allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media" title="long - dynamic content"></iframe>
  </div>
</div>

<a href="#modal1" class="modal-button">modal 1</a>

<a href="#modal2" class="modal-button">modal 2</a>


Solution

  • SO Snippets block <iframe>s so of course the example does not function properly. In order to review a functioning example you can copy and paste it into a text file and change the extension from .txt to .html. A copy of the example is also provided at CodePen as well.

    A HTMLDialogElement is used instead of a <div> because the "close" event is used as one of the methods to control the Vimeo player. It's also semantically appropriate and simple to use. The example does a lot more than what is actually needed so I will only address the code that should resolve your problem.

    • By default the Vimeo player has a autopause feature enabled. autopause will allow only one player to play at a time.

    • When closing a modal the player continues to play. In the example below the player is paused when the "close" event is fired on the <dialog>. Since your modal has no special events to hook into, try pausing the player when the user clicks outside of the modal or clicks the close button. Do the following:

      /**
       * First collect all player objects into an array.
       * Which you've already done which is great.
       */
      let players = iframesArray.map(iframe => new Vimeo.Player(iframe))
      
      /**
       * Next, define a function that'll iterate through the 
       * players array and pause each one.
       */
      const pauseAll = () => {
        players.forEach((p) => {
          p.pause()
           .then()
           .catch()
        });
      };
      
      /**
       * Finally, add the function to the code block responsible 
       * for the close button and to the code block that closes
       * the modal when the user clicks outside of it.
       */
       for (var i = 0; i < spans.length; i++) {
         spans[i].onclick = function () {
           pauseAll()
           ....
      

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Vimeo Playlist Modal</title>
      <meta name="description" content="155 chars">
      <style>
        *,
        *::before,
        *::after {
          box-sizing: border-box;
        }
    
        :root {
          font: 500 5vmin/1.5 "Segoe UI";
        }
    
        body {
          overflow: scroll;
        }
    
        dialog {
          padding: 0;
          border: 0;
          border-radius: 5px;
          background: transparent;
          box-shadow: 0 10px 6px -6px #777;
          -ms-overflow-style: none;
          scrollbar-width: none;
        }
    
        dialog::backdrop {
          background: rgba(50, 50, 50, 0.3);
        }
    
        dialog::-webkit-scrollbar {
          display: none;
        }
    
        #ui {
          padding: 0;
          border: 1.5px solid #bbb;
          border-radius: 5px;
          background: #eee;
        }
    
        .btn {
          display: inline-flex;
          justify-content: center;
          align-items: center;
          padding: 0;
          border: 1px ridge #ddd;
          border-radius: 5px;
          font: inherit;
          font-size: 2rem;
          line-height: normal;
          background: transparent;
          cursor: pointer;
          box-shadow: 0 6px 4px -4px #bbb;
        }
    
        .btn:hover {
          color: #0a87a1;
          box-shadow: 0 6px 8px -4px #999;
        }
    
        .btn:active {
          color: #0a87a1;
          transform: scale(0.95);
        }
    
        .content {
          display: flex;
          flex-flow: column nowrap;
          justify-content: center;
          align-items: center;
          width: 100vh;
          padding: 0 0.5rem;
          border: 0;
          background: #eee;
        }
    
        .content legend {
          width: 100%;
        }
    
        .content legend .btn {
          float: right;
          height: 1.5rem;
          margin: 0.25rem -0.25rem 0.25rem 0;
          padding-bottom: 0.45rem;
          line-height: 0;
          color: #888;
        }
    
        .control {
          display: flex;
          justify-content: center;
          align-items: center;
          margin: 0 0 0.25rem;
          padding: 0;
          border: 0;
        }
    
        #prev,
        #next {
          width: 2rem;
          height: 2rem;
          padding: 0;
          border: 0;
          line-height: 1;
          background: #eee;
        }
    
        #counter {
          padding: 0.75rem 1rem 0;
          font-size: 1.25rem;
          font-family: Consolas;
          color: #0a87a1;
        }
    
        #video {
          width: 100%;
          margin: 0;
          padding: 0;
          border: 0;
        }
    
        .playlist a {
          position: relative;
          display: list-item;
          width: max-content;
          color: #18272f;
          text-decoration: none;
        }
    
        .playlist a+a {
          margin-top: 0.5rem;
        }
    
        .playlist a::before {
          content: "";
          position: absolute;
          bottom: -0.25rem;
          left: 0;
          width: 100%;
          height: 0.1rem;
          border-radius: 4px;
          background: #0a87a1;
          transform-origin: right;
          transform: scaleX(0);
          transition: transform 0.3s ease-in-out;
        }
    
        .playlist a::marker {
          content: "\0000bb\002009";
          display: inline-block;
          font-size: 1.4rem;
          color: #0a87a1;
        }
    
        .playlist a:hover::before {
          color: #0a87a1;
          transform-origin: left;
          transform: scaleX(1);
        }
    
        .playlist a b {
          font-weight: 600;
          font-size: 1.1rem;
          font-variant: small-caps;
        }
    
        .hidden {
          display: none;
        }
      </style>
    </head>
    
    <body>
    
      <button id="start">Double Click</button>
    
      <dialog>
        <form id="ui" method="dialog">
          <fieldset class="content">
            <legend>
              <input class="btn" type="submit" value="⨯">
            </legend>
            <fieldset id="video"></fieldset>
            <fieldset class="control">
              <input id="prev" class="btn" type="button" value="⏮">
              <output id="counter"></output>
              <input id="next" class="btn" type="button" value="⏭">
            </fieldset>
          </fieldset>
        </form>
      </dialog>
    
      <menu class="playlist"></menu>
    
      <script src="https://player.vimeo.com/api/player.js"></script>
      <script>
        let idx = 0;
        let players = [],
          links = [],
          media,
          iframes;
    
        const vIDs = ["148551759", "64142541", "108977978"];
    
        const ui = document.forms.ui;
        const io = ui.elements;
        const prev = io.prev;
        const next = io.next;
        const count = io.counter;
        const vid = io.video;
    
        const modal = document.querySelector("dialog");
        const list = document.querySelector(".playlist");
    
        const setVideos = (vIDs) => {
          vIDs.forEach((id) => {
            const obj = document.createElement("object");
            obj.name = "media";
            obj.className = "hidden";
            obj.dataset.vimeoId = id;
            obj.dataset.vimeoWidth = "720";
            vid.append(obj);
            players.push(new Vimeo.Player(obj));
          });
        };
    
        const smallCaps = (str) => {
          if (str.includes(" - ")) {
            return str.split(/(\s-\s)/)
              .map((w, i) => {
                return i === 0 ? `<b>${w}</b>` : w;
              })
              .join("");
          } else {
            return str.split(" ")
              .map((w) => {
                return /[A-Z]{2,}/.test(w) ?
                  `<b>${w[0] + w.slice(1).toLowerCase()}</b>` : w;
              })
              .join(" ");
          }
        };
    
        const getTitles = () => {
          iframes = [...document.querySelectorAll("iframe")];
          media = Array.from(io.media);
          return iframes.map((f) => smallCaps(f.title));
        };
    
        const setLinks = (titles) => {
          titles.forEach((t) => {
            const link = document.createElement("a");
            link.href = "#";
            list.append(link);
            link.insertAdjacentHTML("beforeend", t);
            links.push(link);
          });
        };
    
        const pauseAll = () => {
          players.forEach((p) => p.pause()
            .then()
            .catch());
        };
    
        const switchMedia = (idx) => {
          media.forEach((m) => m.classList.add("hidden"));
          pauseAll();
          media[idx].classList.remove("hidden");
          count.value = idx + 1;
        };
    
        const clickList = (e) => {
          const clk = e.target;
          if (clk.matches("a")) {
            idx = links.indexOf(clk);
            switchMedia(idx);
            modal.showModal();
          }
        };
    
        list.onclick = clickList;
    
        const reverse = (e) => {
          idx--;
          idx = idx < 0 ? iframes.length - 1 : idx;
          switchMedia(idx);
        };
    
        const forward = (e) => {
          idx++;
          idx = idx > iframes.length - 1 ? 0 : idx;
          switchMedia(idx);
        };
    
        prev.onclick = reverse;
        next.onclick = forward;
    
        modal.onclick = (e) => e.currentTarget.close();
        ui.onclick = (e) => e.stopPropagation();
    
        modal.onclose = (e) => pauseAll();
    
    
        document.getElementById("start").onclick = init;
    
        function init(e) {
          setVideos(vIDs);
          setLinks(getTitles());
          setTimeout(() => this.className = "hidden", 3000);
        }
      </script>
    </body>
    
    </html>