Как выполнить итерацию с событием click через массив div

Я хочу, чтобы текстовые части появлялись и исчезали при нажатии.

До первого клика видно только баннер, а стихов пока нет; при первом щелчке появляется первый стих, при втором щелчке вместо первого появляется второй куплет и так далее.

Я пытаюсь добиться этого, скрывая элементы, помещая их в массив и позволяя им отображаться, когда количество вызовов функции соответствует индексу стиха.

Я новичок в 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>

Я застрял и последние часы просматривал похожие вопросы. Заранее благодарю за любую помощь

Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Введение в технологический стек Twitch
Введение в технологический стек Twitch
В этой статье мы подробно рассмотрим стек Twitch, который подразделяется на следующий набор технологий:
8 полезных HTML-тегов, которые лучше использовать вместо <div>
8 полезных HTML-тегов, которые лучше использовать вместо <div>
Когда я только начинал изучать html, я использовал div для всего, это был один из первых тегов, которые я выучил, и казалось, что он работает в любой...
HTML5: API локального хранилища (Local Storage)
HTML5: API локального хранилища (Local Storage)
LocalStorage - это простой способ хранения данных в браузере пользователя.
Доступность HTML - программирование с инклюзивной перспективой
Доступность HTML - программирование с инклюзивной перспективой
Представьте, что вы хотите поехать на пляж. Представьте, что вы упорно трудились весь год и заслужили это. Прибыв на место, вы обнаруживаете, что...
Именование классов CSS: Конвенция именования BEM
Именование классов CSS: Конвенция именования BEM
Сопровождаемость кода, сама по себе, является пульсирующим эффектом нескольких факторов. Когда часть программного обеспечения читабельна, ясна,...
0
0
55
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Это должно сделать это:

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>

Ой! Большое вам спасибо, также добавление тега script в конец кода наконец-то заставило меня что-то сделать.

maybe_JPEG 10.04.2022 14:45

@ Энди, да, извини, я не совсем точно прочитал требования, соответственно обновил фрагмент JS. @maybe_JPEG Да, если вы хотите, чтобы ваш скрипт имел доступ к DOM, вам нужно разместить селекторы внутри DOMContentLoaded eventListener или вам нужно поместить тег <script> внизу тела!

Cesare Polonara 10.04.2022 15:06
Ответ принят как подходящий

ничего не меняя в 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
  } 
}

Можно ли сделать эту функцию частью innerHTML текстовой константы? Итак, блок баннера исчезает, а вместо него появляются текстовые элементы?

maybe_JPEG 10.04.2022 14:50

@maybe_JPEG Пожалуйста, добавьте это к своему вопросу, чтобы другие тоже могли показать вам свой путь.

Oskar Grosser 10.04.2022 14:52
  1. Вы можете устранить необходимость в массиве, присвоив всем элементам стиха один и тот же класс: verse. Мы можем захватить их с помощью querySelectorAll.

  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>

Дополнительная документация

Исправление ошибки

Ошибка «Не удается прочитать свойства null» возникает из-за того, что вы пытаетесь получить доступ к свойствам null. Ваш массив содержит нули, потому что вы пытались получить элементы до того, как браузер вставил их в свою модель DOM.

Браузер анализирует HTML так же, как вы его читаете: слева направо и сверху вниз.

Если браузер встречает обычный элемент <script>, он останавливает синтаксический анализ и сначала выполняет JavaScript. Естественно, некоторые элементы могут быть еще недоступны в DOM.

Существует несколько способов отложить выполнение скрипта:

  • Добавьте атрибут defer к <script>: будет выполняться после того, как DOM будет полностью построен.
  • Добавьте атрибут type="module" к <script>: аналогично defer, но ваш код также будет рассматриваться как JS-модуль. Это также заставит ваш код работать в строгий режим.
  • Используйте JS событие DOMContentLoaded: аналогично defer, но инкапсулировано в ваш JS-файл.
  • Используйте JS событие load: аналогично DOMContentLoaded, но дополнительно будет ждать загрузки ресурсов все (например, изображений, видео). Отдайте предпочтение DOMContentLoaded, если применимо.
  • Переместите <script> в конец HTML: фактически как defer. Скрипты с defer будут по-прежнему загружаться после скриптов внизу.

Самым простым решением было бы использовать defer, так как с ним вам не нужно было бы менять код JS:

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

Кстати: не дайте себя одурачить фрагментами StackOverflow; при использовании фрагментов на месте <script> для кода перемещается в нижнюю часть HTML!

Особенность!

Переменное время жизни

Переменные в JS сохраняются только на время своего контекста. (Закрытия заставит их окружающую область сохраняться до тех пор, пока они живут!)

Чтобы переменная сохранялась при вызовах функции, ее необходимо объявить вне этой функции:

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

Это означает, что переменная для отслеживания того, какой стих показывать, должна быть объявлена ​​вне вашей функции.

Покажи и спрячь!

Вычисляя индекс предыдущего стиха с индексом следующего стиха, нам нужно сохранить только один счетчик. С двумя счетчиками они могут рассинхронизироваться, если мы не обработаем их правильно.

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. Оно не улучшает исходное имя и является еще одним потенциальным источником ошибок, если оно не синхронизировано с исходной переменной.

Правильно используйте class и id

В настоящее время в ваших стихах используется 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: fixed и top, left и т. д.: (Пожалуйста, не делайте этого.)
    Визуально переместит элемент за пределы сайта. Он по-прежнему будет частью дерева специальных возможностей и будет работать только для языков с предполагаемым направлением письма (например, он не будет работать для языков справа налево).
  • Установка width, height, margin, padding и border на 0: элемент будет скрыт только визуально; он по-прежнему будет частью дерева специальных возможностей и остановит коллапс маржи. Классы только для чтения с экрана используйте это для невизуальных элементов, очень полезно.

Ваш раздел «Переменное время жизни» вводит в заблуждение. Замыкания устраняют необходимость в глобальной переменной.

Andy 10.04.2022 20:54

Другие вопросы по теме