如何通过 div 数组使用 click 事件进行迭代

发布于 2025-01-20 09:18:27 字数 1716 浏览 3 评论 0原文

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

在第一次点击之前,您只能看到横幅,还没有经文。在第一个单击第一节时,出现第一节经文,第二次单击第二节出现第二节,以代替第一个经文,依此类推。

我试图通过隐藏元素,将它们放在数组中,并在称为函数的次数时将其显示为符合经文的索引时,将它们显示为实现。

我是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>

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

I want text parts to appear, and disappear, on click.

Before the first click you can only see the banner, and no verses yet; on the first click the first verse appears, on second click the second verse appears in place of the first, and so on.

I am trying to achieve this with hiding the elements, placing them in an array, and let them display when the number of times the function gets called fits the index of the verse.

I am new to JavaScript, and don't understand the exceptions thrown. If i try to inspect my code online, I get this Exception when O try to call the function by clicking on the website:

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

This is my code so far:

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>

I am stuck, and clicked through similar questions for the last hours. Thanks in advance for any help

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

忆伤 2025-01-27 09:18:27

没有更改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
  } 
}

without changing anything in the HTML, you can do something like this in 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
  } 
}
泪冰清 2025-01-27 09:18:27
  1. 您可以通过为所有 verse 元素指定相同的类来消除对数组的需求:verse。我们可以使用 querySelectorAll< 来获取它们/a>.


  2. 为每节经文添加数据属性来识别它们。

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

// 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>

其他文档

  1. You can remove the need for an array by giving all the verse elements the same class: verse. We can grab them with querySelectorAll.

  2. Add a data attribute to each verse to identify them.

  3. In order to limit the number of global variables we can use a closure - in the addEventListener we call the handleClick function which initialises the count, and then returns a function that will be assigned to the listener. This is a closure. It maintains a copy of its outer lexical environment (ie variables) that it can use when it's returned.

// 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>

Additional documentation

难得心□动 2025-01-27 09:18:27

这应该做到这一点:

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>

This should make it:

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>

仄言 2025-01-27 09:18:27

修复错误

由于您尝试访问 null 的属性,因此会出现“无法读取 null 的属性”错误。您的数组包含空值,因为您在浏览器将元素插入其 DOM 之前查询了这些元素。

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

如果浏览器遇到常规

有多种方法可以推迟脚本执行:

  • 添加 属性 defer
  • 添加 属性 type="module"
  • 使用 JS 事件 DOMContentLoaded:与 defer 类似,但封装在 JS 文件中。
  • 使用 JS 事件 load:与DOMContentLoaded类似,但还会等待所有资源(例如图像、视频)加载完毕。 首选 DOMContentLoaded(如果适用)。

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

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

顺便说一句:不要被 StackOverflow 代码片段所迷惑;使用现场片段时,代码的

特点!

变量的生命周期

JS 中的变量只有在使用时才会持续存在。

使用在函数外部声明的变量将创建一个闭包 围绕你的函数和变量。这意味着,只要使用该变量的函数存在,该变量就会持续存在:

let someVariable = 0;
function someFunction() {
  // `someVariable` is used, so it will persist across calls!
  return someVariable;
}

这意味着,要让用于“跟踪要显示的经文”的变量持续存在,必须在外部声明它你的职能。

显示和隐藏!

通过计算前一节的索引和下一个要显示的节的索引,我们只需要保留一个计数器。对于两个计数器,如果我们不正确处理它们,它们可能会不同步。

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 一样;他们各自使用不同的类。这并没有错,但从语义上讲,我会使用 id 来达到此目的。

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

更容易获取元素

与编码世界中的一切一样,问题有多种解决方案。您以相当乏味的方式解决了获取元素的问题,但还有其他选择:(非详尽列表)

你可能已经注意到我是如何获得所有诗句的。事实上,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:将通过使其不可见来隐藏该元素;它仍然占用空间,并且仍然应该是辅助功能树(有意见)。
  • 使用 position:fixedtopleft 等属性将其移至异地:(请勿这样做。)
    将从视觉上将元素移出现场。它仍然是可访问性树的一部分,并且仅适用于具有预期书写方向的语言(例如,它不适用于 从右到左语言)。
  • widthheightmarginpaddingborder 设置为 0:将隐藏该元素仅在视觉上;它仍然是可访问性树的一部分,并且将停止 margin崩溃仅屏幕阅读器类将此用于非视觉元素,非常有用。

Fixing the error

The "Cannot read properties of null" error occurs because you try to access the properties of null. Your array holds nulls because you queried for the elements before the browser has inserted them into its DOM.

The browser parses the HTML the same way you would read it: From left to right, and top to bottom.

If the browser encounters a regular <script> element, it halts parsing and first executes the JavaScript. Naturally, some elements may not yet be available in the DOM.

There are multiple ways to defer script execution:

  • Add attribute defer to <script>: Will execute once the DOM is fully built.
  • Add 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.
  • Use JS event DOMContentLoaded: Similar to defer, but encapsulated in your JS-file.
  • Use JS event load: Similar to DOMContentLoaded, but will additionally wait until all resources (e.g. images, videos) have loaded. Prefer DOMContentLoaded if applicable.
  • Move <script> to the bottom of the HTML: Effectively like defer. Scripts with defer will still load after scripts at the bottom.

The simplest solution would be to use defer, as with it you wouldn't have to change your JS code:

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

By the way: Don't be fooled by the StackOverflow snippets; when using the on-site snippets, the <script> for the code is moved to the bottom of the HTML!

The feature!

Variable lifetimes

Variables in JS only persist for as long as they are used.

Using a variable that is declared outside a function will create a closure around your function and variable. That means, that variable will persist for as long as the function exists that uses it:

let someVariable = 0;
function someFunction() {
  // `someVariable` is used, so it will persist across calls!
  return someVariable;
}

This means, to have the variable for "keeping track of what verse to show" persist, it has to be declared outside your function.

Show and hide!

By calculating the previous verse's index with the index of the next-to-show verse, we only have to keep one counter. With two counters, they might get out of sync if we don't handle them correctly.

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

In our case, the user (or rather, their clicks) will play the role of the loop. They will cause our click handler (the function) to run occasionally, at which point we have to swap the verses.

Swapping the verses can be done in many ways, but we'll stick to your "inline style" way: (Final code)

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>

Improvements?!

There is no need for the variable versesLength; you can directly replace each of its occurences with verses.length. It doesn't improve on the original name, and is one more potential source for bugs if not synchronized with the original variable.

Correctly use class and id

Currently, your verses use class as if it was id; they each use a different class. This is not wrong, but semantically I would use id for this purpose.

To use the class attribute effectively, you should give each verse the class verse. This way, you can select them more easily via JS (see next section).

Easier getting of elements

As with everything in the coding world, there are many solutions to a problem. You solved getting the elements in a rather tedious way, but there are alternatives: (Non-exhaustive list)

You may have already noticed how I get all the verses. In fact, verses (in the final code) doesn't even reference an array, but an HTMLCollection. It is very similar to an array, with the exception of it updating live to changes:

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>

Alternative way of hiding

Here are ways of hiding the elements:

For small style changes I too would use inline styling. But for only hiding elements I would use the hidden attribute.

Also, there are multiple CSS ways of hiding elements:

  • Using display: none: Will hide the element as if it doesn't exist.
  • Using opacity: 0: Will hide the element by making it invisible; it still takes up space, and should still be part of the accessibility tree (opinionated).
  • Moving it off-site with position: fixed and top, left, etc. properties: (Please don't.)
    Will move the element off-site, visually. It will still be part of the accessibility tree, and will only work for languages with the intended writing direction (e.g. it won't work for right-to-left languages).
  • Setting width, height, margin, padding and border to 0: Will hide the element only visually; it will still be part of the accessibility tree, and will stop margin collapse. Screen-reader only classes use this for non-visual elements, very useful.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文