Neat HaCSS, or let's de-JS the Web a bit
I like playing with technology, and I am particularly fond of playing with CSS/HTML. Not a big fan of JavaScript, though.
Don’t get me wrong, I understand the utility of JavaScript, and the power of jQuery, I really do. However, I believe both are overused and abused – much, if not most, of functionality of “rich Internet applications” (also known simply as “websites”), including transitions, animations and whatnot, can be implemented in HTML5 and CSS3, with JS used for AJAX requests. The advantages being: faster processing, smoother animations, more readable code, better separation of logic and presentation.
Lately I dived into 2 side-projects that allowed me to dust-off my CSS-fu while making a point about how JS is becoming less and less needed as far as presentation layer is concerned.
Sailing in the Cloud¶
The first little project is Sailors.MD, a simple website for my sailors-and-medical-professionals friends.
The second one is implementing something ownCloud (that great little project letting everyone self-host their own “cloud”) once had had that has later sadly been removed: sharing calendars and events publicly via a link with a token, or “link-sharing”. I know of at least one potential deployment where that was a show-stopper. So after complaining for a few months I decided to implement it myself.
JS-in-a-dropbox¶
Sailors.MD was a no-brainer – a completely new project, a simple static website (that will grow, some day, but not just yet), no magical mumbo-jumbo, it just needs to look nice and be functional. No JS needed there, period!
Now, ownCloud, on the other hand… JavaScript. JavaScript everywhere! Hacking together a nice CSS/HTML-based interface for internal- and link-sharing of calendars and events, with JS providing just the bare minimum of AJAX-ness, stemmed from the frustration of debugging JavaScript-based interface.
The Problem Challenge¶
I wanted the interface to retain full functionality – including animations, showing/hiding parts of the interface, etc – even without JavaScript enabled. There were several things that needed implementing in pure CSS/HTML, which seemed hard without JS:
- sections of the interface collapsing/extending upon click…
- …that are both directly linkable (i.e. via
:target
links), and persistent, allowing the user to interact with their contents and sub-sections; - showing some controls only when JS is enabled (a
reverse-
<noscript>
, if you will); - elegant tooltips.
- making element’s appearance depend on the state of elements that
follow it (remember, there
is no CSS selector/operator for being a parent of, and the
general
sibling operator
~
is one-way only);
IMPOSSIBURU, right?
Let’s hack!¶
First of all, some caveats: the below has not been tested on anything other than up-to-date versions of Firefox, Chromium and Rekonq (although IE 10+ should work). If you want to test them in anything else, be my guest, and I would love some feedback.
Secondly, all code in examples linked below is MIT-licensed (info in
the examples themselves also).
Why at all? Because it’s good practice to put some
license on your (even simple) code so that the rules of the road are
clear for other developers.
Why MIT? Well, I’m a staunt supporter of copyleft licenses like
the AGPL, but
this is just too small a thing, and bulding on too many great ideas of
other people for me to feel comfortable with slapping AGPL on it.
Okay, enough of this, on with the code!
The Targetable Displayable¶
Making sections of a website show/collapse upon click is not that hard once you wrap your head around one beautifully simple hack: CSS Checkbox Tabs. But I wanted more:
- being able to put the menu in a completely different place in code than the section (i.e. making a menu on top of the site, for example, with sections hidden within the bowels);
- section targeting via
:target
, so that they are directly linkable by users.
The first one is rather easy once you get that you can put a <label>
wherever you like in the document, regardless where the relevant
checkbox is, as long as you set label’s for
attribute to
checkboxes id
.
Enabling :target
was more tricky, tough:
#target
links do not set checkboxes :checked
.
Also, simply setting the id
attribute on the section we
want to have #target
-linkable will not work: when the user
clicks on a checkbox label to choose a different section, that one will
still be expanded. Checkbox checking does not change the
:target
.
We could use:
:checked ~ :target {
/*
* rules making the :target collapsed
*/
}
…but that would not work for any situation where the
:target
element is before the
:checked
checkbox (the sibling operator ~
is
one-way, remember?).
Hey, why not use just :target
and forget about
checkboxes? Well, then we wouldn’t be able to have sub-sections, as
there would be no way of saying “keep this section open even if the user
chooses something else (the subsection)”, and there is no “parent of”
operator (so there is no way of saying “keep it open if any of its
children is a :target
). So:
-
:checked
on checkboxes/radioboxes keeps state, and that’s a biggie; -
:target
is directly linkable; - there is no way to connect the two.
Or is there? If the :target
elements are always
before the checkboxes, and these are always directly in front of
the element that contains our expandable/contractable section, we might
be able to get what we want. As long as all
:target
-able elements are in before of all relevant
checkboxes. Tada!
What happens is:
- from the get-go, no navigational checkbox/radiobox is checked;
- if there is a
#target
, CSS rules for the right section container (based on the:target
-ed hidden element) kick-in; - if now the user selects any sections, these are handled via
checkboxes/radioboxes, and because the CSS
:target
rules for specific elements have the:not(:checked)
sibling in chain required,:target
rules stop working.
Reverse-<noscript>
¶
This seems a simple thing, right? Display elements when JavaScript is
enabled gets tricky, however, when we’re not allowed to use JS to
display them. Now, we all know the <noscript>
tag, but here we need to do something exactly opposite, and <script>
won’t do, as we’re not going to use JS for that.
Of course we can always use a style-within-noscript:
<noscript>
<style>
#some-element {
display:none;
}
</style>
</noscript>
…but that’s inelegant. First of all, we’re not supposed to have
<style>
tags within <body>
,
just as we’re not supposed to have <noscript>
within
<head>
.
Secondly, we might want to have the element gone from the element tree
when JS is disabled to pull off other hacks – like a
Displayable above, for instance.
Turns out, we can put HTML
comments inside <noscript>
. Not just that: we can
put the start of a comment (<!--
) in a
different <noscript>
element than the end
(-->
). And apparently these will be interpreted as
start/end HTML comment tags only if JS is disabled (they are within
<noscript>
elements, after all!). That means that
this:
<noscript><!--</noscript>
<div>SomeText</div>
<noscript>--></noscript>
…works like a reverse-<noscript>
element. The <div>
will get shown and included in the document tree only if JS is
enabled. Here, check for yourselves by enabling and disabling
JS in your browser and visiting the test
case.
CSS Tooltips (aka TooltipCSS)¶
There is a myriad of tooltip JS/jQuery libraries, I’m not even going
to link to them. Creating a HTML/CSS tooltip for a given element is also
trivially easy (create a child element, use :hover
on the parent to show it). Creating a tool-tip on any element
matching a selector, without any additional HTML (no additional
child elements, etc) – now this is a challenge!
What we need is a way to squeeze a new style-able element or two from
any element in the DOM tree, without adding HTML, in pure CSS. Turns out
that we have two: ::before
and ::after
.
Yes, they are style-able, yes, we can put whatever we want in them.
Unfortunately no, they can’t have child nodes.
But, can we conveniently pass tooltip text to them without making a separate CSS rule for each? Yes, we can.
The content
property conveniently accepts attr()
form. So we can have a single style stanza saying for example:
a[title]::before {
content:attr("title")
}
…and bam!, all <a>
elements with the title
attribute set will have ::before
pseudo-element containing
the value of title
attribute.
We can style ::before
just like any other element; by
making the parent element (the one being “tooltiped”,
<a>
in example above) relatively positioned
and our ::before
positioned absolutely we also have pretty
good control how and where the tooltip appears.
Because pseudo-elements can’t have child elements (all HTML inside
will get rendered as text), it seems we can’t have the small notch that
makes a tooltip from a simple bubble. Ah, but we have
::after
too, right! We can use it as our notch. If only there
was a way to make a pure-CSS triangle from an element…
Add a bit of CSS transitions to the mix (I’m using rgba()
background/border colours instead of opacity
,
as opacity for some reason makes elements move just a tiny, annoying
bit), remember to hide the elements that constitute our new shiny
tooltip so that they won’t obstruct other elements (hint: use visibility:hidden
instead of display:none
,
otherwise transitions won’t work) – et voilà!
A CSS Tooltip appears! It’s super-effective!
Depending on the state of the elements that follow¶
Well, that is simply impossible, as the ~
is one-way,
and there is no parent of
element. No, seriously.
But what we can do is play with the order of elements in the mix. Making all the elements (checkboxes, radio controls) the state we want to depend on precede the element we want to style depending on their state is the only way to go. The next logical step is to make that element be displayed in front of them.
That is no rocket
surgery and can be achieved in a myriad of ways, for example by
enclosing both the depending element and the source elements in a common
container, set position: relative
on the container and use
position:absolute; top:0px
on the element we want to be
displayed first, like
thus – and here’s how it
works.
With all their powers combined…¶
Dry examples are fun and informational (don’t forget to test them with JavaScript disabled, too!), but only once you see it all working together the power of it all gets evident. So, enjoy. This example uses minimal JS to set checkboxes back and forth (in a way that is used in ownCloud calendar sharing interface), but nothing more. With JS disabled it should show the group’s status (“all checked”, “some checked”, “none-checked”).
All examples are valid HTML5 and valid CSS3. Of course, the code is on-line, you can grab it here. I’d love to hear your opinion, or see some other non-JS hacks.
Y U NO USE CSS?¶
There is some serious magic that can be done with CSS. Once we get an “is parent of” and a truly universal sibling selectors it will open up even more possibilities. JS seems convenient, but more and more people are looking with distrust (or disgust) at JS-infested websites, due to performance and privacy issues involved.
If something can be done in pure CSS/HTML, why not do it that way?