{"id":2424,"date":"2024-05-29T10:40:25","date_gmt":"2024-05-29T16:40:25","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=2424"},"modified":"2024-05-29T10:40:26","modified_gmt":"2024-05-29T16:40:26","slug":"how-to-make-a-css-timer","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/how-to-make-a-css-timer\/","title":{"rendered":"How to Make a CSS Timer"},"content":{"rendered":"\n<p>There are <em>times<\/em> when we can use a <em>timer<\/em> on websites. \ud83d\ude09<\/p>\n\n\n\n<p>Perhaps we want to time a quiz or put pressure on a survey. Maybe we are just trying to making a dramatic countdown after a user has done something great like successfully booking a concert ticket. We could be trying to build a micro time management tool (think <em>Pomodoro<\/em>). Or it could just be an alternative to a spinner UI. <\/p>\n\n\n\n<p>When those situations come up, I have no qualms employing JavaScript which is probably a more powerful tool for this sort of thing generally. And yet! CSS substitutes are just as fun and efficient when the simplest option is the best one.\u00a0Some email clients these days are highly CSS capable, but would never run JavaScript, so perhaps that situation could be an interesting progressive enhancement.<\/p>\n\n\n\n<p>Let&#8217;s take a look at what it takes to cook up a CSS timer. We&#8217;ll use some modern CSS tech to do it. The ingredients?<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>CSS Counters<\/li>\n\n\n\n<li>@property<\/li>\n\n\n\n<li>pseudo elements<\/li>\n\n\n\n<li>@keyframes<\/li>\n\n\n\n<li>A little Celtic sea salt to taste<\/li>\n<\/ol>\n\n\n\n<p>To get started, <a href=\"https:\/\/pen.new\/\">fire up a (Code)Pen<\/a> and keep it warm. Below is the demo we\u2019ll be working towards (later, I\u2019ll show some stylized examples):<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_GRaZbBB\" src=\"\/\/codepen.io\/anon\/embed\/GRaZbBB?height=450&amp;theme-id=47434&amp;slug-hash=GRaZbBB&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed GRaZbBB\" title=\"CodePen Embed GRaZbBB\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>There are three main requirements for our CSS Timer:<\/p>\n\n\n\n<ol class=\"wp-block-list\" start=\"1\">\n<li>A number that can decrement from 5 to 0<\/li>\n\n\n\n<li>A way to time five seconds, and decrement the number in each<\/li>\n\n\n\n<li>A way to display the decreasing number on page&nbsp;<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">The Number<\/h2>\n\n\n\n<p>For our first requirement, the update-able number, we\u2019ll use <code><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@property\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@property\" target=\"_blank\" rel=\"noreferrer noopener\">@property<\/a><\/code><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@property\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a> to create a custom property that will hold a value of type <code>&lt;integer&gt;<\/code>.&nbsp;<\/p>\n\n\n\n<p><strong><em>Note<\/em><\/strong>: <a target=\"_blank\" href=\"https:\/\/www.britannica.com\/science\/integer\" rel=\"noreferrer noopener\">Integer<\/a> numbers can be zero, or a positive or negative whole number. If you want numbers with decimal points, use &lt;number&gt;, which holds a real number.&nbsp;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@property<\/span> --n {\n\u00a0 <span class=\"hljs-selector-tag\">syntax<\/span>: \"&lt;<span class=\"hljs-selector-tag\">integer<\/span>&gt;\";\n\u00a0 <span class=\"hljs-selector-tag\">inherits<\/span>: <span class=\"hljs-selector-tag\">false<\/span>;\n\u00a0 <span class=\"hljs-selector-tag\">initial-value<\/span>: 0;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">The Counting<\/h2>\n\n\n\n<p>For tracking seconds, while decreasing the number, we go to <a target=\"_blank\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@keyframes\" rel=\"noreferrer noopener\">\ufffc<\/a><a target=\"_blank\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@keyframes\" rel=\"noreferrer noopener\">@keyframes<\/a><a target=\"_blank\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@keyframes\" rel=\"noreferrer noopener\">\ufffc<\/a> animation.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@keyframes<\/span> count {\n\u00a0 <span class=\"hljs-selector-tag\">from<\/span> { <span class=\"hljs-attribute\">--n<\/span>: <span class=\"hljs-number\">5<\/span>; }\n\u00a0 <span class=\"hljs-selector-tag\">to<\/span>   { <span class=\"hljs-attribute\">--n<\/span>: <span class=\"hljs-number\">0<\/span>; }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The animation function is put to action with the <a target=\"_blank\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/animation\" rel=\"noreferrer noopener\">\ufffc<\/a><a target=\"_blank\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/animation\" rel=\"noreferrer noopener\">animation<\/a><a target=\"_blank\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/animation\" rel=\"noreferrer noopener\">\ufffc<\/a> property.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.timer<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> {\n\u00a0 <span class=\"hljs-attribute\">animation<\/span>: <span class=\"hljs-number\">5s<\/span> linear count;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here\u2019s what\u2019s happening:&nbsp;<\/p>\n\n\n\n<p>When we <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/CSS_Properties_and_Values_API\/guide#using_registered_custom_properties\" target=\"_blank\" rel=\"noreferrer noopener\">register a custom property<\/a> for a specific value type, <code>&lt;integer><\/code>, <code>&lt;percentage><\/code>, or <code>&lt;color><\/code>, for instances, the browser knows that the property is created to work with that <em>specific<\/em> type of value.\u00a0<\/p>\n\n\n\n<p>With that knowledge the browser confidently updates the custom property\u2019s value in the future, even throughout an animation.&nbsp;<\/p>\n\n\n\n<p>That\u2019s why our property <code>--n<\/code> can go from 5 to 0 within an animation, and since the animation is set for five seconds, that\u2019s essentially <strong>counting from five to zero over a period of five seconds<\/strong>. Hence, a timer is born.\u00a0<\/p>\n\n\n\n<p>But there\u2019s still the matter of printing out the counted numbers onto the page. If you hadn\u2019t noticed earlier, I\u2019d assigned the animation to a pseudo-element, and that should give you a clue for our next move \u2014 <a target=\"_blank\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/content\" rel=\"noreferrer noopener\">\ufffc<\/a><a target=\"_blank\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/content\" rel=\"noreferrer noopener\">content<\/a>.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Display<\/h2>\n\n\n\n<p><strong>The property, <code>content<\/code>, can display contents we have not yet added to the HTML ourselves<\/strong>. We generally use this property for a variety of things, because this accepts a variety of values \u2014 images, strings, counters, quotation marks, even attribute values. It doesn\u2019t, however, directly takes a number. So, we\u2019ll feed it our number <code>--n<\/code> through <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/counter\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/counter\" target=\"_blank\" rel=\"noreferrer noopener\"><code>counter<\/code><\/a>.\u00a0<\/p>\n\n\n\n<p>A counter can be set with either <code>counter-reset<\/code> or <code>counter-increment<\/code>. We\u2019ll use <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/counter-reset\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/counter-reset\" target=\"_blank\" rel=\"noreferrer noopener\"><code>counter-reset<\/code><\/a>. This property\u2019s value is a counter name and an integer. Since <code>counter-reset<\/code> doesn\u2019t correctly process a CSS variable or custom property for an integer yet, but does accept <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/calc\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/calc\" target=\"_blank\" rel=\"noreferrer noopener\"><code>calc()<\/code><\/a><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/calc\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a>, the <code>calc()<\/code> function becomes our Trojan Horse, inside of which we\u2019ll send in &#8211;n.\u00a0<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-selector-class\">.timer<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>\u00a0 <span class=\"hljs-attribute\">animation<\/span>: <span class=\"hljs-number\">5s<\/span> linear count;\n<\/span><\/span><span class='shcb-loc'><span>\u00a0 <span class=\"hljs-attribute\">animation-fill-mode<\/span>: forwards;\u00a0\n<\/span><\/span><mark class='shcb-loc'><span>\u00a0 <span class=\"hljs-attribute\">counter-reset<\/span>: n <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">0<\/span> + var(--n));\n<\/span><\/mark><mark class='shcb-loc'><span>\u00a0 <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-built_in\">counter<\/span>(n);\n<\/span><\/mark><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That is:<\/p>\n\n\n\n<ol class=\"wp-block-list\" start=\"1\">\n<li>Our animate-able number, <code>--n<\/code>, is first fed to<code> calc()<\/code>&nbsp;<\/li>\n\n\n\n<li><code>calc()<\/code> is then fed to <code>counter()<\/code><\/li>\n\n\n\n<li>The <code>counter()<\/code> in turn is given to <code>content<\/code>, finally rendering <code>--n<\/code> on the page.\u00a0<\/li>\n<\/ol>\n\n\n\n<p>The rest is taken care of by the browser. It knows <code>--n<\/code> is an integer. The browser keeps up with animation changing this integer from 5 to 0 in five seconds. Then, because the integer is used in a <code>content<\/code> value, the browser displays the integer on the page as it updates.<\/p>\n\n\n\n<p>At the end of the animation, the <code>animation-fill-mode: forwards;<\/code> style rule prevents the timer from reverting back to the initial <code>--n<\/code> value, zero, right away.&nbsp;<\/p>\n\n\n\n<p>Once again, here\u2019s the final demo:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_GRaZbBB\" src=\"\/\/codepen.io\/anon\/embed\/GRaZbBB?height=450&amp;theme-id=47434&amp;slug-hash=GRaZbBB&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed GRaZbBB\" title=\"CodePen Embed GRaZbBB\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>For design variants you can count up or down, or play with its appearance, or you can combine this with other typical loader or progress designs, like a <a href=\"https:\/\/www.smashingmagazine.com\/2023\/10\/animate-along-path-css\/\" target=\"_blank\" rel=\"noreferrer noopener\">circular animation<\/a>.&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_XWwdLPK\" src=\"\/\/codepen.io\/anon\/embed\/XWwdLPK?height=450&amp;theme-id=47434&amp;slug-hash=XWwdLPK&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed XWwdLPK\" title=\"CodePen Embed XWwdLPK\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_ExzxrWX\" src=\"\/\/codepen.io\/anon\/embed\/ExzxrWX?height=450&amp;theme-id=47434&amp;slug-hash=ExzxrWX&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed ExzxrWX\" title=\"CodePen Embed ExzxrWX\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>At the time of writing, Firefox is the only missing browser support for <code>@property<\/code>, but they have <a href=\"https:\/\/groups.google.com\/a\/mozilla.org\/g\/dev-platform\/c\/UhQSvl_v6xk\/m\/HngYYWOJBwAJ\">announced an intent to ship<\/a>, so shouldn&#8217;t be long now. For support reference, here\u2019s the <a href=\"https:\/\/caniuse.com\/mdn-css_at-rules_property\" target=\"_blank\" rel=\"noreferrer noopener\">caniuse.com page for <code>@property<\/code><\/a>.\u00a0<\/p>\n\n\n\n<p>CSS Custom Properties can also be set and updated in JavaScript. So, if at some point you would like to be able to update the property in JavaScript, just about with any other CSS property, you can do it using the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/CSSStyleDeclaration\/setProperty\" target=\"_blank\" rel=\"noreferrer noopener\"><code>setProperty()<\/code><\/a> function. And if you wish to create a new custom property in JavaScript, that can be done with <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/CSS\/registerProperty_static\" target=\"_blank\" rel=\"noreferrer noopener\"><code>registerProperty()<\/code><\/a>.\u00a0The other direction, if you wanted to let JavaScript know a CSS animation has completed, you could listen for the <code><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Element\/animationend_event\">animationend<\/a><\/code> end event.<\/p>\n\n\n\n<p>If you&#8217;re really into this sort of thing, also check out Yuan Chuan&#8217;s recent <a href=\"https:\/\/yuanchuan.dev\/time-based-css-animations\">Time-based CSS Animations<\/a> article. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s look at using CSS as an efficient alternative to JavaScript for creating simple timers. We&#8217;ll use modern CSS properties like @property, @keyframes, and pseudo-elements with counter() values.<\/p>\n","protected":false},"author":20,"featured_media":2428,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[134,7,180],"class_list":["post-2424","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-property","tag-css","tag-custom-properties"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/pexels-photo-1078057.jpeg?fit=1880%2C1058&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2424","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=2424"}],"version-history":[{"count":4,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2424\/revisions"}],"predecessor-version":[{"id":2439,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2424\/revisions\/2439"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/2428"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=2424"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=2424"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=2424"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}