Responsive SVG viewBox

10,490

Solution 1

Add width & height properties to your svg css. With width set and height auto.

body {
  margin: 0;
  text-align: center;
}

svg#ham-btn {
  margin: 2rem;
  border: 1px solid black;
  fill: #383838;
  /* percentage of viewport - height will autocalculate */
  width: 7vw;
  height: auto;
}
<svg id="ham-btn" width="107.5px" height="80px" viewBox="0 0 80 80" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
  <style>
    #ham-btn {
      cursor: pointer;
    }
    #ham-btn:hover #r1 {
      transition: transform 0.2s;
      transform-origin: 50% 50%;
      transform: rotate(37deg) translate(0%, 28.75%) scaleX(1.1);
    }
    #ham-btn:hover #r2 {
      transition: x 0.2s;
      x: 120%;
    }
    #ham-btn:hover #r3 {
      transition: transform 0.2s;
      transform-origin: 50% 50%;
      transform: rotate(-37deg) translate(0%, -28.75%) scaleX(1.1);
    }
    
  </style>
  <rect id="r3" x="0" y="71.25%" width="100%" height="15%" rx="5%" />
  <rect id="r2" x="0" y="42.5%" width="100%" height="15%" rx="5%"/>
  <rect id="r1" x="0" y="13.75%" width="100%" height="15%" rx="5%" />
</svg>

Solution 2

As I've told you in my comment: I would remove the width and height of the svg element. Then in CSS I would made SVG's width:20%. I'm making the SVG's width 50% of the width of the window.

In order to keep the proportions I've putted the "hamburger" inside a <symbol viewBox="0 0 80 80">. Of coarse in this case you don't need preserveAspectRatio="xMidYMid meet" anymore.

body {
  margin: 0;
  text-align: center;
}

svg#ham-btn {
  width: 20%;
  margin:auto;
  top:0;bottom:0;left:0;right:0;
  border: 1px solid black;
  fill: #383838;
  position:absolute; 
}
<svg id="ham-btn" viewBox="0 0 107.5 80" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
  <style>
    #ham-btn {
      cursor: pointer;
    }
    #ham-btn:hover #r1 {
      outline: 1px solid transparent;
      transition: transform 0.2s;
      transform-origin: 50% 50%;
      transform: rotate(37deg) translate(0%, 28.75%) scaleX(1.1);
    }
    #ham-btn:hover #r2 {
      transition: transform 0.2s;
      transform: translate(120%, 0);
    }
    #ham-btn:hover #r3 {
      outline: 1px solid transparent;
      transition: transform 0.2s;
      transform-origin: 50% 50%;
      transform: rotate(-37deg) translate(0%, -28.75%) scaleX(1.1);
    }
    
  </style>
  <symbol id="s" viewBox="0 0 80 80"> 
  <rect id="r3" x="0" y="71.25%" width="100%" height="15%" rx="5%" />
  <rect id="r2" x="0" y="42.5%" width="100%" height="15%" rx="5%"/>
  <rect id="r1" x="0" y="13.75%" width="100%" height="15%" rx="5%" />
  </symbol>  
  
  <use xlink:href="#s"  width="80" x="13.75"/>
</svg>
Share:
10,490
Magnus
Author by

Magnus

General Information By day: Technology venture capital investor, at Cidron Ventures. By night: Full stack developer with a focus on automation. For fun: Surfing, skiing, climbing, psychology, politics. Social Media I post on Twitter, LinkedIn, Medium and YouTube about technology, venture capital, entrepreneurship, programming, and more. Get In Touch Always interested in meeting new people. Get in touch if you are: • Fundraising for a technology startup • Looking for a board member / advisor

Updated on June 06, 2022

Comments

  • Magnus
    Magnus almost 2 years

    I made a "hamburger button" in SVG, as seen below.

    body {
      margin: 0;
      text-align: center;
    }
    
    svg#ham-btn {
      margin: 2rem;
      border: 1px solid black;
      fill: #383838;
    }
    <svg id="ham-btn" width="107.5px" height="80px" viewBox="0 0 80 80" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
      <style>
        #ham-btn {
          cursor: pointer;
        }
        #ham-btn:hover #r1 {
          outline: 1px solid transparent;
          transition: transform 0.2s;
          transform-origin: 50% 50%;
          transform: rotate(37deg) translate(0%, 28.75%) scaleX(1.1);
        }
        #ham-btn:hover #r2 {
          transition: transform 0.2s;
          transform: translate(120%, 0);
        }
        #ham-btn:hover #r3 {
          outline: 1px solid transparent;
          transition: transform 0.2s;
          transform-origin: 50% 50%;
          transform: rotate(-37deg) translate(0%, -28.75%) scaleX(1.1);
        }
        
      </style>
      <rect id="r3" x="0" y="71.25%" width="100%" height="15%" rx="5%" />
      <rect id="r2" x="0" y="42.5%" width="100%" height="15%" rx="5%"/>
      <rect id="r1" x="0" y="13.75%" width="100%" height="15%" rx="5%" />
    </svg>

    Question

    Right now, if I want to make the button smaller, I have to manually make the dimensions of the viewport and viewBox smaller by the same amount of px.

    Is there any way to make that more responsive? For instance, by making viewBox be a percentage of the viewport?

    According to the spec, it seems viewBox has to be a <number>, and thus cannot be a percentage.

    Any thoughts?

    Thank you.


    Note 1

    I have not added any accessibility or other features to the button. This question is about its responsiveness.


    Note 2

    I know I can make the SVG viewport a percentage of its container / the browser viewport.

    That does not solve my issue unfortunately, as to make this button work my viewBox-to-viewport ratio must stay fixed (otherwise the button looses its shape).

    Thus, I wanted to make the viewBox be a percentage of the viewport.

    In case it is of interest, my solution takes advantage of the following:

    • viewBox is wider than viewport, but has the same height
    • Thus, preserveAspectRatio is used to horizontally center the rects
    • rect dimensions are in percentage of viewBox

    The outline is to fix a Firefox issue with jagged lines after transform.


    Edit:

    In case helpful to future visitors, the final button, including full accessibility, can be found here: https://codepen.io/magnusriga/pen/KrrJKa

    • enxaneta
      enxaneta over 5 years
      I would remove the width and height of the svg element. Then in CSS I would made SVG's width:50%. You don't need a declaration for the height unless you need to squish / stretch the image.
    • Temani Afif
      Temani Afif over 5 years
      not intrested in a pure CSS solution ? like this one : stackoverflow.com/questions/53555133/… or this one :stackoverflow.com/questions/53270861/…
    • Magnus
      Magnus over 5 years
      Hi @TemaniAfif. Thanks for the links. I also previously implemented the menu in HTML+CSS, but recently dived into SVG (as you know) and find it incredibly powerful for graphics and animations. In my opinion, for the hamburger menu, SVG feels like an easier and less hacky approach, compared to HTML+CSS with absolute elements.
    • Magnus
      Magnus over 5 years
      I now just have to understand why setting width / height of the SVG in CSS is not the same as specifying it on the svg's presentation attributes themselves (like I thought the spec said).
    • Magnus
      Magnus over 5 years
      @enxaneta The initial value of the width / height presentation attributes on the SVG element, is 100% (of parent container) if I understand correctly. Removing the height, will break the difference in width / height ratio between the SVG Viewport and the viewBox, which the centering in the viewport depends on (it uses preserveAspectRatio).
  • Magnus
    Magnus over 5 years
    Hi. I know I can make the svg viewport a percentage of its container. I was thinking about the viewBox.
  • Ludovit Mydla
    Ludovit Mydla over 5 years
    @Magnus any specific reason why change viewBox attribute in HTML/SVG? Afaik, there are no means to change that via CSS, but JS might help there, although not necessary. You can achieve desired effect (if I understood correctly) by setting width/height.
  • Magnus
    Magnus over 5 years
    If you change the width/height of the viewport without changing the viewBox, the button looses its shape. I just added some notes to the OP, that explains how my solution works.
  • Magnus
    Magnus over 5 years
    PS: The black box around the hamburger (the viewport) should be in the exact same % distance from the rects, at any container size. So, if you make it slimmer the way you did, the whole svg looses its shape. The black box (viewport) and the hamburger belongs together. The cross neatly points to the corners of that black box (the viewport), when you hover over.
  • Ludovit Mydla
    Ludovit Mydla over 5 years
    @Magnus I've updated answer - added height: auto This should preserve the aspect ratio.
  • Magnus
    Magnus over 5 years
    I thought setting svg width / height in CSS would overwrite the SVG viewports own width / heigth (107.5px and 80px). Does it not do that?
  • Magnus
    Magnus over 5 years
    Also, any idea why the edges look jagged after transforming, for about 0.5s, in Firefox? The same thing does not happen in chrome.
  • Ludovit Mydla
    Ludovit Mydla over 5 years
    @Magnus yes. CSS properties would "overwrite" HTML default attributes.
  • Magnus
    Magnus over 5 years
    I do not think it is overwriting the svg viewport dimensions of 107.5px and 80px, though? If you try to manually change those, without changing the viewBox, you will see that the shape looses its form.
  • Magnus
    Magnus over 5 years
    I fixed the Firefox issues.
  • Magnus
    Magnus over 5 years
    Interesting solution, establishing another viewport within the outer viewport. I guess another option would be to used a nested svg, instead of symbol, as they both establish a new viewport. Regardless, could you please add an explanation to how all the dimensions hang together, i.e. what each initial and computed values is. My guess is that inner-viewport width/height is 100% (initial) of outer width dimensions, which in turn is 100% of parent container. However, we set width in CSS, so outer svg gets a viewport width of 20%, then it uses the SVG's intrinsic dimension to fill in height. 1/2
  • Magnus
    Magnus over 5 years
    2/3 .... The intrinsic dimensions (as per the spec: w3.org/TR/SVG2/coords.html#SizingSVGInCSS) is equal to viewBox.width / viewBox.height (since we did not specify both viewport width and viewport height as absolute values). Finally, this means that the inner viewport and outer viewport are equal in height / width, and both have an intrinsic aspect ratio of 107.5 / 80, which is used by CSS to calculate height (whose initial value when not specified is auto).
  • Magnus
    Magnus over 5 years
    3/3 ... All of that means, that we actually do not need the inner viewport that you created. We can just have one viewport (as in OP), keep the viewport width and height to make the intrinsic dimensions 107 / 80, then change the width in CSS as desired and set the height to auto (so it does not fall back to 80px). In that solution, the explicitly set viewport width and height is only used to set an intrinsic dimension different from what viewBox indicates. The actual viewport width and height are overwritten by CSS.
  • Magnus
    Magnus over 5 years
    I think I understand why your solution works. It is because the explicitly set viewport width and height is only used to define an intrinsic aspect ratio different from what viewBox would set (if no width / height was explicitly stated). The actual viewport width and height are then overwritten by CSS, but the svg intrinsic aspect ratio is maintained (107.5 / 80). That aspect ratio is then used when indicating height: auto in CSS. Had we not specified auto in CSS, the height would fall back to 80px, as set by the svg. If that is correct, could you please add that explanation to your answer?