在网站上使用SVG标签是很方便的,但它的界面可能与我们习惯的不同。在这篇文章中,我们将看到如何让SVG的视口每次都适合其内容。
问题所在
在某些情况下,我们可能有一个SVG,里面有一些任意的形状或路径。这些形状和路径可能有特定的点,但并不总是适合你的视口。
让我们来看看下面这个SVG。我们有两条路径,是我从美国地图上截取的。一个是马里兰州的路径,另一个是纽约州的路径。
<svg style="height: 300px;">
<path
d="m 822.9,269.3 0,-1.7 h -.8 l 0,1.8 z m 11.8,-3.9 1.2,-2.2 .1,-2.5 -.6,-.6 -.7,.9 -.2,2.1 -.8,1.4 -.3,1.1 -4.6,1.6 -.7,.8 -1.3,.2 -.4,.9 -1.3,.6 -.3,-2.5 .4,-.7 -.8,-.5 .2,-1.5 -1.6,1 v -2 l 1.2,-.3 -1.9,-.4 -.7,-.8 .4,-1.3 -.8,-.6 -.7,1.6 .5,.8 -.7,.6 -1.1,.5 -2,-1 -.2,-1.2 -1,-1.1 -1.4,-1.7 1.5,-.8 -1,-.6 v -.9 l .6,-1 1.7,-.3 -1.4,-.6 -.1,-.7 -1.3,-.1 -.4,1.1 -.6,.3 .1,-3.4 1,-1 .8,.7 .1,-1.6 -1,-.9 -.9,1.1 -1,1.4 -.6,-1 .2,-2.4 .9,-1 .9,.9 1.2,-.7 -.4,-1.7 -1,1 -.9,-2.1 -.2,-1.7 1.1,-2.4 1.1,-1.4 1.4,-.2 -.5,-.8 .5,-.6 -.3,-.7 .2,-2.1 -1.5,.4 -.8,1.1 1,1.3 -2.6,3.6 -.9,-.4 -.7,.9 -.6,2.2 -1.8,.5 1.3,.6 1.3,1.3 -.2,.7 .9,1.2 -1.1,1 .5,.3 -.5,1.3 v 2.1 l -.5,1.3 .9,1.1 .7,3.4 1.3,1.4 1.6,1.4 .4,2.8 1.6,2 .4,1.4 v 1 h -.7 l -1.5,-1.2 -.4,.2 -1.2,-.2 -1.7,-1.4 -1.4,-.3 -1,.5 -1.2,-.3 -.4,.2 -1.7,-.8 -1,-1 -1,-1.3 -.6,-.2 -.8,.7 -1.6,1.3 -1.1,-.8 -.4,-2.3 .8,-2.1 -.3,-.5 .3,-.4 -.7,-1 1,-.1 1,-.9 .4,-1.8 1.7,-2.6 -2.6,-1.8 -1,1.7 -.6,-.6 h -1 l -.6,-.1 -.4,-.4 .1,-.5 -1.7,-.6 -.8,.3 -1.2,-.1 -.7,-.7 -.5,-.2 -.2,-.7 .6,-.8 v -.9 l -1.2,-.2 -1,-.9 -.9,.1 -1.6,-.3 -.9,-.4 .2,-1.6 -1,-.5 -.2,-.7 h -.7 l -.8,-1.2 .2,-1 -2.6,.4 -2.2,-1.6 -1.4,.3 -.9,1.4 h -1.3 l -1.7,2.9 -3.3,.4 -1.9,-1 -2.6,3.8 -2.2,-.3 -3.1,3.9 -.9,1.6 -1.8,1.6 -1.7,-11.4 60.5,-11.8 7.6,27.1 10.9,-2.3 0,5.3 -.1,3.1 -1,1.8 z m -13.4,-1.8 -1.3,.9 .8,1.8 1.7,.8 -.4,-1.6 z"
></path>
<path
d="m 872.9,181.6 -1.3,.1 -.5,1 z m -30.6,22.7 .7,.6 1.3,-.3 1.1,.3 .9,-1.3 h 1.9 l 2.4,-.9 5.1,-2.1 -.5,-.5 -1.9,.8 -2,.9 .2,-.8 2.6,-1.1 .8,-1 1.2,.1 4.1,-2.3 v .7 l -4.2,3 4.5,-2.8 1.7,-2.2 1.5,-.1 4.5,-3.1 3.2,-3.1 3,-2.3 1,-1.2 -1.7,-.1 -1,1.2 -.2,.7 -.9,.7 -.8,-1.1 -1.7,1 -.1,.9 -.9,-.2 .5,-.9 -1.2,-.7 -.6,.9 .9,.3 .2,.5 -.3,.5 -1.4,2.6 h -1.9 l .9,-1.8 .9,-.6 .3,-1.7 1.4,-1.6 .9,-.8 1.5,-.7 -1.2,-.2 -.7,.9 h -.7 l -1.1,.8 -.2,1 -2.2,2.1 -.4,.9 -1.4,.9 -7.7,1.9 .2,.9 -.9,.7 -2,.3 -1,-.6 -.2,1.1 -1.1,-.4 .1,1 -1.2,-.1 -1.2,.5 -.2,1.1 h -1 l .2,1 h -.7 l .2,1 -1.8,.4 -1.5,2.3 z m -.8,-.4 -1.6,.4 v 1 l -.7,1.6 .6,.7 2.4,-2.3 -.1,-.9 z m -10.1,-95.2 -.6,1.9 1.4,.9 -.4,1.5 .5,3.2 2.2,2.3 -.4,2.2 .6,2 -.4,1 -.3,3.8 3.1,6.7 -.8,1.8 .9,2.2 .9,-1.6 1.9,1.5 3,14.2 -.5,2 1.1,1 -.5,15 .7,1 2.8,16.3 1.8,1.5 -3.5,3.4 1.7,2.2 -1.3,3.3 -1.5,1.7 -1.5,2.3 -.2,-.7 .4,-5.9 -14.6,-4.9 -1.6,-1.1 -1.9,.3 -3,-2.2 -3,-5.8 h -2 l -.4,-1.5 -1.7,-1.1 -70.5,13.9 -.8,-6 4.3,-3.9 .6,-1.7 3.9,-2.5 .6,-2.4 2.3,-2 .8,-1.1 -1.7,-3.3 -1.7,-.5 -1.8,-3 -.2,-3.2 7.6,-3.9 8.2,-1.6 h 4.4 l 3.2,1.6 .9,-.1 1.8,-1.6 3.4,-.7 h 3 l 2.6,-1.3 2.5,-2.6 2.4,-3.1 1.9,-.4 1.1,-.5 .4,-3.2 -1.4,-2.7 -1.2,-.7 2,-1.3 -.1,-1.8 h -1.5 l -2.3,-1.4 -.1,-3.1 6.2,-6.1 .7,-2.4 3.7,-6.3 5.9,-6.4 2.1,-1.7 2.5,.1 20.6,-5.2 z"
></path>
</svg>
我们可能期望看到的是这个。
但是,如果我们启动我们的网络浏览器并浏览这个页面,我们就不会看到任何东西。为什么会这样呢?嗯,这是因为这些路径来自于美国的完整地图,所以所使用的坐标系的原点(0,0)是在整个国家的左上方*--紧挨着*阿拉斯加。
这就不好了,因为我们希望我们的SVG能够完全适合我们的两个州。
我们的选择
我们有几个合理的选择。
- 改变每条路径,使其适合我们当前的SVG视图
- *改变我们的SVG "viewBox "*以适应我们的路径。
我个人喜欢第二种选择,并将在这篇文章中介绍这种选择
首先,一个弃权行为和一个小小的数学问题
让我们暂时回避一下国家的例子,只在SVG中使用几个任意的路径。
我们可以看到,我们的SVG是100x100,但是我们的形状实际上只从x = 40,y = 30 开始,然后在x = 80,y = 90 附近结束。我们希望我们的SVG能够放大这个区域,所以我们的视图看起来是这样的。
我们怎样才能做到这一点呢?事实证明,svg HTML元素有一个方便的属性viewBox ,它可以让我们把想要的原点x 和y ,以及想要的width 和height 。
就我们的形状而言,我们有以下情况。
我们可以看到,我们的原点是在x=40, y=30 ,然后我们必须为我们的宽度和高度做一点数学计算。
width = xMax - xMin = 80 - 40 = 40
height = yMax - yMin = 90 - 30 = 60
因此,我们可以为我们的SVG指定如下viewBox 。
<svg viewBox="40 30 40 60">
<!-- Shapes here -->
</svg>
这样就可以了!请注意,在默认情况下,SVG会将对象集中在它的viewBox ,而不是将它们扭曲。
这似乎很繁琐
是的,当我们想使用SVG的时候,做这样的数学运算是超级乏味的,如果我们的动态SVG的孩子或许多孩子,似乎几乎不可能。
幸运的是,我们是程序员。我们喜欢把事情自动化,所以让我们把刚才在脑子里做的过程自动化。
自动寻找边界
我喜欢的自动寻找边界的方法是遍历svg 的所有子节点。我使用一个名为getBBox() 的本地svg方法,它将返回一个元素的x 、y 、width 、height 。然后,只是一些简单的比较:如果x 低于我们目前看到的所有其他x 的值,它就是新的xMin 。同理,y 。为了得到xMax 和yMax ,我们做一个类似的操作,只是我们必须确保我们在每个元素的x + width 和y + height ,因为x 和y 只给我们元素的左上角,而我们想要右下角。
下面是这个代码的样子。
const svg = document.querySelector('svg');
const { xMin, xMax, yMin, yMax } = [...svg.children].reduce((acc, el) => {
const { x, y, width, height } = el.getBBox();
if (!acc.xMin || x < acc.xMin) acc.xMin = x;
if (!acc.xMax || x + width > acc.xMax) acc.xMax = x + width;
if (!acc.yMin || y < acc.yMin) acc.yMin = y;
if (!acc.yMax || y + height > acc.yMax) acc.yMax = y + height;
return acc;
}, {});
我们使用document.querySelector('svg') 来抓取我们页面上唯一的SVG(但是如果你需要不同的选择器,请确保使用一个ID或类!)。接下来,我使用[...svg.children] ,(a)获得我们的svg 的所有子元素,(b)使用传播操作符(...),将HTMLCollection 转换为一个元素数组。有了数组,我就可以使用reduce 方法,该方法需要一个回调函数和一个初始累积器(一个空对象{} )。
在reduce 回调函数中,我在每个元素上使用getBBox() 方法来获得x,y,width, 和height 。使用上述逻辑,我们有条件地覆盖我们的累积器上的xMin,xMax,yMin, 和yMax, 属性。
我们的最后一步是设置父级svg 的viewBox 属性。记住,viewBox 被设置为x y width height ,所以我们必须将我们的xMax 和yMax 分别转换为width 和height!
我们可以用我们在简化的例子中讨论的数学方法来做这个。
width = xMax - xMin
height = yMax - yMin
让我们把它放在这里。
const svg = document.querySelector('svg');
const { xMin, xMax, yMin, yMax } = [...svg.children].reduce((acc, el) => {
const { x, y, width, height } = el.getBBox();
if (!acc.xMin || x < acc.xMin) acc.xMin = x;
if (!acc.xMax || x + width > acc.xMax) acc.xMax = x + width;
if (!acc.yMin || y < acc.yMin) acc.yMin = y;
if (!acc.yMax || y + height > acc.yMax) acc.yMax = y + height;
return acc;
}, {});
const viewbox = `${xMin} ${yMin} ${xMax - xMin} ${yMax - yMin}`;
svg.setAttribute('viewBox', viewbox);
我们可以通过我们的状态SVG看到它的作用。事实上,我们吹捧了这个解决方案的灵活性,所以它最好能够容纳一个额外的州让我们把北卡罗来纳州加进去,以利于衡量。
总结
谢谢你今天和我一起玩形状、州,甚至玩了一点数学。希望你今天学到了一些新东西,并且有了一个方便的工具来适应你的SVG中的项目。