I’ve at all times been fascinated with how a lot we are able to do with simply HTML and CSS. The brand new interactive options of the Popover API are one more instance of simply how far we are able to get with these two languages alone.
You will have seen different tutorials on the market displaying off what the Popover API can do, however that is extra of a beating-it-mercilessly-into-submission type of article. We’ll add a little bit extra pop music to the combination, like with balloons… some literal “pop” if you’ll.
What I’ve finished is make a recreation — utilizing solely HTML and CSS, after all — leaning on the Popover API. You’re tasked with popping as many balloons as attainable in beneath a minute. However watch out! Some balloons are (as Gollum would say) “tricksy” and set off extra balloons.
I’ve cleverly referred to as it Pop(over) the Balloons and we’re going to make it collectively, step-by-step. After we’re finished it’ll look one thing like (OK, precisely like) this:
Dealing with the popover attribute
Any aspect is usually a popover so long as we vogue it with the popover attribute:
<div popover>…</div>
We don’t even have to produce popover with a worth. By default, popover‘s preliminary worth is auto and makes use of what the spec calls “mild dismiss.” Meaning the popover could be closed by clicking anyplace exterior of it. And when the popover opens, except they’re nested, some other popovers on the web page shut. Auto popovers are interdependent like that.
The opposite possibility is to set popover to a handbook worth:
<div popover=“handbook”>…</div>
…which signifies that the aspect is manually opened and closed — we actually should click on a particular button to open and shut it. In different phrases, handbook creates an ornery popup that solely closes whenever you hit the right button and is totally impartial of different popovers on the web page.
Utilizing the <particulars> aspect as a starter
One of many challenges of constructing a recreation with the Popover API is you could’t load a web page with a popover already open… and there’s no getting round that with JavaScript if our objective is to construct the sport with solely HTML and CSS.
Enter the <particulars> aspect. In contrast to a popover, the <particulars> aspect could be open by default:
<particulars open>
<!– remainder of the sport –>
</particulars>
If we pursue this route, we’re capable of present a bunch of buttons (balloons) and “pop” all of them right down to the final balloon by closing the <particulars>. In different phrases, we are able to plop our beginning balloons in an open <particulars> aspect so they’re displayed on the web page on load.
That is the fundamental construction I’m speaking about:
<particulars open>
<abstract>🎈</abstract>
<button>🎈</button>
<button>🎈</button>
<button>🎈</button>
</particulars>
On this manner, we are able to click on on the balloon in <abstract> to shut the <particulars> and “pop” all the button balloons, leaving us with one balloon (the <abstract> on the finish (which we’ll clear up how one can take away a little bit later).
You would possibly assume that <dialog> could be a extra semantic path for our recreation, and also you’d be proper. However there are two downsides with <dialog> that received’t allow us to use it right here:
The one solution to shut a <dialog> that’s open on web page load is with JavaScript. So far as I do know, there isn’t a detailed <button> we are able to drop within the recreation that can shut a <dialog> that’s open on load.
<dialog>s are modal and forestall clicking on different issues whereas they’re open. We have to permit players to pop balloons exterior of the <dialog> to be able to beat the timer.
Thus we might be utilizing a <particulars open> aspect as the sport’s top-level container and utilizing a plain ol’ <div> for the popups themselves, i.e. <div popover>.
All we have to do in the intervening time is make sure that all of those popovers and buttons are wired collectively in order that clicking a button opens a popover. You’ve most likely discovered this already from different tutorials, however we have to inform the popover aspect that there’s a button it wants to reply to, after which inform the button that there’s a popup it must open. For that, we give the popover aspect a singular ID (as all IDs must be) after which reference it on the <button> with a popovertarget attribute:
<!– Stage 0 is open by default –>
<particulars open>
<abstract>🎈</abstract>
<button popovertarget=”lvl1″>🎈</button>
</particulars>
<!– Stage 1 –>
<div id=”lvl1″ popover=”handbook”>
<h2>Stage 1 Popup</h2>
</div>
That is the thought when all the pieces is wired collectively:
Opening and shutting popovers
There’s a little bit extra work to do in that final demo. One of many downsides to the sport up to now is that clicking the <button> of a popup opens extra popups; click on that very same <button> once more they usually disappear. This makes the sport too simple.
We are able to separate the opening and shutting conduct by setting the popovertargetaction attribute (no, the HTML spec authors weren’t involved with brevity) on the <button>. If we set the attribute worth to both present or disguise, the <button> will solely carry out that one motion for that particular popover.
<!– Stage 0 is open by default –>
<particulars open>
<abstract>🎈</abstract>
<!– Present Stage 1 Popup –>
<button popovertarget=”lvl1″ popovertargetaction=”present”>🎈</button>
<!– Cover Stage 1 Popup –>
<button popovertarget=”lvl1″ popovertargetaction=”disguise”>🎈</button>
</particulars>
<!– Stage 1 –>
<div id=”lvl1″ popover=”handbook”>
<h2>Stage 1 Popup</h2>
<!– Open/Shut Stage 2 Poppup –>
<button popovertarget=”lvl2″>🎈</button>
</div>
<!– and so forth. –>
Be aware, that I’ve added a brand new <button> contained in the <div> that’s set to focus on one other <div> to pop open or shut by deliberately not setting the popovertargetaction attribute on it. See how difficult (in a great way) it’s to “pop” the weather:
Styling balloons
Now we have to model the <abstract> and <button> components the identical so {that a} participant can’t inform which is which. Be aware that I mentioned <abstract> and not <particulars>. That’s as a result of <abstract> is the precise aspect we click on to open and shut the <particulars> container.
Most of that is fairly normal CSS work: setting backgrounds, padding, margin, sizing, borders, and so forth. However there are a few essential, not essentially intuitive, issues to incorporate.
First, there’s setting the list-style-type property to none on the <abstract> aspect to eliminate the triangular marker that signifies whether or not the <particulars> is open or closed. That marker is absolutely helpful and nice to have by default, however for a recreation like this, it could be higher to take away that trace for a greater problem.
Safari doesn’t like that very same strategy. To take away the <particulars> marker right here, we have to set a particular vendor-prefixed pseudo-element, abstract::-webkit-details-marker to show: none.
It’d be good if the mouse cursor indicated that the balloons are clickable, so we are able to set cursor: pointer on the <abstract> components as properly.
One final element is setting the user-select property to none on the <abstract>s to stop the balloons — that are merely emoji textual content — from being chosen. This makes them extra like objects on the web page.
And sure, it’s 2024 and we nonetheless want that prefixed -webkit-user-select property to account for Safari assist. Thanks, Apple.
Placing all of that in code on a .balloon class we’ll use for the <button> and <abstract> components:
.balloon {
background-color: clear;
border: none;
cursor: pointer;
show: block;
font-size: 4em;
top: 1em;
list-style-type: none;
margin: 0;
padding: 0;
text-align: heart;
-webkit-user-select: none; /* Safari fallback */
user-select: none;
width: 1em;
}
One drawback with the balloons is that a few of them are deliberately doing nothing in any respect. That’s as a result of the popovers they shut should not open. The participant would possibly assume they didn’t click on/faucet that specific balloon or that the sport is damaged, so let’s add a little bit scaling whereas the balloon is in its :energetic state of clicking:
.balloon:energetic {
scale: 0.7;
transition: 0.5s;
}
Bonus: As a result of the cursor is a hand pointing its index finger, clicking a balloon type of seems to be just like the hand is poking the balloon with the finger. 👉🎈💥
The best way we distribute the balloons across the display is one other essential factor to think about. We’re unable to place them randomly with out JavaScript in order that’s out. I attempted a bunch of issues, like making up my very own “random” numbers outlined as customized properties that can be utilized as multipliers, however I couldn’t get the general consequence to really feel all that “random” with out overlapping balloons or establishing some type of visible sample.
I finally landed on a way that makes use of a category to place the balloons in numerous rows and columns — not like CSS Grid or Multicolumns, however imaginary rows and columns primarily based on bodily insets. It’ll look a bit Grid-like and is much less “randomness” than I would like, however so long as not one of the balloons have the identical two lessons, they received’t overlap one another.
I made a decision on an 8×8 grid however left the primary “row” and “column” empty so the balloons are away from the browser’s left and high edges.
/* Rows */
.r1 { –row: 1; }
.r2 { –row: 2; }
/* all the best way as much as .r7 */
/* Columns */
.c1 { –col: 1; }
.c2 { –col: 2; }
/* all the best way as much as .c7 */
.balloon {
/* That is how they’re positioned utilizing the rows and columns */
high: calc(12.5vh * (var(–row) + 1) – 12.5vh);
left: calc(12.5vw * (var(–col) + 1) – 12.5vw);
}
Congratulating The Participant (Or Not)
We have now many of the recreation items in place, but it surely’d be nice to have some type of victory dance popover to congratulate gamers after they efficiently pop all the balloons in time.
Every little thing goes again to a <particulars open> aspect. As soon as that aspect is not open, the sport must be over with the final step being to pop that last balloon. So, if we give that aspect an ID of, say, #root, we may create a situation to cover it with show: none when it’s :not() in an open state:
#root:not([open]) {
show: none;
}
That is the place it’s nice that we’ve got the :has() pseudo-selector as a result of we are able to use it to pick the #root aspect’s mother or father aspect in order that when #root is closed we are able to choose a toddler of that mother or father — a brand new aspect with an ID of #congrats — to show a fake popover displaying the congratulatory message to the participant. (Sure, I’m conscious of the irony.)
#recreation:has(#root:not([open])) #congrats {
show: flex;
}
If we had been to play the sport at this level, we may obtain the victory message with out popping all of the balloons. Once more, handbook popovers received’t shut except the right button is clicked — even when we shut its ancestral <particulars> aspect.
Is there a manner inside CSS to know {that a} popover remains to be open? Sure, enter the :popover-open pseudo-class.
The :popover-open pseudo-class selects an open popover. We are able to use it together with :has() from earlier to stop the message from displaying up if a popover remains to be open on the web page. Right here’s what it seems to be wish to chain this stuff collectively to work like an and conditional assertion.
/* If #recreation does *not* have an open #root
* however has a component with an open popover
* (i.e. the sport is not over),
* then choose the #congrats aspect…
*/
#recreation:has(#root:not([open])):has(:popover-open) #congrats {
/* …and conceal it */
show: none;
}
Now, the participant is simply congratulated after they truly, you realize, win.
Conversely, if a participant is unable to pop all the balloons earlier than a timer expires, we ought to tell the participant that the sport is over. Since we don’t have an if() conditional assertion in CSS (not but, a minimum of) we’ll run an animation for one minute in order that this message fades in to finish the sport.
#fail {
animation: fadein 0.5s forwards 60s;
show: flex;
opacity: 0;
z-index: -1;
}
@keyframes fadein {
0% {
opacity: 0;
z-index: -1;
}
100% {
opacity: 1;
z-index: 10;
}
}
However we don’t need the fail message to set off if the victory display is displaying, so we are able to write a selector that stops the #fail message from displaying similtaneously #congrats message.
#recreation:has(#root:not([open])) #fail {
show: none;
}
We want a recreation timer
A participant ought to know the way a lot time they should pop all the balloons. We are able to create a somewhat “easy” timer with a component that takes up the display’s full width (100vw), scaling it within the horizontal path, then matching it up with the animation above that enables the #fail message to fade in.
#timer {
width: 100vw;
top: 1em;
}
#bar {
animation: 60s timebar forwards;
background-color: #e60b0b;
width: 100vw;
top: 1em;
transform-origin: proper;
}
@keyframes timebar {
0% {
scale: 1 1;
}
100% {
scale: 0 1;
}
}
Having only one level of failure could make the sport a little bit too simple, so let’s strive including a second <particulars> aspect with a second “root” ID, #root2. As soon as extra, we are able to use :has to verify that neither the #root nor #root2 components are open earlier than displaying the #congrats message.
#recreation:has(#root:not([open])):has(#root2:not([open])) #congrats {
show: flex;
}
Wrapping up
The one factor left to do is play the sport!
Enjoyable, proper? I’m positive we may have constructed one thing extra sturdy with out the self-imposed limitation of a JavaScript-free strategy, and it’s not like we gave this a good-faith accessibility move, however pushing an API to the restrict is each enjoyable and academic, proper?
I’m : What different wacky concepts are you able to assume up for utilizing popovers? Possibly you will have one other recreation in thoughts, some slick UI impact, or some intelligent manner of mixing popovers with different rising CSS options, like anchor positioning. No matter it’s, please share!
Pop(over) the Balloons initially revealed on CSS-Tips, which is a part of the DigitalOcean household. You need to get the e-newsletter.
Subscribe to MarketingSolution.
Receive web development discounts & web design tutorials.
Now! Lets GROW Together!