【实战】网站图标新技术「SVG-Sprite」

# 前言

  前端攻城狮们在做前后台项目的时候经常会用到很多icon图标,刚开始还好,用的图片不多,没什么感觉,但随着项目迭代,体积与功能不断增大,修改和添加图标变得比较麻烦,而且总觉的不够优雅。

# 几种图标使用办法

  • 标签 <image>
      想必大家多多少少都用过。简单,让设计师提供一张图片一丢即可。但其缺点尤为突出。image作为网页依赖资源存在,在页面载入时需要去服务器获取下载,这不仅占用了网络资源,更严重的是拖慢了页面的展示。

  • 雪碧图 sprite
      前几年雪碧图几乎成为前端面试中必问的题,它的出现就是为了解决<image>占用过多下载线程。它就是将多个图片合成一个图片,然后利用 css 的 background-position 定位显示不同的 icon 图标。但使用过的同学都有感受,维护起来比较困难。每新增一个图标,都需要改动原图片,还可能不小心出错影响到前面定位好的图片;不仅如此,一修改雪碧图,之前的缓存就失效了。

  • Iconfont
      相信这是现阶段绝大多数开发者使用的办法。在此之前,Font Awesome也是主流用法之一,笔者用的较少。现在的UI库就拿 element-UI 来说,本身提供有少量高可用图标,但依旧不能满足日益迭代的特别是设计师的刁钻要求。阿里爸爸开发的iconfont开源图库提供了海量的图标,支持自定义图标库下载,单图标定制颜色,下载不同类型(svg,png,ai),使用上也支持(unicode,font-class,symbol )三种方式
      有一点不方便就是,你在编辑自定义图库时需要预知网站将用到些什么图标,这太难了,往往后续需要使用添加图标库或者修改已有库的方式来扩大图标,好在一次性可以增加多个图标,比雪碧图改善了太多

  • SVG类雪碧图 SVG-Sprite
    SVG-Sprite,不仅有着雪碧图的优势,又避免了改动和维护困难的问题,而且具有svg矢量图标的优点,集多优势于一身的它,必将成为网站图标的未来之星。

# SVG Sprite 技术实现

  SVG Sprite 与普通 Sprite 具有类似思想,即将图标图形整合在一起,利用CSS实现精准定位显示特定图标。

1.【基础】SVG Sprite与symbol元素

  目前,SVG Sprite的最佳实践是symbol元素。单纯翻译即“符号”,但在这不是这意思。它类似于Flash中的“影片剪辑”,或者“元件”,理解为元件,模块更加贴切。

  一个干净的 SVG Sprite 由 <svg>, <symbol>标签组合而成,类似如下

<svg>
  <symbol id="icon-name1">...</symbol> // 第一个图标路径形状
  <symbol id="icon-name2">...</symbol> // 第二个图标路径形状
  <symbol id="icon-name3">...</symbol> // 第三个图标路径形状
</svg>

  如上<svg>容器就类似于雪碧图的那张大图,而每一个<symbol>则是每一个被实际定位显示的图标,SVG Sprite 使用id作为精准定位依据提取目标资源,最终复制到SVG use 位置,强烈建议id语义化定义。不理解的可以接着往下看。

2.【基础】SVG 中的 use元素

use元素是SVG中非常重要的一个元素,它支持在SVG内提取目标节点,并在别的地方复制它们。其效果等同于这些节点被深克隆到一个不可见的DOM中,然后粘贴到use使用的位置。它有两个特征:
(1)可以重复调用
  SVG是代码型矢量图片,其占用代码行较多。一个图片多处写会严重影响代码可阅读性。use完美拯救了这个问题,通过xlink:href定位需要的元件,使用如下,#号表示取id

<use xlink:href="#icon-name1" x="50" y="50" /> // x, y 定位显示位置

(2)支持跨SVG调用

跨SVG调用特征是 SVG-Sprite 技术使用核心所在

  简单理解就是,SVG内部可以use别的SVG内定义的symbol。假设有 1 中的SVG Sprite图,则使用如下

<svg class="scgClass">
  <use xlink:href="#icon-name1" />
</svg>

  这里我们还设计了一个svgClass,在Vue项目中可以通过prop进来,通过这个类来控制SVG显示的样式如大小,颜色等。
  从这个案例中获取你已经感受到,我们会封装一个<svg-icon>的组件,外部引用这个组件,传入 svgClass 及图标的icon-name控制展示及样式,达到HTML层面优雅的使用效果

3.【基础】SVG 的 aria-hidden 和替代文本

  类似<img alt="提示文案">中的alt属性,SVG提供有替代文本以便屏幕阅读器遇到SVG图标时读取的文案。SVG一般都需要设置aria-hidden="true"以便阅读器跳过阅读

<svg aria-hidden="true">
  <use xlink:href="#icon-name1" />
</svg>

  当遇到 a标签或者button的内容只有是图标时,可以在abutton标签上设置阅读替代文本属性aria-label,而SVG依然跳过,保证用户体验

<a href="/news/" arialabel="Latest News">
  <svg aria-hidden="true"><use xlink:href="#icon-name1"></svg>
</a>

4. 【实战】webpack配置svg-sprite-loader
  1. package.json中添加插件
npm install svg-sprite-loader --save-dev
  1. 配置webpack,修改vue.config.js
      注意默认的SVG使用的是url-loader来解析的,我们只针对icon部分使用svg-sprite-loader处理,因此需要用到exculdeinclude命令区分。
// 设置svg图片-图标处理方式
config.module
  .rule('svg')
  .exclude.add(resolve('src/icons')) // 除了src/icons下的svg都是用url-loader来处理
  .end()
config.module
  .rule('icons')
  .test(/\.svg$/)
  .include.add(resolve('src/icons')) // svg-sprite-loader只处理src/icons下的svg
  .end()
  .use('svg-sprite-loader')
  .loader('svg-sprite-loader')
  .options({
    symbolId: 'icon-[name]'
  })
  .end()

  有了这个区分,就要求我们规范的管理SVG图标,将其统一放置在src/icons目录下。

【注】为什么不是src/icons/svg?因为在下一步会去生成SVG Sprite,而他不在该目录里。即src/icons/svg只是源svg图标,SVG Sprite在src/icons里,所以用src/icons。只要保证正常SVG图片不往这里放即可。

5. 【实战】自动导入生成SVG Sprite require.context

  先理解一下,既要有维护容易的增添单个图标,又要有使用方便的SVG Sprite。这两者本身相悖,为满足这个功能,我们需要一个从图标组里生成SVG Sprite的能力,好在webpack提供了require.context自动导入功能,满足了该需求。我们在与@/icons/svg同级目录下编写该逻辑

Sprite生成文件

  先入为主,SvgIcon.svg是下一步要实现的组件封装,因为必然有许多地方需要用到图标,所以对SvgIcon组件进行全局注册

// /icons/index.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'

// 全局注册SvgIcon组件 
Vue.component('svg-icon', SvgIcon)

const req = require.context('./svg', false, /\.svg$/) 
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

require.context接受三个参数,1-源svg文件夹,2-是否检索下级目录,3-匹配资源正则表达式。

  以上代码片段的意思是:在svg文件夹下,不检索其子目录,寻找后缀名为.svg的能被require所有资源图标。需要注意的是生成的SVG Sprite元件的id名,会加上icon-前缀。其最终会生成一个类似如下的SVG Sprite

SVG Sprite 样例

6. 【实战】上手写<svg-icon>组件

  接下来实现4中的SvgIcon组件,用于在HTML中优雅的编写引入的图标。我们在该组件中还区分了图标是否是外联SVG图标,iconClass表示图标的语义化名称(一般为svg图片名字),className表示图标个性化样式。因为生成SVG Sprite时symbol的id被加上了icon-,为了不影响引用,我们需要在组件实现时把类名的前缀加上

// components/svg-icon.vue
<template>
  <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
  <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
    <use :xlink:href="iconName" />
  </svg>
</template>
<script>

export default {
  name: 'SvgIcon',
  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    }
  },
  computed: {
    isExternal() {
      return this.isExternal(this.iconClass)
    },
    iconName() {
      return `#icon-${this.iconClass}`
    },
    svgClass() {
      if (this.className) {
        return 'svg-icon ' + this.className
      } else {
        return 'svg-icon'
      }
    },
    styleExternalIcon() {
      return {
        mask: `url(${this.iconClass}) no-repeat 50% 50%`,
        '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
      }
    }
  },
  methods: {
    // 判断是否是外部图标
    isExternal(path) {
      return /^(https?:|mailto:|tel:)/.test(path)
    }
  }
}
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}

.svg-external-icon {
  background-color: currentColor;
  mask-size: cover!important;
  display: inline-block;
}
</style>

7. 页面使用SVG Sprite

  有了生成SVG Sprite的能力,也有了优雅使用 SVG Icon 组件,接下来就是怎么用了。

  如果你有设计师提供SVG图标最好,有许多设计师使用sketch来工作,可以很方便的导出SVG。如果没有设计师或不方便实时沟通,可以借用阿里爸爸的IconFont,它支持下载SVG格式的图片,下载下来放到src/icons/svg文件加下即可,require.context会自动更新成SVG Sprite,开发者只需要管怎么引用即可。

<svg-icon icon-class="eye" class-name="eyeStyle" />

  本例中,eye表示SVG图标的名称(eye.svg),eyeStyle是该svg图标的自定义样式。如果不需要自定义样式,则代码段更短。

# 拓展

生成的SVG Sprite会不会太大?

  首先我们来看一下从阿里icofont 网站上导出的svg长什么样

iconfont 的 svg 格式文件

  对比于设计师可能给我们的svg图标,这个已经很精简了,但其实还有很多无用的信息,造成了冗余。

  好在svg-sprite-loader考虑到了这点,它目前只会获取svg中path的内容,而其他的信息一概不要,因此生成的sprite也都是精华的集合,且SVG本身就是代码型图片,无需太担心资源大小问题。当然也要适合的控制你的svg图标,没用的就不要加进来了。

# 本文参考

未来必热:SVG Sprites技术介绍
SVG图标制作指南
svg | MDN
优雅的使用icon

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容