图标(icon)是一个应用中非常重要的角色。在应用内设置了合适的图标,可以极大的丰富用户界面,提升用户体验。

在前端业务开发过程中,几乎每天都会接触到图标,如果项目使用了组件库,那么通常是直接使用组件库提供的Icon组件;或者是使用UI设计师给的图标;又或者自己封装一个Icon组件。

最近在研究组件库,看到Icon组件的时候,发现并没有想象中那么简单。于是仔细研究了一下图标,希望你看完这篇文章后,不再为图标而烦恼。

图标类型

图标是一个统称,它包含了不同的格式或实现方式。图标不一定是一个图片,比如字体图标(Iconfont)。以图标的格式或实现方式可以分为一下几种:

  • 图片图标:png、jpg等图片格式,栅格化图标
  • SVG图标:SVG文档,矢量化图标
  • 字体图标:使用字体来渲染图标,矢量化图标

图片图标在使用上和图片无异,在过去的网页开发中是非常常用的图标格式,通常会使用雪碧图进行加载优化。

SVG图标是现在最常见的图标形式之一,因为它可以自由定制,也能很方便的导出为图片格式。

字体图标则是将图标以字体的形式渲染出来,它也可以使用SVG图标来制作。

在这里我们主要研究SVG图标和字体图标。

字体图标原理

字体图标相对比较特殊,这里简单介绍一下它的原理。浏览器渲染文字的时候,会先将其转为unicode编码,然后去匹配对应的字体文件并将其渲染到屏幕上。基于这个原理,我们便能制作一个特殊的字体,渲染的是一个图形,而不是一个文字。

浏览器有一些默认支持的字体图标,它是一组unicode字符集,完整列表可访问此链接。下面是一个示例,直接在HTML中输入unicode编码,就能显示对应的字体图标。支持十进制和十六进制两种格式。下面是一个简单的示例。

<p>Icon: &#9733;</p>
<p>Icon: &#x2605;</p>

Image.png

除了直接在标签中使用,我们也可以将unicode通过css代码放到content属性中,font-awesome等多数字体图标库都是基于此方式实现的。

<p>
  Icon:
  <i class="icon-star"></i>
</p>
.icon-star::before {
  content: "\2605";
}

Image.png

在content属性中的语法是 “\ + 十六进制代码”,与HTML中有点区别。

图标尺寸

图标尺寸通常不是固定的,在不同的位置需要设置不同的大小。我们在设置尺寸时一般都会使用 widthheight 来设置宽高。但是对于图标来说,经常需要与文字对齐,使用宽高来设置尺寸会比较麻烦。因此我们可以使用另一种方式来设置图标的尺寸—— font-size

使用font-size设置尺寸

使用 font-size 来设置尺寸属于css的一个技巧,其原理是基于css中的 em 单位,它是一个动态的单位,真实大小取决于 font-size 的值。因此我们可以将需要定制尺寸的地方,按比例替换为 em 单位,理论上就能使用 font-size 来控制尺寸。比如下面的代码,我们尝试使用 font-size 来控制一个 div 的大小。

// <div class="box"></div>

.box {
  width: 1em;
  height: 1em;
  background: red;
}

此时我们去设置 divfont-size ,便可以改变它的尺寸。下图左边是默认(16px)尺寸,右边的 font-size 为32px。

Image.png

需要注意的是,emrem 的区别,两者依赖的 font-size 不同,em 依赖于父级的 font-size ,而 rem 依赖的是根节点(html)的 font-size 大小。对于大部分需要做自适应尺寸的场景,使用 rem 是更好的选择。

我们的SVG图标就很适合使用该方式来控制尺寸,对于 viewBox 为正方形的图标,我们只需要为SVG节点设置宽高分别为 1em 的样式即可。而对于非正方形的 viewBox ,按照长宽比例分别设置宽高样式,数值大的一边设置为 1em ,数值小的一边按比例设置。

为了方便扩展,SVG图标通常要将 viewBox 规范化为正方形。

字体图标尺寸

字体图标由于本身就是文字,可以直接应用 font-size 样式。有些时候字体图标的尺寸看起来不是那么协调,那么可以单独为其设置字体大小。

图标颜色

如果要指出图片图标最大的一个缺点,就是颜色的定制。由于无法用css修改图片内容的样式,所以图片图标出生什么样,永远就是什么样。即使使用 filter 也只能进行非常局限的定制。

了解到一种使用png透明背景来实现图标颜色定制的操作,大概原理是使用png图片有alpha通道的特性,将图标的纹理部分做成透明的,然后通过css给图片设置背景色来实现png图标变色。

SVG图标颜色

SVG标签是可以使用css来控制样式的,修改个颜色当然是很轻松的,我们可以直接将样式作用于图标的线条、填充等位置。

<span class="icon-star">
  <svg width="48" height="48" viewBox="0 0 48 48" fill="none" stroke="#000" stroke-width="4">
    <path d="M22.5516 6.90849C22.735 6.53687 23.265 6.53687 23.4484 6.90849L28.4676 17.0786C28.5405 17.2262 28.6812 17.3285 28.8441 17.3521L40.0675 18.983C40.4776 19.0426 40.6414 19.5466 40.3446 19.8358L32.2233 27.7522C32.1054 27.867 32.0517 28.0325 32.0795 28.1947L33.9967 39.3728C34.0667 39.7812 33.638 40.0927 33.2712 39.8999L23.2327 34.6223C23.087 34.5457 22.913 34.5457 22.7673 34.6223L12.7288 39.8999C12.362 40.0927 11.9333 39.7812 12.0033 39.3728L13.9205 28.1947C13.9483 28.0325 13.8946 27.867 13.7767 27.7522L5.6554 19.8358C5.35864 19.5466 5.5224 19.0426 5.93251 18.983L17.1559 17.3521C17.3188 17.3285 17.4595 17.2262 17.5324 17.0786L22.5516 6.90849Z" stroke-linecap="butt"></path>
  </svg>
</span>
<span class="icon-star-fill">
  <svg width="48" height="48" viewBox="0 0 48 48" fill="none" stroke-width="4">
    <path d="M22.6828 5.41468C23.2513 4.37227 24.7481 4.37227 25.3166 5.41468L30.8238 15.513C31.0396 15.9086 31.4217 16.1862 31.8646 16.2691L43.1705 18.3864C44.3376 18.6049 44.8001 20.0284 43.9844 20.8912L36.0822 29.2495C35.7726 29.5769 35.6267 30.0262 35.6847 30.473L37.1648 41.8798C37.3176 43.0573 36.1067 43.9371 35.034 43.4279L24.6429 38.4953C24.2358 38.3021 23.7635 38.3021 23.3564 38.4953L12.9653 43.4279C11.8927 43.9371 10.6817 43.0573 10.8345 41.8798L12.3147 30.473C12.3726 30.0262 12.2267 29.5769 11.9171 29.2495L4.01491 20.8912C3.1992 20.0284 3.66173 18.6049 4.8288 18.3864L16.1347 16.2691C16.5776 16.1862 16.9597 15.9086 17.1755 15.513L22.6828 5.41468Z" fill="#000" stroke="none" stroke-width="none" stroke-linecap="butt"></path>
  </svg>
</span>
.icon-star {
  svg {
    path {
      stroke: red;
    }
  }
}

.icon-star-fill {
  svg {
    path {
      fill: blue;
    }
  }
}

上面的示例通过使用css给path设置 strokefill 来直接修改图标颜色,这样做是能达到目的的,看下面的图片。

Image.png

但是很显然,这种方式太粗糙,开发起来繁琐,扩展性差。那有没有更优雅的方式呢?答案是肯定的,我们依然可以像用 font-size 来定制大小一样,直接使用 color 来定制颜色。而具体的实现需要依赖css中的一个关键字——currentColor

currentColor 这个关键字接触很少,我也是最近才去了解它。这是一个非常好用的一个关键字,从它的字面意思“当前颜色”可以猜测他应该是引用了某个颜色的值,它就是 color 的值。准确的说:currentColor 是它的父级(或当前元素)的 color 值。

有了 currentColor ,我们可以直接将SVG的path和fill都替换掉,最终便可以直接使用 color 来控制图标颜色。下面的示例基于前面的代码,HTML部分没有变动,只修改样式。

.icon-star {
  color: red;

  svg {
    stroke: currentColor;
  }
}

.icon-star-fill {
  color: blue;

  svg {
    path {
      fill: currentColor;
    }
  }
}

Image.png

这样我们就实现了使用 color 来控制SVG图标的颜色。

这里的代码可能看上去并没有比前面的更简洁,实际上我们在设计SVG图标的时候,会将fill和stroke都规范为 currentColor ,这样在使用图标的时候就不用再去编写多余的样式。另外,使用color控制图标颜色,可以轻松的让图标跟随字体改变颜色,比如鼠标hover状态等。

字体图标颜色

字体图标的颜色和尺寸类似,都是字体,直接使用 color 样式即可。

多色图标

由于原理上的限制,字体图标并不支持多色图标,这算是字体图标的一个缺点。但是SVG图标则对多色图标支持良好,只不过多色图标如果要进行可定制,实现起来会比较麻烦。

通常多色图标的色彩搭配都需要再设计阶段进行固化,很少有进行可编程定制的需求,因此对于SVG多色图标定制颜色的功能暂时不深入研究,等遇到真实场景的时候,再回来填这个坑。

图标变体

除了尺寸和颜色,还有一些小细节可以进行调整,比如图标的线宽、拐角、端点等。而这些方面的定制能力,则是SVG图标天然的优势。

SVG图标变体

除了颜色,svg图标还有一些可以定制的地方,比如常见的线宽、拐角和端点。可以通过为svg标签设置属性来控制这三个样式,实现图标的变体。

字体图标变体

字体图标变体的实现就没那么简单了,总的有两个方向:

  1. 每个变体都对应一个unicode实现
  2. 通过font-weight等可用的样式来实现
  3. 通过改变svg的属性来控制样式,常用的有
    • stroke-linecap:线条端点
    • stroke-linejoin:线条连接点(拐角)
    • stroke-width:线条宽度

总结

为了控制文章篇幅,省略了“图标对齐”相关的内容,这部分内容可以和垂直居中相关的知识放到一起。

图标设计到的东西并不复杂,但是可能是因为简单,很多人没有仔细了解过,包括我在内。正好现在研究组件库,终于花了些时间搞了一下Icon。

参考资料

「每日一题」聊一聊字体图标的实现原理

网站的文字渲染原理与形式 - Frontopen

CSS values and units - Learn web development | MDN

- CSS(层叠样式表) | MDN

currentColor-CSS3超高校级好用CSS变量 « 张鑫旭-鑫空间-鑫生活

CSS背景色镂空技术实际应用及进阶 « 张鑫旭-鑫空间-鑫生活