Note: Read this to the end before you get too excited. This is an interesting CSS hack/exercise, but it faces a major hurdle that limits its applicability.
File this under: cool tricks with CSS that might be a poor use of your time, or might be more awesome than two unicorns doing synchronized backflip space diving.
Over the weekend while brushing up on CSS animations, I had an errant thought involving how the brains of certain types of flies operate.
Which I won’t go into for now. Let’s just say that nature is weird, providing very disparate solutions to the same problem. (In the case of the flies it was how they think.)
The relation to what I was doing at the time is that we’re in a situation where we can animate elements in a browser with the aid of Javascript, or we can now do it with just CSS. I was exploring the range of wacky things I could accomplish with pure CSS, sans Javascript. One of the interactions I wanted to see if I could accomplish was hitbox detection.
In video game programming, hitbox detection is used to determine when one moving element (like Blinky the ghost’s ectoplasmic tendrils of love) touches another moving element (like Pac-Man’s glorious, circular body.) And once that event is detected, the game then causes something to happen. (In the family-safe industry of 80′s arcade games, this means that Pac-Man dies a horrible death, questioning the futility of his existence. In my fevered dreams it would instead result in an erotic love scene of the Pac-Man/Blinky slash variety. Which, I think we all agree, would make for an excellent arcade machine hack.)
We’ve established that CSS can animate objects. But detecting a collision between two of them? Impossible!
Actually…
CSS does possess some rudimentary event detection based around the mouse interacting with page elements. :focus, :checked, :hover, etc. Most of them require users to actively do a discrete event, which is less useful for actively polling for something like the collision between two objects in real time.
:hover, however, provides us with a bit of a possibility. The mouse is just hanging out there on the page, doing its thing. It’s typically hovering over something. Can we make that work for us?
Hover over what?
The mouse hovers over one element at any given point in time, which triggers the :hover pseudo-class for CSS on that element and any of its parents, all the way up to the root.
Let’s assume a very simple problem situation which we’ll be turning into this demo. We have a stationary ‘avatar’, which for the purposes of our demo is a green circle sitting in the middle of the page. We also have an animated ‘mob’, which we’ll render as a black square. The mob is animated to sweep horizontally back and and forth across the page underneath the circle.
What we want to do is detect when the mob passes under the avatar in a simplified, horizontal-only collision detection.
We’ll want this to happen regardless of where our mouse is, which presents a problem. We can’t be hovering the cursor over the avatar, so how will we know when the mob lines up with us?
Sensors
What we want is the ability to detect an overlap at any point in time, regardless of the mouse’s position. To do this, we’re going to create a grid of invisible elements that are aligned around both the avatar and the mob. In our demo, we’ll call the ones attached to the avatar .switch, and the ones attached to the mob .sensor. We’re going to create enough sensors on each side of the elements so that regardless of where they are on our page, the mouse can be hovering over a sensor for each of them. In our case we’re only detecting for a horizontal overlap, so our sensors are as tall as the page. In a situation where we’re worried about vertical overlap, we’d be creating a grid of squares, greatly increasing the amount of discrete sensors we’d be making.
The avatar is stationary, so its sensors can remain stationary with it. The mob is not, so its sensors will have to be animated as well to follow its movements.
Double Hover Detection With Pointer-Events
The reason we’ve made all these sensor elements is to help us detect the avatar and mob overlapping regardless of where the mouse is. Each sensor grid is aligned in space around their respective icon, so when the mouse lines up over both the avatar’s sensors and the mob’s sensors of the same corresponding position, we know that where the avatar and mob are, they too are overlapped. (So, for example, if the leftmost avatar sensor and the leftmost mob sensor are both under the mouse, we can know that the avatar and mob are also overlapped).
Of course, the problem is that CSS can’t detect if the mouse is hovering over more than one element. The first one it encounters interrupts detection on the others.
Pointer-events is a CSS property that I don’t see discussed much, but that proves to be very convenient. It interacts with SVG in all sorts of strange ways, but with ordinary markup and CSS it still has two options. When set to “auto”, the element interacts with the mouse as desired. When set to “none”, the element no longer interacts with the mouse at all, becoming ‘transparent’ to mouse events. Hovers, clicks, it completely disregards them, and the events pass under it to whatever element is beneath.
We can exploit this to provide a sort of double hover detection.
In our demo, we make sure that all our .switch elements for the avatar’s sensors are set to a low z-index, and that the .sensor elements of the mob’s sensors are set to a higher z-index, but that .sensor also has been set with pointer-events: none. We also make sure that our .switch elements come earlier in our markup than the sensors.
.switch { z-index: 1; }
.sensor { z-index: 2; pointer-events; none; }
We then give .switch a rule for when it’s hovered over that causes its corresponding .sensor to regain its pointer-events (and keep it as long as the mouse is over it). In my demo I’ve given each sensor/switch pair a specific class that corresponds with each other. So for the leftmost pair we’d see a rule like this:
.switch.s1:hover ~ .sensor.s1 { pointer-events: auto; }
.sensor:hover { pointer-events: auto; }
This then causes the .sensor in question to be able to interact with the mouse. Since it has a higher z-index than .switch, it will then catch the mouse’s attention.
If at any point in time .sensor is being hovered over by the mouse and has its pointer-events: auto catching it, this means that it is also overlapping with the corresponding .switch. At which point we can say that we know that the mob and avatar are overlapped. So all we need now is to create a rule that acts on the hover event for .sensor. In this case, we’ll be turning the avatar red to show the event.
.sensor:hover ~ #avatar { background: red; }
Don’t Pop The Cork Yet
Victory!
Well, sadly, no.
I made this demo while using Firefox. And current builds of Firefox are very distinct from Webkit and Opera in one very crucial fashion: Firefox actively polls the mouse to determine if hover events are occurring even when the mouse is holding still. So if an animated element (like the mob or its sensors) pass under the mouse, it triggers :hover.
However, in these other browsers, they only bother polling for :hover when the mouse is being used (moving it about, clicking, scroll-wheel, etc). Otherwise, it stops looking and any moving elements passing under it don’t catch the :hover.
So here’s my demo of CSS Hitbox Detection. If you check it in a current Firefox build, you’ll see it working. If you check it in any other browser, you will have to keep wiggling the mouse about to make it work. It also appears that nightly builds of Firefox no longer support this, although I don’t know if that’s by intent or not.
I’ve used pretty big sensor boxes in the demo, as I’m only doing a proof-of-concept here. As such, there can be very significant overlap before the collision is detected. In normal circumstances you’d want your sensors to be as tiny as possible (and therefore have tons of them) to provide a much quicker detection.
As it stands, without the support of actively polling for hover, this isn’t useful for any scenario that requires real-time polling. But it may have other useful applications in an on-user demand. (It would be, for example, bad for a CSS-only Pac-Man game [which would be a nightmare in its own right], but might be usable in a situation where you click a button at a specific point to see if two elements are overlapped).
Notes
Here’s a pared down test of the use case of how browsers handle hover when the mouse is stationary when elements move under it.
Here’s a message by Eric Meyer on www-talk about the issue it brings up.
Thoughts? Workarounds? Uses?
So, CSS Hitbox Detection can exist. But due to how browsers support the :hover event, it may only have a narrow band of application. Obviously this is the sort of interaction that would be better handled by JS (which I knew all along, but was curious to see if a CSS alternative could be made to work).
That said, I’m just one guy with just one brain. I may have overlooked a workaround, or might be failing to see ways this would apply to something really cool despite it’s apparent limitations. Do any of you have thoughts? Ideas? Applications? Please let me know!