Query live counter values via CSSOM


#1

The value of a CSS counter on an element cannot be queried directly. One has to jump through loops, querying the stylesheets and searching for previous DOM nodes to re-calculate it (or copy the logic from CSS to the JS file).

CSSOM should attend to it. The Element interface should therefore be extended like this:

partial interface Element {
  Integer getCounterValue(DOMString counterName);
};

The method element.getCounterValue('counter-name') returns the value of the counter counter-name at the position of element in the DOM. If no counter with this name exists, the method returns 0. (This is in sync with the specced behavior for non-existing counters.)

Example:

<body>
  <h2>Foo</h2>
  <p>Bar</p>
</body>

/* stylesheet: */
h2 { counter-increment: foo; }

Then the following holds:

document.querySelector('body').getCounterValue('foo') === 0;
document.querySelector('h2').getCounterValue('foo') === 1;
document.querySelector('p').getCounterValue('foo') === 1;
document.querySelector('p').getCounterValue('non-existing') === 0;

Use case

One use case is cross-referencing figures. If <figure> elements increment a counter and display it in the <figcaption>, it would be great to auto-generate references on other positions in the document, like see fig. 4, much like LaTeX’s \ref{figure}.

<figure id="foo">
  <img src="getCounterValue_enhances_workflow.png" alt="Proof for thesis">
  <figcaption>Labeled with :before { content: "Fig. " counter(figure); }</figcaption>
</figure>

<p>See <a href="#foo">the figure above</a> for a more detailed image.</p>
                      ^---- replace with "figure 4"

#2

At first when I read this I was pretty sceptical but the use case provided seems to do it some justice and actually I have found that myself as a use case.

Would it be worth considering querying the active counters on the elements also to get back as an array?


#3

I wanted to keep it simple intentionally. My guess from implementations would be, that such a list might be expensive to calculate, whereas calculating a single counter on request is something the CSS processor does all the time.

However, when such a list is indeed cheap to compile, it could be of good use, e.g. to decouple CSS and JS further by dynamically retrieving defined counter names.


#4

I think that was my initial fear that this feature adds a pretty deep coupling from JS to CSS, which feels pretty ugly. My main suggestion for exposing the counters was to remove some of that coupling; however as you mention that could be expensive to compute dependent on browser implementation.

My other reason for feeling this was ugly is that it does feel a little like a work around for the use case you are after. For example the following:

  1. Moving the DOM elements around would change the counters; Mutation observers could be used etc - however its getting pretty heavyweight.
  2. Unlike LaTeX; HTML is changed and modified often across multiple documents. You couldn’t split up a document easily across a URL and keep the same counters. (To my knowledge there is no counter extension to start at a certain offset)

I think for those two reasons I would perhaps be opting for data attributes which you can then use in content: attr(data-heading-number) -


#5

counter-reset lets you start the counter at whatever value you want.


#6

@tabatkins I should sleep more often; thanks.

Realistically you would likely want counter-reset exposed in CSSOM too if this feature were to go ahead; which certainly the recalculation of counters after paint doesn’t appear to be possible with CSS using animations at the moment in modern browsers (Quick mockup) so potentially another cost with the repainting etc.


#7

What do you mean by “exposed in CSSOM”? You can read and set the property via the normal APIs already.

Your example doesn’t make much sense. The counter-* properties aren’t animatable. Even if they were, you can’t animate from counter-reset to counter-increment.


#8

@tabatkins I mean editable much like the rest of element.style works; you mention that you already can yet what would be the point of this proposal? If I set or read from style.counterReset or style.counterIncrement nothing happens in JS. So I was suggesting a setter as well as the proposed getter method.

That was mostly my point; it wasn’t really a sensible example per se; just that it is not possible whatever I try - so browsers are unlikely to be geared to expecting the counters to change.


#9

counter-reset and counter-increment are definitely editable from .style, like every other property. This proposal is about getting the current value of a given counter at some point in the dom, which is not directly exposed today.


#10

Ah I get it now; accept my apologies.


#11

Does this address the use case? http://books.spec.whatwg.org/#cross-references


#12

Yes, for simple cross references. However, I doubt that browser vendors are too pumped to implement this (given the current support for @page properties et al.).

Also more complex situations would be easier addressed in Javascript. So I’d still love to be able to do that in the DOM, too.


#13

https://drafts.csswg.org/css-content/#target-counter is a better reference than the books spec, which is more of a single person’s research project than a work-in-progress specification.

And while it is true that browsers are not in a hurry to support paginated stuff, that feature can still be used for other use cases, since it can be used with any counter, not just the page counter:

http://vivliostyle.github.io/vivliostyle.js/viewer/vivliostyle-viewer.html#x=http://output.jsbin.com/xowuro&spread=false&f=epubcfi(/2!)

Source here (http://output.jsbin.com/xowuro)