If you can do it with CSS do it with CSS

I read Twitter with Tweetdeck. And I use the excellent Better Tweetdeck to improve my Tweetdeck experience. And I had an idea.

You see, emoji, much as they’re the way we communicate now, they’re actually quite hard to read. And Slack does this rather neat thing where if you respond to a message with an emoji, it displays that emoji bigger than normal text so you can see it clearly. And some people just write tweets which are, like, two emoji and that’s it, and it would be really handy if they were large enough to read.

So I thought, here’s an idea; how about, if there’s a tweet which is just emoji, then display those emoji larger so they’re easier to see? Only if there are, say, four or less; you don’t want those people who write a whole huge tweet as emoji to get enlarged. Just the ones where someone responds with two little pictures and that’s it; let’s make that nice and visible, like Slack does.

This is clearly a thing for Better Tweetdeck to do. (They already provide a config option to make emoji a little bigger, which I appreciate.) So… how do we do this?

Well, one obvious way is to do it with JavaScript. Every time we read a new tweet, look to see whether it contains nothing but emoji, and if it does and there are less than four of them, add inline styles to make them larger. Job done.

But… that’s not very efficient, is it? You have to do that every time a new tweet appears, in any column, and that happens a lot. What would actually be better is to write some CSS which does this, and add that CSS one time, when you load up, and then you’re done. Have the browser do the heavy lifting, not us. It is a principle with me that if you can do a thing with CSS, then you should do it with CSS. JavaScript is there for things that CSS can’t do. Don’t use JavaScript, which makes you do the work, when you can use CSS and make the browser do it instead. The browser is better than you at it.

So… what you want to do is this. In pure CSS, if there’s a tweet (which in Tweetdeck is a <p> element) which contains <img class="emoji"> and nothing else, and there are four or fewer of these img.emoji elements, then make those images larger.

In pure CSS. No JavaScript. This is harder than it looks. Go and try to work it out, if you don’t believe me.

Well, the key insight here is that if you are an element, and you are :last-child(X), and you are also last-of-type(X), then there can’t be any elements after you which are not the same as you. So, if an img.emoji is the first of its type, and also the first element, and it’s the third last of its type, and also the third last element, then we know that it is element 1 of three identical elements. So an img:nth-child(1) which is also an img:nth-of-type(1) and which is also an img:nth-last-of-type(3) and also an img:nth-last-child(3) must be the first <img> in a group of three <img> elements. So that solves our problem! All we need is a selector which matches an img which is:

  • img 1 in a group of 1 image, or
  • img 1 in a group of 2 images, or
  • img 2 in a group of 2 images, or
  • img 1 in a group of 3 images, or
  • img 2 in a group of 3 images, or
  • img 3 in a group of 3 images, or
  • img 1 in a group of 4 images, or
  • …etc

…and that’s pretty easy, although long, to express as a CSS selector. So, to resize all img.emoji elements where (a) there are only img.emoji elements in this tweet and no text, and (b) there are four or fewer img.emoji in the tweet, we need a selector like this:

p > .emoji:nth-child(1):nth-of-type(1):nth-last-child(1):nth-last-of-type(1), /* 1 of 1 */
p > .emoji:nth-child(1):nth-of-type(1):nth-last-child(2):nth-last-of-type(2), /* 1 of 2 */
p > .emoji:nth-child(2):nth-of-type(2):nth-last-child(1):nth-last-of-type(1), /* 2 of 2 */
p > .emoji:nth-child(1):nth-of-type(1):nth-last-child(3):nth-last-of-type(3), /* 1 of 3 */
p > .emoji:nth-child(2):nth-of-type(2):nth-last-child(2):nth-last-of-type(2), /* 2 of 3 */
p > .emoji:nth-child(3):nth-of-type(3):nth-last-child(1):nth-last-of-type(1), /* 3 of 3 */
p > .emoji:nth-child(1):nth-of-type(1):nth-last-child(4):nth-last-of-type(4), /* 1 of 4 */
p > .emoji:nth-child(2):nth-of-type(2):nth-last-child(3):nth-last-of-type(3), /* 2 of 4 */
p > .emoji:nth-child(3):nth-of-type(3):nth-last-child(2):nth-last-of-type(2), /* 3 of 4 */
p > .emoji:nth-child(4):nth-of-type(4):nth-last-child(1):nth-last-of-type(1) {/* 4 of 4 */
    styles here

It looks long and cryptic and mystic, but actually it’s not that complicated at all. And, importantly, this is all the work you have to do. Add that CSS, and then any new tweets that come along which match our criteria get automatically styled to match. You don’t have to inspect every tweet and tweak it. The browser does the work, which is what the browser is designed for. If you can do a thing with CSS, then do it with CSS. Job done.

I’m quite proud of this. There’s an increasing, and depressing, movement to add more JavaScript to web pages, to write more code client side, to deal with huge JS downloads by improving compression rather than by just doing less JavaScript. I, myself, I’m in favour of having CSS do the things it can do, even if you have to be creative to solve that problem. Falling back to JavaScript to do styling is a failure. Use CSS where you can; being clever in how you do that CSS is part of the fun. You don’t need JS for this, really you don’t. CSS actually is awesome.

My pull request at Better Tweetdeck is, at time of writing, still pending. Proof that this technique works is in jsbin. Fingers crossed my PR gets accepted, and we can solve another problem with pure CSS.

Purity is great. And if you’re thinking, CSS can’t solve real problems… check out Bence Szab√≥‘s amazing pure CSS stacking game. I was open mouthed with awe. If you aren’t, maybe you should spend some time going back over how the web works, and then you will be too.

More in the discussion (powered by webmentions)

  • (no mentions, yet.)