{"id":4594,"date":"2024-12-03T15:50:56","date_gmt":"2024-12-03T20:50:56","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=4594"},"modified":"2024-12-03T15:50:57","modified_gmt":"2024-12-03T20:50:57","slug":"pure-css-halftone-effect-in-3-declarations","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/pure-css-halftone-effect-in-3-declarations\/","title":{"rendered":"Pure CSS Halftone Effect in 3 Declarations"},"content":{"rendered":"\n<p>About half a decade ago, I got an idea about how to create a halftone effect with pure CSS. My original idea (which Michelle Barker&nbsp;<a href=\"https:\/\/css-irl.info\/css-halftone-patterns\/\" target=\"_blank\" rel=\"noreferrer noopener\">wrote about<\/a>&nbsp;a couple of years ago) was a bit inefficient, but in the years that followed, I&#8217;ve managed to polish it and reduce it to a single <code>&lt;div&gt;<\/code>, no pseudos and just three CSS properties.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What&#8217;s a halftone effect?<\/h2>\n\n\n\n<p>If you don&#8217;t know what a&nbsp;<a href=\"https:\/\/en.wikipedia.org\/wiki\/Halftone\" target=\"_blank\" rel=\"noreferrer noopener\">halftone<\/a>&nbsp;effect is, a very basic pattern looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"368\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/1c35b289ba9c002b.png?resize=900%2C368&#038;ssl=1\" alt=\"\" class=\"wp-image-4598\" style=\"width:460px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/1c35b289ba9c002b.png?w=900&amp;ssl=1 900w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/1c35b289ba9c002b.png?resize=300%2C123&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/1c35b289ba9c002b.png?resize=768%2C314&amp;ssl=1 768w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><figcaption class=\"wp-element-caption\">The simplest possible halftone pattern<\/figcaption><\/figure>\n\n\n\n<p>This is what we&#8217;ll be creating with a single <code>&lt;div&gt;<\/code> (no pseudo-elements) and only three CSS declarations. Afterwards, we&#8217;ll go through a bunch of variations and see some cooler-looking demos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The 3 CSS Declarations<\/h2>\n\n\n\n<p>The&nbsp;<strong>first declaration<\/strong>&nbsp;is a&nbsp;<code>background<\/code> and it consists of two layers. One is the&nbsp;<strong>pattern<\/strong>&nbsp;&#8211; the dots in our most basic case. The other is the&nbsp;<strong>map<\/strong>&nbsp;&#8211; this decides where the dots are bigger and where they are smaller. In the most simple case, it&#8217;s a linear gradient. So what we have so far in terms of code looks like this:<\/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-selector-tag\">background<\/span>: \n  <span class=\"hljs-selector-tag\">radial-gradient<\/span>(<span class=\"hljs-selector-tag\">closest-side<\/span>, <span class=\"hljs-selector-id\">#000<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>) 0\/ 1<span class=\"hljs-selector-tag\">em<\/span> 1<span class=\"hljs-selector-tag\">em<\/span> <span class=\"hljs-selector-tag\">space<\/span>, \n  <span class=\"hljs-selector-tag\">linear-gradient<\/span>(90<span class=\"hljs-selector-tag\">deg<\/span>, <span class=\"hljs-selector-id\">#000<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>);<\/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<p>We&#8217;ve made sure we have an integer number of dots along both axes by using the&nbsp;<a href=\"https:\/\/mastodon.social\/@anatudor\/113470617581654218\" target=\"_blank\" rel=\"noreferrer noopener\"><code>space<\/code><\/a>&nbsp;value for&nbsp;<code>background-repeat<\/code>.<\/p>\n\n\n\n<p>Taken separately, the two layers look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"720\" height=\"440\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/8c3b7923f2d3f881.png?resize=720%2C440&#038;ssl=1\" alt=\"\" class=\"wp-image-4603\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/8c3b7923f2d3f881.png?w=720&amp;ssl=1 720w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/8c3b7923f2d3f881.png?resize=300%2C183&amp;ssl=1 300w\" sizes=\"auto, (max-width: 720px) 100vw, 720px\" \/><figcaption class=\"wp-element-caption\">the pattern and the map<\/figcaption><\/figure>\n\n\n\n<p>Before we move any further, let&#8217;s take a closer look at these gradients. Each of the two layers goes from&nbsp;<code>black<\/code>, which can also be written as&nbsp;<code>rgb(0%, 0%, 0%)<\/code>&nbsp;or&nbsp;<code>hsl(0, 0%, 0%)<\/code>&nbsp;to&nbsp;<code>white<\/code>, which can also be written as&nbsp;<code>rgb(100%, 100%, 100%)<\/code>&nbsp;or&nbsp;<code>hsl(0, 0%, 100%)<\/code>.<\/p>\n\n\n\n<p>Dead in the middle we have&nbsp;<code>grey<\/code>, which is&nbsp;<code>rgb(50%, 50%, 50%)<\/code>&nbsp;or&nbsp;<code>hsl(0, 0%, 50%)<\/code>. This is the&nbsp;<code>50%<\/code>&nbsp;lightness grey or, in short, as we&#8217;ll be calling it from now on, the&nbsp;<code>50%<\/code>&nbsp;grey.<\/p>\n\n\n\n<p>Note that in the case of&nbsp;<em>any<\/em>&nbsp;grey, wherever it may be situated in between black and white, the saturation (the &#8216;S&#8217; in HSL) is always&nbsp;<code>0%<\/code>, while the hue (the &#8216;H&#8217; in HSL) is irrelevant, so we just use&nbsp;<code>0<\/code>. The only value that changes is the lightness (the &#8216;L&#8217; in HSL), which goes from&nbsp;<code>0%<\/code>&nbsp;for&nbsp;<code>black<\/code>&nbsp;to&nbsp;<code>100%<\/code>&nbsp;for&nbsp;<code>white<\/code>.<\/p>\n\n\n\n<p>Basically, going from&nbsp;<code>0%<\/code>&nbsp;to&nbsp;<code>100%<\/code>&nbsp;along the gradient line means going from&nbsp;<code>0%<\/code>&nbsp;to&nbsp;<code>100%<\/code>&nbsp;along the lightness axis of the&nbsp;<a href=\"https:\/\/codepen.io\/thebabydino\/full\/NvKEpd\">HSL bicone<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"720\" height=\"620\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/76247481aa23bfc8.png?resize=720%2C620&#038;ssl=1\" alt=\"\" class=\"wp-image-4604\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/76247481aa23bfc8.png?w=720&amp;ssl=1 720w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/76247481aa23bfc8.png?resize=300%2C258&amp;ssl=1 300w\" sizes=\"auto, (max-width: 720px) 100vw, 720px\" \/><figcaption class=\"wp-element-caption\">HSL bicone slice showing the lightness axis (<a href=\"https:\/\/codepen.io\/thebabydino\/full\/ZELeqVN\">live demo<\/a>)<\/figcaption><\/figure>\n\n\n\n<p>So in general, any&nbsp;<code>p%<\/code>&nbsp;grey can be written as&nbsp;<code>rgb(p%, p%, p%)<\/code>&nbsp;or&nbsp;<code>hsl(0, 0%, p%)<\/code>.<\/p>\n\n\n\n<p>This can be seen in the interactive demo below where you can drag the bar along the entire lightness range.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_BaXNORo\" src=\"\/\/codepen.io\/anon\/embed\/BaXNORo?height=450&amp;theme-id=47434&amp;slug-hash=BaXNORo&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed BaXNORo\" title=\"CodePen Embed BaXNORo\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Going back to our&nbsp;<code>background<\/code>&nbsp;with the pattern dots layer on top of the&nbsp;<code>linear-gradient()<\/code>&nbsp;map layer, we cannot see the map layer because it&#8217;s fully covered by the pattern layer. So the next step is to blend these two&nbsp;<code>background<\/code>&nbsp;layers using the&nbsp;<code>multiply<\/code>&nbsp;blend mode.<\/p>\n\n\n\n<p>This means the&nbsp;<strong>second declaration<\/strong>&nbsp;is:<\/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-selector-tag\">background-blend-mode<\/span>: <span class=\"hljs-selector-tag\">multiply<\/span><\/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>This works on a per pixel, per channel basis. We consider each layer to be a grid of pixels, we take every pair of corresponding pixels from the two layers and, for each of the three RGB channels, we multiply the corresponding channel values.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"999\" height=\"561\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/blend_pixels.png?resize=999%2C561&#038;ssl=1\" alt=\"\" class=\"wp-image-4606\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/blend_pixels.png?w=999&amp;ssl=1 999w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/blend_pixels.png?resize=300%2C168&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/blend_pixels.png?resize=768%2C431&amp;ssl=1 768w\" sizes=\"auto, (max-width: 999px) 100vw, 999px\" \/><figcaption class=\"wp-element-caption\">blending two layers at a pixel level<\/figcaption><\/figure>\n\n\n\n<p>So for each pair of pixels, the result of this blending operation is an RGB value where each channel value is the result of multiplying the corresponding channel values from the two layers.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">R = R\u2080\u00b7R\u2081\nG = G\u2080\u00b7G\u2081\nB = B\u2080\u00b7B\u2081<\/pre>\n\n\n\n<p>Note that what we&#8217;re multiplying is the decimal representation of percentage RGB values &#8211; that is, numbers in the&nbsp;<code>[0, 1]<\/code>&nbsp;interval. And when multiplying values in this interval, the result is always smaller or equal to the smallest of the two values multiplied.<\/p>\n\n\n\n<p>In our case, both gradients go from black to white, all we have in between are greys, which have all three RGB channels equal. So if at some point, both pixels in the pair of corresponding ones from the two layers have&nbsp;<code>rgb(50%, 50%, 50%)<\/code>, then the result of the&nbsp;<code>multiply<\/code>&nbsp;blend mode is&nbsp;<code>.25 = .5\u00b7.5<\/code>&nbsp;for each channel.<\/p>\n\n\n\n<p>We can see that the result of the&nbsp;<code>multiply<\/code>&nbsp;blend mode is always&nbsp;<em>at least as dark<\/em>&nbsp;as the darker of the two pixels whose RGB values we multiply. This is because the two RGB values are in the&nbsp;<code>[0, 1]<\/code>&nbsp;interval and, as mentioned before, multiplying such values always gives us a result that&#8217;s at most as big as the smallest of the two numbers multiplied. The smaller the channel values are, the darker the grey they represent is.<\/p>\n\n\n\n<p>After blending our pattern and map layers, we can see how overall, the pattern dots are now darker on the left where the map is closer to&nbsp;<code>black.<\/code><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"720\" height=\"240\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/fec92f6bed95ff66.png?resize=720%2C240&#038;ssl=1\" alt=\"\" class=\"wp-image-4607\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/fec92f6bed95ff66.png?w=720&amp;ssl=1 720w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/fec92f6bed95ff66.png?resize=300%2C100&amp;ssl=1 300w\" sizes=\"auto, (max-width: 720px) 100vw, 720px\" \/><figcaption class=\"wp-element-caption\">our two gradient layers, blended<\/figcaption><\/figure>\n\n\n\n<p>Below, you can see two scaled up dots from different points along the gradient line of the map. The second dot is further to the right (lighter) than the first one. The dark red circles mark the&nbsp;<code>50%<\/code>&nbsp;grey limit for each.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"720\" height=\"358\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/acf9b33d53e9759e.png?resize=720%2C358&#038;ssl=1\" alt=\"\" class=\"wp-image-4608\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/acf9b33d53e9759e.png?w=720&amp;ssl=1 720w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/acf9b33d53e9759e.png?resize=300%2C149&amp;ssl=1 300w\" sizes=\"auto, (max-width: 720px) 100vw, 720px\" \/><figcaption class=\"wp-element-caption\">darker dot vs. lighter dot<\/figcaption><\/figure>\n\n\n\n<p>For the darker dot, the&nbsp;<code>50%<\/code>&nbsp;grey limit is a bigger circle than in the case of the lighter dot. Inside each dark red circle, we have greys darker than a&nbsp;<code>50%<\/code>&nbsp;one. Outside, we have greys lighter than a&nbsp;<code>50%<\/code>&nbsp;one. Keep this in mind for later.<\/p>\n\n\n\n<p>The third and&nbsp;<strong>final declaration<\/strong>&nbsp;is a&nbsp;<code>filter<\/code>&nbsp;using a large&nbsp;<code>contrast()<\/code>&nbsp;value.<\/p>\n\n\n\n<p>For those not familiar with how&nbsp;<code>contrast()<\/code>&nbsp;works, it does one of two things, depending on whether its argument is subunitary or not.<\/p>\n\n\n\n<p>If its argument is subunitary, then it pushes every channel value towards&nbsp;<code>.5<\/code>&nbsp;, the middle of the&nbsp;<code>[0, 1]<\/code>&nbsp;interval. A value of&nbsp;<code>1<\/code>&nbsp;means no change, while a value of&nbsp;<code>0<\/code>&nbsp;means the channel has been pushed all the way to&nbsp;<code>.5<\/code>.<\/p>\n\n\n\n<p>This means that&nbsp;<code>contrast(0)<\/code>&nbsp;always gives us a&nbsp;<code>50%<\/code>&nbsp;grey, regardless of the&nbsp;<code>filter<\/code>&nbsp;input.<\/p>\n\n\n\n<p>You can see this in the interactive demo below &#8211; regardless of whether we apply our&nbsp;<code>filter<\/code>&nbsp;on a plain solid&nbsp;<code>background<\/code>&nbsp;box, opaque or semitransparent, a gradient or an image one, dragging the contrast down to&nbsp;<code>0<\/code>&nbsp;always turns it into a&nbsp;<code>50%<\/code>&nbsp;grey with the same alpha as the input.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_eYqpjOw\" src=\"\/\/codepen.io\/anon\/embed\/eYqpjOw?height=450&amp;theme-id=47434&amp;slug-hash=eYqpjOw&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed eYqpjOw\" title=\"CodePen Embed eYqpjOw\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Note that&nbsp;<code>contrast(100%)<\/code>&nbsp;is the same as&nbsp;<code>contrast(1)<\/code>,&nbsp;<code>contrast(50%)<\/code>&nbsp;is the same as&nbsp;<code>contrast(.5)<\/code>&nbsp;and so on.<\/p>\n\n\n\n<p>If the argument of the&nbsp;<code>contrast()<\/code>&nbsp;function is greater than&nbsp;<code>1<\/code>&nbsp;however, then each channel value gets pushed towards either&nbsp;<code>0<\/code>&nbsp;or&nbsp;<code>1<\/code>, whichever is closer. A contrast large enough can push the channel values all the way to&nbsp;<code>0<\/code>&nbsp;or&nbsp;<code>1<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_jOgbeQK\" src=\"\/\/codepen.io\/anon\/embed\/jOgbeQK?height=450&amp;theme-id=47434&amp;slug-hash=jOgbeQK&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed jOgbeQK\" title=\"CodePen Embed jOgbeQK\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>If we have a large enough contrast, all channel values are either zeroed (<code>0%<\/code>) or maxed out (<code>100%<\/code>) meaning we can only get one of eight possible results.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"202\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/0f7000a3388819f3.png?resize=1024%2C202&#038;ssl=1\" alt=\"\" class=\"wp-image-4609\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/0f7000a3388819f3.png?resize=1024%2C202&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/0f7000a3388819f3.png?resize=300%2C59&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/0f7000a3388819f3.png?resize=768%2C152&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/0f7000a3388819f3.png?resize=1536%2C303&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/0f7000a3388819f3.png?w=1541&amp;ssl=1 1541w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">8 possible RGB values where all channels are either zeroed or maxed out<\/figcaption><\/figure>\n\n\n\n<p>Coming back to our halftone pattern, we use:<\/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-tag\">filter<\/span>: <span class=\"hljs-selector-tag\">contrast<\/span>(16)<\/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, all greys darker than a&nbsp;<code>50%<\/code>&nbsp;one (<code>grey<\/code>&nbsp;or&nbsp;<code>rgb(50%, 50%, 50%)<\/code>&nbsp;or&nbsp;<code>hsl(0, 0%, 50%)<\/code>) get pushed to&nbsp;<code>black<\/code>&nbsp;and all the others to&nbsp;<code>white<\/code>.<\/p>\n\n\n\n<p>Now remember how the&nbsp;<code>50%<\/code>&nbsp;grey limit was a bigger circle if the dot was darker? That&#8217;s our limit for the contrast.<\/p>\n\n\n\n<p>Inside that circle, we have greys darker than a&nbsp;<code>50%<\/code>&nbsp;one, so they get pushed to&nbsp;<code>black<\/code>&nbsp;by large contrast vales. Outside it, the greys are lighter than a&nbsp;<code>50%<\/code>&nbsp;one, so they get pushed to&nbsp;<code>white<\/code>&nbsp;by large contrast values.<\/p>\n\n\n\n<p>Since the darker the dot, the bigger the&nbsp;<code>50%<\/code>&nbsp;limit circle, this means the halftone dots in the darker area of the map are bigger.<\/p>\n\n\n\n<p>So here&#8217;s the result we get after the third and final declaration:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"901\" height=\"368\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/509305e81f382281.png?resize=901%2C368&#038;ssl=1\" alt=\"\" class=\"wp-image-4610\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/509305e81f382281.png?w=901&amp;ssl=1 901w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/509305e81f382281.png?resize=300%2C123&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/509305e81f382281.png?resize=768%2C314&amp;ssl=1 768w\" sizes=\"auto, (max-width: 901px) 100vw, 901px\" \/><figcaption class=\"wp-element-caption\">the result so far<\/figcaption><\/figure>\n\n\n\n<p>We&#8217;re starting to get somewhere, but what we have so far is not ideal. And it makes sense we aren&#8217;t there yet.<\/p>\n\n\n\n<p>Since the left half of the map is darker than a&nbsp;<code>50%<\/code>&nbsp;grey (the RGB channel values are below&nbsp;<code>50%<\/code>&nbsp;or&nbsp;<code>.5<\/code>&nbsp;in decimal representation of the percentage), blending any other layer with it using the&nbsp;<code>multiply<\/code>&nbsp;blend mode gives us a result that&#8217;s at least as dark.<\/p>\n\n\n\n<p>This means the result of blending across the entire left half is a grey darker than a&nbsp;<code>50%<\/code>&nbsp;one, so that large value contrast pushes everything in the left half to&nbsp;<code>black<\/code>.<\/p>\n\n\n\n<p>The fix for this is pretty straightforward: we don&#8217;t make our gradients go all the way from black to white, but rather from mid greys to white. Furthermore, for best results, the map at its darkest should be a little bit brighter than a&nbsp;<code>50%<\/code>&nbsp;grey, while the pattern can be a bit darker.<\/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\"><span class=\"hljs-selector-tag\">background<\/span>: \n  <span class=\"hljs-selector-tag\">radial-gradient<\/span>(<span class=\"hljs-selector-tag\">closest-side<\/span>, <span class=\"hljs-selector-id\">#777<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>) 0\/ 1<span class=\"hljs-selector-tag\">em<\/span> 1<span class=\"hljs-selector-tag\">em<\/span> <span class=\"hljs-selector-tag\">space<\/span>, \n  <span class=\"hljs-selector-tag\">linear-gradient<\/span>(90<span class=\"hljs-selector-tag\">deg<\/span>, <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#fff<\/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>Much better!<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_dyoPdqj\" src=\"\/\/codepen.io\/anon\/embed\/dyoPdqj?height=450&amp;theme-id=47434&amp;slug-hash=dyoPdqj&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed dyoPdqj\" title=\"CodePen Embed dyoPdqj\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Now one thing to note here is that the contrast value needs to be enough to compensate for the blur radius of our dots. So if we increase the pattern size (the&nbsp;<code>background-size<\/code>&nbsp;for the pattern layer), then we also need to increase the contrast value accordingly.<\/p>\n\n\n\n<p>Let&#8217;s say we increase the&nbsp;<code>background-size<\/code>&nbsp;from&nbsp;<code>1em<\/code>&nbsp;to <code>9em<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"368\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/68e49afffb168fbf.png?resize=900%2C368&#038;ssl=1\" alt=\"\" class=\"wp-image-4611\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/68e49afffb168fbf.png?w=900&amp;ssl=1 900w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/68e49afffb168fbf.png?resize=300%2C123&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/68e49afffb168fbf.png?resize=768%2C314&amp;ssl=1 768w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><figcaption class=\"wp-element-caption\">bigger dots, blurry edges<\/figcaption><\/figure>\n\n\n\n<p>The dot edges are now blurry, so we also increase the contrast value from&nbsp;<code>16<\/code>&nbsp;to let&#8217;s say&nbsp;<code>80<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"368\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ae3db55090272760.png?resize=900%2C368&#038;ssl=1\" alt=\"\" class=\"wp-image-4612\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ae3db55090272760.png?w=900&amp;ssl=1 900w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ae3db55090272760.png?resize=300%2C123&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ae3db55090272760.png?resize=768%2C314&amp;ssl=1 768w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><figcaption class=\"wp-element-caption\">increased contrast, jagged edges<\/figcaption><\/figure>\n\n\n\n<p>Unfortunately, this results in ugly edges.<\/p>\n\n\n\n<p>A fix for this would be to then chain a slight blur and a contrast that&#8217;s enough to offset it. Generally, a contrast value that&#8217;s 2-3 times the blur value in pixels works pretty well.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">filter: contrast(80) blur(2px) contrast(5)<\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"368\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/13512b65def103c7.png?resize=900%2C368&#038;ssl=1\" alt=\"\" class=\"wp-image-4613\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/13512b65def103c7.png?w=900&amp;ssl=1 900w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/13512b65def103c7.png?resize=300%2C123&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/13512b65def103c7.png?resize=768%2C314&amp;ssl=1 768w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><figcaption class=\"wp-element-caption\">tiny blur + contrast smoothing fix<\/figcaption><\/figure>\n\n\n\n<p>An&nbsp;<a href=\"https:\/\/mastodon.social\/@anatudor\/112523336154596358\">even better fix<\/a>&nbsp;would involve using a custom SVG&nbsp;<code>filter<\/code>, but SVG filters are outside the scope of this article, so we&#8217;re not going there.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Variations<\/h2>\n\n\n\n<p>Now that we&#8217;ve gone through the basics, we can start making things more interesting in order to get a lot of cool results by varying at least one of the pattern or map layers.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">background<\/span>: \n  <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--pattern<\/span>, <span class=\"hljs-selector-tag\">radial-gradient<\/span>(<span class=\"hljs-selector-tag\">closest-side<\/span>, <span class=\"hljs-selector-id\">#777<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>) 0\/ 1<span class=\"hljs-selector-tag\">em<\/span> 1<span class=\"hljs-selector-tag\">em<\/span> <span class=\"hljs-selector-tag\">space<\/span>)),\n  <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--map<\/span>, <span class=\"hljs-selector-tag\">linear-gradient<\/span>(90<span class=\"hljs-selector-tag\">deg<\/span>, <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>));\n  <span class=\"hljs-selector-tag\">background-blend-mode<\/span>: <span class=\"hljs-selector-tag\">multiply<\/span>;\n  <span class=\"hljs-selector-tag\">filter<\/span>: <span class=\"hljs-selector-tag\">contrast<\/span>(16)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><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<h3 class=\"wp-block-heading\">Pattern variations<\/h3>\n\n\n\n<p>In this part, we&#8217;re keeping the map gradient unchanged and keeping the same hex values for the pattern gradients, though the pattern gradients themselves change. Depending on the pattern, we might also adjust the contrast.<\/p>\n\n\n\n<p>If you search for halftone patterns online, you&#8217;ll see that most of them don&#8217;t show a straight grid like we had above. So let&#8217;s fix that with a pattern made up of two layers.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--dot<\/span>: <span class=\"hljs-selector-tag\">radial-gradient<\/span>(<span class=\"hljs-selector-tag\">closest-side<\/span>, <span class=\"hljs-selector-id\">#777<\/span>, <span class=\"hljs-selector-id\">#fff<\/span> <span class=\"hljs-selector-tag\">calc<\/span>(100%\/<span class=\"hljs-selector-tag\">sqrt<\/span>(2)));\n<span class=\"hljs-selector-tag\">--pattern<\/span>: <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--dot<\/span>) 0 0\/ 2<span class=\"hljs-selector-tag\">em<\/span> 2<span class=\"hljs-selector-tag\">em<\/span>, <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--dot<\/span>) 1<span class=\"hljs-selector-tag\">em<\/span> 1<span class=\"hljs-selector-tag\">em<\/span>\/ 2<span class=\"hljs-selector-tag\">em<\/span> 2<span class=\"hljs-selector-tag\">em<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><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>In practice, I&#8217;d probably use a variable instead of&nbsp;<code>2em<\/code>&nbsp;and compute the offsets for the second layer of dots to be half of that.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"542\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ede0f7f9f78f8a37.png?resize=800%2C542&#038;ssl=1\" alt=\"\" class=\"wp-image-4614\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ede0f7f9f78f8a37.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ede0f7f9f78f8a37.png?resize=300%2C203&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ede0f7f9f78f8a37.png?resize=768%2C520&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">real halftone dots pattern<\/figcaption><\/figure>\n\n\n\n<p>Also, since we&#8217;ve increased the size of the dots, we&#8217;ve also bumped up the contrast value from&nbsp;<code>16<\/code>&nbsp;to&nbsp;<code>24<\/code>.<\/p>\n\n\n\n<p>Another option would be to use a&nbsp;<code>repeating-radial-gradient()<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">--pattern: repeating-radial-gradient(circle, #777, #fff, #777 1em)<\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"542\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/77e86cea5d4e399c.png?resize=800%2C542&#038;ssl=1\" alt=\"\" class=\"wp-image-4615\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/77e86cea5d4e399c.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/77e86cea5d4e399c.png?resize=300%2C203&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/77e86cea5d4e399c.png?resize=768%2C520&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">halftone ripples<\/figcaption><\/figure>\n\n\n\n<p>Something like this can even be animated or made interactive. We can place these halftone ripples&nbsp;<code>at var(--x) var(--y)<\/code>&nbsp;and change these custom properties on&nbsp;<code>mousemove<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_GRVzeRa\" src=\"\/\/codepen.io\/anon\/embed\/GRVzeRa?height=450&amp;theme-id=47434&amp;slug-hash=GRVzeRa&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed GRVzeRa\" title=\"CodePen Embed GRVzeRa\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We don&#8217;t have to limit ourselves to radial gradients. Linear ones work just as well. We can use a&nbsp;<code>repeating-linear-gradient()<\/code>, for example:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">--pattern: repeating-linear-gradient(#777, #fff, #777 1em)<\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"542\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ff734f29576a22b2.png?resize=800%2C542&#038;ssl=1\" alt=\"\" class=\"wp-image-4616\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ff734f29576a22b2.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ff734f29576a22b2.png?resize=300%2C203&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/ff734f29576a22b2.png?resize=768%2C520&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">thinning lines<\/figcaption><\/figure>\n\n\n\n<p>We can also animate the gradient angle (like in the demo below on hover) or make it change as we move the cursor over the pattern.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_MWNLMwp\" src=\"\/\/codepen.io\/anon\/embed\/MWNLMwp?height=450&amp;theme-id=47434&amp;slug-hash=MWNLMwp&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed MWNLMwp\" title=\"CodePen Embed MWNLMwp\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We can also restrict the&nbsp;<code>background-size<\/code>&nbsp;of a&nbsp;<code>linear-gradient()<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">--pattern: linear-gradient(45deg, #fff, #777) 0 \/ 1em 1em<\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"542\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/0b652054e1566c02.png?resize=800%2C542&#038;ssl=1\" alt=\"\" class=\"wp-image-4617\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/0b652054e1566c02.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/0b652054e1566c02.png?resize=300%2C203&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/0b652054e1566c02.png?resize=768%2C520&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">triangles<\/figcaption><\/figure>\n\n\n\n<p>Just like for the first dots pattern variation, here we&#8217;ve also bumped up the contrast.<\/p>\n\n\n\n<p>We can also add one extra stop:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">--pattern: linear-gradient(45deg, #fff, #777, #fff) 0 \/ 1em 1em<\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"542\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/67445d9e99cfbf3e.png?resize=800%2C542&#038;ssl=1\" alt=\"\" class=\"wp-image-4618\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/67445d9e99cfbf3e.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/67445d9e99cfbf3e.png?resize=300%2C203&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/67445d9e99cfbf3e.png?resize=768%2C520&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">fragments<\/figcaption><\/figure>\n\n\n\n<p>For both of the previous ones, the gradient angle can also be animated. This can be seen on hovering the panels in the demo below.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_WNVmGqa\" src=\"\/\/codepen.io\/anon\/embed\/WNVmGqa?height=450&amp;theme-id=47434&amp;slug-hash=WNVmGqa&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed WNVmGqa\" title=\"CodePen Embed WNVmGqa\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We can also play with conic gradients here. A simple repeating one produces rays that are thicker on the left than on the right.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--pattern<\/span>: <span class=\"hljs-selector-tag\">repeating-conic-gradient<\/span>(<span class=\"hljs-selector-id\">#777<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>, <span class=\"hljs-selector-id\">#777<\/span> 2<span class=\"hljs-selector-class\">.5<\/span>%)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><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>Without any&nbsp;<code>filter<\/code>&nbsp;adjustment however, the edges of these rays look bad, and so does the middle.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"542\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/9a13d610e3cf9b1e.png?resize=800%2C542&#038;ssl=1\" alt=\"\" class=\"wp-image-4619\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/9a13d610e3cf9b1e.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/9a13d610e3cf9b1e.png?resize=300%2C203&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/9a13d610e3cf9b1e.png?resize=768%2C520&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">rays, but with ugly edges<\/figcaption><\/figure>\n\n\n\n<p>Using the tiny blur plus a contrast value that&#8217;s 2-3 times the blur tactic fixes the ray edges:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"368\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/72904c5f8bde7582.png?resize=900%2C368&#038;ssl=1\" alt=\"\" class=\"wp-image-4620\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/72904c5f8bde7582.png?w=900&amp;ssl=1 900w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/72904c5f8bde7582.png?resize=300%2C123&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/72904c5f8bde7582.png?resize=768%2C314&amp;ssl=1 768w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><figcaption class=\"wp-element-caption\">smooth ray edges, but faded pattern edges<\/figcaption><\/figure>\n\n\n\n<p>&#8230; but the pattern&#8217;s edges are now faded! We have two possible fixes here.<\/p>\n\n\n\n<p>The first would be to remove the&nbsp;<code>filter<\/code>&nbsp;from the element itself and apply it on another element stacked on top of it as a&nbsp;<code>backdrop-filter<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_poMYWdz\" src=\"\/\/codepen.io\/anon\/embed\/poMYWdz?height=450&amp;theme-id=47434&amp;slug-hash=poMYWdz&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed poMYWdz\" title=\"CodePen Embed poMYWdz\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The second would be to make the element extend outwards a bit using a negative&nbsp;<code>margin<\/code>&nbsp;and then clip its edges by the same amount using&nbsp;<code>inset()<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_mdNopBO\" src=\"\/\/codepen.io\/anon\/embed\/mdNopBO?height=450&amp;theme-id=47434&amp;slug-hash=mdNopBO&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed mdNopBO\" title=\"CodePen Embed mdNopBO\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Things get a lot more fun if we limit the&nbsp;<code>background-size<\/code>&nbsp;of such a&nbsp;<code>conic-gradient()<\/code>&nbsp;pattern and then play with the start angle&nbsp;<code>--a<\/code>&nbsp;and the end percentage&nbsp;<code>--p<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--pattern<\/span>: \n  <span class=\"hljs-selector-tag\">repeating-conic-gradient<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>), \n    <span class=\"hljs-selector-id\">#fff<\/span>, <span class=\"hljs-selector-id\">#777<\/span>, <span class=\"hljs-selector-id\">#fff<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--p<\/span>)) 0\/ 3<span class=\"hljs-selector-tag\">em<\/span> 3<span class=\"hljs-selector-tag\">em<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_bGXZvaE\" src=\"\/\/codepen.io\/anon\/embed\/bGXZvaE?height=680&amp;theme-id=47434&amp;slug-hash=bGXZvaE&amp;default-tab=result\" height=\"680\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed bGXZvaE\" title=\"CodePen Embed bGXZvaE\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Map variations<\/h3>\n\n\n\n<p>In this part, we&#8217;re keeping the pattern constant and trying out different maps.<\/p>\n\n\n\n<p>Our&nbsp;<code>linear-gradient()<\/code>&nbsp;map doesn&#8217;t necessarily need to go along the&nbsp;<var>x<\/var>&nbsp;axis &#8211; it can of course have a variable angle:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--map<\/span>: <span class=\"hljs-selector-tag\">linear-gradient<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>), <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><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 demo below shows this angle being animated on hover:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_PoMvave\" src=\"\/\/codepen.io\/anon\/embed\/PoMvave?height=450&amp;theme-id=47434&amp;slug-hash=PoMvave&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed PoMvave\" title=\"CodePen Embed PoMvave\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We can also add an extra stop:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--map<\/span>: <span class=\"hljs-selector-tag\">linear-gradient<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>), <span class=\"hljs-selector-id\">#fff<\/span>, <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><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>Again, hovering the demo below animates the map direction.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_aberjzm\" src=\"\/\/codepen.io\/anon\/embed\/aberjzm?height=450&amp;theme-id=47434&amp;slug-hash=aberjzm&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed aberjzm\" title=\"CodePen Embed aberjzm\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We can also make our gradient a repeating one:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--map<\/span>: \n  <span class=\"hljs-selector-tag\">repeating-linear-gradient<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>), <span class=\"hljs-selector-id\">#fff<\/span>, <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#fff<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--p<\/span>))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_bGXyKjK\" src=\"\/\/codepen.io\/anon\/embed\/bGXyKjK?height=450&amp;theme-id=47434&amp;slug-hash=bGXyKjK&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed bGXyKjK\" title=\"CodePen Embed bGXyKjK\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Or we can switch to a&nbsp;<code>radial-gradient()<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--map<\/span>: \n  <span class=\"hljs-selector-tag\">radial-gradient<\/span>(<span class=\"hljs-selector-tag\">circle<\/span> <span class=\"hljs-selector-tag\">at<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--x<\/span>) <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--y<\/span>), <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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>In the demo below, the radial gradient&#8217;s position follows the cursor:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_NWQVBjV\" src=\"\/\/codepen.io\/anon\/embed\/NWQVBjV?height=450&amp;theme-id=47434&amp;slug-hash=NWQVBjV&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed NWQVBjV\" title=\"CodePen Embed NWQVBjV\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The radial gradient can be a repeating one too:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--map<\/span>: \n  <span class=\"hljs-selector-tag\">repeating-radial-gradient<\/span>(<span class=\"hljs-selector-tag\">circle<\/span> <span class=\"hljs-selector-tag\">at<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--x<\/span>) <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--y<\/span>), \n    <span class=\"hljs-selector-id\">#fff<\/span>, <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#fff<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--p<\/span>))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_MWNdxNJ\" src=\"\/\/codepen.io\/anon\/embed\/MWNdxNJ?height=450&amp;theme-id=47434&amp;slug-hash=MWNdxNJ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed MWNdxNJ\" title=\"CodePen Embed MWNdxNJ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Same thing goes for conic gradients.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--map<\/span>: \n  <span class=\"hljs-selector-tag\">conic-gradient<\/span>(<span class=\"hljs-selector-tag\">from<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>) <span class=\"hljs-selector-tag\">at<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--x<\/span>) <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--y<\/span>), \n    <span class=\"hljs-selector-id\">#fff<\/span>, <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_vYoqKBJ\" src=\"\/\/codepen.io\/anon\/embed\/vYoqKBJ?height=450&amp;theme-id=47434&amp;slug-hash=vYoqKBJ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed vYoqKBJ\" title=\"CodePen Embed vYoqKBJ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We can use a repeating one and control the number of repetitions as well.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--map<\/span>: \n  <span class=\"hljs-selector-tag\">repeating-conic-gradient<\/span>(<span class=\"hljs-selector-tag\">from<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>) <span class=\"hljs-selector-tag\">at<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--x<\/span>) <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--y<\/span>), \n    <span class=\"hljs-selector-id\">#fff<\/span>, <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#fff<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--p<\/span>))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_JjgQKbE\" src=\"\/\/codepen.io\/anon\/embed\/JjgQKbE?height=450&amp;theme-id=47434&amp;slug-hash=JjgQKbE&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed JjgQKbE\" title=\"CodePen Embed JjgQKbE\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>One thing that bugs me about some of the map variation demos, particularly about this last one, is the dot distortion. We can make it look less bad by sizing the element with the halftone&nbsp;<code>background<\/code>&nbsp;such that both its dimensions are multiples of the dot size and change the position in increments of the same dot size.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--d<\/span>: 1<span class=\"hljs-selector-tag\">em<\/span>;\n<span class=\"hljs-selector-tag\">--pattern<\/span>: \n  <span class=\"hljs-selector-tag\">radial-gradient<\/span>(<span class=\"hljs-selector-tag\">closest-side<\/span>, <span class=\"hljs-selector-id\">#777<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>) \n    0\/ <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--d<\/span>) <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--d<\/span>);\n<span class=\"hljs-selector-tag\">--map<\/span>: \n  <span class=\"hljs-selector-tag\">repeating-conic-gradient<\/span>(<span class=\"hljs-selector-tag\">from<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>) \n    <span class=\"hljs-selector-tag\">at<\/span> <span class=\"hljs-selector-tag\">round<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--x<\/span>), <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--d<\/span>)) <span class=\"hljs-selector-tag\">round<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--y<\/span>), <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--d<\/span>)), \n    <span class=\"hljs-selector-id\">#fff<\/span>, <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#fff<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--p<\/span>));\n<span class=\"hljs-selector-tag\">width<\/span>: <span class=\"hljs-selector-tag\">round<\/span>(<span class=\"hljs-selector-tag\">down<\/span>, 100<span class=\"hljs-selector-tag\">vw<\/span>, <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--d<\/span>));\n<span class=\"hljs-selector-tag\">height<\/span>: <span class=\"hljs-selector-tag\">round<\/span>(<span class=\"hljs-selector-tag\">down<\/span>, 100<span class=\"hljs-selector-tag\">vh<\/span>, <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--d<\/span>));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><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>But it&#8217;s&nbsp;<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/dyxBXgm\" target=\"_blank\" rel=\"noreferrer noopener\">not enough<\/a>. In order for our dots to always be perfectly round, we need an SVG&nbsp;<code>filter<\/code>&nbsp;<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/rNXjzLq\" target=\"_blank\" rel=\"noreferrer noopener\">solution<\/a>. However, that&#8217;s outside the scope of this article, so we&#8217;re not discussing it here.<\/p>\n\n\n\n<p>Even more interestingly, our map can be an image too. Taking any random image as it is won&#8217;t work well.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"542\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/6d4acb5fd5471079.png?resize=800%2C542&#038;ssl=1\" alt=\"\" class=\"wp-image-4621\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/6d4acb5fd5471079.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/6d4acb5fd5471079.png?resize=300%2C203&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/6d4acb5fd5471079.png?resize=768%2C520&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">using a random image as it is for the map doesn&#8217;t work well<\/figcaption><\/figure>\n\n\n\n<p>We need to bring its saturation down to zero and, for this particular technique, we need to make sure the lightness of its pixels is pretty much in the&nbsp;<code>[50%, 100%]<\/code>&nbsp;interval.<\/p>\n\n\n\n<p>The&nbsp;<a href=\"https:\/\/github.com\/web-platform-tests\/interop\/issues\/717\" target=\"_blank\" rel=\"noreferrer noopener\"><code>filter()<\/code><\/a>&nbsp;function&nbsp;<a href=\"https:\/\/codepen.io\/thebabydino\/full\/rNgBqNy\" target=\"_blank\" rel=\"noreferrer noopener\">could help<\/a>&nbsp;here, but, sadly, for&nbsp;<a href=\"https:\/\/iamvdo.me\/en\/blog\/advanced-css-filters#filter\" target=\"_blank\" rel=\"noreferrer noopener\">almost a decade now<\/a>, Safari has remained the only browser implementing it. We could make the pattern and the map layer each be a pseudo of an element, blend them together and apply the contrast&nbsp;<code>filter<\/code>&nbsp;on the pseudo-elements&#8217; parent. This way, the map pseudo could have a&nbsp;<code>filter<\/code>&nbsp;applied on it too. However, here we&#8217;re looking for solutions that don&#8217;t involve extra elements or pseudo-elements.<\/p>\n\n\n\n<p>Something we can do is make the map be the result of multiple blended background layers. Making the&nbsp;<code>background-color<\/code>&nbsp;<em>any<\/em>&nbsp;grey and blending it with the map image using the&nbsp;<code>luminosity<\/code>&nbsp;blend mode gives us a result that has the luminosity of the map image on top, the saturation of the&nbsp;<code>background-color<\/code>&nbsp;below and, since this is a grey (its saturation is&nbsp;<code>0%<\/code>), the hue becomes irrelevant.<\/p>\n\n\n\n<p>Note that luminosity is&nbsp;<a href=\"https:\/\/codepen.io\/thebabydino\/full\/RwoOMOZ\" target=\"_blank\" rel=\"noreferrer noopener\">not the same as lightness<\/a>&nbsp;(which is the &#8216;L&#8217; in HSL), though in a lot of cases, they&#8217;re close enough.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--pattern<\/span>: \n  <span class=\"hljs-selector-tag\">radial-gradient<\/span>(<span class=\"hljs-selector-tag\">closest-side<\/span>, <span class=\"hljs-selector-id\">#777<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>) 0\/ 1<span class=\"hljs-selector-tag\">em<\/span> 1<span class=\"hljs-selector-tag\">em<\/span> <span class=\"hljs-selector-tag\">space<\/span>;\n<span class=\"hljs-selector-tag\">--map<\/span>: <span class=\"hljs-selector-tag\">url<\/span>(<span class=\"hljs-selector-tag\">my-image<\/span><span class=\"hljs-selector-class\">.jpg<\/span>) 50%\/ <span class=\"hljs-selector-tag\">cover<\/span> <span class=\"hljs-selector-tag\">grey<\/span>;\n<span class=\"hljs-selector-tag\">background<\/span>: <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--pattern<\/span>), <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--map<\/span>);\n<span class=\"hljs-selector-tag\">background-blend-mode<\/span>: \n  <span class=\"hljs-selector-tag\">multiply<\/span> <span class=\"hljs-comment\">\/* between pattern &amp; map *\/<\/span>, \n  <span class=\"hljs-selector-tag\">luminosity<\/span> <span class=\"hljs-comment\">\/* between map layers *\/<\/span>;\n<span class=\"hljs-selector-tag\">filter<\/span>: <span class=\"hljs-selector-tag\">contrast<\/span>(16)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><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>We seem to be going in the right direction.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"542\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/43f69454ed0b1b46.png?resize=800%2C542&#038;ssl=1\" alt=\"\" class=\"wp-image-4622\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/43f69454ed0b1b46.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/43f69454ed0b1b46.png?resize=300%2C203&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/43f69454ed0b1b46.png?resize=768%2C520&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">using a fully desaturated map obtained via blending<\/figcaption><\/figure>\n\n\n\n<p>But it&#8217;s still not what we want, as this desaturated map is too dark, just like the first&nbsp;<code>black<\/code>&nbsp;to&nbsp;<code>white<\/code>&nbsp;map gradient we tried.<\/p>\n\n\n\n<p>We can brighten our map using the&nbsp;<code>screen<\/code>&nbsp;blend mode. Think of this blend mode as being the same as&nbsp;<code>multiply<\/code>, only with the ends of the lightness interval reversed.&nbsp;<code>multiply<\/code>&nbsp;always produces a result that&#8217;s at least as dark as the darkest of its two inputs,&nbsp;<code>screen<\/code>&nbsp;always produces a result that&#8217;s at least as bright as the brightest of its two inputs.<\/p>\n\n\n\n<p>In our case, if we use&nbsp;<code>screen<\/code>&nbsp;to blend the desaturated image we got at the previous step with a midway grey like&nbsp;<code>#888<\/code>, then the result is always at least as bright as&nbsp;<code>#888<\/code>. And it is&nbsp;<code>#888<\/code>&nbsp;only where we blend it with pure black pixels. Wherever we blend it with pixels brighter than pure black, the result is brighter than&nbsp;<code>#888<\/code>. So basically, we get a map that&#8217;s&nbsp;<code>#888<\/code>&nbsp;at its darkest, just like our base map gradient.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--pattern<\/span>: \n  <span class=\"hljs-selector-tag\">radial-gradient<\/span>(<span class=\"hljs-selector-tag\">closest-side<\/span>, <span class=\"hljs-selector-id\">#777<\/span>, <span class=\"hljs-selector-id\">#fff<\/span>) 0\/ 1<span class=\"hljs-selector-tag\">em<\/span> 1<span class=\"hljs-selector-tag\">em<\/span> <span class=\"hljs-selector-tag\">space<\/span>;\n<span class=\"hljs-selector-tag\">--map<\/span>: \n  <span class=\"hljs-selector-tag\">conic-gradient<\/span>(<span class=\"hljs-selector-id\">#888<\/span> 0 0), \n  <span class=\"hljs-selector-tag\">url<\/span>(<span class=\"hljs-selector-tag\">my-image<\/span><span class=\"hljs-selector-class\">.jpg<\/span>) 50%\/ <span class=\"hljs-selector-tag\">cover<\/span>\n  <span class=\"hljs-selector-tag\">grey<\/span>;\n<span class=\"hljs-selector-tag\">background<\/span>: <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--pattern<\/span>), <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--map<\/span>);\n<span class=\"hljs-selector-tag\">background-blend-mode<\/span>: \n  <span class=\"hljs-selector-tag\">multiply<\/span> <span class=\"hljs-comment\">\/* between pattern &amp; map *\/<\/span>, \n  <span class=\"hljs-selector-tag\">screen<\/span> <span class=\"hljs-comment\">\/* between map layers *\/<\/span>, \n  <span class=\"hljs-selector-tag\">luminosity<\/span> <span class=\"hljs-comment\">\/* between map layers *\/<\/span>;\n<span class=\"hljs-selector-tag\">filter<\/span>: <span class=\"hljs-selector-tag\">contrast<\/span>(16)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><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>Much better!<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"542\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/e26625a9ee83b58f.png?resize=800%2C542&#038;ssl=1\" alt=\"\" class=\"wp-image-4623\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/e26625a9ee83b58f.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/e26625a9ee83b58f.png?resize=300%2C203&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/e26625a9ee83b58f.png?resize=768%2C520&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">using a fully desaturared and brightened map via blending (<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/MWNMjGO\" target=\"_blank\" rel=\"noreferrer noopener\">live demo<\/a>)<\/figcaption><\/figure>\n\n\n\n<p>Again, some of the dots aren&#8217;t fully round, but in order to get fully round dots, we&#8217;d need an SVG&nbsp;<code>filter<\/code>&nbsp;and that&#8217;s a way too big of a topic to discuss here.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Palette variations<\/h3>\n\n\n\n<p>The simplest possible variation would be having white halftone dots on a black background. To do this, we can simply chain&nbsp;<code>invert(1)<\/code>&nbsp;to our&nbsp;<code>filter<\/code>.<\/p>\n\n\n\n<p>Or&#8230; we can do something else! We can use the&nbsp;<code>screen<\/code>&nbsp;blend mode we&#8217;ve used before to brighten the image map. As mentioned, this works like&nbsp;<code>multiply<\/code>, but with the ends of the lightness interval reversed. So let&#8217;s reverse them for both the pattern and the map.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">background<\/span>: \n  <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--pattern<\/span>, \n    <span class=\"hljs-selector-tag\">radial-gradient<\/span>(<span class=\"hljs-selector-tag\">closest-side<\/span>, <span class=\"hljs-selector-id\">#888<\/span>, <span class=\"hljs-selector-id\">#000<\/span>) 0\/ 1<span class=\"hljs-selector-tag\">em<\/span> 1<span class=\"hljs-selector-tag\">em<\/span> <span class=\"hljs-selector-tag\">space<\/span>), \n  <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--map<\/span>, \n    <span class=\"hljs-selector-tag\">linear-gradient<\/span>(90<span class=\"hljs-selector-tag\">deg<\/span>, <span class=\"hljs-selector-id\">#777<\/span>, <span class=\"hljs-selector-id\">#000<\/span>));\n<span class=\"hljs-selector-tag\">background-blend-mode<\/span>: <span class=\"hljs-selector-tag\">screen<\/span>;\n<span class=\"hljs-selector-tag\">filter<\/span>: <span class=\"hljs-selector-tag\">contrast<\/span>(16)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><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<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"600\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/da3b0c684bccb94d.png?resize=800%2C600&#038;ssl=1\" alt=\"\" class=\"wp-image-4624\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/da3b0c684bccb94d.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/da3b0c684bccb94d.png?resize=300%2C225&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/da3b0c684bccb94d.png?resize=768%2C576&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">inverted halftone dots pattern (<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/YzmmaXp\">live demo<\/a>)<\/figcaption><\/figure>\n\n\n\n<p>But we&#8217;re not limited to just black and white.<\/p>\n\n\n\n<p>Remember the part about how contrast works? Large contrast values push all pixels of the&nbsp;<code>filter<\/code>&nbsp;input to one of 8 possible RGB values. So far, our&nbsp;<code>filter<\/code>&nbsp;input has been just greys, so they got pushed to either black or white. But we don&#8217;t necessarily need to have just greys there. We could tweak those values to either zero or max out a channel or two everywhere.<\/p>\n\n\n\n<p>For example, if we max out one of the channels, then our black dots get that channel added to them. Maxing out the red channel gives us red dots, maxing out the blue channel gives us blue dots, maxing out both the red and blue channels gives us magenta dots.<\/p>\n\n\n\n<p>Going the other way, if we zero one of the channels, then it gets subtracted out of the white background. Zeroing the blue channel gives us a yellow background (the red and green channels are still maxed out for the background and combined, they give yellow). Zeroing the red channel gives us a cyan background. Zeroing both the blue and green channels gives us a red background.<\/p>\n\n\n\n<p>You can play with various scenarios in the interactive demo below:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_RwXmQYZ\" src=\"\/\/codepen.io\/anon\/embed\/RwXmQYZ?height=450&amp;theme-id=47434&amp;slug-hash=RwXmQYZ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed RwXmQYZ\" title=\"CodePen Embed RwXmQYZ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We can of course also have more interesting palettes and we can even have halftone dots on top of image backgrounds using the pure CSS&nbsp;<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/qBGWXNR\" target=\"_blank\" rel=\"noreferrer noopener\">blending technique<\/a>&nbsp;I detailed in&nbsp;<a href=\"https:\/\/codepen.io\/thebabydino\/project\/full\/ZjwjBe\" target=\"_blank\" rel=\"noreferrer noopener\">a talk on the topic<\/a>&nbsp;I used to give in 2020 or by using&nbsp;<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/abMvzoo\" target=\"_blank\" rel=\"noreferrer noopener\">SVG<\/a>&nbsp;<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/bGXeGoa\" target=\"_blank\" rel=\"noreferrer noopener\">filters<\/a>. Both of these approaches however require more than just one element with no pseudos and three CSS properties, so we won&#8217;t be going into details about them here.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Combining these variations (and more!)<\/h3>\n\n\n\n<p>Varying more than one of the above can help with interesting results.<\/p>\n\n\n\n<p>By using top to bottom linear gradients for both the pattern and the map, with the pattern one having its size limited to&nbsp;<code>10%<\/code>&nbsp;of the element, we can get the effect below without needing to use a&nbsp;<code>mask<\/code>&nbsp;gradient with many irregulrly placed stops. Blending with some extra layers helps us with a nicer palette for the final result.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_jOvMQMO\" src=\"\/\/codepen.io\/anon\/embed\/jOvMQMO?height=450&amp;theme-id=47434&amp;slug-hash=jOvMQMO&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed jOvMQMO\" title=\"CodePen Embed jOvMQMO\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We can also animate a map&#8217;s&nbsp;<code>background-position<\/code>&nbsp;to get a blinds effect like below:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_MWPWXXw\" src=\"\/\/codepen.io\/anon\/embed\/MWPWXXw?height=450&amp;theme-id=47434&amp;slug-hash=MWPWXXw&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed MWPWXXw\" title=\"CodePen Embed MWPWXXw\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>In the demo above, we&#8217;ve also blended the halftone pattern with an image. Here&#8217;s another such example (note that this doesn&#8217;t work in Firefox due to&nbsp;<a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=1481498\" target=\"_blank\" rel=\"noreferrer noopener\">bug 1481498<\/a>, which has everything to do with the text on the right side and nothing to do with the halftone part):<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"540\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/6f1546f3d6c887ad.png?resize=800%2C540&#038;ssl=1\" alt=\"\" class=\"wp-image-4625\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/6f1546f3d6c887ad.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/6f1546f3d6c887ad.png?resize=300%2C203&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/6f1546f3d6c887ad.png?resize=768%2C518&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">card with halftone effect (<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/GRNKqRv\" target=\"_blank\" rel=\"noreferrer noopener\">live demo<\/a>)<\/figcaption><\/figure>\n\n\n\n<p>Note that the code for all these demos so far is heavily commented, explaining the purpose of pretty much every CSS declaration in there.<\/p>\n\n\n\n<p>The example below uses a\u00a0<code>repeating-radial-gradient()<\/code> pattern and a\u00a0<code>conic-gradient()<\/code> map, which funny enough, also creates a tiny heart in the middle.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_oNWjoqN\" src=\"\/\/codepen.io\/anon\/embed\/oNWjoqN?height=575&amp;theme-id=47434&amp;slug-hash=oNWjoqN&amp;default-tab=result\" height=\"575\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed oNWjoqN\" title=\"CodePen Embed oNWjoqN\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>For a bit of a different effect, here&#8217;s a rhombic halftone one created by using two blended layers for the map &#8211; two otherwise identical linear gradients going in different directions:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_xxYMOoQ\" src=\"\/\/codepen.io\/anon\/embed\/xxYMOoQ?height=450&amp;theme-id=47434&amp;slug-hash=xxYMOoQ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed xxYMOoQ\" title=\"CodePen Embed xxYMOoQ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The demo below is a combination of two halftone patterns stacked one on top of the other, the top one being masked using a&nbsp;<code>conic-gradient()<\/code>&nbsp;<a href=\"https:\/\/css-tricks.com\/background-patterns-simplified-by-conic-gradients\/#aa-checkerboard\" target=\"_blank\" rel=\"noreferrer noopener\">checkerboard<\/a>&nbsp;<code>mask<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_LYyNVJQ\" src=\"\/\/codepen.io\/anon\/embed\/LYyNVJQ?height=450&amp;theme-id=47434&amp;slug-hash=LYyNVJQ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed LYyNVJQ\" title=\"CodePen Embed LYyNVJQ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Here are a few more halftone samples as card backgrounds:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_QWMRWRQ\" src=\"\/\/codepen.io\/anon\/embed\/QWMRWRQ?height=450&amp;theme-id=47434&amp;slug-hash=QWMRWRQ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed QWMRWRQ\" title=\"CodePen Embed QWMRWRQ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Even more such halftone samples can be found in this gallery:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_NWxBzRv\" src=\"\/\/codepen.io\/anon\/embed\/NWxBzRv?height=450&amp;theme-id=47434&amp;slug-hash=NWxBzRv&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed NWxBzRv\" title=\"CodePen Embed NWxBzRv\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We aren&#8217;t limited to 2D. We can also use such paterns in 3D and even animate them.<\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player\" style=\"\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='306' src='https:\/\/videopress.com\/embed\/eeDElcKd?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=0&amp;persistVolume=1&amp;playsinline=0&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent=\"true\" allow='clipboard-write'><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1725245713'><\/script><\/div>\n\t\t\t<figcaption>excavated cube with animated halftone (<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/RwQjpmQ\">live demo<\/a>)<\/figcaption>\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p>Finally, even more demos showcasing halftone patterns can be found in&nbsp;<a href=\"https:\/\/codepen.io\/collection\/JYoLNJ\" target=\"_blank\" rel=\"noreferrer noopener\">this CodePen collection<\/a>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/codepen.io\/collection\/JYoLNJ\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"781\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/14cc30f5f74044f0.png?resize=1024%2C781&#038;ssl=1\" alt=\"\" class=\"wp-image-4633\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/14cc30f5f74044f0.png?resize=1024%2C781&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/14cc30f5f74044f0.png?resize=300%2C229&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/14cc30f5f74044f0.png?resize=768%2C586&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/14cc30f5f74044f0.png?w=1180&amp;ssl=1 1180w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/a><figcaption class=\"wp-element-caption\">the CodePen collection<\/figcaption><\/figure>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A halftone is a pattern of dots that vary in size and spacing. It&#8217;s a printing technique that you normally don&#8217;t see, but blown up in size, is a cool aesthetic. This is a deep dive on how it can be done in CSS alone, starting quite simply! <\/p>\n","protected":false},"author":32,"featured_media":4598,"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":[7,262,201,263,89],"class_list":["post-4594","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-css","tag-filter","tag-gradients","tag-halftone","tag-mask"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/1c35b289ba9c002b.png?fit=900%2C368&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4594","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\/32"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=4594"}],"version-history":[{"count":16,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4594\/revisions"}],"predecessor-version":[{"id":4676,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4594\/revisions\/4676"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/4598"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=4594"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=4594"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=4594"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}