如何让多个计数器按钮互不干扰

how to make multiple counter buttons not interfere with each other

基本上我正在制作一个在线餐厅网站,您可以在其中点餐。我将为列出的每种食品制作卡片。现在,我正在制作按钮来增加和减少客户想要购买的每件商品的数量。

var counter = document.querySelector(".count");
var currentNumber = 0;
var classicBurger = 5.18;
var bargeBurger = 6.13;

function addOne(price) {
  currentNumber += 1;
  counter.innerHTML = currentNumber;
  document.querySelector(".total-price").innerHTML =
    "$" + (price * currentNumber).toFixed(2);
}

function subtractOne(price) {
  if (currentNumber > 0) {
    currentNumber += -1;
    counter.innerHTML = currentNumber;
    document.querySelector(".total-price").innerHTML =
      "$" + (price * currentNumber).toFixed(2);
  } else {
    counter.innerHTML = 0;
  }
}
   <html>
     <div class="food-item">
        <h3 class="item-title">Classic Burger</h3>
        <p class="item-description">
          Classic beef patty with lettuce, tomato, and cheese.
        </p>

        <div class="add-subtract-items">
          <button class="subtract-item" onclick="subtractOne(classicBurger)">
            -
          </button>
          <span class="count">0</span>
          <button class="add-item" onclick="addOne(classicBurger)">+</button>
        </div>

        <p class="total-price">[=11=].00</p>
      </div>

      <div class="food-item">
        <h3 class="item-title">Barge Burger</h3>
        <p class="item-description">
          Classic beef patty with lettuce, tomato, and cheese.
        </p>

        <div class="add-subtract-items">
          <button class="subtract-item" onclick="subtractOne(bargeBurger)">
            -
          </button>
          <span class="count">0</span>
          <button class="add-item" onclick="addOne(bargeBurger)">+</button>
        </div>

        <p class="total-price">[=11=].00</p>
      </div>
      <html/>

如何让第二个按钮影响正确的 HTML 而不是第一个项目的总数?

主要问题是 currentNumber 由两个计数器共享,因此无法区分彼此。

此外,在 HTML(每个汉堡的当前价格)中保持状态并使用 onclick 使得管理范围界定变得困难。

我建议从 HTML 中删除所有状态,然后在 .food-item 元素上编写一个循环以将点击处理程序添加到它们的按钮并能够操纵它们的每个输出。您可以使用像 burgers 这样的数据结构来跟踪每个汉堡的价格,而不是单独的变量,这些变量不适合循环。

这是一种方法:

const burgers = [
  {name: "Classic Burger", price: 5.18},
  {name: "Barge Burger", price: 6.13},
];
const foodItemEls = [...document.querySelectorAll(".food-item")];
foodItemEls.forEach((e, i) => {
  let quantity = 0;
  e.querySelector(".add-item").addEventListener("click", () => {
    ++quantity;
    showTotal();
  });
  e.querySelector(".subtract-item").addEventListener("click", () => {
    quantity = Math.max(quantity - 1, 0);
    showTotal();
  });
  const showTotal = () => {
    e.querySelector(".total-price").textContent = 
      "$" + (burgers[i].price * quantity).toFixed(2);
  };
});
<div class="food-item">
  <h3 class="item-title">Classic Burger</h3>
  <p class="item-description">
    Classic beef patty with lettuce, tomato, and cheese.
  </p>

  <div class="add-subtract-items">
    <button class="subtract-item">-</button>
    <span class="count">0</span>
    <button class="add-item">+</button>
  </div>

  <p class="total-price">[=11=].00</p>
</div>

<div class="food-item">
  <h3 class="item-title">Barge Burger</h3>
  <p class="item-description">
    Classic beef patty with lettuce, tomato, and cheese.
  </p>

  <div class="add-subtract-items">
    <button class="subtract-item">-</button>
    <span class="count">0</span>
    <button class="add-item">+</button>
  </div>

  <p class="total-price">[=11=].00</p>
</div>

如果您有更多汉堡,您可能希望将它们全部保存在如上所示 burgers 的数组中,并使用对该数据结构的循环生成并注入 HTML。为此,您还要将描述添加到数组中。也就是说,在没有更多上下文的情况下,我不确定它是否适合您的应用程序。

您的代码的问题在于您声明变量的位置,以及您 select 元素的方式:

// here we get the first element in the document:
var counter = document.querySelector(".count");
// we set the currentNumber (the 'count') variable to 0:
var currentNumber = 0;

/* ... */

function addOne(price) {
  // regardless of which button, for which menu-item,
  // is pressed we increase the count (but this variable
  // is applied as the counter for *all* menu-items):
  currentNumber += 1;

  // here we're using the first '.count' element in the
  // document (regardless of which menu-item we're
  // trying to order, or remove from the order):
  counter.innerHTML = currentNumber;

  // and again, we're using the first element within the
  // document (regardless of which menu-item the buttons
  // relate to):
  document.querySelector(".total-price").innerHTML =
    "$" + (price * currentNumber).toFixed(2);
}

// the same is true, below, for the subtraction function
// which I've removed for brevity

相反,我们需要查看按下了哪个 <button>,然后从那里找到正确的 menu-item 来递增或递减;所以在下面的代码中,我利用 EventTarget.addEventListener() 将对触发事件的引用传递给绑定为 event-listener:

的函数

// declaring named functions using Arrow functions (since we're not using the
// 'this' variable).
// a named function to format the cost to two decimal places:
const formatCost = (cost) => {
    return (Math.ceil(cost * 100) / 100).toFixed(2);
  },
  // the following functions take advantage of EventTarget.addEventListener()
  // passing the event-object to the function that's bound as the event-listener:
  addOne = (evt) => {
    // from the event-object we first retrieve the 'evt.currentTarget', this
    // is the element to which the event-listener was bound (in this case the
    // <button class="add-item"> elements), from there we use Element.closest()
    // to find the ancestor <div class="food-item"> element (effectively to find
    // the first ancestor that wraps the <button> elements as well as the other
    // elements we wish to use):
    let cardParent = evt.currentTarget.closest('div.food-item'),
      // from the cardParent we then use Element.querySelector() to select
      // the first element within that Element (the cardParent) that matches
      // the supplied CSS selector:
      costElement = cardParent.querySelector('span.cost'),
      countElement = cardParent.querySelector('span.count'),

      // from those elements we determine the cost, using parseFloat()
      // to convert the text-content of the element into a number we
      // can work with:
      cost = parseFloat(costElement.textContent),
      // we update the current count by adding 1 to the number
      // retrieved with parseInt():
      count = parseInt(countElement.textContent) + 1;

    // we then update the countElement to reflect the new - increased - count:
    countElement.textContent = count;

    // and we then find the '.total-price' element, and update its text-content
    // to reflect the formatted cost:
    cardParent.querySelector('.total-price').textContent = formatCost(cost * count);
  },
  subtractOne = (evt) => {
    // here we do almost exactly as we did above:
    let cardParent = evt.currentTarget.closest('div.food-item'),
      costElement = cardParent.querySelector('span.cost'),
      countElement = cardParent.querySelector('span.count'),
      cost = parseFloat(costElement.textContent),
      // obviously we don't (yet) adjust the count:
      count = parseInt(countElement.textContent);

    // because we first need to check that the count is greater than zero:
    if (count > 0) {
      // if so, we then subtract one from the count:
      count = count - 1;
      // we then update the countElement to reflec the new count:
      countElement.textContent = count;
    }

    // and finally we update the '.total-price' element to reflect the new cost:
    cardParent.querySelector('.total-price').textContent = formatCost(cost * count);
  };

// here we select all elements in the document that match the supplied CSS selector, and
// use NodeList.forEach() to iterate over those elements, and:
document.querySelectorAll('button.subtract-item').forEach(
  // we then bind the subtractOne() function (note the lack of parentheses)
  // as the 'click' handler for the current element of the NodeList:
  (subtraction) => subtraction.addEventListener('click', subtractOne)
);
// as above, but obviously we're binding the addOne() function
// to the 'button.add-item' elements:
document.querySelectorAll('button.add-item').forEach(
  (addition) => addition.addEventListener('click', addOne)
);
*,
 ::before,
 ::after {
  box-sizing: border-box;
  font: normal 400 1rem / 1.5 sans-serif;
  margin: 0;
  padding: 0;
}

body {
  display: flex;
  flex-wrap: wrap;
  gap: 1em;
  padding: 1em;
}

.food-item {
  border: 1px solid #000;
  border-radius: 1em;
  padding: 0.5em;
}

h3 {
  font-weight: 600;
}

.add-subtract-items {
  align-content: center;
  display: flex;
  gap: 0.5em;
  justify-content: center;
  width: minmax(6em, 40%);
}

button,
span.count {
  text-align: center;
}

button {
  cursor: pointer;
  width: 2em;
}

:is(.cost, .total-price)::before {
  content: "$";
}
<div class="food-item">
  <h3 class="item-title">Classic Burger</h3>
  <span class="cost">5.18</span>
  <p class="item-description">
    Classic beef patty with lettuce, tomato, and cheese.
  </p>

  <div class="add-subtract-items">
    <button class="subtract-item">
      -
    </button>
    <span class="count">0</span>
    <button class="add-item">+</button>
  </div>

  <p class="total-price">0.00</p>
</div>

<div class="food-item">
  <h3 class="item-title">Barge Burger</h3>
  <span class="cost">6.13</span>
  <p class="item-description">
    Classic beef patty with lettuce, tomato, and cheese.
  </p>

  <div class="add-subtract-items">
    <button class="subtract-item">
      -
    </button>
    <span class="count">0</span>
    <button class="add-item">+</button>
  </div>

  <p class="total-price">0.00</p>
</div>

<div class="food-item">
  <h3 class="item-title">Milkshake</h3>
  <span class="cost">4.35</span>
  <p class="item-description">
    Tastes like a five-dollar shake, for a little bit less.
  </p>

  <div class="add-subtract-items">
    <button class="subtract-item">
      -
    </button>
    <span class="count">0</span>
    <button class="add-item">+</button>
  </div>

  <p class="total-price">0.00</p>
</div>

JS Fiddle demo.

参考文献: