如何通过 div 数组迭代点击事件

How to iterate with click event through Array of divs

我希望文本部分在单击时出现和消失。

第一次点击前只能看到banner,还没有经文;第一次单击时出现第一节,第二次单击时第二节出现在第一节的位置,依此类推。

我试图通过隐藏元素来实现这一点,将它们放在一个数组中,并让它们在调用函数的次数符合诗句的索引时显示。

我是 JavaScript 的新手,不了解抛出的异常。如果我尝试在线检查我的代码,当 O 尝试通过单击网站调用该函数时,我会收到此异常:

Uncaught TypeError: Cannot read properties of null (reading 'style')

到目前为止,这是我的代码:

const text = document.querySelector(".banner")
document.addEventListener('click', myFunction);

const verse1 = document.querySelector(".verse1")
const verse2 = document.querySelector(".verse2")
const verse3 = document.querySelector(".verse3")
const verse4 = document.querySelector(".verse4")
const verse5 = document.querySelector(".verse5")
const verses = [verse1, verse2, verse3, verse4, verse5]
let versesLength = verses.length;

function myFunction() {
  for (let i = 0; i < versesLength; i++) {
    text.innerHTML = verses[i].style.display = 'block';
  }
}
<div class="banner">
  <script src="main.js"></script>
  <img src="files/SomeLogo.jpg" alt="We are still building on our Website:-)">
</div>

<div id="verses">
  <div class="verse1" style="display: none">Lorem Ipsum</div>
  <div class="verse2" style="display: none">Lorem Ipsum2</div>
  <div class="verse3" style="display: none">Lorem Ipsum3</div>
  <div class="verse4" style="display: none">Lorem Ipsum4</div>
  <div class="verse5" style="display: none">Lorem Ipsum5</div>
</div>

我被卡住了,在过去的几个小时里点击了类似的问题。在此先感谢您的帮助

这应该可以做到:

const verses = document.querySelectorAll('.verse');
const banner = document.querySelector('.banner');

const length = verses.length;
let counter = 0;

document.onclick = () => {
  if (counter === 0) banner.classList.add('hide');
  if (counter >= length) return;
  verses[counter].classList.add('show');
  if (verses[counter - 1]) verses[counter - 1].classList.remove('show');
  counter++;
};
body {
  background: orange;
}
.hide {
  display: none !important;
}

.show {
  display: block !important;
}
<html>
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" type="text/css" href="styles.css" />
  </head>
  <body>
    <div class="banner">
      <img
        src="files/SomeLogo.jpg"
        alt="We are still building on our Website:-)"
      />
    </div>
    <div id="verses">
      <div class="verse1 verse hide">Lorem Ipsum</div>
      <div class="verse2 verse hide">Lorem Ipsum2</div>
      <div class="verse3 verse hide">Lorem Ipsum3</div>
      <div class="verse4 verse hide">Lorem Ipsum4</div>
      <div class="verse5 verse hide">Lorem Ipsum5</div>
    </div>
    <script src="main.js"></script>
  </body>
</html>

无需更改 HTML 中的任何内容,您可以在 javascript

中执行类似的操作
const text = document.querySelector(".banner")
document.addEventListener('click', myFunction);

let verses = document.querySelector("#verses").children
let count = 0
function myFunction() {
  Array.from(verses).forEach(el=> el.style.display="none")
  if(count < verses.length){
    verses[count].style.display = 'block'
    count ++
    if(count===verses.length) count =0
  } 
}
  1. 您可以通过为所有经文元素提供相同的 class: verse 来消除对数组的需要。我们可以用 querySelectorAll.

    来抓取它们
  2. 在每节经文中添加 a data attribute 以识别它们。

  3. 为了限制全局变量的数量,我们可以使用闭包 - 在 addEventListener 中,我们调用初始化计数的 handleClick 函数,然后 returns 将分配给侦听器的功能。这是a closure。它维护着它返回时可以使用的外部词法环境(即变量)的副本。

// Cache the elements with the verse class
const banner = document.querySelector('.banner');
const verses = document.querySelectorAll('.verse');

// Call `handleClick` and assign the function it
// returns to the listener
document.addEventListener('click', handleClick());

function handleClick() {

  // Initialise `count`
  let count = 1;

  // Return a function that maintains a
  // copy of `count`
  return function () {

    // If the count is 5 or less
    if (count < verses.length + 1) {

      // Remove the banner
      if (count === 1) banner.remove();

      // Remove the previous verse
      if (count > 1) {
        const selector = `[data-id="${count - 1}"]`;
        const verse = document.querySelector(selector);
        verse.classList.remove('show');
      }

      // Get the new verse
      const selector = `[data-id="${count}"]`;
      const verse = document.querySelector(selector);

      // And show it
      verse.classList.add('show');

      // Increase the count
      ++count;

    }

  }

}
.verse { display: none; }
.show { display: block; margin-top: 1em; padding: 0.5em; border: 1px solid #787878; }
[data-id="1"] { background-color: #efefef; } 
[data-id="2"] { background-color: #dfdfdf; } 
[data-id="3"] { background-color: #cfcfcf; } 
[data-id="4"] { background-color: #bfbfbf; } 
[data-id="5"] { background-color: #afafaf; }
<div class="banner">
  <img src="https://dummyimage.com/400x75/404082/ffffff&text=We+are+still+building+our+website" alt="We are still building on our Website:-)">
</div>

<div>
  <div data-id="1" class="verse">Lorem Ipsum 1</div>
  <div data-id="2" class="verse">Lorem Ipsum 2</div>
  <div data-id="3" class="verse">Lorem Ipsum 3</div>
  <div data-id="4" class="verse">Lorem Ipsum 4</div>
  <div data-id="5" class="verse">Lorem Ipsum 5</div>
</div>

其他文档

修复错误

出现“无法读取 null 的属性”错误是因为您尝试访问 null 的属性。您的数组包含空值,因为您试图在浏览器将元素插入其 DOM.

之前获取这些元素

浏览器解析 HTML 的方式与您阅读它的方式相同:从左到右,从上到下。

如果浏览器遇到常规 <script> 元素,它会停止解析并首先执行 JavaScript。当然,某些元素可能在 DOM.

中尚不可用

有多种延迟脚本执行的方法:

  • attribute defer添加到<script>:将在DOM完全构建后执行。
  • 添加attribute type="module" to <script>: Similar to defer, but will also make your code be treated as a JS module. This will also make your code run in strict mode.
  • 使用 JS event DOMContentLoaded:类似于 defer,但封装在您的 JS-file.
  • 使用 JS event load:类似于 DOMContentLoaded,但会额外等待 所有 资源(例如图像、视频)加载完毕。 如果适用,首选DOMContentLoaded
  • <script> 移至 HTML 的底部:效果与 defer 相同。带有 defer 的脚本仍将在底部的脚本之后加载。

最简单的解决方案是使用 defer,因为这样您就不必更改 JS 代码:

<script src="main.js" defer></script>

顺便说一下:不要被 Whosebug 的代码片段所愚弄;使用 on-site 片段时,代码的 <script> 被移至 HTML!

的底部

特色!

可变生命周期

JS 中的变量仅在其上下文期间持续存在。 (Closures 会导致他们周围的范围在他们活着的时候一直存在!)

要使变量在调用函数时保持不变,必须在此函数外部声明它:

let someVariable = 0;
function aFunc() {
  // `someVariable` will persist across calls!
}

这意味着,必须在您的函数外部声明用于跟踪要显示的经文的变量。

显示和隐藏!

用next-to-show节的索引计算上一节的索引,我们只需要保留一个计数器。有两个计数器,如果我们不正确处理它们,它们可能会不同步。

let nextToShow = 0;
function showNextVerse() {
  const previousIndex = nextToShow - 1;
  // ...
  
  ++nextToShow; // Increase counter for next call
}

在我们的例子中,用户(或者更确切地说,他们的点击)将扮演循环的角色。它们偶尔会导致我们的点击处理程序(函数)运行,这时我们必须交换经文。

可以通过多种方式交换经文,但我们会坚持使用您的“内联样式”方式:(最终代码)

document.addEventListener("click", showNextVerse);

const banner = document.querySelector(".banner");
const verses = document.getElementById("verses").children; // More on this later

let nextToShow = 0;
function showNextVerse() {
  const previousIndex = nextToShow - 1;
  
  // On every call, hide the banner
  banner.style.display = "none"; // Use `.style` instead of `.innerHTML` to preserve its HTML!
  
  // Hide previous if valid index
  if (previousIndex >= 0 && previousIndex < verses.length) {
    verses[previousIndex].style.display = "none";
  }
  
  // Show next if valid index
  if (nextToShow >= 0 && nextToShow < verses.length) {
    verses[nextToShow].style.display = "block";
  }
  
  ++nextToShow;
}
<div class="banner">
  <script src="main.js" defer></script>
  <img alt="We are still building on our Website:-)">
</div>

<div id="verses">
  <div style="display:none">Lorem Ipsum1</div>
  <div style="display:none">Lorem Ipsum2</div>
  <div style="display:none">Lorem Ipsum3</div>
  <div style="display:none">Lorem Ipsum4</div>
  <div style="display:none">Lorem Ipsum5</div>
</div>

改进?!

不需要变量versesLength;您可以直接用 verses.length 替换它的每个出现。它不会改进原始名称,如果不与原始变量同步,它又是一个潜在的错误来源。

正确使用classid

目前,您的诗句使用 class 就好像它是 id;他们每个人都使用不同的class。这没有错,但在语义上我会为此目的使用 id

要有效地使用 class 属性,您应该为每一节指定 class verse。这样,您可以通过 JS 更轻松地 select 它们(请参阅下一节)。

更容易获取元素

与编码世界中的所有事物一样,一个问题有多种解决方案。您解决了以相当繁琐的方式获取元素的问题,但还有其他选择:(Non-exhaustive list)

您可能已经注意到我是如何获得所有经文的。事实上,verses(在最后的代码中)甚至没有引用一个数组,而是一个 HTMLCollection。它与数组非常相似,除了它会根据更改实时更新:

const elementsWrapper = document.getElementById("elements");

const collection = elementsWrapper.children;
const array = Array.from(elementsWrapper.children);

document.getElementById("bt-add").addEventListener("click", function() {
  const newElement = document.createElement("div");
  newElement.textContent = "Added later";
  elementsWrapper.appendChild(newElement);
});
document.getElementById("bt-log").addEventListener("click", function() {
  console.log("collection.length:", collection.length);
  console.log("array.length:", array.length);
});
<button id="bt-add">Add new element</button>
<button id="bt-log">Log <code>.length</code></button>

<p>
  Try logging first! Then try to add elements, and log again! See the difference?
</p>

<div>Elements:</div>
<div id="elements">
  <div>Initially existent</div>
  <div>Initially existent</div>
</div>

另一种隐藏方式

隐藏元素的方法如下:

对于小的样式更改,我也会使用内联样式。但是对于仅隐藏元素,我会使用 hidden 属性。

此外,还有多种CSS隐藏元素的方法:

  • 使用display: none:将隐藏元素,就好像它不存在一样。
  • 使用opacity: 0:将元素隐藏起来;它仍然占用 space,并且仍然应该是 accessibility tree 的一部分(自以为是)。
  • 使用 position: fixedtopleft 等属性移动它 off-site:(请不要。)
    将在视觉上移动元素 off-site。它仍将是可访问性树的一部分,并且仅适用于具有预期书写方向的语言(例如,它不适用于 right-to-left 语言)。
  • widthheightmarginpaddingborder 设置为 0:将仅在视觉上隐藏元素;它仍然是可访问性树的一部分,并且ill stop margin collapse. Screen-reader only classes 将此用于 non-visual 元素,非常有用。