{"id":2774,"date":"2024-06-21T05:17:46","date_gmt":"2024-06-21T11:17:46","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=2774"},"modified":"2024-06-21T05:17:48","modified_gmt":"2024-06-21T11:17:48","slug":"pure-css-circular-text-without-requiring-a-monospace-font","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/pure-css-circular-text-without-requiring-a-monospace-font\/","title":{"rendered":"Pure CSS Circular Text (without Requiring a Monospace Font)"},"content":{"rendered":"\n<p class=\"has-drop-cap\">There is no simple and obvious way to set text on a circle in CSS. Good news though! You can create a beautiful, colorful, and even\u00a0<em>rotating<\/em>\u00a0circular text with pure CSS. It just takes a bit of work and we&#8217;ll go over that here.<\/p>\n\n\n\n<p>Circular-set text can be used as a decorative element, a cool headline, wrapping a call-to-action button, a loading animation, and really anything else you can think of.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"507\" height=\"487\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/5Jl637dV.png?resize=507%2C487&#038;ssl=1\" alt=\"\" class=\"wp-image-2776\" style=\"width:300px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/5Jl637dV.png?w=507&amp;ssl=1 507w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/5Jl637dV.png?resize=300%2C288&amp;ssl=1 300w\" sizes=\"auto, (max-width: 507px) 100vw, 507px\" \/><\/figure>\n<\/div>\n\n\n<p>This whole concept of circular text is not new. I encountered a few articles and videos about it over the years, starting with&nbsp;<a href=\"https:\/\/css-tricks.com\/set-text-on-a-circle\/\">Chris Coyier\u2019s article from way back in 2012<\/a>, up until this&nbsp;<a href=\"https:\/\/dev.to\/jh3y\/circular-text-with-css-57jf\">article by Jhey Tompkins<\/a>&nbsp;from last year that shows a great use of CSS trig functions. Both of them, and others, use a very similar method: splitting the text into individual characters, then rotating these characters around a common center. Like Chris shows in his article:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"260\" height=\"274\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/RBLFebgK.png?resize=260%2C274&#038;ssl=1\" alt=\"\" class=\"wp-image-2777\" style=\"width:230px;height:auto\"\/><figcaption class=\"wp-element-caption\">Each letter becomes a very tall rectangle rotated around a common point at the far end away from the letter.<\/figcaption><\/figure>\n<\/div>\n\n\n<p>While it does work, and looks great, it limits us to using a monospace font.\u00a0Otherwise, the characters start to overlap each other, it doesn\u2019t look as good, and the text can be unreadable. But I&#8217;ve never liked this limitation (or limitations in general to be honest), so we\u2019re going to look at a completely different method that allows us to use any font we want, not necessarily monospace, including weird cursive fonts, and even emojis. And hopefully we can learn a new thing or two along the way, so let\u2019s begin!<\/p>\n\n\n\n<p>In order to get the desired result, we will divide our work into three steps.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-1-splitting\">Step 1) Splitting<\/h2>\n\n\n\n<p>As I said, up until now, people tended to split the text into individual characters (e.g.&nbsp;<a href=\"https:\/\/splitting.js.org\/\">Splitting.js<\/a>), but now we&#8217;re actually going to split the text into equal width segments (using&nbsp;<code>&lt;span&gt;<\/code>&nbsp;elements) and each segment will have a visual piece of the final text. In order to do that we will add a number of span elements into our text container, each containing the desired text (please read the accessibility note at the end of this part).<\/p>\n\n\n\n<p>In the example we&#8217;re building toward here I\u2019m using 24&nbsp;<code>&lt;span&gt;<\/code>s, but the number of segments is up to you. The more segments you use the better it will look, just don&#8217;t use <em>too<\/em> much as it might affect performance, especially if you&#8217;re going to animate the text.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"textContainer\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">\"--i: 0;\"<\/span>&gt;<\/span>Lorem ipsum dolor sit amet consectetur adipisicing elit<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">\"--i: 1;\"<\/span>&gt;<\/span>Lorem ipsum dolor sit amet consectetur adipisicing elit<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">\"--i: 2;\"<\/span>&gt;<\/span>Lorem ipsum dolor sit amet consectetur adipisicing elit<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-comment\">&lt;!-- add more spans --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As you can see, I&#8217;ve also added a&nbsp;<code>style<\/code>&nbsp;attribute to each span and inline a custom property of&nbsp;<code>--i<\/code>&nbsp;with an ascending value. We will use this value later on to position each segment.<\/p>\n\n\n\n<p>For the CSS, let&#8217;s set the&nbsp;<code>font-size<\/code>&nbsp;to something a bit bigger, say&nbsp;<code>60px<\/code>, and we&#8217;re going to set the&nbsp;<code>width<\/code>&nbsp;of each span to&nbsp;<code>1em<\/code>. Next, we want to keep the text in one line so we&#8217;ll add&nbsp;<code>white-space<\/code>&nbsp;of&nbsp;<code>nowrap<\/code>, and set the inner position of each text using&nbsp;<code>text-indent<\/code>&nbsp;with a simple calc function in it of the&nbsp;<code>--i<\/code>&nbsp;custom property times&nbsp;<code>-1em<\/code>&nbsp;(the width). And the last thing, to keep everything inside the span, we will use&nbsp;<code>overflow: hidden<\/code>.<\/p>\n\n\n\n<p>We&#8217;re going to add two more things (just temporarily, so we can see things better): a&nbsp;<code>display<\/code>&nbsp;of&nbsp;<code>inline-block<\/code>&nbsp;as the default display of a span is&nbsp;<code>inline<\/code>, and some of the properties we set don&#8217;t work on inline elements. And we will add an&nbsp;<code>outline<\/code>&nbsp;for each span to see they&#8217;re outline.<\/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-class\">.textContainer<\/span> {\n  span {\n    <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">1em<\/span>;\n    <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-number\">60px<\/span>;\n    <span class=\"hljs-attribute\">white-space<\/span>: nowrap;\n    <span class=\"hljs-attribute\">text-indent<\/span>: <span class=\"hljs-built_in\">calc<\/span>(var(--i) * -<span class=\"hljs-number\">1em<\/span>);\n    <span class=\"hljs-attribute\">overflow<\/span>: hidden;\n    \n    <span class=\"hljs-comment\">\/* temporarily *\/<\/span>\n    <span class=\"hljs-attribute\">display<\/span>: inline-block;\n    <span class=\"hljs-attribute\">outline<\/span>: <span class=\"hljs-number\">2px<\/span> solid red;   \n  }\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>And this is the result so far:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_zYQppdZ\/a26fac856060f2597509881a43c784e8\" src=\"\/\/codepen.io\/anon\/embed\/zYQppdZ\/a26fac856060f2597509881a43c784e8?height=450&amp;theme-id=47434&amp;slug-hash=zYQppdZ\/a26fac856060f2597509881a43c784e8&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed zYQppdZ\/a26fac856060f2597509881a43c784e8\" title=\"CodePen Embed zYQppdZ\/a26fac856060f2597509881a43c784e8\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>As you can see, we ended up with a bunch of red rectangles, each containing a continuous segment of the text. I&#8217;m also using the well-known Lobster font here, as it is cursive, and I kind of like it, so why not. <\/p>\n\n\n\n<div class=\"wp-block-group box\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<header>\n  Accessibility Note\n<\/header>\n\n\n\n<div class=\"wp-block-group box-content\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<p><strong>Please note that this is<\/strong>&nbsp;<strong>not accessible!<\/strong>&nbsp;If we leave it as is, screen readers will read the text repeatedly for each span. This is not good, so in order to avoid it, I&#8217;ve added the&nbsp;<code>aria-hidden<\/code>&nbsp;attribute on the text container.&nbsp;If you wanted the text to be read by screen readers (once), you could add&nbsp;<code>aria-hidden<\/code>&nbsp;to all but the first one.<\/p>\n<\/div><\/div>\n<\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-2-positioning\">Step 2) Positioning<\/h2>\n\n\n\n<p>Our next step, similarly to monospace font techniques, is to position these segments in a circle. This positioning will be done using <code>transform<\/code>, but first let&#8217;s add a&nbsp;<code>position: absolute<\/code>&nbsp;to each segment, and a&nbsp;<code>position: relative<\/code>&nbsp;to the&nbsp;<code>.textContainer<\/code> as the main context for the positioning.<\/p>\n\n\n\n<p>On the <code>transform<\/code> property we&#8217;re going to need to add a few things. First, we want to center these elements so we&#8217;ll add a&nbsp;<code>translate<\/code>&nbsp;of <code>-50%<\/code> on each axis. Now we need to&nbsp;<code>rotate<\/code>&nbsp;each span, and the angle of the rotation depends on the number of elements we are using. Here, because we are using 24 elements, the rotation for each element will be 360\u00b0 divided by 24, that is 15\u00b0. <\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">360\u00b0 \u00f7 24 Segments = 15\u00b0 per Segment<\/pre>\n\n\n\n<p>So we&#8217;ll add a&nbsp;<code>rotate<\/code>&nbsp;with a calc function of&nbsp;<code>15deg<\/code>&nbsp;times the&nbsp;<code>--i<\/code>&nbsp;custom property. Now we can&nbsp;<code>translate<\/code>&nbsp;each element again, moving it upwards (on the y-axis), and to figure out exactly how much we need to move it. We&#8217;re going to use some basic math functions here:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">.textContainer {\n  <span class=\"hljs-attr\">position<\/span>: relative;\n\n  span {\n    <span class=\"hljs-attr\">position<\/span>: absolute;\n    transform:\n      translate(<span class=\"hljs-number\">-50<\/span>%, <span class=\"hljs-number\">-50<\/span>%)\n      rotate(calc(<span class=\"hljs-number\">15<\/span>deg * <span class=\"hljs-keyword\">var<\/span>(--i)))\n      translateY(calc(<span class=\"hljs-number\">-1<\/span>em \/ sin(<span class=\"hljs-number\">15<\/span>deg)));\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now let&#8217;s add this code to what we already have (note that we don&#8217;t need the&nbsp;<code>display: inline-block<\/code>&nbsp;anymore as we are using&nbsp;<code>position: absolute<\/code>&nbsp;on the span), and what we get is a nice circle of partially overlapping segments.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_BaeJJrg\/b464525bb0ea2e95546caac55a3ffed2\" src=\"\/\/codepen.io\/anon\/embed\/BaeJJrg\/b464525bb0ea2e95546caac55a3ffed2?height=450&amp;theme-id=47434&amp;slug-hash=BaeJJrg\/b464525bb0ea2e95546caac55a3ffed2&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed BaeJJrg\/b464525bb0ea2e95546caac55a3ffed2\" title=\"CodePen Embed BaeJJrg\/b464525bb0ea2e95546caac55a3ffed2\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>If we take a closer look at how these segments are touching each other, we can see that the segments are overlapping at the bottom (the inner side of the circle), and are far away from each other on the top (the outer side of the circle).<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"848\" height=\"540\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/zGF7BKgb.png?resize=848%2C540&#038;ssl=1\" alt=\"\" class=\"wp-image-2779\" style=\"width:544px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/zGF7BKgb.png?w=848&amp;ssl=1 848w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/zGF7BKgb.png?resize=300%2C191&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/zGF7BKgb.png?resize=768%2C489&amp;ssl=1 768w\" sizes=\"auto, (max-width: 848px) 100vw, 848px\" \/><\/figure>\n<\/div>\n\n\n<p>This is of course not what we want, we want these segments to connect smoothly and seamlessly. <strong>This is where the magic happens<\/strong>!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-3-perspective\">Step 3) Perspective<\/h2>\n\n\n\n<p>In order for these rectangles to connect seamlessly they actually shouldn&#8217;t be rectangles, but some sort of trapezoids. Wider at the top and thinner at the bottom than the original rectangle. In CSS, when we want to make a trapezoid out of a rectangle, most of the time we will use a&nbsp;<code>clip-path<\/code>&nbsp;or a&nbsp;<code>mask<\/code>, but that won&#8217;t be ideal in our situation as we might clip out (or mask out) important visual information of the text itself.<\/p>\n\n\n\n<p>What we need to do is to &#8216;stretch out&#8217; the top part of each segment and &#8216;squeeze in&#8217; the lower part, so that the segments will connect as they should without us losing any visual information of the text. And we can actually do it using&nbsp;<code>perspective<\/code>.<\/p>\n\n\n\n<p>The most basic thing about <code>perspective<\/code> is that things that are closer to us look bigger, and things that are farther away from us look smaller. So we can utilize perspective, rotate the segments in a way that the top part would be closer to us (and look wider), and the bottom part would be farther away from us and would look smaller.<\/p>\n\n\n\n<p>To do that, we will add the&nbsp;<code>perspective<\/code>&nbsp;property on the text container, and then all we need to do is to rotate the segments on the x-axis&nbsp;<code>90deg<\/code>&nbsp;towards us.<\/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-class\">.textContainer<\/span> {\n  <span class=\"hljs-attribute\">perspective<\/span>: <span class=\"hljs-number\">15em<\/span>;\n  \n  span {\n    <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">rotateX<\/span>(-<span class=\"hljs-number\">90deg<\/span>);\n  }\n}<\/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>Adding this to our previous code will result in a seamless connection between the segments and a continuous text, regardless of the type of font you&#8217;re using. (we&#8217;re going to talk about the value of the&nbsp;<code>perspective<\/code>&nbsp;in the next part)<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_zYQpRvW\/0e87d30a0b7e19d61c07a9c7b5ffafc6\" src=\"\/\/codepen.io\/anon\/embed\/zYQpRvW\/0e87d30a0b7e19d61c07a9c7b5ffafc6?height=450&amp;theme-id=47434&amp;slug-hash=zYQpRvW\/0e87d30a0b7e19d61c07a9c7b5ffafc6&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed zYQpRvW\/0e87d30a0b7e19d61c07a9c7b5ffafc6\" title=\"CodePen Embed zYQpRvW\/0e87d30a0b7e19d61c07a9c7b5ffafc6\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"so-whats-really-going-on\">So what&#8217;s really going on?<\/h2>\n\n\n\n<p>Although it doesn&#8217;t seem like it right now, what we&#8217;ve created is actually a ring of flat elements, but because we&#8217;re looking at it from a specific perspective and don&#8217;t have visual context to compare it with, it looks like the ring is flat.<\/p>\n\n\n\n<p>To help us understand it better I&#8217;ve created this animation:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/yAM3pvtg.gif?resize=1024%2C576&#038;ssl=1\" alt=\"\" class=\"wp-image-2780\" style=\"width:647px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/yAM3pvtg.gif?resize=1024%2C576&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/yAM3pvtg.gif?resize=300%2C169&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/yAM3pvtg.gif?resize=768%2C432&amp;ssl=1 768w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n<\/div>\n\n\n<p>As we can see, we didn&#8217;t actually transform the rectangles into trapezoids, they are just arranged one next to the other. But because we are looking at them from a specific perspective, where the top part of the rectangle is closer to us and the bottom part is farther away, it looks like they are trapezoids.<\/p>\n\n\n\n<p>The distance at which we&#8217;re looking at the ring (that is, the value of the\u00a0<code>perspective<\/code>) determines the shape of the trapezoids, so if we set the\u00a0<code>perspective<\/code>\u00a0to a lower value the trapezoids would look longer, and if we set it to a higher value it would look smaller. So it&#8217;s a bit of a game of trial and error to find the right value for the text to look nice.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Can&#8217;t SVG do this?<\/h2>\n\n\n\n<p>It&#8217;s true SVG has a relatively straightforward <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/SVG\/Element\/textPath\"><code>&lt;textPath><\/code> element<\/a> that was designed for setting text on curved paths. The unfortunate part is that it is still based on splitting the text into <em>individual\u00a0characters<\/em>, so while we&#8217;re no longer limited to monospace fonts, cursive fonts will still look broken. See:<em><br><\/em><\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_LYaWpJR\/edbe1240fed4fa508a1dc84ce10bb036\" src=\"\/\/codepen.io\/anon\/embed\/LYaWpJR\/edbe1240fed4fa508a1dc84ce10bb036?height=450&amp;theme-id=47434&amp;slug-hash=LYaWpJR\/edbe1240fed4fa508a1dc84ce10bb036&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed LYaWpJR\/edbe1240fed4fa508a1dc84ce10bb036\" title=\"CodePen Embed LYaWpJR\/edbe1240fed4fa508a1dc84ce10bb036\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"final-touches-and-animation\">Final Touches &amp; Animation<\/h2>\n\n\n\n<p>Now we can get rid of the red outline to get a continuous text, that was just a fun visual aid. Sadly, this may result in some thin gaps between the segments (hairlines), but there&#8217;s a very easy fix for that. All we need to do is to bring those segments a bit closer to each other, and for that we need to adjust the&nbsp;<code>translateY<\/code>&nbsp;function in the&nbsp;<code>transform<\/code>. In this case I&#8217;ve added&nbsp;<code>0.1em<\/code>&nbsp;as it&#8217;s more than enough.<\/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\">translateY<\/span>(<span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-tag\">-1em<\/span> \/ <span class=\"hljs-selector-tag\">sin<\/span>(15<span class=\"hljs-selector-tag\">deg<\/span>) + 0<span class=\"hljs-selector-class\">.1em<\/span>))<\/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<p>To add some color to our text, there&#8217;s a few methods we can use. We could simply set the&nbsp;<code>color<\/code>&nbsp;property on the text container to have one solid color, or we can make it more colorful by utilizing the&nbsp;<code>--i<\/code>&nbsp;custom property to set a different color for each segment. For example:<\/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\">span<\/span> {\n  <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-built_in\">hsl<\/span>(calc(var(--i) * <span class=\"hljs-number\">15<\/span>) <span class=\"hljs-number\">100%<\/span> <span class=\"hljs-number\">50%<\/span>);\n}<\/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>We can also use a&nbsp;<code>background-image<\/code>&nbsp;on the span elements and use the&nbsp;<code>background-clip: text<\/code>&nbsp;method if we want to get something more complex.<\/p>\n\n\n\n<p>And as for the animation, here I&#8217;m using a relatively simple rotation (I&#8217;m adding a bit of a \u2018fake skew\u2019), but in fact we are only limited by our own imagination, and we can add whatever animation we want on the text container.<\/p>\n\n\n\n<p>The final result of what we&#8217;ve built so far looks like this:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_WNBdMdj\/0c5e263349a5acd19126bea3e820c109\" src=\"\/\/codepen.io\/anon\/embed\/WNBdMdj\/0c5e263349a5acd19126bea3e820c109?height=740&amp;theme-id=47434&amp;slug-hash=WNBdMdj\/0c5e263349a5acd19126bea3e820c109&amp;default-tab=result\" height=\"740\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed WNBdMdj\/0c5e263349a5acd19126bea3e820c109\" title=\"CodePen Embed WNBdMdj\/0c5e263349a5acd19126bea3e820c109\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>If you liked this concept,&nbsp;<a href=\"https:\/\/www.youtube.com\/watch?v=6yN7F2-JOTw\">I&#8217;ve done a live stream<\/a>&nbsp;a few months back where I go much deeper into the whole topic of forced perspective and explain how to create a circular text that is much more responsive and adjustable (and a bit more complicated).<\/p>\n\n\n\n<p>Hope you found this method helpful and maybe learned something new. If you have any comments or have used this on a project somewhere, I&#8217;d love to hear them.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Setting text on a circle in CSS isn&#8217;t straightforward, but it is possible with some effort. This technique splits text into segments and uses transforms and perspective to pull it off. <\/p>\n","protected":false},"author":27,"featured_media":2784,"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],"class_list":["post-2774","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-css"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/circle-text-thumb.jpg?fit=1000%2C500&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2774","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\/27"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=2774"}],"version-history":[{"count":14,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2774\/revisions"}],"predecessor-version":[{"id":2801,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2774\/revisions\/2801"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/2784"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=2774"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=2774"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=2774"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}