svg rectangle with radius, dash-array and dash-offset
Asked Answered
T

1

5

I am trying to create a rectangle in svg with a border / stroke at the top and bottom and with rounded corners by using a radius (rx, ry). By using the css property "dasharray: width, height" it is possible to style only the top and the bottom with a border/stroke. Without a radius this works as expected.

However, when the radius is added, the top border no longer starts in the top left corner of the rectangle. I tried to correct this behavior by using the css property dash-offset, but this seems to make the top border disappear partly (althought it seems to work for the bottom border).

Any help on how to create a rectangle with a radius and only a top and bottom border is much appreciated, as is some insight into what is happening.

see the fiddle at: https://jsfiddle.net/Lus8ku7p/

<svg>
  <rect x =10 y=10 id=a ></rect>
  <rect x = 10 y=170 id=b class=round></rect>
  <rect x=170 y=10 id=c ></rect>
  <rect x=170 y=170 id=d class=round></rect>
</svg>
svg{
    width:400px;
    height:400px
}
rect {
   width: 150px;
   height: 150px;
   stroke-width:4px;
   stroke:black;
   fill:red;
   stroke-dasharray: 150 150;
}
.round{
   rx:20px;
   ry:20px;
 }
#c, #d{
   stroke-dashoffset: 40px;
 }
Tirza answered 4/1, 2017 at 22:3 Comment(1)
You'd need different stroke-dasharry values for different circles because the circumference of a circle varies as a function of the radius. If you want this to work anywhere other than Chrome you'll also need to make the rect width/height values and the .round rx and ry values attributes rather than CSS properties.Decaliter
G
9

In order for dash patterns to render the same in all SVG renderers, the SVG specification defines where the dash pattern should begin for all shapes. Including rectangles.

For a rectangle, the start point is at the left end of the horizontal section of the top side of the rectangle. So if there is a rounded corner, it is at the point where the curve meets the straight. The dash pattern then proceeds around the rectangle in a clockwise direction.

That start point is effectively a tunnel or portal through which the dash pattern emerges and disappears into. You can't change its location, you just have to construct your dash pattern carefully with that start point in mind.

You don't say where exactly how much of the rounded corner you want included in the top stroke, but I am going to assume you want to include the whole of the curve.

In your example the rectangle is 150x150 with a corner radius of 20. Let's call those w, h, and r.

To calculate the dash pattern correctly we need to work out the lengths of all the sides and rounded corners accurately.

The top and bottom will each be length (w - 2 * r) = 110. Lets call this xlen.

The left and right sides will be length (w - 2 * r) = 110. Let's call this ylen.

Each of the rounded corners will be length (PI * 2 * r) / 4 = 31.416. Let's call this rlen

Dash pattern version 1

We could just make our dash pattern up from the different lengths of pattern that we need to complete the circumference of the rectangle. Ie.

  • Top of the rect (not including left corner): xlen + rlen = 141.416.
  • Right side gap in pattern: ylen = 110.
  • Bottom side (including both corners: rlen + xlen + rlen = 172.832.
  • Left side gap in pattern: ylen = 110.
  • Top left corner: rlen = 31.416.

So the dash pattern would be "141.416 110 172.832 110 31.416".

rect {
  stroke-width: 4px;
  stroke: black;
  fill:red;
  stroke-dasharray: 141.416 110 172.832 110 31.416;
}
<svg width="400" height="400">
  <rect x="10" y="10" width="150" height="150" rx="20"/>
</svg>

Dash pattern version 2

The other, simpler, but less obvious solution perhaps is to take advantage of the fact that dash patterns repeat. Combine this with a dash offset and we can make the dash pattern a lot simpler.

  • Top of the rect (including both corners): rlen + xlen + rlen = 172.832.
  • Right side gap in pattern: ylen = 110.
  • Then let it repeat to form the bottom and left sides.

So the dash pattern would be "172.832 110".

"But wait" you might say, That won't wwork because the pattern will be shifted clockwise by the length of a rounded corner. You are right.

rect {
  stroke-width: 4px;
  stroke: black;
  fill:red;
  stroke-dasharray: 172.832 110;
}
<svg width="400" height="400">
  <rect x="10" y="10" width="150" height="150" rx="20"/>
</svg>

However if we use stroke-dashoffset to shift the pattern left/anti-clockwise, we will lose some of the first dash, but we will also be "pulling" some of the next repeat of the dash pattern back through that start/end "portal". This relies on the dash pattern being the exact right length. That's why we were so careful at the start to calculate all the lengths accurately.

Spo what is the value we need to use for stroke-dashoffset? The way the dash offset works is that it specifies how far into the pattern we should start drawing from. So it needs to be after the length of the rounded corner, or 31.416.

So if we add that we get:

rect {
  stroke-width: 4px;
  stroke: black;
  fill:red;
  stroke-dasharray: 172.832 110;
  stroke-dashoffset: 31.416;
}
<svg width="400" height="400">
  <rect x="10" y="10" width="150" height="150" rx="20"/>
</svg>

You can see that if you want your dash pattern to start and stop at the correct places, it is possible. You just need to calculate the lengths in your dash pattern accurately.

If you need to have the same sort of pattern on rectangle of other sizes you need to recaclculate the lengths using the formulas I list at the start of this answer.

Gerstner answered 5/1, 2017 at 6:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.