响应式SVG viewBox

4
我在SVG中制作了一个“汉堡按钮”,如下所示。

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>

问题

现在,如果我想要让按钮变小,我必须手动将视口和viewBox的尺寸都减小相同数量的像素。

有没有办法使其更具响应性?例如,通过使viewBox成为视口的百分比?

根据规范,似乎viewBox必须是一个<number>,因此不能是一个百分比。

有什么想法吗?

谢谢。


注1

我没有为按钮添加任何可访问性或其他功能。本问题与其响应性有关。


注2

我知道我可以使SVG视口成为其容器/浏览器视口的百分比。

不幸的是,这并不能解决我的问题,因为为了使这个按钮工作,我的视图框(viewBox)与视口之比必须保持不变(否则按钮会失去其形状)。

因此,我想让viewBox成为视口的百分比。

如果有兴趣,我的解决方案利用了以下内容:

  • viewBox比视口更宽,但高度相同
  • 因此,preserveAspectRatio用于使rect水平居中
  • rect的尺寸是viewBox的百分比

outline用于修复火狐浏览器在变换后的锯齿线问题。


编辑:

如果对未来的访问者有帮助,可以在这里找到包括完整可访问性的最终按钮:https://codepen.io/magnusriga/pen/KrrJKa


我会移除SVG元素的宽度和高度。然后在CSS中,我会将SVG的width设置为50%。除非你需要压缩/拉伸图像,否则不需要声明高度。 - enxaneta
不想使用纯CSS解决方案吗?像这个:https://dev59.com/cLDla4cB1Zd3GeqP3yvk#53555325 或者这个:https://stackoverflow.com/questions/53270861/transforming-a-hamburger-icon-fails/53270909#53270909 - Temani Afif
嗨@TemaniAfif。感谢提供的链接。我之前也用HTML+CSS实现了菜单,但最近开始涉猎SVG(就像你所知道的那样),发现它在图形和动画方面具有惊人的强大能力。在我看来,对于汉堡菜单来说,与使用绝对元素的HTML+CSS相比,SVG感觉更容易、更少地使用hacky方法。 - Magnus
现在我只需要理解为什么在CSS中设置SVG的宽度/高度与在SVG的演示属性上指定它不同(就像我认为规范所说的那样)。 - Magnus
@enxaneta 如果我理解正确,SVG元素上宽度/高度呈现属性的初始值为100%(父容器的)。移除高度将打破SVG视口和viewBox之间宽高比差异,而视口居中依赖于此(它使用preserveAspectRatio)。 - Magnus
2个回答

3

在你的SVG CSS中添加宽度和高度属性。将宽度设置为具体数值,高度设置为自适应。

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>


嗨。我知道我可以使SVG视口成为其容器的百分比。我在考虑viewBox。 - Magnus
@Magnus 有没有特定的原因要在HTML/SVG中更改viewBox属性?据我所知,没有通过CSS更改它的方法,但JS可能会有所帮助,尽管不是必需的。如果我理解正确,您可以通过设置宽度/高度来实现所需的效果。 - Ludovit Mydla
如果您更改了视口的宽度/高度而没有更改“viewBox”,则按钮将失去其形状。我刚刚向OP添加了一些注释,解释了我的解决方案如何工作。 - Magnus
PS:汉堡包周围的黑色框(视口)应该与矩形保持完全相同的百分比距离,无论容器大小如何。因此,如果您像您所做的那样使其变窄,则整个SVG将失去其形状。黑色框(视口)和汉堡包是一起的。当您悬停时,十字架会整齐地指向该黑色框(视口)的角落。 - Magnus
@Magnus 我已经更新了答案 - 添加了 height: auto。这应该可以保持宽高比。 - Ludovit Mydla
显示剩余6条评论

1

如我在评论中所述:我会删除svg元素的宽度和高度。然后在CSS中,我会将SVG的宽度设置为20%。我将SVG的宽度设为窗口宽度的50%。

为了保持比例,我将“汉堡包”放在<symbol viewBox="0 0 80 80">中。当然,在这种情况下,您不再需要preserveAspectRatio="xMidYMid meet"

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>


有趣的解决方案,在外部视口内建立了另一个视口。我猜另一个选项是使用嵌套的 SVG,而不是符号,因为它们都会建立一个新的视口。无论如何,请问您能否解释一下所有尺寸是如何相互关联的,例如每个初始值和计算值是什么。我的猜测是内部视口的宽度/高度是外部宽度尺寸的100%(初始值),而外层又是父容器的100%。但是,我们在CSS中设置了宽度,因此外部SVG的视口宽度为20%,然后它使用SVG的固有尺寸填充高度。 - Magnus
2/3 ....内在尺寸(根据规范:https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS)等于viewBox.width / viewBox.height(因为我们没有将视口宽度和视口高度指定为绝对值)。最后,这意味着内部视口和外部视口在高度/宽度上相等,并且两者都具有107.5/80的固有纵横比,该纵横比由CSS用于计算高度(当未指定时其初始值为auto)。 - Magnus
所有这些意味着,我们实际上不需要您创建的内部视口。我们可以只有一个视口(与OP中相同),保持视口宽度和高度以使固有尺寸为107/80,然后根据需要在CSS中更改宽度,并将高度设置为自动(这样它就不会回退到80px)。在这个解决方案中,明确设置的视口宽度和高度仅用于设置与viewBox指示不同的固有尺寸。实际的视口宽度和高度被CSS覆盖。 - Magnus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接