内联SVG显示不全或消失,调试时该盯紧哪些地方?

3,738字
16–24 分钟
in

调试内联SVG就像追一部悬疑剧,明明代码都敲进去了,浏览器里愣是啥也没有,或者只冒出半个身子。这种翻车现场其实大部分时候都栽在几个固定的坑里。因为内联SVG本身就是DOM树的一部分,打开浏览器的开发者工具就能直接审查元素,所以排查起来并不玄乎。下面这六个方向,基本能把绝大多数渲染异常的问题拎出来。

目录

viewBox玄机

viewBox这东西,说人话就是给SVG这个无限大的画布装上一个取景框。画布本身没有边界,但取景框(也就是viewport)决定了能看见哪一块。viewBox属性需要四个数值:起始x坐标 起始y坐标 宽度 高度。如果忘记写viewBox,SVG虽然也能显示,但会失去随容器缩放的能力,这在响应式布局里特别容易翻车。

实际操作中碰到图像被裁剪,第一反应就是检查viewBox。假设原本写的代码是这样:

<svg viewBox="0 0 700 700" width="700" height="700">
  <circle cx="900" cy="900" r="50" fill="blue" />
</svg>

这里蓝色圆形的圆心在(900, 900),但viewBox只显示了从(0,0)到(700,700)的区域,圆形自然就被裁掉了。临时救急的办法是在<svg>标签上加overflow="visible",但这样如果有背景色或者周围有其他元素,画面可能会乱套。更彻底的解决方案是调整viewBox的前两个数值,把取景框挪到正确位置:

<svg viewBox="300 200 700 700" width="700" height="700">
  <!-- 现在取景框起点移动了,原本被裁掉的内容可能就露出来 -->
</svg>

如果希望缩小图像让更多内容塞进viewport,就把viewBox的后两个数值调大。比如把700改成900,同样的700×700窗口里会塞进900×900单位的内容,每个单位变小了,画面就缩小了。反过来调小后两个数值就是放大。这里有个小细节:改完viewBox后最好同时检查一下周围的布局,因为缩放会改变元素的实际占位。

宽高缺一不可

很多内联SVG突然消失,罪魁祸首就是没写widthheight属性。尤其是当SVG被塞进一个绝对定位的容器或者弹性盒子里时,Safari浏览器会抽风似的把宽度算成0px,而不是自动撑开。对比一下三个浏览器的表现就能看出差异:Chrome和Firefox通常还能猜一下,Safari直接摆烂。

解决方案并不复杂,补上一个宽度或高度就行。可以直接写成标签属性:

<svg width="700" height="700" viewBox="0 0 100 100">

或者通过内联样式:

<svg style="width: 700px; height: 700px;">

甚至写在CSS里。但有个容易踩的坑:单独使用height: 100%height: auto而没给宽度,在很多浏览器里会失效。稳妥的做法是宽度和高度成对出现,或者至少给一个明确的数值。如果非要用百分比,确保父容器有确定的高度。

下面这个表格整理了不同写法的兼容表现:

写法ChromeSafariFirefox
无宽高正常崩了正常
仅宽度正常翻车正常
宽高都有稳了稳了稳了

颜色翻车现场

有时候SVG不是没了,而是跟背景融为一体看不见了。这种情况多半是fillstroke颜色值在打架。比方说在<svg>标签上通过CSS设置了一个红色填充,但内部某个路径的fill属性写的是白色,那这个路径就会在白底上隐身。

打开开发者工具,选中那个消失的图形区域,看右侧样式面板里fillstroke到底生效了什么。一个粗暴但有效的办法:把SVG内部所有内联的fillstroke属性先删掉,只保留外层统一控制。比如下面这段代码:

<svg fill="red">
  <path fill="white" d="..." />
  <circle fill="blue" cx="10" cy="10" r="5" />
</svg>

删掉内部的fill="white"fill="blue"之后,所有图形都会变成红色,至少能确认图形本身是存在的。之后再根据需要逐个恢复颜色。如果用的是CSS变量或者currentColor,也要检查变量有没有被意外覆盖。

ID对不上咋整

在Illustrator、Figma这类设计软件里导出SVG时,图层名字会变成id属性。很多人习惯用这些ID来写CSS样式,比如#giraffe-leg { fill: brown; }。但问题来了:第二次导出同一份文件时,哪怕只是复制粘贴了一个形状,所有ID都可能被重新生成,原来的#giraffe-leg变成了#giraffe-leg-2,CSS里还挂着旧ID,样式自然失效。

这种场景下图形不会完全消失,但某些部分的颜色、描边会变得完全不对劲。排查方法很简单:在开发者工具里选中那个表现异常的图形,看它当前的id是什么,再去CSS文件里搜一下这个ID存不存在。如果文件特别大,手动找ID就像大海捞针,这时候直接用开发者工具的“审查元素”功能点选图形,比翻代码快得多。

另一个实用技巧:导出SVG之后,用代码编辑器打开文件,全局搜索id=,把那些自动生成的混乱ID改成有意义的英文单词,顺便把CSS里的选择器也同步改掉。这样哪怕重新导出,只要不改图层名,ID就不会变。

裁剪蒙版排雷

clip-pathmask是SVG里两个容易让人血压升高的家伙。图形被裁了一半或者完全消失,但viewBox又没问题,那八成是裁剪或蒙版在捣鬼。clip-path是硬切,边缘锋利;mask是渐变透,可以有半透明效果。

调试时按下面这个清单来走:

<clipPath><mask>里面的代码复制一份,粘贴到</svg>标签前面,给这些形状临时加上fill="red"之类的颜色,看看它们实际占的位置和大小。如果压根看不见,说明坐标对不上。这时候需要调整xywidthheight或者用transform把它们挪到正确位置。

检查clip-pathmask引用的ID跟实际定义的是否一致。一个笔误写成url(#clipPath)而定义的是id="clip",那就白瞎了。另外<clipPath>内部的元素设置fillstrokeopacity都没用,clip-path只关心图形的几何轮廓。如果用<line>做裁剪路径,因为线没有面积,裁剪效果直接归零,必须换成<polygon><path>这种有面积的东西。

处理mask时,蒙版内容的亮度决定了最终显示的不透明度。纯黑色(fill="black")会让图形完全透明消失,纯白色(fill="white")完全不透明。如果蒙版内容全是黑的,那加了mask就跟没加一样——全看不见。把蒙版里的某个形状临时改成白色或灰色,就能判断是不是亮度的问题。

<mask id="test-mask">
  <rect width="100" height="100" fill="white" /> <!-- 这个区域会显示 -->
  <circle cx="50" cy="50" r="30" fill="black" /> <!-- 这个区域会隐藏 -->
</mask>

命名空间别漏

SVG骨子里是XML语言,所以需要xmlns属性来告诉浏览器“我是SVG家族的人”。现代浏览器大多比较宽容,不写也能猜出来,但碰上老版本浏览器或者Gecko内核(比如某些旧版Firefox),缺了命名空间就直接摆烂不渲染。

最保险的做法是在<svg>标签上加上:

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

如果SVG被塞进HTML文件里,而<html>标签本身也声明了xmlns,两者不会冲突。更隐蔽的坑出现在CSS背景图里使用内联SVG的时候。比如表单验证成功后显示一个对勾图标:

input:valid {
  background: url('data:image/svg+xml,
    <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26">
      <circle cx="13" cy="13" r="13" fill="#abedd8"/>
      <path fill="none" stroke="white" stroke-width="2" d="M5 15.2l5 5 10-12"/>
    </svg>') no-repeat;
}

如果漏掉xmlns="http://www.w3.org/2000/svg",整个背景图就会消失。另外旧代码里常见的xlink:href属性,比如引用渐变或滤镜时写成xlink:href="#grad",需要同时带上xmlns:xlink="http://www.w3.org/1999/xlink"这个命名空间。SVG 2已经推荐直接用href,但为了兼容老古董,两个都写上也行。

<use href="#icon" xlink:href="#icon" />

处理完这六个方向,内联SVG基本就乖乖现身了。剩下的那些奇葩问题,大概率是某个属性拼写错了或者标签没闭合,开发者工具里红彤彤的错误提示会直接告诉答案。