user7402861
user7402861

Reputation:

Radial gradient shows some backlines, gap, spaces or margins

I am new with radial-gradient and I don't know what are those back lines or spaces between the cubes? How to remove them?

* {margin: 0; outline: 0; border: 0;}
.round {
  background: radial-gradient(circle at 0 100%, rgba(204, 0, 0, 0) 70%, #c00 71%),
              radial-gradient(circle at 100% 100%, rgba(204, 0, 0, 0) 70%, #c00 71%), 
              radial-gradient(circle at 100% 0, rgba(204, 0, 0, 0) 70%, #c00 71%), 
              radial-gradient(circle at 0 0, rgba(204, 0, 0, 0) 70%, #c00 71%);
  background-position: bottom left, bottom right, top right, top left;
  background-size: 50% 50%;
  background-repeat: no-repeat;
  width: 300px;
  height: 300px;
  padding: 10%;
  transform: rotate(45deg);
}
p {
  transform: rotate(-45deg);
  width: 100px;
  margin: 100px;
}
<div class="round">
  <p>By using radial gradients, you can simulate rounded corners with a negative radius. Just in this case,</p>
</div>

Upvotes: 1

Views: 852

Answers (1)

Harry
Harry

Reputation: 89770

There were two problems in your snippet and when we fix both those that weird gap or line that shows up in the middle would be gone completely..

  • Issue 1: You were setting padding: 10% on the element instead of a fixed value. When we give the value as a percentage, we are at the mercy of the UA in terms of rounding-off logic. Each UA has its own rounding-off logic. Take for example the width of the box's containing block is 510px, now 10% comes to 51px. Actual width of an element includes its padding, so here it will become 351px and when UA tries to calculate 50% of the width for background-size, the resulting value will be 175.5px. This is where the catch is. Some browsers round it down to 175px, some like FF has unique logic which rounds down some but rounds up the others, some just round it up etc. When the value is rounded down, the total size of the two background pieces becomes 350px but the actual width of the box is 351px and this 1px difference is the gap that you see.
  • Issue 2: When we apply a transform on an element, this sort of gap always comes up. My feeling is that this has something to do with the backface of the element because It goes away when we set backface-visibility to hidden. We have added a detailed explanation at the bottom of the answer. (This is tested in Chrome but should work in all. If it doesn't then there is no solution.)

In the below snippet, I've fixed both these issues and you can see how it works fine. There is also an extra catch with the backface-visibility: hidden. When you set this on a container element, all the text that is within that element get blurred. So, it is better to create the background using a pseudo as it would not affect the display of the p element.

* {
  margin: 0;
  outline: 0;
  border: 0;
}
.round {
  position: relative;
  width: 300px;
  height: 300px;
  padding: 30px;
}
.round:after {
  position: absolute;
  content: '';
  height: 100%;
  width: 100%;
  top: 0px;
  left: 0px;
  background: radial-gradient(circle at 0 100%, rgba(204, 0, 0, 0) 70%, #c00 71%), radial-gradient(circle at 100% 100%, rgba(204, 0, 0, 0) 70%, #c00 71%), radial-gradient(circle at 100% 0, rgba(204, 0, 0, 0) 70%, #c00 71%), radial-gradient(circle at 0 0, rgba(204, 0, 0, 0) 70%, #c00 71%);
  background-position: bottom left, bottom right, top right, top left;
  background-size: 50% 50%;
  background-repeat: no-repeat;
  transform: rotate(45deg);
  backface-visibility: hidden;
  z-index: -1;
}
p {
  width: 125px;
  margin: 85px;
}
<div class="round">
  <p>By using radial gradients, you can simulate rounded corners with a negative radius. Just in this case,</p>
</div>


As mentioned by vals' in his comment, the Issue 2 can also be solved by moving the rendering of the element from the CPU to the GPU layer. This is normally done by adding a translateZ(1px) to the transform stack. As he also points out, in some machines this improves the anti-aliasing too.

* {
  margin: 0;
  outline: 0;
  border: 0;
}
.round {
  position: relative;
  width: 300px;
  height: 300px;
  padding: 30px;
  background: radial-gradient(circle at 0 100%, rgba(204, 0, 0, 0) 70%, #c00 71%), radial-gradient(circle at 100% 100%, rgba(204, 0, 0, 0) 70%, #c00 71%), radial-gradient(circle at 100% 0, rgba(204, 0, 0, 0) 70%, #c00 71%), radial-gradient(circle at 0 0, rgba(204, 0, 0, 0) 70%, #c00 71%);
  background-position: bottom left, bottom right, top right, top left;
  background-size: 50% 50%;
  background-repeat: no-repeat;
  transform: rotate(45deg) translateZ(1px);
}
p {
  transform: rotate(-45deg) translateZ(-1px);
  width: 125px;
  margin: 85px;
}
<div class="round">
  <p>By using radial gradients, you can simulate rounded corners with a negative radius. Just in this case,</p>
</div>


Explanation for Issue 2:

Here is a more detailed explanation of what could be the cause of the second issue. Thanks to vals for his excellent help in coming up with this reason.

Let's say that the two different gradients are rendered directly to the canvas, without an intermediate buffer. Along the diagonal line that is the boundary between the two gradients, there are pixels that are covered only 50% by the first gradient. And later, will be covered also at 50% by the second gradient.

This should amount to a full pixel (50% + 50%). But the way to render it is with an alpha of 0.5. The problem is that in this case, the operation is a multiplication instead of an addition and rendering two times with an alpha of 0.5 will only give an alpha of 0.75, instead of 1. So the pixel is still transparent to some extent, and the background shows.

This can be verified (sort of) by enabling the "Show Paint Rects" and "Show Layer Borders" options in the Chrome Dev console. For the snippet in question, no layers are created. That is, the element is painted in its rotated form and so the point of separation between the two gradient images is along a diagonal line. Since it is along a diagonal, 50% of the pixel must be allotted to each image.

The GPU render is done on an intermediate buffer (or layer), prior to the rotation, and so the pixels fall on an even boundary. It could also use a sub-pixel render.

This can also be verified by using Chrome Dev console. Attaching any 3D transform properties (like backface-visibility, translateZ) result in the creation of a layer. Now, the element is first painted in its normal form (no rotation) and then the layer alone is rotated. So, the point of separation is along a straight line instead of a diagonal line and so there is no need for 50-50 logic. When we inspect the two snippets in the answer we can see an orange-ish border around the element (that won't be present for the snippet in question). This is the layer's border (we enabled its display via the earlier setting).

Upvotes: 2

Related Questions