首页 > 解决方案 > 如何在 JS 中创建一个函数来关闭具有不同内容的模态

问题描述

我有三个包含视频的模态。每个按钮都会打开相同的模式,但会根据您单击的按钮显示不同的视频。它工作正常,但我想在关闭模式时关闭声音。有一个名为 closeVideo() 的函数可以执行此操作,但它仅适用于第一个模态,而不适用于其他模态。有没有办法只创建一个函数来关闭每个模态的声音?


                    <div class="box">
                       <div class="piece">
                            <h2>Medcom</h2>
                            <div class="xbox">
                                <a class="ibutton trigger">
                                    <h3>Ver video</h3>
                                </a>
                            </div>
                        </div>
                    </div>
                    <div class="box">
                        <div class="piece">
                            <h2>Juan Valdez</h2>
                            <div class="xbox">
                                <a class="ibutton trigger">
                                    <h3>Ver video</h3>
                                </a>
                            </div>
                        </div>
                    </div>
                    <div class="box">
                        <div class="piece">
                            <h2>Grupo Epasa</h2>
                            <div class="xbox">
                                <a class="ibutton trigger">
                                    <h3>Ver video</h3>
                                </a>
                            </div>
                        </div>
                    </div>

<div class="modal">
  <div class="modal-content">
    <span class="btn-close" (click)='closeVideo()'>
        <img src="../../../../../assets/img/close.svg" alt="close">
    </span>
        <iframe id="player" width="560" height="315" src="https://www.youtube.com/embed/N8ABAZvh8WE" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
  </div>
</div>

<div class="modal">
  <div class="modal-content">
    <span class="btn-close" (click)='closeVideo()'>
        <img src="../../../../../assets/img/close.svg" alt="close">
    </span>
        <iframe id="player" width="560" height="315" src="https://www.youtube.com/embed/rn937OyA00g" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
  </div>
</div>

<div class="modal">
  <div class="modal-content">
    <span class="btn-close" (click)='closeVideo()'>
        <img src="../../../../../assets/img/close.svg" alt="close">
    </span>
        <iframe id="player" width="560" height="315" src="https://www.youtube.com/embed/EUjWFr3w7RU" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
  </div>
</div>


    /*    SHOW MODALS   */
    const triggers = document.getElementsByClassName('trigger');
    const triggerArray = Array.from(triggers).entries();
    const modals = document.getElementsByClassName('modal');
    const closeButton = document.getElementsByClassName('btn-close');

    const myPlayer = document.getElementById('player');

    for (let [index, trigger] of triggerArray) {
      let triggerIndex = index;
      function toggleModal() {
        modals[triggerIndex].classList.toggle('show-modal');
      }
      trigger.addEventListener("click", toggleModal);
      closeButton[triggerIndex].addEventListener("click", toggleModal);
    }

  closeVideo(){
    myPlayer.setAttribute("src", " ");
    console.log('video is closed now!');
  }

标签: javascripthtmlcss

解决方案


您遇到麻烦的原因是由于id所有<iframe>元素的重复;因为 anyid只能在 HTML 文档中使用一次,所以 JavaScript 只会查找具有给定 的元素id;因此在这种情况下,如果它找到具有该属性值的元素,id="player"它将不会寻找具有该id属性值的任何其他元素,因为不应该存在。

因此,一种解决方案是将 更改id为 a class,因此 JavaScript 将查找具有该类名的所有元素,然后您可以遍历这些元素并隐藏所有元素:

closeVideo(){
  let myPlayers = document.querySelectorAll('.player');

  // here we use NodeList.forEach() to iterate through the
  // NodeList returned by document.querySelectorAll(), and
  // use an Arrow function as the callback:
  myPlayers.forEach(
    // here 'player' is the current element of the NodeList
    // over which we're iterating; and we set the src of each
    // element of the NodeList in turn to an empty string:
    (player) => player.setAttribute('src','')
  );
  console.log('video is closed now!');
}

当然,这需要将 HTML 更新为以下内容:

<div class="modal">
  <div class="modal-content">
    <span class="btn-close" (click)='closeVideo()'>
        <img src="../../../../../assets/img/close.svg" alt="close">
    </span>
    <!-- note the change from id="player" to class="player"
         this is true of all elements, though for brevity I'm
         only showing one element -->
    <iframe class="player" width="560" height="315" src="https://www.youtube.com/embed/N8ABAZvh8WE" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
  </div>
</div>

但是,完成此操作后,您仍然拥有三个模态元素,每个元素中都有不同的视频。重要的是要记住,大多数编程的一般原则之一是“DRY”:不要重复自己。

<button>考虑到这一点,特别是考虑到元素本身的重复,使用元素来传递相关数据似乎更容易,利用自定义data-*属性,例如:

<!-- here we use the data-video-id attribute to hold the video's id,
     the data-platform-base attribute to hold the relevant base-URL
     of the platform, and we use the videoPlayer function itself to
     create the relevant src: -->
<button data-video-id="N8ABAZvh8WE" data-platform-base="https://www.youtube.com/embed/">Open: </button>

// here the event argument is passed in automatically by
// the later use of EventTarget.addEventListener(); we're using
// Arrow function syntax, since we have no need to use 'this'
// in the function:
const closeVideo = (event) => {

    // event.target is the element that originally triggered
    // the eventListener was listening for:
    let closeButton = event.target,
      // we use Element.closest() to find the first ancestor
      // element of the event.target that matches the supplied
      // CSS selector:
      modal = closeButton.closest('div.modal'),
      // from that modal element we use querySelector() to find
      // the first of any elements that match the supplied CSS
      // selector:
      player = modal.querySelector('iframe');


    // here we update the 'hidden' property of the modal element
    // to true in order to hide the element:
    modal.hidden = true;
    // we update - or rather remove - the player's src, by setting
    // that src to null (in order to stop the video and any sound
    // from playing):
    player.src = null;
  },

  // here we retrieve all the <button> elements that have a
  // data-video-id attribute and which have a data-platform-base
  // attribute:
  buttons = document.querySelectorAll('button[data-video-id][data-platform-base]'),
  // we find the modal element using document.querySelector() to search
  // the document for the first of any elements that match the supplied
  // CSS selector:
  modal = document.querySelector('div.modal');

// here we use querySelector() again to search within the modal
// element (the div.modal element found above) to find the first
// <span> element with the class of 'btn-close', and use
// EventTarget.addEventListener() to bind the closeVideo() function
// (note the deliberate lack of parentheses on the function name)
// as the event-handler for the 'click' event:
modal.querySelector('span.btn-close').addEventListener('click', closeVideo);

// we use NodeList.forEach() to iterate over the NodeList returned by
// document.querySelectorAll():
buttons.forEach(
  // using Arrow function syntax:
  // we again use EventTarget.addEventListener() to bind the
  // anonymous function as the event-handler for the 'click'
  // event:
  (button) => button.addEventListener('click', () => {

    // here we cache a reference to the button.dataset DOMStringMap
    // of custom data-* attributes and properties:
    const data = button.dataset,
      // here we create a url from the attribute-values held in the
      // data-platform-base and data-video-id properties (note that
      // these attribute-names are camel-cased as normal in JavaScript;
      // these properties are retrieved within the jQuery-like
      // ${...} and interpolated into the String:
      url = `${data.platformBase}${data.videoId}`,
      // we find the modal element:
      modal = document.querySelector('div.modal'),
      // we find the player element:
      player = modal.querySelector('iframe');

    // we update the src attribute of the player:
    player.src = url;
    // then we update the modal.hidden property to true, in order
    // to show that element:
    modal.hidden = false;
  })
);
*,
 ::before,
 ::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}


/*
  here we position the modal element centred on the page,
  and with a low z-index to keep it below the page content:
*/

.modal {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: -1000;
}


/*
  here we update the z-index to a high number, in order to lift
  it above the page-content; the selector here selects:
  - all elements with a class of 'modal',
  - which do not have the hidden attribute, using the
    attribute-selector ([hidden]) within CSS' negation-operator
    ( :not() )
*/

.modal:not([hidden]) {
  z-index: 1000;
}


/*
  here we show the video id in the <button> elements:
*/

button::after {
  content: attr(data-video-id);
}

span.btn-close {
  display: inline-block;
  cursor: pointer;
  border: 2px solid #aaa;
  width: 3ex;
  height: 3ex;
  text-align: center;
  line-height: 3ex;
}
<!-- Note the addition of the 'hidden' attribute, below: -->
<div class="modal" hidden>
  <div class="modal-content">
    <span class="btn-close">X</span>
    <iframe id="player" width="560" height="315" src="https://www.youtube.com/embed/N8ABAZvh8WE" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
  </div>
</div>

<button data-video-id="N8ABAZvh8WE" data-platform-base="https://www.youtube.com/embed/">Open: </button>
<button data-video-id="rn937OyA00g" data-platform-base="https://www.youtube.com/embed/">Open: </button>
<button data-video-id="EUjWFr3w7RU" data-platform-base="https://www.youtube.com/embed/">Open: </button>

JS 小提琴演示

[在] 情况下,我不想应用 'DRY' 概念并在我的 HTML 中保留几个模式,有没有办法使用该forEach()功能,并且仍然能够打开并查看其他视频?我的意思是,在使用类 'player' 迭代元素之后?因为我已经看到它隐藏了所有剩余的视频。只是好奇而已。

来自 OP 的评论,如下:如何创建一个函数来关闭 JS 中具有不同内容的模态

我不完全确定我在初读时理解了这个要求,我的第一个想法如下。这种方法模拟了第一种方法(上图),因为在任何时候都只有一个视频/“模态”可见。如果我误解了,请在下面的评论中澄清,我会尝试改进答案以满足您的需求。

// here we define a 'modals' Object that serves as a
// repository of functions and controls for the
// interactivity:
const modals = {
  // a 'buttons' Object that caches the relevant
  // control elements:
  buttons: {
    show: document.querySelectorAll('.trigger'),
    hide: document.querySelectorAll('.btn-close'),
  },

  // functions, which serve to provide the interactivity;
  // here we have an EventObject ('evt') passed to the
  // show() function (passed automatically by the later
  // use of EventTarget.addEventListener()):
  show: function(evt) {
    // EventObject.currentTarget returns the element to
    // which the event-listener was bound
    // (EventObject.target returns the element that
    // triggered the listened-for action, which may be
    // the element itself or a descendant of that element):
    let clicked = evt.currentTarget,

      // we retrieve the relevant index from the element's
      // dataset (we assign that value in later code) and
      // and cast it to an integer, with parseInt(), in
      // base-10 (hence the '10' as the second argument):
      i = parseInt(clicked.dataset.index, 10),

      // here we retrieve the elements with a class of
      // 'modal':
      modalBoxes = document.querySelectorAll('.modal');

    // iterating over those elements using
    // NodeList.prototype.forEach():
    modalBoxes.forEach(

      // we pass two arguments into the function, both
      // of which are available automatically; 'modal'
      // is a reference to the current Node of the
      // NodeList over which we're iterating, and 'index'
      // is the index of the current Node within the
      // NodeList itself.
      // in the function we're updating the modal.hidden
      // property, we want to show the modal if its index
      // matches; so if the i is not equal to index the
      // modal is hidden, otherwise the modal is shown by
      // as its index is equal to 'i' and therefore the
      // assessment is false, so hidden its hidden property
      // is false, therefore it's shown:
      (modal, index) => modal.hidden = i !== index
    );
  },
  hide: (evt) => {
    // here we're navigating from the element to which
    // the event-listener was bound up to the first
    // ancestor element with the class of 'modal':
    const m = evt.currentTarget.closest('.modal'),

          // from there we retrieve the <iframe> element:
          iframe = m.querySelector('iframe');

    // here we hide the modal:
    m.hidden = true;

    // here we update the src of the <iframe> to be equal
    // its own src; this causes a reload of the content
    // and as such stops it from playing:
    iframe.src = iframe.src;
  },

  // an initialisation function:
  init: function() {
    // here we're using destructuring to assign the
    // 'hide' and 'show' properties of the
    // this.buttons Object to variables of the same
    // name:
    const {
      show,
      hide
    } = this.buttons;

    // here we retrieve the NodeList of '.modal' elements
    // and then iterate over them:
    document.querySelectorAll('.modal').forEach(
      (modal) => {
        // we're initially hiding all '.modal' elements:
        modal.hidden = true;
      });

    // iterating over the 'show' elements:
    show.forEach(
      // passing in two arguments, 'btn' is the reference
      // to the current Node in the NodeList, and 'i' is
      // the current Node's index in that NodeList:
      (btn, i) => {

        // here we set the element's data-index attribute by
        // setting its dataset.index property to be equal to
        // the index of the current 'show' element:
        btn.dataset.index = i;

        // binding the function of the current modals Object
        // as the event-handling function of the 'click' event:
        btn.addEventListener('click', this.show);
      });

    // similar to the above, but binding the hide() function
    // of the modals Object as the event-handler for the
    // 'click' event on each of the 'hide' elements:
    hide.forEach(
      (btn) => {
        btn.addEventListener('click', this.hide);
      });
  },
};

// initialising the functionality:
modals.init();
*,
 ::before,
 ::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

main {
  width: 80%;
  margin: 0 auto;
}

header {
  display: flex;
  justify-content: space-between;
}

.box {
  text-align: center;
}
<main>
  <header>
    <div class="box">
      <div class="piece">
        <h2>Medcom</h2>
        <div class="box">
          <button class="ibutton trigger">
            <h3>Ver video</h3>
          </button>
        </div>
      </div>
    </div>
    <div class="box">
      <div class="piece">
        <h2>Juan Valdez</h2>
        <div class="xbox">
          <button class="ibutton trigger">
            <h3>Ver video</h3>
          </button>
        </div>
      </div>
    </div>
    <div class="box">
      <div class="piece">
        <h2>Grupo Epasa</h2>
        <div class="xbox">
          <button class="ibutton trigger">
            <h3>Ver video</h3>
          </button>
        </div>
      </div>
    </div>
  </header>

  <div class="videoContent">
    <div class="modal">
      <div class="modal-content">
        <span class="btn-close">
          <img src="../../../../../assets/img/close.svg" alt="close">
        </span>
        <iframe class="player" width="560" height="315" src="https://www.youtube.com/embed/N8ABAZvh8WE" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
      </div>
    </div>
    <div class="modal">
      <div class="modal-content">
        <span class="btn-close">
          <img src="../../../../../assets/img/close.svg" alt="close">
        </span>
        <iframe class="player" width="560" height="315" src="https://www.youtube.com/embed/rn937OyA00g" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
      </div>
    </div>

    <div class="modal">
      <div class="modal-content">
        <span class="btn-close">
          <img src="../../../../../assets/img/close.svg" alt="close">
        </span>
        <iframe class="player" width="560" height="315" src="https://www.youtube.com/embed/EUjWFr3w7RU" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
      </div>
    </div>
  </div>
</main>

JS 小提琴演示

参考:


推荐阅读