如何让 Sketch 导出的 SVG 图标显示缩略图

背景

可能不少设计师朋友都发现了这样一个问题,使用 Sketch 或者 Zeplin 等工具导出的 SVG 图标在 Mac 的 Finder 列表中,其预览缩略图展示的图标非常小,很难识别(如下图左侧);而使用 Adobe Illustrator 导出的图标,则可以显示正常大小的缩略图(如下图右侧)。

缩略图可以帮我们快速找到自己想要的文件,尤其是我的开源项目 Remix Icon 有1000 多枚图标,本地管理很不方便,我也希望这个项目开源后其他设计师在使用的时候不会再受此困扰。不过,我的主要生产工具是 Sketch ,因为它在易用性和效率上都要比 Ai 好很多(即便功能没有 Ai 强大)。虽然可以把 Sketch 导出的图标放进 Ai 重新保存输出,但 1000 多枚图标这么玩下来估计我都快废了,即使通过录制 动作 的方式来处理,成套图标下来依然是个庞大的工作量。

我尝试通过百度和谷歌寻求方案,皆无果,不过不灰心,我可以自己尝试去研究一下🤔。

大家可以跳过研究部分直接看结论,因为研究的部分内容较多,除了解决缩略图的问题之外,还顺便解读了一些 SVG 的属性,以及对 SVG 图标代码压缩的策略,喜欢钻研技术的朋友可以了解一下研究的部分。

初步研究

我们知道,SVG 是一种基于 XML 语言描述的矢量文件,也就是说,SVG 图片用文本编辑器打开的话,其实就是一堆代码字符串。而如果 Sketch 导出的图标和 Ai 导出的图标在缩略图展示上有所差异的话,那么八成他们的代码字符串也有差异,只要找到差异,修改调整代码,就搞定了,如果能通过批量查找和替换的方式修改所有图标的代码,那么批处理 1000 多枚图标也就是几秒的事情😋。

我们通过上图中的 landscpe-fill.svg举例,我用 Sketch 导出一枚经过SVGO压缩的图标 landscape-sketch.svg,使用文本编辑器打开,其代码结构如下:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
    <g fill="none" fill-rule="evenodd">
        <path d="M0 0h24v24H0z"/>
        <path fill="#000" d="M16 21l-4.762-8.73L15 6l8 15h-7zM8 10l6 11H2l6-11zM5.5 8a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5z"/>
    </g>
</svg>

SVGO 是业界通用的一款对 SVG 代码进行压缩的工具,有脚本版的,有独立软件版的,有在线版的… 我这里使用的是 Sketch 官方出品的一款插件版本,Sketch 安装该插件之后在导出 SVG 图标的时候会自动对该图标进行压缩, 插件地址

然后使用 Adobe Illustrator 打开该图标,另存为 landscape-Ai.svg,其代码结构如下:

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
	<style type="text/css">
		.st0 {
			fill: none;
		}
		.st1 {
			fill-rule: evenodd;
			clip-rule: evenodd;
		}
	</style>
	<g>
		<path class="st0" d="M0,0h24v24H0V0z"/>
		<path class="st1" d="M16,21l-4.8-8.7L15,6l8,15H16z M8,10l6,11H2L8,10z M5.5,8C4.1,8,3,6.9,3,5.5S4.1,3,5.5,3S8,4.1,8,5.5
		S6.9,8,5.5,8z"/>
	</g>
</svg>

通过代码来查看图标,我们就清晰多了😀,只要通过对比两个图标的代码的不同之处,我们就能够找到其中的猫… 😳 … 😦…我的妈呀,😱咋俩代码差距这么大!?不一样的地方太多了😰这TM就尴尬了…哪部分代码是关键啊?

怎么办?似乎好办,通过对比来看,主要是 Ai 的代码比 Sketch 多一些,那么很可能多的这部分起到了什么作用,于是我通过控制单一变量逐行删减的方式对 landscape-Ai.svg 的代码进行了排查。

结果让我非常失望,依旧没有任何线索...

进一步研究

看来,还是得了解一下 SVG 格式的代码语义才能更有效地解决问题,以后也能触类旁通解决其他类似的问题。于是通过搜索找到了 阮一峰的一篇SVG介绍文章,在此基础上又查看了一些手册类说明 W3school/SVG,好在 SVG 只是一种标记语言,没有复杂的编程逻辑在里面,作为一名设计师也可以很容易理解。

我们来一起分析一下 Ai 导出的 SVG 文件代码:

<?xml version="1.0" encoding="utf-8"?>

上面这一行是 XML 声明,在 Sketch 导出的 SVGO压缩处理的图标中没有这一行,这说明这一行是可以被压缩删除的。

所谓 SVG 的压缩,其实是将一些可有可无的代码删除,将一些复杂代码简化,去除空行等。

<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->

这一行是通过代码注释(中间的内容是注释部分)的方式描述的,是 Adobe Illustrator的水印,作用是告诉其他人:“你现在看到的这个 SVG 图标,可是通过我 Ai 生成的哦!” 所以,非常可以删除!

<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">

这行代码和中,version 属性用于定义所使用的 SVG 版本,可以删除,而 id 属性通常在和 JS 配合的时候会用到,这里我们只是用来做展示,可以删除,而且即便保留,id 的值也需要是英文;xmlns 属性用于定义 SVG 命名空间,这一部分我参阅了 这篇文章 文中提到有的浏览器对命名空间很严格,需要保留,而且我们留意到在 Sketch 导出的压缩过的代码中,也保留了 xmlns 属性,这里我们也保留;xmlns:xlink 是 “命名空间前缀” 的命名空间,刚刚的文章中,作者也建议保留,而我发现 Sketch 导出的压缩版本中将本属性压缩掉了,所以我认为本属性应该也可以被删除,否则 Sketch 应该早就收到用户对插件的反馈对其进行添加修复了;x="0px" y="0px" 是对图标内容的坐标进行定位,而后面 viewBox 属性中也包含定位参数,妥妥删掉 。viewBox 相当于是 SVG 的展示窗口,只有位于窗口中间的内容才能被展示,有点像我们 Sketch 和 Ai 中的画板,不论图像内容多大,只能导出位于画板中间的内容部分,0 0 24 24 中,前两个数代表横纵坐标,后面两个数代表画板的宽和高,连在一起就是:展示从左上角 0,0 的位置开始,向右展示宽度为 24 的内容,向下展示高度为 24 的内容(我的图标是按照 24x24 设计的),这个属性的一个典型作用就是可以把一堆图标放在一个 SVG 文件上,通过调整 viewBox 来显示展示哪个图标,优势是可以减少服务器请求,加快渲染速度。style="enable-background:new 0 0 24 24;" 是 SVG1.1的一个新特性,用于允许滤镜使用背景色等,对我们单纯的图标而言,没什么鸟用。而 xml:space 这个属性,查了一下发现,已经从 Web 标准中删除了,所以妥妥没用。

如果你强忍着耐心,已经看到这了,我还是得非常遗憾地说,刚刚我们将可以删除的代码删除后,剩余的部分依旧没有影响到 Ai 导出的图标的缩略图效果,也就是说,刚才那堆代码和缩略图压根就没关系。妈哒!生气😤!

<style type="text/css">
		.st0 {
			fill: none;
		}
		.st1 {
			fill-rule: evenodd;
			clip-rule: evenodd;
		}
	</style>

这一部分是一个 CSS 的样式定义,Ai 抽离出了“类” .st0.st1 ,而且还定义了每个类的填充属性。抽离“类”这个操作,对于复用性较高内容比较有用,可以批量管理一些样式和属性,但对于单枚图标而言,就有点脱裤子放屁了,还增加了代码的复杂性。

这里比较值得一提的是 fill-ruleclip-rule 不常见,而且仅在 clippath 中的元素起作用,否则效果和 fill-rule 一致,所以我们再“重点简单”说一下 fill-rule 这个属性以及它的两个参数 evenoddnonzero。这个是图像填充的两种规则,具体关于规则的详细说明可以看张鑫旭的这篇文章,说的很透彻,但是对设计师而言并不容易理解,结论是这个信息其实并不会影响缩略图,而且如果我们是通过标准的布尔运算设计图标(避免单一路径交叉),最后生成的图标中虽然有这个参数和属性,但是不论删除还是修改,一般情况下都不影响图形的样式。另外建议大家在生成图标的时候将图标内的所有形状都做转曲展平形状 flatten 处理,这样不但能减少很多问题(尤其在生成字体的时候),还能起到代码精简作用(除非你要保留图标内的一些独立元素做动画等处理)。 保险起见,大家可以在今后的SVG图标代码精简中保留这个属性 fill-rule

<g>
		<path class="st0" d="M0,0h24v24H0V0z"/>
		<path class="st1" d="M16,21l-4.8-8.7L15,6l8,15H16z M8,10l6,11H2L8,10z M5.5,8C4.1,8,3,6.9,3,5.5S4.1,3,5.5,3S8,4.1,8,5.5
		S6.9,8,5.5,8z"/>
</g>

看完上面的部分,剩下的就简单多了。g 代表 group,就是用来把元素进行组合的容器元素。path 是路径的意思,矢量图形都是基于路径的表述,我们通过对路径的填充和描边,就形成了最终看到的图形效果。

我们可以看到上图有两个路径,类名为 st0 的这个路径,d 的参数中还能看到有“24”这个值,这个路径就是我们绘制图标时候采用的 24x24 大小的背景托盘,以此来保证统一的规格,实际上最后导出的图标中没有这个托盘路径也可以,通过前面提到的 viewBox="0 0 24 24" 我们也能保证图标是包含边距的统一大小正方形,但是如果我们将不含托盘的图标放回 Sketch,就没有 24x24 的统一大小了,所以为了方便其他设计师可以很方便排版我们的 Remix Icon,我给每一枚图标都增加了托盘,但这样也增加了每一枚图标的体积。其他设计师朋友后期可以根据自己的诉求来考虑是否需要保留托盘。类名为 st1 的这个路径,就是我们图标的实体了,这一部分我们不要动,动了图标就变样了。

图标处理

我们将 Ai 的图标 landscape-Ai.svg 按照刚刚我们研究的属性规则重新进行手动调整压缩:去除不必要属性、抽离的类转为行内样式...压缩后结果如下:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
	<g>
		<path fill="none" d="M0,0h24v24H0V0z"/>
		<path fill-rule="evenodd" d="M16,21l-4.8-8.7L15,6l8,15H16z M8,10l6,11H2L8,10z M5.5,8C4.1,8,3,6.9,3,5.5S4.1,3,5.5,3S8,4.1,8,5.5
		S6.9,8,5.5,8z"/>
	</g>
</svg>

此时,landscape-Ai.svg 依然有正常的缩略图。这一次我们再按照一开始的思路拿 Ai 图标和 Sketch 图标 landscape-sketch.svg 进行对比,一切就清晰明朗了。

我们把 landscape-sketch.svg 的代码重新复制过来,对比一下:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
    <g fill="none" fill-rule="evenodd">
        <path d="M0 0h24v24H0z"/>
        <path fill="#000" d="M16 21l-4.762-8.73L15 6l8 15h-7zM8 10l6 11H2l6-11zM5.5 8a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5z"/>
    </g>
</svg>

<g> 部分的的差异经过我尝试,发现将 fill="#000" 属性 去除后,图标依旧为黑色,所以从工程实践角度来看,去掉也不是坏事,因为我们很可能会通过css等方式去后期动态控制颜色,去除后默认展示黑色。

重点来了✊:而真正影响到缩略图展示的,其实是宽高属性 width="24" height="24"。去掉宽高属性,只保留 viewBox 属性的话,SVG 图像的默认大小是它的父元素的大小,如果没有父元素(比如我们的图标没有放到页面中,也就没有父元素),那么就是视窗 body 的100%大小。所以之前缩略图之所以展示很小的缩略图,是因为 Finder 认为它在一个视窗页面中,只占用24的宽高大小,再缩放到缩略图之后,就什么也看不清了。那么问题来了:去掉这个宽高属性,不就没有宽高了吗?不会影响图标置入页面之后的效果吗?是会的,比如之前你的页面中插入一个带有宽高的图标,不需要 CSS 或其他方法指定额外的宽高,图标也是按照你设计的宽高展示的,但一旦换一个尺寸不一样的图标,展示的效果就变了,而这可能并不是你想要的,所以通过 CSS 等方法额外指定宽高大小,反而代码会比较健壮;不仅如此,去掉宽高属性的 SVG 重新导回到 Sketch 或者 Ai 的话,还是之前你设计的时候的大小,非常棒~

既然如此,我们引申一下,如果我们去掉宽高属性之后,将 viewBox0 0 24 24 改为 0 0 48 24 ,图标会变成什么样呢?等比拉伸吗?刚刚我们有提到,viewBox 类似于 Sketch 的画布,所以修改这个之后,只是把画布的宽高做了修改,图标不会拉伸,但是图标的形状和边距就变了。

除了发现影响缩略图的关键因素,最终我们也将 SVG 代码压缩成了这样:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
	<g>
		<path fill="none" d="M0,0h24v24H0V0z"/>
		<path fill-rule="evenodd" d="M16,21l-4.8-8.7L15,6l8,15H16z M8,10l6,11H2L8,10z M5.5,8C4.1,8,3,6.9,3,5.5S4.1,3,5.5,3S8,4.1,8,5.5
		S6.9,8,5.5,8z"/>
	</g>
</svg>

其他对 SVG 代码压缩的小伙伴可以按这个模板来。

结论

想要让缩略图展示大图标,只要去除代码中的宽高指定属性 widthheight 就可以了。比如:

去除之前:

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

去除之后:

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

去除之后需要在 CSS 等控制样式的参数里重新指定大小。

如果你也有好多个图标,想要批处理,只要使用代码文本编辑器,通过在文件夹里批量查找和替换的方式,把宽高属性连带后面的空格替换为空值就可以了。有能力的小伙伴可以写一个小脚本处理这个问题,到时候就更简便了,记得分享给我哦哈哈。

代码文本编辑器,我使用的是 Atom,也推荐 Sublime Text

“大哥,你这么长一篇文章,结论就这么点?”

“额…是啊,结论其实挺简单的,所以文章一开头就建议大家可以先跳过研究的部分直接看结论,但是文章的研究部分讲述的是一个解决问题的思路和过程,同时也讲了一些 SVG 属性的知识,下一次遇到类似的问题,就可以自己解决了!”

由于个人网站暂时还没有评论功能,如果还有什么问题,可以到 Spectrum 留言。