[ad_1]
I’ve always been fascinated by how much we can achieve with HTML and CSS alone. The new interactive features of the Popover API are another example of how far we can go with just these two languages.
You may have seen other tutorials showing what the Popover API can do, but this is more of an article that mercilessly beats it into submission. We’ll add a little more pop Music to the mix, like balloons… a literal “pop,” if you will.
I’ve created a game – using only HTML and CSS, of course – based on the Popover API. Your task is to pop as many balloons as possible in less than a minute. But be careful! Some balloons are (as Gollum would say) “tricky” and will trigger more balloons.
I called it smart Let the balloons burst and we will do it together step by step. When we are done, it will look something like this (OK, Exactly like this:
popover
attribute
Dealing with Any element can be a popover as long as we use the popover
Attribute:
...
We don’t even have to deliver popover
with a value. By default popover
The initial value is auto
and uses what the spec calls “light dismiss.” This means that the popover can be closed by clicking anywhere outside of it. And when the popover opens, all other popovers on the page are closed unless they are nested. Automatic popovers are interdependent in this regard.
The other possibility is popover
to a manual
Value:
...
…which means that the element is opened and closed manually – we literally have to click a specific button to open and close it. In other words, manual
creates a stubborn popup that only closes when you press the right button and is completely independent of other popups on the page.
Element as starter
With the help of One of the challenges of building a game with the Popover API is that you can’t load a page if a popover is already open. And there’s no way around that with JavaScript if our goal is to build the game using only HTML and CSS.
Enter the
Element can be open by default:
If we follow this path, we can display a series of buttons (balloons) and “pop” them all until the very last balloon by pressing the
element so that they appear on the page when it loads.
I am talking about the basic structure:
This way we can click on the balloon in
to close the
and pop all the button balloons so that only one balloon remains (the
at the end (how to remove it, we’ll learn a little later).
You might think that
would be a more semantic direction for our game, and you would be right. But there are two disadvantages with
that doesn’t let us use it here:
- The only way to
that opens when the page loads is done with JavaScript. As far as I know, there is no way to
we can enter the game that is a
which is open when loading.
s are modal and prevent clicking on other things while they are open. We need to allow players to pop balloons outside of the
to beat the timer.
We therefore use a open>
Element as the top-level container of the game and with a simple
for the popups themselves, i.e.
.
All we need to do for now is make sure that all of these popovers and buttons are connected together so that when a button is clicked, a popover opens. You've probably learned this already in other tutorials, but we need to tell the popover element that there is a button it needs to respond to, and then tell the button that there is a popup it needs to open. To do this, we give the popover element a unique ID (as all IDs should be) and then reference the
with a popovertarget
Attribute:
Level 1 Popup
This is the idea when everything is wired together:
Opening and closing popoversThere is still a little work to be done in this final demo. One of the drawbacks of the game so far is that clicking on the
one popup
opens more popups; click on the same
again and they disappear. This makes the game too easy.
We can separate the opening and closing behavior by popovertargetaction
Attribute (no, the authors of the HTML specification were not interested in brevity) on the
If we set the attribute value to either show
or hide
The
only performs this one action for this particular popover.
Level 1 Popup
Note that I added a new
within the
which is aimed at a different goal
intentionally open or close not Setting the popovertargetaction
attribute on it. See how challenging (in a good way) it is to make the elements “pop”:
Styling balloonsNow we need to style the
And
elements are the same, so a player cannot tell which is which. Note that I said
And not
. This is because
is the actual element with which we
Container.
Most of it is pretty standard CSS work: setting backgrounds, padding, margins, sizes, borders, etc. However, there are a few important, not necessarily intuitive things to consider.
- First, the
list-style-type
Ownership of none
on the
Element to remove the triangular mark that indicates whether the
is open or closed. This marker is really useful and it's great to have it by default, but for a game like this it would be better to remove this hint to provide more of a challenge.
- Safari doesn't like this approach. To
Marker here we have to set a special pseudo-element with vendor prefix, summary::-webkit-details-marker
To display: none
.
- It would be good if the mouse pointer indicated that the speech bubbles are clickable, so that we
cursor: pointer
on the
Elements too.
- A final detail is the setting of the
user-select
Ownership of none
on the
s to prevent the speech bubbles - which are simply emoji text - from being selected. This makes them more like objects on the page.
- And yes, it is 2024 and we still need the prefix
-webkit-user-select
Feature to account for Safari support. Thanks, Apple.
All this in code on a .balloon
Class we use for the
And
Elements:
.balloon {
background-color: transparent;
border: none;
cursor: pointer;
display: block;
font-size: 4em;
height: 1em;
list-style-type: none;
margin: 0;
padding: 0;
text-align: center;
-webkit-user-select: none; /* Safari fallback */
user-select: none;
width: 1em;
}
One problem with the balloons is that some of them intentionally do nothing at all. This is because the popovers they close are not open. The player might think they didn't click/tap on that particular balloon or that the game is broken, so we add a little scaling while the balloon is in its :active
Clicking state:
.balloon:active {
scale: 0.7;
transition: 0.5s;
}
Bonus: Because the cursor
is a hand pointing at a balloon with the index finger. Clicking on a balloon looks like the hand is poking the balloon with the finger.
The way we distribute the bubbles on the screen is another important point to consider. Without JavaScript, we can't position them randomly, so that's out of the question. I tried a lot of things, like creating my own "random" numbers defined as custom properties that can be used as multipliers, but I couldn't make the overall result look so "random" without bubbles overlapping or creating some kind of visual pattern.
I ended up settling on a method that uses a class to position the balloons in different rows and columns - not like CSS Grid or Multicolumns, but imaginary rows and columns based on physical insets. It looks a bit like a grid and is less "random" than I'd like, but as long as none of the balloons have the same two classes, they don't overlap.
I chose an 8×8 grid, but left the first “row” and “column” blank so that the speech bubbles don’t stick out at the left and top edges of the browser.
/* Rows */
.r1 { --row: 1; }
.r2 { --row: 2; }
/* all the way up to .r7 */
/* Columns */
.c1 { --col: 1; }
.c2 { --col: 2; }
/* all the way up to .c7 */
.balloon {
/* This is how they're placed using the rows and columns */
top: calc(12.5vh * (var(--row) + 1) - 12.5vh);
left: calc(12.5vw * (var(--col) + 1) - 12.5vw);
}
Congratulate the player (or not)We have most of the pieces in place, but it would be great to have some kind of victory dance Popover to congratulate players when they manage to pop all the balloons in time.
Everything goes back to a open>
Element. As soon as this element not open
the game should be over and the last step is to pop the last balloon. So if we give this element an ID, we say: #root
we could create a condition to hide it with display: none
if it :not()
in a open
Condition:
#root:not([open]) {
display: none;
}
Here it is great that we have the :has()
Pseudo-selector because we can use it to #root
parent element of the element, so that if #root
is closed, we can select a child element of this parent element — a new element with the ID #congrats
— to display a fake popover showing the congratulations message to the player. (Yes, I'm aware of the irony.)
#game:has(#root:not([open])) #congrats {
display: flex;
}
If we were to play the game at this point, we could get the victory message without all the balloons popping. Again, manual popovers will not close unless the correct button is clicked - even if we close the original button.
Element.
Is there a way to detect within CSS that a popover is still open? Yes, enter the :popover-open
Pseudoclass.
The :popover-open
Pseudo class selects an open popover. We can use it in combination with :has()
from before to prevent the message from being displayed if a popover is still open on the page. This is what it looks like when you chain these things together so that they look like a and
conditional statement.
/* If #game does *not* have an open #root
* but has an element with an open popover
* (i.e. the game isn't over),
* then select the #congrats element...
*/
#game:has(#root:not([open])):has(:popover-open) #congrats {
/* ...and hide it */
display: none;
}
Well, the player is only congratulated if he actually, you know, win.
Conversely, we should inform the player that the game is over if a player fails to pop all the balloons before the timer runs out. Since we do not have a if()
conditional statement in CSS (not yet, at least) we run an animation for one minute so that this message appears to end the game.
#fail {
animation: fadein 0.5s forwards 60s;
display: flex;
opacity: 0;
z-index: -1;
}
@keyframes fadein {
0% {
opacity: 0;
z-index: -1;
}
100% {
opacity: 1;
z-index: 10;
}
}
However, we don't want the error message to be triggered when the victory screen is displayed, so we can write a selector that prevents this. #fail
Message simultaneously with #congrats
News.
#game:has(#root:not([open])) #fail {
display: none;
}
We need a game timerA player should know how much time he has to pop all the balloons. We can create a rather “simple” timer with an element that takes up the entire width of the screen (100vw
), scales it horizontally and then adjusts it to the animation above, which makes it #fail
Message to display.
#timer {
width: 100vw;
height: 1em;
}
#bar {
animation: 60s timebar forwards;
background-color: #e60b0b;
width: 100vw;
height: 1em;
transform-origin: right;
}
@keyframes timebar {
0% {
scale: 1 1;
}
100% {
scale: 0 1;
}
}
If there is only one point of failure, the game can become a little too easy, so let's try adding a second one.
Element with a second “root” ID, #root2
. We can use again :has
to verify that neither the #root
still #root2
Elements are open
before displaying the #congrats
News.
#game:has(#root:not([open])):has(#root2:not([open])) #congrats {
display: flex;
}
PackingNow all you have to do is play the game!
Fun, right? I'm sure we could have built something more robust without the self-imposed limitation of a JavaScript-free approach, and it's not like we released this in good faith as an accessibility pass, but pushing an API to the limit is fun. And instructive, right?
I'm curious: What other crazy ideas do you have for using popovers? Maybe you have another game in mind, a nifty UI effect, or a clever way to combine popovers with other new CSS features. like anchor positioning. Whatever it is, please let us know!
[ad_2]
Source link
Posted in Technology
would be a more semantic direction for our game, and you would be right. But there are two disadvantages with
that doesn’t let us use it here:
that opens when the page loads is done with JavaScript. As far as I know, there is no way to
we can enter the game that is a
which is open when loading.
s are modal and prevent clicking on other things while they are open. We need to allow players to pop balloons outside of the
to beat the timer. open>
Element as the top-level container of the game and with a simple
.
All we need to do for now is make sure that all of these popovers and buttons are connected together so that when a button is clicked, a popover opens. You've probably learned this already in other tutorials, but we need to tell the popover element that there is a button it needs to respond to, and then tell the button that there is a popup it needs to open. To do this, we give the popover element a unique ID (as all IDs should be) and then reference the
with a popovertarget
Attribute:
Level 1 Popup
This is the idea when everything is wired together:
Opening and closing popoversThere is still a little work to be done in this final demo. One of the drawbacks of the game so far is that clicking on the
one popup
opens more popups; click on the same
again and they disappear. This makes the game too easy.
We can separate the opening and closing behavior by popovertargetaction
Attribute (no, the authors of the HTML specification were not interested in brevity) on the
If we set the attribute value to either show
or hide
The
only performs this one action for this particular popover.
Level 1 Popup
Note that I added a new
within the
which is aimed at a different goal
intentionally open or close not Setting the popovertargetaction
attribute on it. See how challenging (in a good way) it is to make the elements “pop”:
Styling balloonsNow we need to style the
And
elements are the same, so a player cannot tell which is which. Note that I said
And not
. This is because
is the actual element with which we
Container.
Most of it is pretty standard CSS work: setting backgrounds, padding, margins, sizes, borders, etc. However, there are a few important, not necessarily intuitive things to consider.
- First, the
list-style-type
Ownership of none
on the
Element to remove the triangular mark that indicates whether the
is open or closed. This marker is really useful and it's great to have it by default, but for a game like this it would be better to remove this hint to provide more of a challenge.
- Safari doesn't like this approach. To
Marker here we have to set a special pseudo-element with vendor prefix, summary::-webkit-details-marker
To display: none
.
- It would be good if the mouse pointer indicated that the speech bubbles are clickable, so that we
cursor: pointer
on the
Elements too.
- A final detail is the setting of the
user-select
Ownership of none
on the
s to prevent the speech bubbles - which are simply emoji text - from being selected. This makes them more like objects on the page.
- And yes, it is 2024 and we still need the prefix
-webkit-user-select
Feature to account for Safari support. Thanks, Apple.
All this in code on a .balloon
Class we use for the
And
Elements:
.balloon {
background-color: transparent;
border: none;
cursor: pointer;
display: block;
font-size: 4em;
height: 1em;
list-style-type: none;
margin: 0;
padding: 0;
text-align: center;
-webkit-user-select: none; /* Safari fallback */
user-select: none;
width: 1em;
}
One problem with the balloons is that some of them intentionally do nothing at all. This is because the popovers they close are not open. The player might think they didn't click/tap on that particular balloon or that the game is broken, so we add a little scaling while the balloon is in its :active
Clicking state:
.balloon:active {
scale: 0.7;
transition: 0.5s;
}
Bonus: Because the cursor
is a hand pointing at a balloon with the index finger. Clicking on a balloon looks like the hand is poking the balloon with the finger.
The way we distribute the bubbles on the screen is another important point to consider. Without JavaScript, we can't position them randomly, so that's out of the question. I tried a lot of things, like creating my own "random" numbers defined as custom properties that can be used as multipliers, but I couldn't make the overall result look so "random" without bubbles overlapping or creating some kind of visual pattern.
I ended up settling on a method that uses a class to position the balloons in different rows and columns - not like CSS Grid or Multicolumns, but imaginary rows and columns based on physical insets. It looks a bit like a grid and is less "random" than I'd like, but as long as none of the balloons have the same two classes, they don't overlap.
I chose an 8×8 grid, but left the first “row” and “column” blank so that the speech bubbles don’t stick out at the left and top edges of the browser.
/* Rows */
.r1 { --row: 1; }
.r2 { --row: 2; }
/* all the way up to .r7 */
/* Columns */
.c1 { --col: 1; }
.c2 { --col: 2; }
/* all the way up to .c7 */
.balloon {
/* This is how they're placed using the rows and columns */
top: calc(12.5vh * (var(--row) + 1) - 12.5vh);
left: calc(12.5vw * (var(--col) + 1) - 12.5vw);
}
Congratulate the player (or not)We have most of the pieces in place, but it would be great to have some kind of victory dance Popover to congratulate players when they manage to pop all the balloons in time.
Everything goes back to a open>
Element. As soon as this element not open
the game should be over and the last step is to pop the last balloon. So if we give this element an ID, we say: #root
we could create a condition to hide it with display: none
if it :not()
in a open
Condition:
#root:not([open]) {
display: none;
}
Here it is great that we have the :has()
Pseudo-selector because we can use it to #root
parent element of the element, so that if #root
is closed, we can select a child element of this parent element — a new element with the ID #congrats
— to display a fake popover showing the congratulations message to the player. (Yes, I'm aware of the irony.)
#game:has(#root:not([open])) #congrats {
display: flex;
}
If we were to play the game at this point, we could get the victory message without all the balloons popping. Again, manual popovers will not close unless the correct button is clicked - even if we close the original button.
Element.
Is there a way to detect within CSS that a popover is still open? Yes, enter the :popover-open
Pseudoclass.
The :popover-open
Pseudo class selects an open popover. We can use it in combination with :has()
from before to prevent the message from being displayed if a popover is still open on the page. This is what it looks like when you chain these things together so that they look like a and
conditional statement.
/* If #game does *not* have an open #root
* but has an element with an open popover
* (i.e. the game isn't over),
* then select the #congrats element...
*/
#game:has(#root:not([open])):has(:popover-open) #congrats {
/* ...and hide it */
display: none;
}
Well, the player is only congratulated if he actually, you know, win.
Conversely, we should inform the player that the game is over if a player fails to pop all the balloons before the timer runs out. Since we do not have a if()
conditional statement in CSS (not yet, at least) we run an animation for one minute so that this message appears to end the game.
#fail {
animation: fadein 0.5s forwards 60s;
display: flex;
opacity: 0;
z-index: -1;
}
@keyframes fadein {
0% {
opacity: 0;
z-index: -1;
}
100% {
opacity: 1;
z-index: 10;
}
}
However, we don't want the error message to be triggered when the victory screen is displayed, so we can write a selector that prevents this. #fail
Message simultaneously with #congrats
News.
#game:has(#root:not([open])) #fail {
display: none;
}
We need a game timerA player should know how much time he has to pop all the balloons. We can create a rather “simple” timer with an element that takes up the entire width of the screen (100vw
), scales it horizontally and then adjusts it to the animation above, which makes it #fail
Message to display.
#timer {
width: 100vw;
height: 1em;
}
#bar {
animation: 60s timebar forwards;
background-color: #e60b0b;
width: 100vw;
height: 1em;
transform-origin: right;
}
@keyframes timebar {
0% {
scale: 1 1;
}
100% {
scale: 0 1;
}
}
If there is only one point of failure, the game can become a little too easy, so let's try adding a second one.
Element with a second “root” ID, #root2
. We can use again :has
to verify that neither the #root
still #root2
Elements are open
before displaying the #congrats
News.
#game:has(#root:not([open])):has(#root2:not([open])) #congrats {
display: flex;
}
PackingNow all you have to do is play the game!
Fun, right? I'm sure we could have built something more robust without the self-imposed limitation of a JavaScript-free approach, and it's not like we released this in good faith as an accessibility pass, but pushing an API to the limit is fun. And instructive, right?
I'm curious: What other crazy ideas do you have for using popovers? Maybe you have another game in mind, a nifty UI effect, or a clever way to combine popovers with other new CSS features. like anchor positioning. Whatever it is, please let us know!
[ad_2]
Source link
Posted in Technology