0

Creating rounded corners is easy, but we are somewhat limited with the flexibility of border-radius when it comes to applying a different curve to the edges and a different curve to the corners. I have a shape (not sure of it's name...) that I would like to create, ideally, using just CSS.

Consider the following shape:

Curved Border

I have played around with various values within border-radius but am fairly confident it needs more than just border-radius. I also considered applying certain transform's but none of the available tranformations seem appropriate.

Can anyone suggest a CSS trick that would allow one to create this shape? Or, alternatively, how I could go about creating this shape in HTML5 canvas?

What I have tried

  • I have played around with combined border-radius values such as 20% / 30% but have not been able to get the desired shape
  • I also tried various transform values but none of the available transforms seem appropriate

NOTE 1: Pseudo-elements are probably not going to work here as a) the end shape will be used as a mask for an image, and b) I need to apply a gradient to the shape (as a border)

NOTE 2: I have added a gradient to the image for illustrative purposes as I will need to apply a border to this shape. However, the actual type of gradient, and colours, will differ from what is illustrated. As long as I can add a gradient, I can apply the necessary styles to ensure the gradient is correct

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Ben Carey
  • 16,540
  • 19
  • 87
  • 169
  • If only I remembe its name .. I remember an old question with the same shape. – Temani Afif Nov 30 '19 at 15:42
  • @TemaniAfif I have searched everywhere for the name of his shape. I have decided to call it 'squashed rounded square' :-P – Ben Carey Nov 30 '19 at 15:44
  • related: https://stackoverflow.com/q/26246629/8620333 but not the one I am looking for – Temani Afif Nov 30 '19 at 15:46
  • You can combine two values for each border's radius, as shown here: https://stackoverflow.com/a/43607780/5641669 – Johannes Nov 30 '19 at 15:49
  • @TemaniAfif - I considered using a mask image - in fact, this is what I expected I would need to use when I first saw the design come through. Before I go ahead with it though, do you happen to know whether a mask image can be applied to a border? Basically, this shape is going to be used for avatars within the application I am working on, and each avatar has a gradient border and needs to follow this shape too... – Ben Carey Nov 30 '19 at 15:50
  • 1
    another one : https://stackoverflow.com/q/29623066/8620333 – Temani Afif Nov 30 '19 at 15:50
  • @Johannes - I am aware of this but I have not be able to acheive the desired shape using combined values... – Ben Carey Nov 30 '19 at 15:51
  • so you want to also apply border to the shape? – Temani Afif Nov 30 '19 at 15:51
  • @TemaniAfif - to put it simply, yes, it needs to have a border... – Ben Carey Nov 30 '19 at 15:52
  • this is an important detail that need to be added to your question then – Temani Afif Nov 30 '19 at 15:53
  • @TemaniAfif - I'll update the question with an example image. Just need to figure out how to create it... Don't really want to take a screenshot of the design as it is confidential – Ben Carey Nov 30 '19 at 15:54
  • @TemaniAfif - See my update :-D – Ben Carey Nov 30 '19 at 16:01
  • I found it since I got the name now: https://stackoverflow.com/a/54082493/8620333 .. you will find there a better SVG path – Temani Afif Nov 30 '19 at 19:26

2 Answers2

3

I believe the shape you're using may be called a "squircle." Regardless, if you can create it as a vector, then you can create an avatar mask using an SVG.

For example, you could make a squircle shape in a vector editing program and use it as a clip path.

HTML/SVG

<img class="clip-svg" src="https://picsum.photos/450" alt="Lorem Picsum">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 450 450"><title>squircle</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1">
  <clipPath id="squircle">
  <path d="M225,449.5c-38.23,0-74.86-3.95-105.92-11.43-33.37-8-58.59-19.87-72.94-34.21S20,364.29,11.93,330.92C4.45,299.86.5,263.23.5,225s4-74.86,11.43-105.92C20,85.71,31.8,60.49,46.14,46.14S85.71,20,119.08,11.93C150.14,4.45,186.77.5,225,.5s74.86,4,105.92,11.43c33.37,8,58.59,19.87,72.94,34.21S430,85.71,438.07,119.08c7.48,31.06,11.43,67.69,11.43,105.92s-3.95,74.86-11.43,105.92c-8,33.37-19.87,58.59-34.21,72.94S364.29,430,330.92,438.07C299.86,445.55,263.23,449.5,225,449.5Z"/><path d="M225,1c38.19,0,74.78,4,105.8,11.42,33.29,8,58.42,19.8,72.7,34.08s26.07,39.41,34.08,72.7c7.47,31,11.42,67.61,11.42,105.8s-3.95,74.78-11.42,105.8c-8,33.29-19.8,58.42-34.08,72.7s-39.41,26.07-72.7,34.08C299.78,445.05,263.19,449,225,449s-74.78-3.95-105.8-11.42c-33.29-8-58.42-19.8-72.7-34.08s-26.07-39.41-34.08-72.7C5,299.78,1,263.19,1,225s4-74.78,11.42-105.8c8-33.29,19.8-58.42,34.08-72.7s39.41-26.07,72.7-34.08C150.22,5,186.81,1,225,1m0-1C150.66,0,76.31,15.26,45.79,45.79c-61,61.05-61,297.37,0,358.42C76.31,434.74,150.66,450,225,450s148.69-15.26,179.21-45.79c61.05-61,61.05-297.37,0-358.42C373.69,15.26,299.34,0,225,0Z"/>
  </clipPath></g></g></svg>

CSS

.clip-svg {
  clip-path: url(#squircle);
}

And here it is as a codepen

EDIT

To add a gradient border, you can use a second path in the squircle. A simple border on the image doesn't work, as it goes around the edges of the rectangular image and gets cut off. Here is a version with a background squircle with a gradient border and a masked image inside it.

HTML/SVG

<svg width="516" height="516" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 516 516">
  <defs>
    <style>
      .background-squircle{fill:url(#linear-gradient);}
      .inner-image{clip-path:url(#clip-path);}
    </style>
    <clipPath id="clip-path">
      <path d="M258,482.5c-38.23,0-74.86-3.95-105.92-11.43-33.37-8-58.59-19.87-72.94-34.21S53,397.29,44.93,363.92C37.45,332.86,33.5,296.23,33.5,258s4-74.86,11.43-105.92c8-33.37,19.87-58.59,34.21-72.94S118.71,53,152.08,44.93C183.14,37.45,219.77,33.5,258,33.5s74.86,4,105.92,11.43c33.37,8,58.59,19.87,72.94,34.21s26.17,39.57,34.21,72.94c7.48,31.06,11.43,67.69,11.43,105.92s-3.95,74.86-11.43,105.92c-8,33.37-19.87,58.59-34.21,72.94S397.29,463,363.92,471.07C332.86,478.55,296.23,482.5,258,482.5Z"/>
    </clipPath>
    <linearGradient id="linear-gradient" y1="262" x2="524" y2="262" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#fff"/><stop offset="1"/>
    </linearGradient>
  </defs>
  <title>SquircleWithImage</title>
  <g id="Avatar_1" data-name="avatar">
    <path class="background-squircle" d="M258,515.5c-43.84,0-85.85-4.53-121.47-13.11-38.28-9.22-67.21-22.79-83.67-39.25s-30-45.39-39.25-83.67C5,343.85.5,301.84.5,258S5,172.15,13.61,136.53C22.83,98.25,36.4,69.32,52.86,52.86s45.39-30,83.67-39.25C172.15,5,214.16.5,258,.5S343.85,5,379.47,13.61c38.28,9.22,67.21,22.79,83.67,39.25s30,45.39,39.25,83.67C511,172.15,515.5,214.16,515.5,258S511,343.85,502.39,379.47c-9.22,38.28-22.79,67.21-39.25,83.67s-45.39,30-83.67,39.25C343.85,511,301.84,515.5,258,515.5Z"/>
    <g class="inner-image">
      <image id="Image" data-name="Layer 0" width="516" height="516" xlink:href="https://picsum.photos/516"/>
    </g>
  </g>
</svg>
Community
  • 1
  • 1
Pwpwpw
  • 151
  • 6
  • This is method that I was anticipating I would need to use... Rather annoyingly I cannot apply a border to the image without it being clipped off. I can see various potential solutions to this but not sure which one would be deemed 'correct'. How would you apply a border to the image? Bear in mind that the border needs to be a gradient border... – Ben Carey Nov 30 '19 at 16:20
  • I'd see if the border can be added as part of the SVG, with the clipping mask only on one path in the SVG. That way, the outer shape can be filled with whatever gradient you need. Worst case, you could overlay one SVG atop another, but that's not as elegant. – Pwpwpw Nov 30 '19 at 16:23
  • That's interesting, I didn't realise you could define a certain part of the SVG as a clipping mask. I'll give that a go. The other solution was what I was thinking but wanted to avoid for obvious reasons... – Ben Carey Nov 30 '19 at 16:25
  • Still a work in progress, but it's definitely possible to have part of the SVG be a clipPath and part have a gradient. For example: https://codepen.io/paulweekswright/pen/RwNbQeJ – Pwpwpw Nov 30 '19 at 16:40
  • Edited to show a version using inline CSS and the image as part of the SVG. This could be dynamically created by JS without as much complication as using absolute positioning to get the effect. It's still a shape atop another shape. – Pwpwpw Nov 30 '19 at 17:16
  • Is there a way to define the size of the clipping path? I was hoping that the SVG would take the size of the image it is clipping but it seems it isn't and not sure if there is a way to control this. If this is the case, I am not sure if this solution will actually suffice – Ben Carey Nov 30 '19 at 18:25
  • You can use `tranform: scale` to change the size of that path and `transform-origin` to keep it centered. Otherwise, you'd need to generate a new SVG with the two paths creating the precise vision you want. This won't automatically scale to the size of the image, but you can change the size of the SVG. That will scale everything inside it. In the example, I made an SVG in Illustrator by blending a circle and a square. Then I copied it an blew it up for the background. This is meant as a proof of concept that the desired behavior can be achieved using SVG and clipPath. – Pwpwpw Nov 30 '19 at 19:09
1

SVG gradient strokes

SVG supports strokes/borders filled with gradients.

So, instead of using two clipped elements you could clip only the <image> element and apply the gradient stroke directly to the squircle shape:

svg {
  width: 10em;
}
<h3>SVG squircle - image clipped</h3>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
  <defs>
  <!-- reusable squircle path -->
    <path id="squircle" d="M 59 10 h 2 c 39.813 0 49 9.188 49 49 v 2 c 0 39.813 -9.188 49 -49 49 h -2 c -39.813 0 -49 -9.188 -49 -49 v -2 c 0 -39.813 9.188 -49 49 -49"/>
    <clipPath id="clip">
      <use href="#squircle" />
    </clipPath>
    <radialGradient id="gradient" cx="0" cy="0" r="2" >
      <stop offset="0%" stop-color="yellow" />
      <stop offset="100%" stop-color="red" />
    </radialGradient>
  </defs>

    <!-- stroke -->
  <image href="https://picsum.photos/id/237/200/200" clip-path="url(#clip)" height="100%" width="100%" preserveAspectRatio="xMidYMid meet"/>
  
  <!-- stroke -->
   <use href="#squircle" fill="none" stroke="url(#gradient)" stroke-width="10" />
 </svg>

SVG: Fill squircle with image

Instead of clipping, you could also fill your squircle shape with an image <pattern>.
See also "Fill SVG path element with a background-image"

<h3>SVG squircle - squircle filled with image</h3>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
  <defs>
  <pattern id="imgFill" patternUnits="userSpaceOnUse" width="100%" height="100%">
    <image href="https://picsum.photos/id/237/200/200" x="0" y="0" width="100%" height="100%" />
  </pattern>
    <radialGradient id="gradient2" cx="0" cy="0" r="2" >
      <stop offset="0%" stop-color="yellow" />
      <stop offset="100%" stop-color="red" />
    </radialGradient>
  </defs>
    <path id="squircle2" d="M 59 10 h 2 c 39.813 0 49 9.188 49 49 v 2 c 0 39.813 -9.188 49 -49 49 h -2 c -39.813 0 -49 -9.188 -49 -49 v -2 c 0 -39.813 9.188 -49 49 -49" fill="url(#imgFill)" stroke="url(#gradient2)" stroke-width="10"/>
 </svg>

CSS: clip wrapping element and image

We need to wrap the clipped image in an element that's also clipped with the same squircle.
The stroke-width is set by a padding value .
The gradient fill is defined by the parent's background color.

*{
  box-sizing: border-box;
}

.clipped {
  aspect-ratio: 1/1;
  width: 100%;
  object-fit: cover;
  object-position: 50%;
  clip-path: url(#clipPathSquircle);
}


.squircle-wrp{
  width:10em;
  height:10em;
  aspect-ratio:1;
  background: radial-gradient(
    farthest-corner at 0px 0px,
    yellow 0%, red 100%);
  padding:15px;
}
<h3>CSS squircle - wrap and image clipped</h3>

<div class="squircle-wrp clipped">
  <img src="https://picsum.photos/id/237/200/200" class="img-square clipped">
</div>

<!-- hidden svg clip asset -->
<svg style="width:0; height:0; position:absolute">
  <clipPath id="clipPathSquircle" clipPathUnits="objectBoundingBox"><path  d="M 0.492 0.083 h 0.017 c 0.332 0 0.408 0.077 0.408 0.408 v 0.017 c 0 0.332 -0.077 0.408 -0.408 0.408 h -0.017 c -0.332 0 -0.408 -0.077 -0.408 -0.408 v -0.017 c 0 -0.332 0.077 -0.408 0.408 -0.408" /></clipPath>
</svg>

See also "How to create a Squircle with a border?"

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34