问题
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) atbody
and its initial value is set as 0.- All elements inherit their parent's counters and so every element within
body
getscounter.h1
. When the firsth1
is encountered, the value ofcounter.h1
is incremented to 1. When the nexth1
is encountered, it inherits counter value from the previous element and then increments to 2. counter.h2
counter is created ath1
element and value is set to 0. This value is visible to the siblings of theh1
and they can all inherit it.- In this snippet, all
h2
elements are actually siblings of theh1
element and so eachh2
element inheritscounter.h2
that was already created at theh1
and just increments its value. So, when the firsth2
is encounteredcounter.h2
becomes 1 and so on. - Similar to
h2
elements, theh3
elements are also siblings of both theh1
andh2
elements and so they also inheritcounter.h1
andcounter.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
createscounter.h2
but this can be inherited only by the siblings of theh1
(of which there are none). - Whenever a
h2
element is encountered, UA tries to increment value ofcounter.h2
within the:before
selector. But here theh2
parent does not inheritcounter.h2
and henceh2:before
doesn't either. Because of thish2:before
will create its owncounter.h2
and increment to 1. - Subsequent
h2
elements also cannot inheritcounter.h2
because the counter is created byh2:before
(which is a child ofh2
). Because of this, each time ah2
is encountered a new counter is created within its:before
and incremented. This is why allh2
show up as 1.1. - Similarly, none of the
h3
elements know aboutcounter.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 byh2
element which is a sibling of allh3
elements. This is whycounter.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 div
s, 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