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

有些话、适合烂在心里 提交于 2019-11-26 16:36:32

问题


In the code below, I need to use the div tag at the top of the HTML for styling. Without the div tag in place, the hx tags are outline numbered correctly, but with the div in place everything goes completely wrong. I need this to work like this, but with the div tag still in place, and I need it to work for divs with different ids. Any ideas?

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) ". "}
<div class="varies">
    <h1>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h1>
</div>
<p>Morbi efficitur nibh metus, a vehicula mauris tristique ac. Duis ornare metus eget iaculis hendrerit.</p>
  <h2>Morbi nisi lacus, ultricies sit amet turpis a, aliquet congue nulla.</h2>
        <p>Duis eget facilisis nisl.</p>
    <h3>Donec tincidunt purus quam, ut accumsan lorem hendrerit a.</h3>
      <p>Aenean in mattis quam.</p>
    <h3>Maecenas a nulla sit amet ligula facilisis tincidunt lacinia non enim.</h3>
      <p>Aliquam dignissim turpis placerat, facilisis magna et, venenatis purus.</p>
  <h2>Suspendisse tempus eu elit nec malesuada.</h2>
        <p>In ut sollicitudin nisi. Praesent non porttitor ante, molestie scelerisque mauris.</p>
  <h2>Vivamus eu turpis efficitur, ornare risus in, consectetur tellus.</h2>
        <p>Cras pellentesque orci eu placerat mollis.</p>      
<h1>Duis eu nulla et tellus porttitor auctor.</h1>

回答1:


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>

Why does the snippet with div not work?

Now let us to come to the snippet that doesn't work (the one where the h1 is present within a div).

  • Here, the h1 creates counter.h2 but this can be inherited only by the siblings of the h1 (of which there are none).
  • Whenever a h2 element is encountered, UA tries to increment value of counter.h2 within the :before selector. But here the h2 parent does not inherit counter.h2 and hence h2:before doesn't either. Because of this h2:before will create its own counter.h2 and increment to 1.
  • Subsequent h2 elements also cannot inherit counter.h2 because the counter is created by h2:before (which is a child of h2). Because of this, each time a h2 is encountered a new counter is created within its :before and incremented. This is why all h2 show up as 1.1.
  • Similarly, none of the h3 elements know about counter.h2 and they don't increment it either and this is why they show up as 1.0.x.
  • However, they all can inherit counter.h3 because it was created by h2 element which is a sibling of all h3 elements. This is why counter.h3 gets incremented properly.

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, sets it to 0 -->
<div class="test"> <!-- Inherits counter.h1 from parent -->
  <h1>Heading 1 <!-- Again inherits counter.h1 from parent, creates counter.h2 -->
    <!-- ::before increments counter.h1 to 1 and display value-->
  </h1>
</div>
<p>Paragraph</p>
<h2>Heading 2 <!-- Inherits counter.h1 as it is from parent but not counter.h2, creates counter.h3 -->
  <!-- ::before has no counter.h2, so creates a counter.h2 and increments to 1 -->
</h2>
<p>Paragraph</p>
<h3>Heading 3 <!-- Inherits counter.h1 as it is from parent, couunter.h3 from sibling but not counter.h2 -->
  <!-- ::before inherits counter.h3 from parent and increments to 1, has no counter.h2 so creates a new counter.h2 and sets to 0 -->
</h3>
<p>Paragraph</p>
<h3>2nd Heading 3 <!-- Inherits counter.h1 as it is from parent, couunter.h3 from sibling but not counter.h2 -->
  <!-- ::before inherits counter.h3 from parent and increments to 2, has no counter.h2 so creates a new counter.h2 and sets to 0 -->
</h3>
<p>Paragraph</p>
<h2>2nd Heading 2 <!-- Inherits counter.h1 as it is from parent, couunter.h3 from sibling but not counter.h2, resets counter.h3 to 0 -->
  <!-- ::before has no counter.h2, so creates a counter.h2 and increments to 1 -->
</h2>
<p>Paragraph</p>
<h2>3rd Heading 2 <!-- Inherits counter.h1 as it is from parent, couunter.h3 from sibling but not counter.h2, resets counter.h3 to 0 -->
  <!-- ::before has no counter.h2, so creates a counter.h2 and increments to 1 -->
</h2>
<p>Paragraph</p>
<h1>2nd Heading 1 <!-- Inherits counter.h1 as it is from parent, couunter.h3 from sibling but not counter.h2, resets counter.h2 to 0 -->
  <!-- ::before inherits counter.h1 from parent and increments to 2 -->
</h1>

What is the solution?

Ideal solution to this problem would be to reset all 3 counters first at body itself so that all elements are aware of the existence of a counter and can inherit or use its value.

body {counter-reset: h1 h2 h3}
h1 {counter-reset: h2 h3}
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, counter.h2, counter.h3 sets all 0 -->
<div class="test"> <!-- Inherits all 3 counters -->
  <h1>Heading 1 <!-- Inherits all 3 counters, resets counter.h2 and counter.h3 to 0 -->
    <!-- ::before also inherits all 3 counters, increments counter.h1 to 1 and displays value -->
  </h1>
</div>
<p>Paragraph</p>
<h2>Heading 2 <!-- Inherits all 3 counters, resets counter.h3 to 0 -->
  <!-- ::before also inherits all 3 counters, increments counter.h2 to 1 and displays value -->
</h2>
<p>Paragraph</p>
<h3>Heading 3 <!-- Inherits all 3 counters -->
  <!-- ::before also inherits all 3 counters, increments counter.h3 to 1 and displays value -->
</h3>
<p>Paragraph</p>
<h3>2nd Heading 3 <!-- Inherits all 3 counters -->
  <!-- ::before also inherits all 3 counters, increments counter.h3 to 2 and displays value -->
</h3>
<p>Paragraph</p>
<h2>2nd Heading 2 <!-- Inherits all 3 counters, resets counter.h3 to 0 -->
  <!-- ::before also inherits all 3 counters, increments counter.h2 to 2, resets counter.h3 to 0 and displays value -->
</h2>
<p>Paragraph</p>
<h2>3rd Heading 2 <!-- Inherits all 3 counters, resets counter.h3 to 0 -->
  <!-- ::before also inherits all 3 counters, increments counter.h2 to 3, resets counter.h3 to 0 and displays value -->
</h2>
<p>Paragraph</p>
<h1>2nd Heading 1 <!-- Inherits all 3 counters, resets counter.h2 and counter.h3 to 0 -->
  <!-- ::before also inherits all 3 counters, increments counter.h1 to 2, resets counter.h2, counter.h3 to 0 and displays value -->
</h1>



回答2:


Consider this (very similar to your structure, just a bit simpler):

body {
  counter-reset: h1;
}

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

h2:before {
  content: counter(h1) "." counter(h2) ". ";
  counter-increment: h2;
}
<div>
  <h1>Heading first level 1</h1>
</div>
  <h2>Heading second level 1</h2>
  <h2>Heading second level 2</h2>
  <h2>Heading second level 3</h2>
  <h2>Heading second level 4</h2>
  <h2>Heading second level 5</h2>    
<h1>Heading first level 2</h1>
  <h2>Test</h2>
  <h2>Test</h2>
  <h2>Test</h2>
<h1>Heading first level 3</h1>
<h1>Heading first level 4</h1>

Why your layout does not work

By recommendation:

Counters are "self-nesting", in the sense that resetting a counter in a descendant element or pseudo-element automatically creates a new instance of the counter. This is important for situations like lists in HTML, where elements can be nested inside themselves to arbitrary depth.

The problem in your layout is that the first <h1> inside the <div> initializes a separate instance of the h1 counter. That happens because counters are sensitive to nested elements. Every time you reset a counter in a different level, that does not affect the same counter instance scoped into another level!

What happens

Following is what happens:

01. <body>                             | h1 = *
02. <div>                              |
03.   <h1>Heading first level 1</h1>   | h1 = 1 |  div-h2 = *
04. </div>                             |
05.   <h2>Heading second level 1</h2>  |        | h2 = 1
06.   <h2>Heading second level 2</h2>  |        | h2 = 1
07.   <h2>Heading second level 3</h2>  |        | h2 = 1
08.   <h2>Heading second level 4</h2>  |        | h2 = 1
09.   <h2>Heading second level 5</h2>  |        | h2 = 1
10. <h1>Heading first level 2</h1>     | h1 = 2 | h2 = *
11.   <h2>Test</h2>                    |        | h2 = 1
12.   <h2>Test</h2>                    |        | h2 = 2
13.   <h2>Test</h2>                    |        | h2 = 3
14. <h1>Heading first level 3</h1>     | h1 = 3
15. <h1>Heading first level 4</h1>     | h1 = 4

As you can see, in 01. we reset counter h1 which is working fine. However in h1 elements we reset counter h2.

The problem occurs in 03. where we reset counter h2 in nested level div, to clarify this, I call this counter: div-h2. In fact, next h2 elements from 05. to 09. are using a different counter which has not been reset! That is counter h2 (no nesting) and even if they try to increment it, there is nothing to increment as a reset is mandatory!

In 10. we do not have a div nesting, thus counter h2 is correctly reset, thus incremented as well.

What to do

In your case, you must avoid generating nested structures that are not homogeneous in your page. This is a suggestion to have a clean HTML, in your case, if you really need to keep that div, just add a div selector where you reset counter h2:

div {
  counter-reset: h2;
}

So here is the final working snippet:

body {
  counter-reset: h1;
}

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

div {
  counter-reset: h2;
}

h2:before {
  content: counter(h1) "." counter(h2) ". ";
  counter-increment: h2;
}
<div>
  <h1>Heading first level 1</h1>
</div>
  <h2>Heading second level 1</h2>
  <h2>Heading second level 2</h2>
  <h2>Heading second level 3</h2>
  <h2>Heading second level 4</h2>
  <h2>Heading second level 5</h2>    
<h1>Heading first level 2</h1>
  <h2>Test</h2>
  <h2>Test</h2>
  <h2>Test</h2>
<div>
  <h1>Heading first level 1</h1>
</div>
  <h2>Test</h2>
  <h2>Test</h2>
  <h2>Test</h2>
<h1>Heading first level 3</h1>
<h1>Heading first level 4</h1>

The solution above works if you only have those div nesting for h1, in case you expect to have other header elements wrapped into divs, than you need to reset h3 counter there too! But I do not recommend this because it is a very messy CSS arrangement: hard to maintain and not easy to follow.

Remember in CSS there is always a way to achieve things, you said you need that div, but I think you did not try all possibilities to get rid of it.



来源:https://stackoverflow.com/questions/31658111/how-do-i-stop-div-tags-interfering-with-counters

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!