18

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>

3
  • so you only want the middle line to stretch over the space? Commented Dec 21, 2016 at 9:14
  • yes, exactly... (but dynamically in response to, lets say a div's width)
    – to7be
    Commented Dec 21, 2016 at 9:29
  • okay give me 5 mins Commented Dec 21, 2016 at 9:29

5 Answers 5

18

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>

4
  • As I can live with the limitations, this perfectly works for me! Thank you!
    – to7be
    Commented Dec 21, 2016 at 12:24
  • how sneaky and clever
    – web-tiki
    Commented Dec 21, 2016 at 12:43
  • hi Paul, there is an even more general solution. using percentages and transform, you can position symbols from the left and from the bottom as well, so you can position any shape at any point in a responsive svg... see my answer below... ;-) Commented Dec 22, 2016 at 8:45
  • So smart. This is something that’s ripe for the benefits of using SVG and I’ve never understood why it isn’t inherently possible. Commented Sep 6, 2017 at 21:46
9

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>

1
  • May I ask the down voter what's wrong with my answer? It will be a pleasure to correct it... Commented Dec 23, 2016 at 14:33
4

For this particlular usecase, I think I would go for a CSS approach, it might (depending on your markup) add a html element if you can't use pseudo elements but it won't require JS or to manualy input values for stroke-width or transform elements.

Here is a way to make this arrow using the paragraph tag :

p {
  position: relative;
  max-width: 300px;
  border-bottom: 4px solid #5FDBC6;
  padding-bottom:6px;
}
p:nth-child(2) {max-width: 400px;}
p:nth-child(3) {max-width: 200px;}
p::before, p::after{
  content:'';
  position:absolute;
  bottom:-8px;
}
p::before {
  height: 12px;
  left:0;
  border-left: 4px solid #5FDBC6;
}
p::after{
  right:-4px;
  border-style:solid;
  border-width:6px 0 6px 8px;
  border-color:transparent transparent transparent #5FDBC6;
}
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. a dapibus, tincidunt velit eget, rutrum urna.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras in purus risus. Ut id est suscipit, tincidunt sem a, fermentum magna. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Ut rutrum ac ligula</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras in purus risus. Ut id est suscipit, tincidunt sem a, fermentum magna.</p>

1
  • This is also an great approach which definitely works. Great to see how this problem could be solved by pseudo elements!
    – to7be
    Commented Dec 21, 2016 at 12:26
1

By using symbol's you can make the shapes you want and just include them in. Then to extend the middle section, just make a new path and make the line longer. They can be positioned wherever you like by using the transform property

<svg xmlns="http://www.w3.org/2000/svg">

  <symbol id="base">
    <g xmlns="http://www.w3.org/2000/svg">
      <path fill="#5FDBC6" d="M0,0 L0,20 L4,20 L4,0z"></path>
    </g>
  </symbol>
  <symbol id="triangle" >
    <g>
      <path fill="#5FDBC6" d="M0,0 L15,10 L0,20z"></path>
    </g>
  </symbol>

  <path stroke-width="4" stroke="#5FDBC6" d="M0,10 L100,10"></path>
  <g class="first" transform="translate(0,00)">
    <use xlink:href="#base" />
  </g>
  <g class="first" transform="translate(90,00)">
    <use xlink:href="#triangle" />
  </g>
  
  <path stroke-width="4" stroke="#5FDBC6" d="M0,50 L200,50"></path>
  <g class="first" transform="translate(0,40)">
    <use xlink:href="#base" />
  </g>
  <g class="first" transform="translate(200,40)">
    <use xlink:href="#triangle" />
  </g>


</svg>

7
  • Thank you! As this is a whole new topic to me, I never cosidered this approach. I have a lot to read here and the next step is to tink around with all positions and translations in order to achieve the wished dynamic bahavior.
    – to7be
    Commented Dec 21, 2016 at 11:26
  • @to7be if it works nicely in the end for you, dont forget to accept :) Commented Dec 21, 2016 at 11:31
  • 1
    This doesn't really answer the OPs question. OP wanted to know how to make a responsive SVG where a part of the SVG doesn't scale. Which is basically impossible. Commented Dec 21, 2016 at 11:40
  • @PaulLeBeau what? SVG's cant scale but ive given a solution that does using symbols? Thats a feasible and easy work around? Commented Dec 21, 2016 at 11:44
  • You'd have to scale the individual parts via javascript updating their position and size as the whole SVG scales. As Paul said a non-javascript solution is not possible here. Commented Dec 21, 2016 at 11:53
0

** EDITED **

This behavior is caused by your viewBox attribute on <svg>. Your SVG’s aspect ratio is being preserved by default. You want this behavior on #Triangle and #Rectangle-3, but not on #Rectangle-2 (you want the height to increase, but the width to stay the same).

I think you'll want to add preserveAspectRatio=none to your <svg> inline CSS and then do some tweaking (since that messes up the aspect ratio of your #Triangle and #Rectangle-3 - or instead of that, you could try changing the height of #triangle-2 to the same height as your wrapper div (in my JS fiddle I've done it this way, setting #Triangle-2s height to 25vh). Play with the height of the window - #Rectangle-2's height elongates but doesn't change width, although I haven’t yet got your #Rectangle-3 to dynamically stick to the bottom of #Rectangle-2 (#Rectangle-3 is stuck at bottom of window): https://jsfiddle.net/KyleVassella/dnagft6q/6/

I suspect you’ll know how to fix this, but either way I’ll keep working on it and in the meantime you can read up here where there is much more info on this subject:

How can I make an svg scale with its parent container?

4
  • Thx for the nice link! In the field of SVG are so many basics you have to understand before you are able to experience successes. preserveAspectRatio was very helpful. For me, your solution goes very much in the right direction. Next step is to automatically align the #Triangle to the top and the #Rectangle-3 to the bottom of a wrapping DIV. This positioning thing is still a challenge for me :) thx!
    – to7be
    Commented Dec 21, 2016 at 11:34
  • 1
    Your advice is faulty. preserveAspectRatio is not a valid attribute for the <rect> element. It will have no effect. Commented Dec 21, 2016 at 11:37
  • @Paul LeBeau You're correct, thanks for pointing that out. I've edited my answer. @ to7be, tell me about it! Next time I'll just leave a comment with the link, I think that would have been much more helpful (and proper) in this case. I hope someone comes along and solves this all the way. I'll keep working on it after some sleep. Commented Dec 21, 2016 at 12:05
  • 1
    Thanks for your effort. In many aspects, I had wrong expectations from SVG and suffered knowledge in this field. The approaches of Paul LeBeau and web-tiki work great. thx anyway.
    – to7be
    Commented Dec 21, 2016 at 12:32

Not the answer you're looking for? Browse other questions tagged or ask your own question.