问题
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) atbodyand its initial value is set as 0.- All elements inherit their parent's counters and so every element within
bodygetscounter.h1. When the firsth1is encountered, the value ofcounter.h1is incremented to 1. When the nexth1is encountered, it inherits counter value from the previous element and then increments to 2. counter.h2counter is created ath1element and value is set to 0. This value is visible to the siblings of theh1and they can all inherit it.- In this snippet, all
h2elements are actually siblings of theh1element and so eachh2element inheritscounter.h2that was already created at theh1and just increments its value. So, when the firsth2is encounteredcounter.h2becomes 1 and so on. - Similar to
h2elements, theh3elements are also siblings of both theh1andh2elements and so they also inheritcounter.h1andcounter.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
h1createscounter.h2but this can be inherited only by the siblings of theh1(of which there are none). - Whenever a
h2element is encountered, UA tries to increment value ofcounter.h2within the:beforeselector. But here theh2parent does not inheritcounter.h2and henceh2:beforedoesn't either. Because of thish2:beforewill create its owncounter.h2and increment to 1. - Subsequent
h2elements also cannot inheritcounter.h2because the counter is created byh2:before(which is a child ofh2). Because of this, each time ah2is encountered a new counter is created within its:beforeand incremented. This is why allh2show up as 1.1. - Similarly, none of the
h3elements know aboutcounter.h2and they don't increment it either and this is why they show up as 1.0.x. - However, they all can inherit
counter.h3because it was created byh2element which is a sibling of allh3elements. This is whycounter.h3gets 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