前端进阶之路让前端飞Web前端之路

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

2019-10-10  本文已影响0人  果汁凉茶丶

# 前言

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

  在阅读张鑫旭大大的文章时看到svg-sprite技术,收获颇深。本文会介绍几种网站图标使用里程碑,重点介绍svg-sprite

# 几种图标使用发展史

# 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

上一篇下一篇

猜你喜欢

热点阅读