You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EventListener加载异常:第三个click事件监听器间歇性失效问题

问题

我在JavaScript中编写了三个click事件监听器,前两个(对应ID为cs-detail-toggle和tc-detail-toggle的元素)始终正常工作,但最后一个(对应ID为cc-detail-toggle的元素)仅间歇性生效。有时浏览器加载网站后,可在开发者工具中看到该事件监听器,有时则无法找到。三个监听器的功能一致:将对应div的CSS display属性在none和block之间切换。

相关代码如下:

JavaScript代码

/** CSS Cheat Sheet Detail Toggle */
const csToggle = document.getElementById('cs-detail-toggle')
const csDetail = document.getElementById('cs-details')

function toggleCsDisplay() {
    if (csDetail.style.display === 'none') {
        csDetail.style.display = 'block'
    } else {
        csDetail.style.display = 'none'
    }
}
csToggle.addEventListener('click', toggleCsDisplay);


/** Tea Cozy Detail Toggle **/
const tcToggle = document.getElementById('tc-detail-toggle')
const tcDetail = document.getElementById('tc-details')

function toggleTcDisplay() {
    if (tcDetail.style.display === 'none') {
        tcDetail.style.display = 'block'
    } else {
        tcDetail.style.display = 'none'
    }
}

tcToggle.addEventListener('click', toggleTcDisplay);


/** Credit Card Checker Detail Toggle **/

const ccToggle = document.getElementById('cc-detail-toggle')
const ccDetail = document.getElementById('cc-details')

function toggleCcDisplay() {
    if (ccDetail.style.display === 'none') {
        ccDetail.style.display = 'block'
    } else {
        ccDetail.style.display = 'none'
    }
}

ccToggle.addEventListener('click', toggleCcDisplay);

HTML代码

<section class="projects">
  <h2>Projects</h2>
  <div class="projects-container">
    <div class="project-card">
      <h3>CSS Positioning Cheat Sheet</h3>
      <img
        src="./assets/css-cheat-sq.png"
        alt="screenshot preview of css positioning cheat sheet project"
      />
      <p id="cs-detail-toggle">+ Details and Links</p>
      <div id="cs-details" class="details">
        <p>Quick reference guide for the 5 main CSS positioning types</p>
        <p>Made as part of Codecademy Web Development Foundations Course</p>
        <p>Tech used - HTML 5 and CSS3</p>
        <a href="https://github.com/YSquid/positioning-cheat-sheet" target="_blank"><i class="fa-brands fa-github"></i
          >GitHub Repository</a>
        <a href="https://codepen.io/ysquid/pen/NWMXxwp" target="_blank"><i class="fa-brands fa-codepen"></i>View in CodePen</a>
      </div>
    </div>

    <div class="project-card">
      <h3>Tea Cozy Landing Page</h3>
      <img src="./assets/tea-cozy-sq.png" alt="screenshot preview of tea cozt landing page project">
      <p id="tc-detail-toggle">+ Details and Links</p>
      <div id="tc-details" class="details">
        <p>Landing page for a ficitonal business</p>
        <p>Created according to provided website spec</p>
        <p>Made as part of Codecademy Web Development Foundations Course</p>
        <p>Tech used - HTML5 and CSS3</p>
        <a href="https://github.com/YSquid/tea-cozy" target="_blank"><i class='fa-brands fa-github'></i>GitHub Repository</a>
      </div>
    </div>

    <div class="project-card">
      <h3>Credit Card Number Validator</h3>
      <img src="./assets/card-checker-sq.png" alt="image of credit card with number highlighted and first step of luhn alorithm shown">
      <p id="cc-detail-toggle">+ Details and Links</p>
      <div id="cc-details" class="details">
        <p>Program for checking credit cards</p>
        <p>Can accept array of credit card numbers, and find the invalid ones</p>
        <p>Takes list of invalid cards, and prints array of the credit card companies whose cards are in the invalid array</p>
        <p>Tech used - Javascript, NodeJS for testing</p>
        <a href="https://github.com/YSquid/credit-card-checker" target="_blank"><i class='fa-brands fa-github'></i>GitHub Repository</a>
        <a href="https://codepen.io/ysquid/pen/gOzxWNN" target="_blank"><i class="fa-brands fa-codepen"></i>View in CodePen</a>
      </div>
    </div>
  </div>
</section>

解决方案

核心原因

间歇性失效的本质是脚本执行时机与DOM加载顺序不匹配:如果JS代码在cc-detail-toggle元素尚未渲染到DOM时就运行,document.getElementById('cc-detail-toggle')会返回null,后续调用addEventListener会抛出错误,导致监听器无法绑定。前两个元素偶尔能生效是因为DOM渲染顺序刚好先处理到它们,但第三个元素的渲染时机不稳定,导致问题显现。

修复步骤

1. 确保脚本在DOM加载完成后执行

有两种可靠方式:

  • 方式一:将脚本放在HTML末尾
    把JS代码移到</body>标签之前,确保所有DOM元素都已加载完成再执行脚本:
<!-- 其他HTML内容 -->
  <script>
    <!-- 这里放你的JS代码 -->
  </script>
</body>
  • 方式二:用DOMContentLoaded事件包裹代码
    如果脚本必须放在页面头部,用DOMContentLoaded事件监听DOM加载完成的时机,再初始化监听器:
document.addEventListener('DOMContentLoaded', function() {
  // CSS Cheat Sheet Toggle
  const csToggle = document.getElementById('cs-detail-toggle')
  const csDetail = document.getElementById('cs-details')
  function toggleCsDisplay() {
      csDetail.style.display = csDetail.style.display === 'none' ? 'block' : 'none';
  }
  csToggle.addEventListener('click', toggleCsDisplay);

  // Tea Cozy Toggle
  const tcToggle = document.getElementById('tc-detail-toggle')
  const tcDetail = document.getElementById('tc-details')
  function toggleTcDisplay() {
      tcDetail.style.display = tcDetail.style.display === 'none' ? 'block' : 'none';
  }
  tcToggle.addEventListener('click', toggleTcDisplay);

  // Credit Card Checker Toggle
  const ccToggle = document.getElementById('cc-detail-toggle')
  const ccDetail = document.getElementById('cc-details')
  function toggleCcDisplay() {
      ccDetail.style.display = ccDetail.style.display === 'none' ? 'block' : 'none';
  }
  ccToggle.addEventListener('click', toggleCcDisplay);
});

2. 优化代码(可选但推荐)

重复的代码不仅冗余,还容易引入错误。可以封装一个通用的切换函数:

function initDetailToggle(toggleId, detailId) {
  const toggle = document.getElementById(toggleId);
  const detail = document.getElementById(detailId);
  if (!toggle || !detail) return; // 避免元素不存在时报错
  toggle.addEventListener('click', () => {
    detail.style.display = detail.style.display === 'none' ? 'block' : 'none';
  });
}

// 初始化所有切换器
document.addEventListener('DOMContentLoaded', function() {
  initDetailToggle('cs-detail-toggle', 'cs-details');
  initDetailToggle('tc-detail-toggle', 'tc-details');
  initDetailToggle('cc-detail-toggle', 'cc-details');
});

3. 更可靠的显示切换方式(推荐)

如果details元素的初始display是通过CSS类设置的(而非内联样式),element.style.display会返回空字符串,导致第一次点击逻辑出错。可以改用类切换的方式,更符合CSS与JS分离的原则:

首先在CSS中定义隐藏类:

.details.hidden {
  display: none;
}

然后修改JS代码:

function initDetailToggle(toggleId, detailId) {
  const toggle = document.getElementById(toggleId);
  const detail = document.getElementById(detailId);
  if (!toggle || !detail) return;
  toggle.addEventListener('click', () => {
    detail.classList.toggle('hidden');
  });
}

这种方式不受初始样式设置方式的影响,稳定性更高。

内容的提问来源于stack exchange,提问作者YSquid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 05:30:42