Hacker News new | ask | show | jobs
by mintplant 3751 days ago
This was a tough puzzle to crack, but I've got it fully solved here:

https://jsfiddle.net/5c0ruh8s/10/

Works cross-browser, submits a POST request to record votes, and hides both vote buttons after submitting.

Each up/down arrow is an <input type="image">, part of a form that submits to a hidden iframe. When either goes :active, a CSS sibling selector sets the height of a floating div from 0px to 100% to cover up the vote arrows. There's no selector for "button that's been clicked once before", so I use a CSS transition that has a delay of 0s to expand the height and a delay of 99999999s (basically forever) to shrink the height back down again when the arrow button goes from active->inactive. While the arrow button is :active I use "pointer-events: none" to make sure the click goes through, but once the form is submitted and the button goes inactive the div becomes opaque to click events again, so the UI only allows a vote to go through once.

No JavaScript required!

1 comments

I feel like there must be a simpler way than that.
That's what I thought too, at first. Some of the iterations I went through before reaching this point:

- Links for the up/down arrows with target=<hidden iframe>, that set "visibility: hidden" on :visited. But oh, only color-changing CSS properties are allowed for :visited selectors.

- Links for the up/down arrows with unicode ▲ and ▼ that set their color to the page background color on :visited, using a sibling selector to hide the other arrow as well once either has been clicked. But oh, sibling selectors are forbidden in conjunction with :visited selectors. Same for selecting nested elements inside a :visited link - only the simplest uses of :visited are allowed. One could wrap both arrows in the same <a> tag, but then there'd be no differentiating between an up/down vote.

- An invisible radio button next to each up/down arrow, with both wrapped inside a <label> so that clicking the arrow causes the radio button to become :checked. Use a sibling selector to hide the arrows when either radio button becomes checked. But oh, if you click a link inside a <label> it's not counted as marking the radio button as selected.

- An invisible radio button hidden under each vote up/down image. Each radio button is wrapped in an <a> tag that causes a hidden iframe to navigate to the voting link. The vote up/down buttons are "pointer-events: none", so trying to click on one really marks the radio button underneath as selected and triggers a link click. This works fine in Firefox, but in Chrome, marking a radio button as selected doesn't trigger a link click.

You could put each of them in an <iframe>.

(now to hide from the abuse, and hope my karma doesn't fall below the voting threshhold for suggesting this)

I thought of that, but then the arrows don't hide immediately after you click them as on HN - they'd still be visible until the request completes. So you'd end up with the same problem: hiding both arrows when either is clicked without using JS.

(I assume you mean putting each pair of arrows in an <iframe>, rather than each individual arrow.)

If it's a button, it'll be in the :focused state, you can use that to style it. Or you could use a CSS transition with a very very long time in one direction on the :active state.

And of course, you can use adjacency selectors with buttons, while you can't with :visited links.

That's exactly what I did, though :P

Check my first comment.