Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resizing single shapes dynamically without scaling the whole SVG

I have an arrow as a SVG which's length I want to alter in response to the width of a wrapping DIV (for example).

All my previous attempts have led into this behavior (scaling the whole SVG):

Scaling whole SVG

Rather than this, I want to achieve this behavior:

Scaling shapes only

Is it possible to just alter single shapes inside of a SVG by simply adding percentual values to the code? If not, how could I perform this?

SVG as code:

<svg width="35px" viewBox="0 0 35 985" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <g id="Group" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(0.000000, 0.000000)">
    <rect id="Rectangle-2" fill="#5FDCC7" x="12" y="19.7987928" width="11" height="100%"></rect>
    <polygon id="Triangle" fill="#5FDBC6" points="17.5 0 35 20.7887324 0 20.7887324"></polygon>
    <rect id="Rectangle-3" fill="#5FDBC6" x="0" y="973.110664" width="35" height="11"></rect>
  </g>
</svg>
like image 268
to7be Avatar asked Dec 21 '16 08:12

to7be


2 Answers

As a general rule, there is no way to create a scalable SVG where parts of it don't scale. If an SVG has a viewBox, and you scale it, then all the contents will scale. There is no way to mark some of the contents as immune to the scaling.

The nearest you can get is to restrict scaling to the X axis. However the arrowhead will still stretch.

<svg width="35px" height="150px" viewBox="0 0 35 985" preserveAspectRatio="none">

        <g id="Group" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(0.000000, 0.000000)">
            <rect id="Rectangle-2" fill="#5FDCC7" x="12" y="19.7987928" width="11" height="100%"></rect>
            <polygon id="Triangle" fill="#5FDBC6" points="17.5 0 35 20.7887324 0 20.7887324"></polygon>
            <rect id="Rectangle-3" fill="#5FDBC6" x="0" y="973.110664" width="35" height="11"></rect>
        </g>
</svg>


<svg width="35px" height="300px" viewBox="0 0 35 985" preserveAspectRatio="none">

        <g id="Group" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(0.000000, 0.000000)">
            <rect id="Rectangle-2" fill="#5FDCC7" x="12" y="19.7987928" width="11" height="100%"></rect>
            <polygon id="Triangle" fill="#5FDBC6" points="17.5 0 35 20.7887324 0 20.7887324"></polygon>
            <rect id="Rectangle-3" fill="#5FDBC6" x="0" y="973.110664" width="35" height="11"></rect>
        </g>
</svg>

If you want a generalised solution, then you have to monitor resize events and update the SVG dynamically.

The limited sneaky clever solution...

Having said all that, there is a clever technique that works in a limited set of circumstances. The limitations are:

  1. The SVG stretches in only one direction.
  2. The "non-scaling" parts are at either or both ends of the stretch
  3. The end parts are solid and don't have any holes.
  4. You are okay with the background of the SVG being a solid color, rather than transparent.

Here is a demo of your shape implemented using this technique. If you resize the window horizontally, you'll see it stretches appropriately.:

<svg width="100%" height="35px">

  <svg viewBox="0 0 1 1" preserveAspectRatio="none">
    <rect x="0" y="0" width="1" height="1" fill="white"/>
    <rect x="0" y="0.4" width="1" height="0.2" fill="#5FDBC6"/>
  </svg>
    
  <svg viewBox="0 0 0.2 1" preserveAspectRatio="xMinYMid">
    <rect x="0" y="0" width="0.2" height="1" fill="#5FDBC6"/>
  </svg>

  <svg viewBox="0 0 0.8 1" preserveAspectRatio="xMaxYMid">
    <rect x="0" y="0" width="0.8" height="1" fill="white"/>
    <polygon points="0,0 0.8,0.5 0,1" fill="#5FDBC6"/>
  </svg>

</svg>
like image 167
Paul LeBeau Avatar answered Sep 22 '22 08:09

Paul LeBeau


Yes, you can use percentages in svg.

for basic shapes its quite simple. You can write your main rect as

<rect x="0" y="5" width="100%" height="10"/>

your second rect is even simpler, as it sits at 0,0 all the time

<rect x="0" y="0" width="10" height="20"/>

but with the arrow there is a problem in pathes and polygon you can not use percentages. To work around this problem there is a two step solution.

first put the path in a symbol element:

<symbol id="arrow" viewBox="0 0 20 20" width="20" height="20">
  <path d="M0,0L20 10L0 20z" />
</symbol>

now you can position this symbol like you would position a rect... easy...

<use xlink:href="#arrow" x="100%" y="0" width="20" height="20"/>

but now your arrow starts a 100% and is completely outside of your viewport. you could just set overflow: visible on your svg and be done with it, but that is not what we want... we want the arrow to end at 100%. But that easy as well, we know that the arrow is 20px wide. So just translate the use back 20px:

<use xlink:href="#arrow" x="100%" y="0" width="20" height="20" transform="translate(-20 0)"/>

using this approach, you can position any shape at any position base on percentages...

to warp it all up, your complete svg now looks like this:

<svg id="svg" height="20px" xmlns:xlink="http://www.w3.org/1999/xlink">
  <symbol id="arrow" viewBox="0 0 20 20" width="20" height="20">
    <path d="M0,0L20 10L0 20z" />
  </symbol>
  <g id="Group" fill="#5FDCC7">
    <rect x="0" y="5" width="100%" height="10" transform="translate(-20 0)" />
    <rect x="0" y="0" width="10" height="20" />
    <use xlink:href="#arrow" width="20" height="20" x="100%" y="0" transform="translate(-20 0)" />
  </g>
</svg>

and here is a snippet using this svg with 3 different widths:

svg:nth-of-type(1) {
  width: 100px
}
svg:nth-of-type(2) {
  width: 200px
}
svg:nth-of-type(3) {
  width: 300px
}
<svg height="20px" xmlns:xlink="http://www.w3.org/1999/xlink">
  <symbol id="arrow" viewBox="0 0 20 20" width="20" height="20">
    <path d="M0,0L20 10L0 20z" />
  </symbol>
  <g id="Group" fill="#5FDCC7">
    <rect x="0" y="5" width="100%" height="10" transform="translate(-20 0)" />
    <rect x="0" y="0" width="10" height="20" />
    <use xlink:href="#arrow" width="20" height="20" x="100%" y="0" transform="translate(-20 0)" />
  </g>
</svg>
<br/>
<svg height="20px" xmlns:xlink="http://www.w3.org/1999/xlink">
  <symbol id="arrow" viewBox="0 0 20 20" width="20" height="20">
    <path d="M0,0L20 10L0 20z" />
  </symbol>
  <g id="Group" fill="#5FDCC7">
    <rect x="0" y="5" width="100%" height="10" transform="translate(-20 0)" />
    <rect x="0" y="0" width="10" height="20" />
    <use xlink:href="#arrow" width="20" height="20" x="100%" y="0" transform="translate(-20 0)" />
  </g>
</svg>
<br/>
<svg height="20px" xmlns:xlink="http://www.w3.org/1999/xlink">
  <symbol id="arrow" viewBox="0 0 20 20" width="20" height="20">
    <path d="M0,0L20 10L0 20z" />
  </symbol>
  <g id="Group" fill="#5FDCC7">
    <rect x="0" y="5" width="100%" height="10" transform="translate(-20 0)" />
    <rect x="0" y="0" width="10" height="20" />
    <use xlink:href="#arrow" width="20" height="20" x="100%" y="0" transform="translate(-20 0)" />
  </g>
</svg>
like image 26
Holger Will Avatar answered Sep 20 '22 08:09

Holger Will