How to Stop <Div> Tags Interfering with Counters

How do I stop <div> tags interfering with counters?

The reason for the behavior can be explained in detail by having a look at what the W3C specs say about creation of counters, their scope and inheritance.

Counter Reset: The counter-reset property creates new counters on an element.

Scope of a Counter: The scope of a counter starts at the first element in the document that has a 'counter-reset' for that counter.

Counter Inheritance: A counter and its value are inherited separately, possibly from different elements. If an element has a previous sibling, it must inherit all of the sibling’s counters. Otherwise, if the element has a parent, it must inherit all of the parent’s counters. Otherwise, the element must have an empty set of counters. The element then inherits counter values from the immediately preceding element in document order.


Why does the snippet without div work?

In the working snippet (the one without the div), the following is what happens:

  • counter.h1 (added a prefix to differentiate from element) is created (or reset) at body and its initial value is set as 0.
  • All elements inherit their parent's counters and so every element within body gets counter.h1. When the first h1 is encountered, the value of counter.h1 is incremented to 1. When the next h1 is encountered, it inherits counter value from the previous element and then increments to 2.
  • counter.h2 counter is created at h1 element and value is set to 0. This value is visible to the siblings of the h1 and they can all inherit it.
  • In this snippet, all h2 elements are actually siblings of the h1 element and so each h2 element inherits counter.h2 that was already created at the h1 and just increments its value. So, when the first h2 is encountered counter.h2 becomes 1 and so on.
  • Similar to h2 elements, the h3 elements are also siblings of both the h1 and h2 elements and so they also inherit counter.h1 and counter.h2. This is why the numbering remains correct in this sample.

body {counter-reset: h1}h1 {counter-reset: h2}h2 {counter-reset: h3}h1:before {counter-increment: h1; content: counter(h1)". "}h2:before {counter-increment: h2; content: counter(h1)"." counter(h2)". "}h3:before {counter-increment: h3; content: counter(h1)"." counter(h2)"." counter(h3)". "}
<!-- body creates counter.h1 and set to 0 --><h1>Heading 1 <!-- Inherits counter.h1 from parent, creates counter.h2 and set to 0 -->  <!-- ::before being a child inherits all counters from parent, increments counter.h1 to 1 and displays value --></h1><p>Paragraph</p><h2>Heading 2 <!-- Inherits counter.h1, counter.h2 from sibling, creates counter.h3 and set to 0 -->  <!-- ::before being a child inherits all counters from parent, increments counter.h2 to 1 and displays value --></h2><p>Paragraph</p><h3>Heading 3 <!-- Inherits counter.h1, counter.h2, counter.h3 -->  <!-- ::before being a child inherits all counters from parent, increments counter.h3 to 1 and displays value --></h3><p>Paragraph</p><h3>2nd Heading 3 <!-- Inherits counter.h1, counter.h2, counter.h3 -->  <!-- ::before being a child inherits all counters from parent, increments counter.h3 to 2 and displays value -->  </h3><p>Paragraph</p><h2>2nd Heading 2 <!-- Inherits counter.h1, counter.h2, counter.h3, resets counter.h3 to 0 -->  <!-- ::before being a child inherits all counters from parent, increments counter.h2 to 2 and displays value --></h2><p>Paragraph</p><h2>3rd Heading 2 <!-- Inherits counter.h1, counter.h2, counter.h3, resets counter.h3 to 0 -->  <!-- ::before being a child inherits all counters from parent, increments counter.h2 to 3 and displays value --></h2><p>Paragraph</p><h1>2nd Heading 1 <!-- Inherits counter.h1, counter.h2, counter.h3, resets counter.h2 to 0 -->  <!-- ::before being a child inherits all counters from parent, increments counter.h1 to 2 and displays value --></h1>

CSS counters for headings nested in div

If we give the divs a class name and switch the counter-reset to these classes it works.

h1 {
counter-reset: h2;
}

.h2 {
counter-reset: h3;
}

.h3 {
counter-reset: h4;
}

h2::before {
counter-increment: h2;
content: counter(h2) ". ";
}

h3::before {
counter-increment: h3;
content: counter(h2) "." counter(h3) ". ";
}

h4::before {
counter-increment: h4;
content: counter(h2) "." counter(h3) "." counter(h4) ". ";
}
<h1>Title</h1>

<div class="h2"><h2>Section 1</h2></div>

<div class="h2"><h2>Section 2</h2></div>

<div class="h3"><h3>Subsection 1</h3></div>

<div class="h3"><h3>Subsection 2</h3></div>

<div class="h4"><h4>Subsubsection 1</h4></div>

<div class="h4"><h4>Subsubsection 2</h4></div>

<div class="h3"><h2>Section 3</h2></div>

Counter-increment with wrapping divs

From the Spec

The scope of a counter starts at the first element in the document that has a 'counter-reset' for that counter and includes the element's descendants and its following siblings with their descendants.

So, you need to call the counter-reset on the wrapping element of your lists, or the first <div class="wrapper">.

Here's an update which initialize the mycounter on body as an example.

And also from the spec

If 'counter-increment' or 'content' on an element or pseudo-element refers to a counter that is not in the scope of any 'counter-reset', implementations should behave as though a 'counter-reset' had reset the counter to 0 on that element or pseudo-element.

so the list item in the second div actually initialized a counter for each of the items.

Numbering h1, h2, h3 with CSS, with presence of article tag

Reasons:

As I had described in my comment to the question, CSS counters are very sensitive to levels and the document structure. If the structure doesn't match a certain pattern then it will affect the entire working of the counters. This is because of how elements inherit the counters and the counter's value. Details about how the counters work, how they are inherited is described in my answer here.

For the sake of more clarity, I have added inline comments in the below snippet to explain the working:

body {
counter-reset: chapter 3 section 0;
}
h2 {
counter-reset: slide 0;
counter-increment: section;
}
h3 {
counter-increment: slide;
}
h1:before {
content: counter(chapter)". ";
}
h2:before {
content: counter(chapter)"." counter(section)" ";
}
h3:before {
content: counter(chapter)"." counter(section)"." counter(slide)" ";
}
<!-- body creates chapter, section counters -->
<article> <!-- this inherits both counters from its parent and also its value because it is the previous element in document order -->
<h1>chapter</h1> <!-- inherits both counters and their value from parent -->
</article>

<article> <!-- this inherits both chapter and section counters from parent (body) and the value for the counters from the previous sibling -->
<h2>section A</h2> <!-- inherits both counters, increments section to 1, creates slide counter. slide counter is visible only to this element but not parent -->
</article>
<article> <!-- this inherits both chapter and section counters from parent (body) and the value for the counters from the previous sibling -->
<h3> slide a</h3> <!-- inherits chapter, section but sees no slide counter and hence creates a new slide counter and increments to 1, the parent doesn't know about this new slide counter -->
</article>
<article> <!-- this inherits both chapter and section counters from parent (body) and the value for the counters from the previous sibling -->
<h3> slide b</h3> <!-- inherits chapter, section but sees no slide counter and hence creates a new slide counter and increments to 1, the parent doesn't know about this new slide counter -->
</article>

<article> <!-- this inherits both chapter and section counters from parent (body) and the value for the counters from the previous sibling -->
<h2>section B</h2> <!-- inherits both counters, increments section to 2, creates slide counter. slide counter is visible only to this element but not parent -->
</article>
<article> <!-- this inherits both chapter and section counters from parent (body) and the value for the counters from the previous sibling -->
<h3> slide a</h3> <!-- inherits chapter, section but sees no slide counter and hence creates a new slide counter and increments to 1, the parent doesn't know about this new slide counter -->
</article>
<article> <!-- this inherits both chapter and section counters from parent (body) and the value for the counters from the previous sibling -->
<h3> slide b</h3> <!-- inherits chapter, section but sees no slide counter and hence creates a new slide counter and increments to 1, the parent doesn't know about this new slide counter -->
</article>

Counter does not increment in CSS

The counters are not incrementing properly because you're resetting them in their own parent.

For example, the counter section is being reset (that is, the value is set to 0) every time the browser encounters a h3 tag and so when the counter-increment is being called within h3:before (children of h3), it just increments from 0 to 1 every time.

You should reset the counters at the grand parent level (or at body if there is no grand parent).

body {  counter-reset: section;}h3 {  font-size: .9em;  counter-reset: subsection;}h4 {  font-size: .7em;}h3:before {  counter-increment: section;  content: counter(section)" ";}h4:before {  counter-increment: subsection 2;  content: counter(section)"." counter(subsection)" ";}
<!-- at body reset section to 0 -->
<h3> <!-- the h3 will inherit counter section from body (its parent) and increment to 1 in :before --> <!-- every time a h3 is encountered, the subsection is reset to 0 --> Favorite Movies</h3><h4> <!-- this inherits subsection counter from previous sibling and increments value to 2 in :before --> District 9</h4><h4> <!-- this inherits subsection counter and its value from previous sibling and increments value to 4 in :before --> The Matrix</h4><h4> <!-- this inherits subsection counter and its value from previous sibling and increments value to 6 in :before --> The Cabin in the Woods</h4>
<h3> <!-- the h3 will inherit counter section from body (its parent), its value from the previous sibling and increment to 2 in :before --> <!-- here the subsection counter is again reset to 0 because the subsection numbering has to restart --> Others</h3><h4> <!-- this inherits subsection counter from previous sibling and increments value to 2 in :before --> Minority Report</h4><h4> <!-- this inherits subsection counter and its value from previous sibling and increments value to 4 in :before --> Lord of the Rings</h4><h4> <!-- this inherits subsection counter and its value from previous sibling and increments value to 6 in :before --> Fargo</h4>

how to make multiple counter buttons not interfere with each other

The problem with your code is the point at which you declare your variables, as well as the means by which you select the elements:

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

Instead, we need to look at which <button> was pressed and from there find the correct menu-item to increment or decrement; so in the following code I've taken advantage of EventTarget.addEventListener() to pass a reference to the triggered Event to the function that is bound as the 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>


Related Topics



Leave a reply



Submit