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

动态生成商品卡片点击返回错误ID且事件仅绑定首个元素的问题求助

动态生成商品卡片点击返回错误ID且事件仅绑定首个元素的问题求助

大家好,我现在遇到了一个动态生成商品卡片的问题,想请大家帮忙看看:

我用JavaScript动态生成了商品卡片,但是点击卡片的时候出现了两个问题:

  • 点击第一个商品时,获取到的是最后一个商品的ID,跳转到详情页显示的是最后一个商品的信息
  • 事件监听器只对第一个元素生效,其他商品点击没有反应

我先把相关的核心代码贴出来,麻烦大家帮我找找问题所在:

我的product-list.js代码

let cardData = [
    {
        id: 1,
        imgFile: 'images/products/marievi5.jpg',
        heading: 'Marievi the Influencer',
        body: 'this is card body',
        price: 150,
    },
    {
        id: 2,
        imgFile: 'images/products/rock1.jpg',
        heading: 'card 2',
        body: 'this is card body',
        price: 200,
    },
    {
        id: 3,
        imgFile: 'images/products/marievi5.jpg',
        heading: 'card 3',
        body: 'this is card body',
        price: 400,
    },
    {
        id: 4,
        imgFile: 'images/products/marievi5.jpg',
        heading: 'card 4',
        body: 'this is card body',
        price: 300,
    },
];

const createCard = () => {
    
    let cardContainer = document.querySelector('.card-container');
    cardContainer.innerHTML = ''; // Clear the grid first

    cardData.forEach((data) => {
        //create product card
        let card = document.createElement('div');
        card.classList.add('card-body');
        card.setAttribute('data-product-id', data.id);

        // Add content inside the product card
        let content = `<div class="card-container  py-5">
        
             <div class="row  ">
                <div class="col">
                <div class="listProduct">
        
                    <div class="card-body product-card" data-product-id="${data.id}">
                        <img class="card-img-top " id="productImg" src="${data.imgFile}" alt="Card image cap">

                            <div class=" product-details" id="details-${data.id}">
                                <h5 class="card-title mb-3 fw-bold ">${data.heading}</h5>
                                <p class="card-text text-muted mb-4">${data.body}</p>
                                <div class="d-flex justify-content-between align-items-center">
                                <span class="product-price">Price:${data.price}</span>
                            </div>
                            <button class="btn btn-custom text-white px-4 py-2 rounded-pill" data-product-id="${data.id}"btn btn-primary">View Details</button>
                    </div>
                    </div>
                </div>
                
                </div>
                </div>
                 
        </div>`;

        cardContainer.innerHTML += content;
       
        document.querySelector('.listProduct').addEventListener('click',function(){
            let productId = data.id;  // This should not be undefined
            console.log(productId);
            window.location.href = `ProductDetails1.html?id=${productId}`;
        });
       
        return card;
    })
}

// Call the function to generate products on page load
createCard(cardData);

问题分析与解决方案

我帮你看了下代码,问题主要出在事件绑定和DOM操作的方式上,具体有这几个点:

1. 事件绑定的双重错误:重复绑定+作用域陷阱

你在forEach循环里每次都执行document.querySelector('.listProduct').addEventListener('click', ...),但querySelector只会返回页面中第一个匹配的元素,所以每次循环都是给同一个.listProduct元素重复绑定点击事件,最后一次循环的绑定会覆盖之前所有的。再加上普通函数的作用域问题,最终触发事件时data.id只会引用最后一次循环的商品ID。

2. DOM拼接的问题:innerHTML += 会丢失之前的事件

你用cardContainer.innerHTML += content的方式拼接DOM,每次都会重新渲染整个容器的内容,之前绑定的事件都会失效,而且你循环里创建的card变量根本没用到,等于白忙活了一场。

修复方案

这里给你两种修改思路,更推荐第二种事件委托的方式,性能更好也更简洁:

方案一:给每个卡片单独绑定事件(修改循环内的DOM操作)
const createCard = () => {
    let cardContainer = document.querySelector('.card-container');
    cardContainer.innerHTML = ''; // Clear the grid first

    cardData.forEach((data) => {
        // 直接创建整个卡片的DOM元素,避免innerHTML拼接的问题
        let cardWrapper = document.createElement('div');
        cardWrapper.className = 'card-container py-5';
        cardWrapper.innerHTML = `
             <div class="row">
                <div class="col">
                <div class="listProduct">
                    <div class="card-body product-card" data-product-id="${data.id}">
                        <img class="card-img-top" src="${data.imgFile}" alt="Card image cap">
                        <div class="product-details" id="details-${data.id}">
                            <h5 class="card-title mb-3 fw-bold">${data.heading}</h5>
                            <p class="card-text text-muted mb-4">${data.body}</p>
                            <div class="d-flex justify-content-between align-items-center">
                                <span class="product-price">Price:${data.price}</span>
                            </div>
                            <button class="btn btn-custom text-white px-4 py-2 rounded-pill" data-product-id="${data.id}">View Details</button>
                        </div>
                    </div>
                </div>
                </div>
             </div>
        `;
        // 把新创建的卡片添加到容器
        cardContainer.appendChild(cardWrapper);

        // 找到当前循环里的.listProduct元素,绑定点击事件
        const currentCard = cardWrapper.querySelector('.listProduct');
        currentCard.addEventListener('click', () => {
            console.log(data.id);
            window.location.href = `ProductDetails1.html?id=${data.id}`;
        });
    });
}

// Call the function to generate products on page load
createCard();
方案二:使用事件委托(更推荐,性能最优)

事件委托是把事件绑定到父容器上,通过事件冒泡来处理所有子元素的点击,不需要给每个卡片单独绑定事件,特别适合动态生成的DOM:

const createCard = () => {
    let cardContainer = document.querySelector('.card-container');
    cardContainer.innerHTML = ''; // Clear the grid first

    // 先把所有卡片的HTML一次性拼接好,再插入DOM
    let allCardsHTML = '';
    cardData.forEach((data) => {
        const cardHTML = `
            <div class="card-container py-5">
                 <div class="row">
                    <div class="col">
                    <div class="listProduct">
                        <div class="card-body product-card" data-product-id="${data.id}">
                            <img class="card-img-top" src="${data.imgFile}" alt="Card image cap">
                            <div class="product-details" id="details-${data.id}">
                                <h5 class="card-title mb-3 fw-bold">${data.heading}</h5>
                                <p class="card-text text-muted mb-4">${data.body}</p>
                                <div class="d-flex justify-content-between align-items-center">
                                    <span class="product-price">Price:${data.price}</span>
                                </div>
                                <button class="btn btn-custom text-white px-4 py-2 rounded-pill" data-product-id="${data.id}">View Details</button>
                            </div>
                        </div>
                    </div>
                    </div>
                 </div>
            </div>
        `;
        allCardsHTML += cardHTML;
    });
    cardContainer.innerHTML = allCardsHTML;
}

// 把事件绑定到父容器上,用事件委托处理所有卡片的点击
document.querySelector('.card-container').addEventListener('click', (e) => {
    // 找到点击路径中带有data-product-id属性的元素(卡片或按钮都可以)
    const target = e.target.closest('[data-product-id]');
    if (target) {
        const productId = target.getAttribute('data-product-id');
        console.log(productId);
        window.location.href = `ProductDetails1.html?id=${productId}`;
    }
});

// Call the function to generate products on page load
createCard();

额外的小修复

你代码里的按钮标签有个语法错误:<button ... data-product-id="${data.id}"btn btn-primary">,这里多了一段多余的"btn btn-primary",应该删掉,不然HTML会解析出错。

备注:内容来源于stack exchange,提问作者Evita

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 08:43:05