Rounded corner only on one side of svg <rect>
Asked Answered
C

8

44

I am trying to implement a bar chart like diagram. I have the following html element

<rect x="35" y="-135" width="10" height="51" style="stroke: rgb(255, 255, 255); opacity: 0.8; fill: rgb(255, 122, 0);"></rect>

I want to give the top corner of the rectangle a rounded shape. How is it possible?
I am not able to apply border-radius property.

Curry answered 21/1, 2016 at 12:29 Comment(1)
You'd have to convert it to a path and model the corners with elliptical arcs.Microdont
M
29

You may use clipPath too. It's kind of a hack but it may be easier to implement.

Assuming the follow:

  • your rect is width="10" and height="51"
  • the top corner will be rx="5" and ry="5"

<svg>
    <defs>
        <clipPath id="round-corner">
            <rect x="0" y="0" width="10" height="56" rx="5" ry="5"/>
         </clipPath>
     </defs>

     <!-- if you want to use x="35" y="-135" put clip-path on a `g` element --> 
     <rect width="10" 
           height="51" 
           clip-path="url(#round-corner)"
           style="stroke: rgb(255, 255, 255); opacity: 0.8; fill: rgb(255, 122, 0);"></rect>
</svg>

Some Notes:
So the clipPath > rect > width is exactly the same as your rect.

Instead for clipPath > rect > height, you have to consider the corner radius on top and thus the height should be rect.height + desired-corner-radius (in this case 51px + 5px).

In that way you won't touch the bottom part of your rect with the clipPath.

Maudiemaudlin answered 26/3, 2018 at 4:58 Comment(1)
Small hint: I used this code for multiple rectangles <rect> with different lenghts. The rounding didn't work always properly. Ofc it turned out that id="round-corner" is a global identifier. So I had to adapt it to something like id={"round-corner" + uniqueIdentifier} to make the solution work for multiple rectangles. Maybe this helps somebody.Tribal
M
20

Wrote an article explaining this https://medium.com/@dphilip/one-side-rounded-rectangle-using-svg-fb31cf318d90

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <path
    d="M10,40
       h50
       q5,0 5,5
       v10
       q0,5 -5,5
       h-50
       z
    "
    fill="#4EDFA5"
  >
<svg>
Mcbride answered 11/3, 2019 at 0:7 Comment(2)
Detailed one explaining each attributePursuit
Great explanation - but using this method it's not as easy updating the width on a path tag as it is a rect tag.Peggi
W
14

As commented by Robert Longson you need to convert your rect element to a path element to control the rounded corners.

In the following example, I used a cubic bezier curve with the Q command to make the top left rounded corner (Q1 1 5 1 in the d attribute):

svg{
  height:90vh;
  width:auto;
  }
<svg viewbox="0 0 10 50">
  <path d="M1 49 V5 Q1 1 5 1 H9 V49z"
        fill="rgba(255, 122, 0, 0.8)" />
</svg>
Wilburn answered 21/1, 2016 at 13:23 Comment(5)
It is incorrect to use a bezier curve. A bezier curve can only ever approximate a circle. You need to use the arc command: devdocs.io/svg/attribute/d#arctoVelites
@Velites bezier curve can definetly make circles. Both commands are apropriate for this situation.Wilburn
@Velites I get your point from a mathematical point of view but I don't think you can see a diffrence in the output of a circle made with bezier curves and using the arc command. On a side note the, the OP isn't trying to make a circle, but rather connecting two lines with a curve, therefore a bezier curve command is apriopriate in this situation.Wilburn
Ok, you're right, the OP only asked for rounded corners, so it's not incorrect per se to use a bezier curve, as it yields to "rounded" corners. But you should be more precise with your language, cause "bezier curves can make circles" is just plain wrong. 😉Velites
From a practical standpoint for website display (raster rendering), bezier curves can draw pixel perfect circles due to the fact that it is rasterized at a finite resolution. The theoretical mathematics does not matter for this conversation, as it has no application to the real life scenario in this case. Choose your dogmatic battles where it actually matters.Goiter
V
10

Use the <path> element with the arc command (http://devdocs.io/svg/attribute/d#arcto).

Syntax: a rx,ry x-axis-rotation large-arc-flag sweep-flag dx,dy

<svg width="200" height="200" viewBox="0 0 10 10">
  <path d="M0,8 v-3 a5,5 0 0 1 5,-5 h3 v8 z" />
</svg>
Velites answered 10/1, 2018 at 14:4 Comment(0)
I
7

The clip-path approach can be simplified by using the inset basic shape function i.e. inset(top, right, bottom, left) which takes px or percent and insets from the shapes border.

The example below has set the offset of left to be equal to border radius hence the left has a straight edge.

The support for css-clip-path is pretty good. It doesn't work in opera mini and IE.

rect {
  clip-path: inset(0px 0px 0px 25px);
}
<svg>
  <rect width=200 height=100 rx=25 />
</svg>
Issue answered 14/3, 2023 at 19:42 Comment(0)
W
2

Or you can use mask instead

Version without bottom corners

<svg viewBox="0 0 177 177" xmlns="http://www.w3.org/2000/svg">
  <rect x="0" y="0" mask="url(#roundTopCorners)" width="8" height="100" rx="2" />
      <defs>
        <mask id="roundTopCorners">
            <rect x="0" y="0" fill="white" width="8" height="98" />
         </mask>
     </defs>
</svg>

Version with corners (default)

<svg viewBox="0 0 177 177" xmlns="http://www.w3.org/2000/svg">
  <rect x="0" y="0" mask="url(#roundTopCorners)" width="8" height="100" rx="2" />
</svg>

Comparison

<svg viewBox="0 0 177 177" xmlns="http://www.w3.org/2000/svg">
  <rect x="20" y="20" mask="url(#roundTopCorners)" width="8" height="100" rx="2" />
  <rect x="30" y="20" fill="black" width="8" height="100" rx="2" />
      <defs>
        <mask id="roundTopCorners">
            <rect x="20" y="20" fill="white" width="8" height="98" />
         </mask>
     </defs>
</svg>
Watermark answered 16/11, 2023 at 11:11 Comment(0)
U
0

This worked for me :)

function createRectanglePath(props) {
  const {
    width,
    height,
    topLeftRadius,
    topRightRadius,
    bottomRightRadius,
    bottomLeftRadius,
  } = props;

  return [
    // Move to the start point, considering top left radius
    `M ${topLeftRadius} 0`,
    // Draw a horizontal line to the top right corner, considering top right radius
    `H ${width - topRightRadius}`,
    // Draw an arc for top right corner if radius is greater than 0
    topRightRadius > 0
      ? `A ${topRightRadius} ${topRightRadius} 0 0 1 ${width} ${topRightRadius}`
      : null,
    // Draw a vertical line to the bottom right corner, considering bottom right radius
    `V ${height - bottomRightRadius}`,
    // Draw an arc for bottom right corner if radius is greater than 0
    bottomRightRadius > 0
      ? `A ${bottomRightRadius} ${bottomRightRadius} 0 0 1 ${
          width - bottomRightRadius
        } ${height}`
      : null,
    // Draw a horizontal line to the bottom left corner, considering bottom left radius
    `H ${bottomLeftRadius}`,
    // Draw an arc for bottom left corner if radius is greater than 0
    bottomLeftRadius > 0
      ? `A ${bottomLeftRadius} ${bottomLeftRadius} 0 0 1 0 ${
          height - bottomLeftRadius
        }`
      : null,
    // Draw a vertical line to the top left corner, considering top left radius
    `V ${topLeftRadius}`,
    // Draw an arc for top left corner if radius is greater than 0
    topLeftRadius > 0
      ? `A ${topLeftRadius} ${topLeftRadius} 0 0 1 ${topLeftRadius} 0`
      : null,
    // Close the path
    'Z',
  ]
    .filter((v) => v != null)
    .join(' ');
}


const node = {
  width: 512,
  height: 512,
  bottomLeftRadius: 10,
  bottomRightRadius: 50,
  topLeftRadius: 100,
  topRightRadius: 200
};

const svgPath = createRectanglePath(node);

document.getElementById('yourSVGPath').setAttribute('d', svgPath);
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <path id="yourSVGPath" fill="#4EDFA5">
</svg>
Unkennel answered 9/7, 2023 at 10:37 Comment(0)
P
0

You could do this by using two rect elements in your clipping. One that has rounded corners for the right-hand side corners and another on the left-hand side for square corners. It should be noted that the two rect elements in the clip-path element are additive. So the clipping region is the sum of the two rect tags.

<html>
<body>

<svg style="border: 1px solid gray" width: "500" height="500">
    <defs>
        <clipPath id="myClip">
            <rect x="25" y="25" width="50" height="50" rx="10"></rect>
            <rect x="25" y="25" width="25" height="50"></rect>
        </clipPath>
    </defs>
  
    <rect
      x="25"
      y="25"
      width="50"
      height="50"
      clip-path="url(#myClip)"
      onclick="alert('Hello!')" 
      fill="black"
    />
</svg>

</body>
</html>
Peggi answered 1/12, 2023 at 22:3 Comment(1)
You'd need to update the sizes of the clipping rectangles if you were to update the size of the rect (eg in an animation).Peggi

© 2022 - 2024 — McMap. All rights reserved.