12

Consider 2 html svg paths , a square (class inside) and a rectangle (class outside) having same height. When I apply stroke-width: 10px, the stroke gets applied 5px inside and 5px outside. Fiddle

enter image description here

How do I stroke only inside or only outside?

.inside { 
  stroke: #333;
  stroke-mode: inside;     // property does not exist
  stroke-width: 5px;
}

.outside {
   stroke: #333;
   stroke-mode: outside;   // property does not exist
   stroke-width: 5px;
}

If there is no such property, is there a workaround to achieve something like:

enter image description here

Saravanabalagi Ramachandran
  • 8,551
  • 11
  • 53
  • 102
  • changing the size of the inside of the inside rect is no option? – BlakkM9 Jul 12 '19 at 13:26
  • It would work for simple paths like rectangles, but will it work for complex SVG containing multiple paths, for example, like a circle removed from a square? – Saravanabalagi Ramachandran Jul 12 '19 at 13:33
  • you probably want to have a look at clip-paths like explained here: https://stackoverflow.com/a/32162431/4428236, seems to be the only solution currently working as the SVG working group has failed to implement stroke-control properties from multiple proposals... – exside Jul 12 '19 at 13:45
  • yeah thats true. when i create svgs i never use strokes just fill. When i want to have a border around something i just draw the path like this. – BlakkM9 Jul 12 '19 at 13:54
  • 3
    You can give the illusion that you stroke only outside by using the [paint-order](https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order) attribute which lets you control the order in which the fill and stroke (and painting markers) are drawn. If you use `paint-order: stroke;` svg will draw the stroke first and then fill. So the inner half of the stroke will be masked by the fill. – enxaneta Jul 12 '19 at 17:54

1 Answers1

13

The comment above by @enxaneta is exactly right.

Normally, where a filled rectangle is 48px wide and has a stroke with a stroke-width of 12px, the rectangle will display with:

  • a 12px wide border
  • an apparent width of 36px

Why 36px rather than 48px?

Because the 12px-wide stroke painted along the left side of the rectangle is obscuring 6px of the rectangle and the12px-wide stroke painted along the right side of the rectangle is obscuring 6px of the rectangle.

See this example where the stroke has 50% opacity - you can see that half of the stroke overlaps the fill:

svg rect {
  x: 10px;
  y: 10px;
  width: 200px;
  height: 100px;
  stroke: rgba(0, 0, 0, 0.5);
  stroke-width: 12px;
  fill: rgb(255, 0, 0);
}
<svg>
  <rect />
</svg>

Solution to create an Outer Border of 12px:

To create an Outer Border, the solution is to paint the stroke first and then paint the fill over the top.

We can achieve this using:

paint-order: stroke;

Now the rectangle will display with:

  • an apparent 6px wide border (because half the width of the 12px wide border is obscured by the fill painted over the top)
  • a width of 48px

svg rect {
  x: 10px;
  y: 10px;
  width: 200px;
  height: 100px;
  stroke: rgba(0, 0, 0, 0.5);
  stroke-width: 12px;
  fill: rgb(255, 0, 0);
  paint-order: stroke;
}
<svg>
  <rect />
</svg>

Finally, to ensure the rectangle displays with:

  • a 12px wide border
  • a width of 48px

change the stroke-width from 12px to 24px (i.e. double the intended display width):

svg rect {
  x: 10px;
  y: 10px;
  width: 200px;
  height: 100px;
  stroke: rgb(0, 0, 0);
  stroke-width: 24px;      /* double the intended display width */
  fill: rgb(255, 0, 0);
  paint-order: stroke;
}
<svg>
  <rect />
</svg>

Solution to create an Inner Border of 12px:

To create an Inner Border, we need three steps instead of two.

The first two steps are straightforward:

  • double the stroke-width (just as with the outer border)
  • use paint-order: fill (instead of paint-order: stroke)

svg rect {
  x: 10px;
  y: 10px;
  width: 200px;
  height: 100px;
  stroke: rgba(0, 0, 0, 0.5);
  stroke-width: 24px;      /* double the intended display width */
  fill: rgb(255, 0, 0);
  paint-order: fill;
}
<svg>
  <rect />
</svg>

The third step is to define and apply a <clipPath> which exactly duplicates the shape which is to have an Inner Border.

For instance, if the shape is:

<rect width="200" height="100" />

Then the <clipPath> should be:

<clipPath id="my-clip-path">
  <rect width="200" height="100" /> <!-- Same as the shape -->
</clipPath>

Working Example:

#my-rect {
  stroke: rgb(0, 0, 0);
  stroke-width: 24px;
  fill: rgb(255, 0, 0);
  paint-order: fill;
  clip-path: url(#my-clip-path);
}

#my-rect,
#my-clip-path-rect {
  x: 10px;
  y: 10px;
  width: 200px;
  height: 100px;
}
<svg>
  <defs>
    <clipPath id="my-clip-path">
      <rect id="my-clip-path-rect" />
    </clipPath>
  </defs>
  
  <rect id="my-rect" />
</svg>

All of the rectangles together:

svg {
  display: block;
  float: left;
  width: 220px;
  margin-left: 12px;
}

svg:nth-of-type(1) rect {
  x: 10px;
  y: 10px;
  width: 200px;
  height: 100px;
  stroke: rgba(0, 0, 0, 0.5);
  stroke-width: 12px;
  fill: rgb(255, 0, 0);
}

svg:nth-of-type(2) rect {
  x: 10px;
  y: 10px;
  width: 200px;
  height: 100px;
  stroke: rgba(0, 0, 0, 0.5);
  stroke-width: 24px;
  fill: rgb(255, 0, 0);
}

svg:nth-of-type(3) rect {
  x: 10px;
  y: 10px;
  width: 200px;
  height: 100px;
  stroke: rgb(0, 0, 0);
  stroke-width: 24px;
  fill: rgb(255, 0, 0);
  paint-order: stroke;
}

#svg3clip {
  width: 200px;
  height: 100px;
}

svg:nth-of-type(4) rect {
  x: 10px;
  y: 10px;
  width: 200px;
  height: 100px;
  stroke: rgb(0, 0, 0);
  stroke-width: 24px;
  fill: rgb(255, 0, 0);
  paint-order: fill;
  clip-path: url(#svg3clip);
}
<svg>
  <rect />
</svg>

<svg>
  <rect />
</svg>

<svg>
  <defs>
    <clipPath id="svg3clip">
      <rect />
    </clipPath>
  </defs>
  
  <rect />
</svg>

<svg>
  <rect />
</svg>

Further Reading:

Rounin
  • 27,134
  • 9
  • 83
  • 108
  • Thanks, that works for outside border. Is there a workaround for inside border? – Saravanabalagi Ramachandran Nov 20 '21 at 04:16
  • 1
    I had a think about this, @SaravanabalagiRamachandran and I came up with a solution for an *Inside Border* using `paint-order: fill` and a `` SVG element (combined with the `clip-path` CSS property). See above for full details. – Rounin Nov 20 '21 at 17:55
  • Thanks very much, I appreciate it. I would like to note that this inside stroke workaround is limited by the fact that we need to duplicate the shape and use it to clip it. I wish there is a way to clip the object to itself removing the need for duplication. – Saravanabalagi Ramachandran Nov 26 '21 at 12:41
  • 1
    Yes - you should be able to do exactly that, @SaravanabalagiRamachandran, by taking advantage of SVG's very useful `` element. See: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use – Rounin Nov 26 '21 at 17:14