SVG图标组件

637 阅读1分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

TIP 👉 休对故人思故国,且将新火试新茶。诗酒趁年华。____苏轼《望江南·超然台作》

前言

在我们日常项目开发中,我们经常会写一些图标,所以封装了这款图标组件。

SVG图标组件

一、SVG文件存放目录

SVG图标组件使用的svg文件存放在src/assets/icon目录下

二、属性

1. name: String
  • 图标名称
  • 值需要与src/assets/icon目录下svg文件的名称一致(name值不含“.svg”后缀)
2. scale: Number | String
  • 缩放图标
  • 默认为: 1
3. spin: Boolean
  • 图标是否有旋转效果
  • 默认为:false
4. pulse: Boolean
  • 图标是否有脉冲旋转的效果
  • 默认为:false
5. flip: String
  • 图标如何翻转
  • 值为:'horizontal'(水平翻转) 或 'vertical'(垂直翻转)
  • 默认为:null
6. class: String
  • 自定义类名
  • 用于自定义样式改变图标的大小和颜色
font-size: 20px; /* 指定图标的大小 */
color: #fff; /* 指定图标的颜色 */

三、示例

1. 简单示例

<Icon name="tick"></Icon>

icon已定义为全局组件,不需要单独引入

2. 自定义样式示例

<Icon name="tick" class="customer-icon"></Icon>

CSS:

.customer-icon{
  font-size: 24px;
  color: #007bff;
}

3. 缩放示例

<Icon name="tick" scale="2"></Icon>

图标放大两倍

4. 旋转效果示例

<Icon name="tick" :spin="true"></Icon>

5. 脉冲旋转示例

<Icon name="tick" :pulse="true"></Icon>

6. 水平翻转示例

<Icon name="tick" flip="horizontal"></Icon>

7. 垂直翻转示例

<Icon name="tick" flip="vertical"></Icon>

实现Icon.vue

<!-- svg图标组件(参考:https://github.com/Justineo/vue-awesome/blob/master/README.zh_CN.md) -->
<template>
  <svg version="1.1"
    :class="klass"
    :role="label ? 'img' : 'presentation'"
    :aria-label="label"
    :x="x"
    :y="y"
    :width="width"
    :height="height"
    :viewBox="box"
    :style="style">
    <slot>
      <template v-if="icon && icon.paths">
        <path v-for="(path, i) in icon.paths" :key="`path-${i}`" v-bind="path"/>
      </template>
      <template v-if="icon && icon.polygons">
        <polygon v-for="(polygon, i) in icon.polygons" :key="`polygon-${i}`" v-bind="polygon"/>
      </template>
      <template v-if="icon && icon.raw"><g v-html="raw"></g></template>
    </slot>
  </svg>
</template>

<style>
.fa-icon {
  /* display: inline-block; */
  width: auto;
  width: 1em\0; /* 兼容IE, 但是只能使用长宽比为1的svg图标 */
  height: 1em;
  /* 要在 Safari 中正常工作,需要再引入如下两行代码 */
  max-width: 100%;
  max-height: 100%;
  fill: currentColor;
}

.fa-flip-horizontal {
  transform: scale(-1, 1);
}

.fa-flip-vertical {
  transform: scale(1, -1);
}

.fa-spin {
  animation: fa-spin 1s 0s infinite linear;
}

.fa-inverse {
  color: #fff;
}

.fa-pulse {
  animation: fa-spin 1s infinite steps(8);
}

@keyframes fa-spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
</style>

<script>
let icons = {}

export default {
  name: 'Icon',
  props: {
    name: {
      type: String,
      validator (val) {
        if (val) {
          if (!(val in icons)) {
            console.warn(`Invalid prop: prop "name" is referring to an unregistered icon "${val}".` +
              `\nPlease make sure you have imported this icon before using it.`)
            return false
          }
          return true
        }
        console.warn(`Invalid prop: prop "name" is required.`)
        return false
      }
    },
    scale: [Number, String],
    spin: Boolean,
    inverse: Boolean,
    pulse: Boolean,
    flip: {
      validator (val) {
        return val === 'horizontal' || val === 'vertical' || val === null
      }
    },
    label: String
  },
  data () {
    return {
      x: false,
      y: false,
      childrenWidth: 0,
      childrenHeight: 0,
      outerScale: 1
    }
  },
  computed: {
    normalizedScale () {
      let scale = this.scale
      scale = typeof scale === 'undefined' ? 1 : Number(scale)
      if (isNaN(scale) || scale <= 0) {
        console.warn(`Invalid prop: prop "scale" should be a number over 0.`, this)
        return this.outerScale
      }
      return scale * this.outerScale
    },
    klass () {
      return {
        'fa-icon': true,
        'fa-spin': this.spin,
        'fa-flip-horizontal': this.flip === 'horizontal',
        'fa-flip-vertical': this.flip === 'vertical',
        'fa-inverse': this.inverse,
        'fa-pulse': this.pulse
      }
    },
    icon () {
      if (this.name) {
        return icons[this.name]
      }
      return null
    },
    box () {
      if (this.icon) {
        return `0 0 ${this.icon.width} ${this.icon.height}`
      }
      return `0 0 ${this.width} ${this.height}`
    },
    ratio () {
      if (!this.icon) {
        return 1
      }
      let { width, height } = this.icon
      return Math.max(width, height) / 16
    },
    width () {
      // eslint-disable-next-line no-mixed-operators
      return this.childrenWidth || this.icon && this.icon.width / this.ratio * this.normalizedScale || 0
    },
    height () {
      // eslint-disable-next-line no-mixed-operators
      return this.childrenHeight || this.icon && this.icon.height / this.ratio * this.normalizedScale || 0
    },
    style () {
      if (this.normalizedScale === 1) {
        return false
      }
      return {
        fontSize: this.normalizedScale + 'em'
      }
    },
    raw () {
      // generate unique id for each icon's SVG element with ID
      if (!this.icon || !this.icon.raw) {
        return null
      }
      let raw = this.icon.raw
      let ids = {}
      raw = raw.replace(/\s(?:xml:)?id=(["']?)([^"')\s]+)\1/g, (match, quote, id) => {
        let uniqueId = getId()
        ids[id] = uniqueId
        return ` id="${uniqueId}"`
      })
      raw = raw.replace(/#(?:([^'")\s]+)|xpointer(id((['"]?)([^')]+)\2)))/g, (match, rawId, _, pointerId) => {
        let id = rawId || pointerId
        if (!id || !ids[id]) {
          return match
        }

        return `#${ids[id]}`
      })

      return raw
    }
  },
  mounted () {
    if (this.icon) {
      return
    }
    this.$children.forEach(child => {
      child.outerScale = this.normalizedScale
    })
    let width = 0
    let height = 0
    this.$children.forEach(child => {
      width = Math.max(width, child.width)
      height = Math.max(height, child.height)
    })
    this.childrenWidth = width
    this.childrenHeight = height
    this.$children.forEach(child => {
      child.x = (width - child.width) / 2
      child.y = (height - child.height) / 2
    })
  },
  register (data) {
    for (let name in data) {
      let icon = data[name]

      if (!icon.paths) {
        icon.paths = []
      }
      if (icon.d) {
        if (icon.d instanceof Array) {
          for (let i in icon.d) {
            icon.paths.push({ d: icon.d[i] })
          }
        }
      }

      if (!icon.polygons) {
        icon.polygons = []
      }
      if (icon.points) {
        icon.polygons.push({ points: icon.points })
      }

      icons[name] = icon
    }
  },
  icons
}

let cursor = 0xd4937
function getId () {
  return `fa-${(cursor++).toString(16)}`
}
</script>

感谢评论区大佬的点拨。

希望看完的朋友可以给个赞,鼓励一下