4

It's been bugging me for a while whenever I try to use the CSS circle() function to do some clipping as in:

.red {
   width: 200px;
        height: 300px;
        background: red;
        border: 2px solid black;
        clip-path: circle(69%);  /*barely cuts off the corners of the .red div */
}

/*  the full circle will enclose the entire box at around 71% or (sqrt(2)/2 * 100%)
 per Mozilla documentation and not at 100% as one might expect */
<div class='red'></div>

the radius never seem to be calculated as I would expect it to. Upon looking into the Mozilla MDN reference (https://developer.mozilla.org/en-US/docs/Web/CSS/basic-shape) it appears they calculate it as follows:



enter image description here



which to me just doesn't seem correct. I would imagine they would calculate the radius of the circumference that encloses the elements rectangle (div, img, etc) as follows:



enter image description here



but that just doesn't seem to be the case. Can anyone shed some light on this. Is this a bug of sorts or am I just not understanding something here?

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
techexpert
  • 2,444
  • 3
  • 20
  • 21
  • @G-Cyr, see, even in your example changing red:hover circle radius to 71% will reveal the entire red rectangle. I'm not sure how this is 'expected' – techexpert Jan 17 '20 at 00:18

1 Answers1

7

It's defined to be like that, they never meant to calculate the radius you are showing. It's also in the specification.

To better understand let's consider a square. You can have a perfect circle if you consider 50% as value

.red {
  width: 200px;
  height: 200px;
  background: red;
  clip-path: circle(50%);
  border:2px solid;
  box-sizing:border-box;
}
<div class='red'></div>

The idea behind is to consider the following figure:

enter image description here

R is the 'c' you are calculating (the Green lines) and r is the reference used (the puple line). You can easily see that r = R/sqrt(2) and R = sqrt(w² + h²). Combining both will give us:

r = sqrt(w² + h²)/sqrt(2)

Which is the formula you see in the MDN page.

Using 50% of this value inside a square will give us the logical circle:

 r/2 = sqrt(w² + h²)/(2*sqrt(2)) = sqrt(2*w²)/(2*sqrt(2)) = w/2 (or h/2)

To cover the whole square we need a value equal to R/2 which is r/sqrt(2) = r/1.41 and since r is 100% you will have the 71% you discovered

.red {
  width: 200px;
  height: 200px;
  background: red;
  clip-path: circle(calc(100% / 1.44)); /* a little bogger  than 1.4 to better see*/
  border:2px solid;
  box-sizing:border-box;
}
<div class='red'></div>

The same logic apply to a non-square shape where the width and height are different but the reference remain the same:

r = sqrt(w² + h²)/sqrt(2)

From the above we can conclude that the 71% is a magic value that will produce the same output whataver the shape since it rely on the radius of the circumference that encloses the elements rectangle wheraes 50% (or any other value) will give different results:

.red {
  width: 200px;
  height: 200px;
  background: red;
  box-shadow:0 0 0 100px yellow;
  display:inline-block;
  clip-path: circle(71%); 
  margin: 70px;
}
<div class='red'></div>
<div class='red' style="width:300px;"></div>
<div class='red' style="width:100px;"></div>
<div class='red' style="width:50px;"></div>

Using 50%

.red {
  width: 200px;
  height: 200px;
  background: red;
  clip-path: circle(50%); 
  border:2px solid;
  box-sizing:border-box;
}
<div class='red'></div>
<div class='red' style="width:300px;"></div>
<div class='red' style="width:100px;"></div>
<div class='red' style="width:50px;"></div>

We may also think that any value bigger than 71% is useless since it we will always give a circle bigger than our element. This is true but we should not forget that we have also the position.

Example of output using 100%,200% and even 300%!

.red {
  width: 200px;
  height: 200px;
  background: red;
  border:2px solid;
  box-sizing:border-box;
}
<div class='red' style="clip-path: circle(100% at  0    50%)"></div>

<div class='red' style="clip-path: circle(200% at -100% 50%)"></div>

<div class='red' style="clip-path: circle(300% at -200% 50%)"></div>

I will consider a different property to better clear the confusion which is radial-gradient.

.box {
  width:200px;
  height:200px;
  border:1px solid;
  background:radial-gradient(circle 50%, red ,blue);
}
<div class="box">

</div>

The below code is meant to define a circle with a radius equal to 50% but it's invalid because we don't know the reference:

Note: Percentages are not allowed here; they can only be used to specify the size of an elliptical gradient, not a circular one. This restriction exists because there is are multiple reasonable answers as to which dimension the percentage should be relative to. A future level of this module may provide the ability to size circles with percentages, perhaps with more explicit controls over which dimension is used.ref

We are dealing with rectangular shapes so we can use the height, the width, the radius you are calculating, etc, etc. A lot of options so they simply decided to make it invalid but for the clip-path they took a decision and defined a reference for the use of percetange.

By the way, you can have better control over your circle considering values like closest-side/farthest-side.

The below will always give us circle touching the closest sides (the same way as contain with background)

.red {
  width: 200px;
  height: 200px;
  background: red;
  clip-path:circle(closest-side); 
  border:2px solid;
  box-sizing:border-box;
}
<div class='red'></div>
<div class='red' style="width:300px;"></div>
<div class='red' style="width:100px;"></div>
<div class='red' style="width:50px;"></div>

The below will always give us circles touching the farthest sides (the same way as cover with background)

.red {
  width: 200px;
  height: 200px;
  background: red;
  clip-path:circle(farthest-side); 
  border:2px solid;
  box-sizing:border-box;
}
<div class='red'></div>
<div class='red' style="width:300px;"></div>
<div class='red' style="width:100px;"></div>
<div class='red' style="width:50px;"></div>

Combined with position they can give some intresting results:

.red {
  width: 200px;
  height: 200px;
  background: red;
  border:2px solid;
  box-sizing:border-box;
  transition:1s all;
}
<div class='red' style="clip-path:circle(farthest-side at left); "></div>
<div class='red' style="clip-path:circle(closest-side at 10% 10%); "></div>
<div class='red' style="clip-path:circle(farthest-side at top left); "></div>
<div class='red' style="clip-path:circle(closest-side at 40% 50%); "></div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • thanks for the quick reply, though per their documentation **The argument represents r, the radius of the circle**. In your case r represents some unrelated surrounding square side, which is also equal to the circle diameter (not radius) if the box happened to be square, not rectangular. And how does this arbitrary circle relate to the DIV or an IMG shape? – techexpert Jan 16 '20 at 23:41
  • @techexpert you are confusing two things. `r` is the radius that will define the cirlce of the clip-path and it can be a pixel or percentage value. In case of pixel it's trivial but in case of percentage value, the Specifcation has defined a reference for that value to be `sqrt(w² + h²)/sqrt(2)`. It could have been something else but it was defined that way, this is your reference in case you are using percentage value. It doesn't necessarely define a shape. Check my last update – Temani Afif Jan 16 '20 at 23:44
  • yeah, it seems that way, though I really do think they had the whole percentage part messed up which never delivers a consistent meaningful result. And values such as 80% or 90% would seem completely irrelevant to the day-to-day usage since they don't even clip any part of the rectangle. Why did they even bothered with the percentages if they don't mean much and we have to do the hard work calculating the pixels by hand in order to get consistent results? I think they should change this spec to something actually meaningful. Thanks for you help! – techexpert Jan 16 '20 at 23:58
  • @techexpert added more details to better understand why such complexity ;) and also other alternative to consider – Temani Afif Jan 17 '20 at 00:07
  • @techexpert you also forget another important part which is the position, so a value of 100% can be useful in some cases. Check again my last update – Temani Afif Jan 17 '20 at 00:22
  • the gradient percentage is a bit different though, imo. Good note on the position, though still the whole percentage thing is very vague for most people. I do think that in the way I laid out my approach would offer a fairly clean and precise solution where r=100% would enclose the whole rectangle with the circle center positioned in the middle, but that's just wishful thinking on my part :) Who knows, maybe someone from W3C or Mozilla will take a look at this sometime and address this issue.. – techexpert Jan 17 '20 at 00:35
  • @techexpert with gradient there is no percentage for the radius, the percentage is only for the color stop inside the circle and in our case we use 100% for white then transparent to have a full opaque circle. The dimension of the circle is defined by the keywords farthest-side or closest-side (or a pixel value) – Temani Afif Jan 17 '20 at 00:46