// 定义 <svg> <symbol viewBox="0 0 20 20"> <rect x="0" y="0" fill="rgb(255,159,0)" /> </symbol> <symbol viewBox="0 0 20 20"> <rect x="0" y="0" fill="rgb(255,159,0)" /> </symbol> </svg> // 使用 <svg> <use xlink:href="#rectangle-1" href="#rectangle" /> </svg>
正好有这么一个 svg 雪碧图的 webpack loader,svg-sprite-loader,下面是代码
首先根据官网修改配置:
// vue.config.js const svgRule = config.module.rule('svg'); // 清除已有的所有 loader。 // 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。 svgRule.uses.clear(); svgRule.exclude.add(/node_modules/); // 添加要替换的 loader // svgRule.use('vue-svg-loader').loader('vue-svg-loader'); svgRule .test(/\.svg$/) .pre() .include.add(/\/src\/icons/) .end() .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: 'icon-[name]' }); const imagesRule = config.module.rule('images'); imagesRule.exclude.add(resolve('src/icons')); config.module.rule('images').test(/\.(png|jpe?g|gif|svg)(\?.*)?$/);
创建 ICON 文件夹,然后在文件夹中创建 svgIcon.vue 组件。
<template> <svg v-show="isShow" :class="svgClass" aria-hidden="true"> <use :xlink:href="iconName" /> </svg> </template> <script lang="ts"> import { Component, Vue, Prop } from 'vue-property-decorator'; @Component export default class SvgIcon extends Vue { @Prop({ required: true }) private readonly name!: string; @Prop({ default: () => '' }) private readonly className!: string; private get isShow() { return !!this.name; } private get iconName() { return `#icon-${this.name}`; } private get svgClass() { if (this.className) { return 'svg-icon ' + this.className; } else { return 'svg-icon'; } } } </script> <style scoped> .svg-icon { width: 1em; height: 1em; fill: currentColor; overflow: hidden; } </style>
在当前目录下创建 index.ts
import Vue from 'vue'; import SvgIcon from './svgIcon.vue'; // svg组件 // 注册到全局 Vue.component('svg-icon', SvgIcon); const requireAll = (requireContext: any) => requireContext.keys().map(requireContext); const req = require.context('./svg', false, /\.svg$/); requireAll(req);
在当前目录下新建 svg 文件夹,用于存放需要的 svg 静态文件。
☁ icons [1.1.0] ⚡ tree -L 2 . ├── index.ts ├── svg │ └── loading.svg └── svgIcon.vue
使用:
<svg-icon></svg-icon>
我们来看一下原理和值得注意的几点:
svg-sprite-loader 处理完通过 import 的 svg 文件后将其生成类似于雪碧图的形式,也就是 symbol, 通过配置中的 .options({ symbolId: 'icon-[name]' });可以使用 <use xlink:href="#symbolId" /> 直接使用这个 svg
添加完 svg-sprite-loader 后,由于 cli 默认对 svg 有处理,所以需要 exclude 指定文件夹的 svg。
使用时由于 svgIcon 组件的处理,只需要将 name 指定为文件名即可。
那么,我们使用 iconfont 和 svg 有什么关系呢?
iconfont 的使用方法有很多种,完全看个人喜好,但是其中一种使用方法,也是用到了 svg symbol 的原理,一般 iconfont 会默认导出这些文件。
☁ iconfont [1.1.0] ⚡ tree -L 2 . ├── iconfont.css ├── iconfont.eot ├── iconfont.js ├── iconfont.svg ├── iconfont.ttf ├── iconfont.woff └── iconfont.woff2
我们关注于其中的 js 文件, 打开文件,可以看出这个 js 文件将所有的 svg 已经处理为了 svg symbol,并动态插入到了 dom 节点当中。
而 iconfont 生成的 symbolId 也符合我们 svg-icon 的 name 命名规则 所以我们在项目的入口文件中引入这个 js 之后可以直接使用。
back-to-up
首先为什么会写这个组件呢,本项目中使用的组件库是 elementUI ,而 UI 库中自带 el-backtop,但是我能说不好用吗? 或者说我太蠢了,在经过一番努力的情况下我还是没能使用成功,所以自己写了一个。
直接上代码:
<template> <transition :name="transitionName"> <div v-show="visible" :style="localStyle" @click="backToTop"> <slot> <svg viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <g> <path d="M12.036 15.59c0 .55-.453.995-.997.995H5.032c-.55 0-.997-.445-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29c.39-.39 1.026-.385 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" fill-rule="evenodd" /> </g> </svg> </slot> </div> </transition> </template> <script lang="ts"> import { Component, Vue, Prop } from 'vue-property-decorator'; @Component export default class BackToTop extends Vue { @Prop({ default: () => 400 }) private readonly visibilityHeight!: number; @Prop({ default: () => 0 }) private readonly backPosition!: number; @Prop({ default: () => ({}) }) private readonly customStyle!: any; @Prop({ default: () => 'fade' }) private readonly transitionName!: string; private visible: boolean = false; private interval: number = 0; private isMoving: boolean = false; private detaultStyle = { width: '40px', height: '40px', 'border-radius': '50%', color: '#409eff', display: 'flex', 'align-items': 'center', 'justify-content': 'center', 'font-size': '20px', cursor: 'pointer', 'z-index': 5 }; private get localStyle() { return { ...this.detaultStyle, ...this.customStyle }; } private mounted() { window.addEventListener('scroll', this.handleScroll); } private beforeDestroy() { window.removeEventListener('scroll', this.handleScroll); if (this.interval) { clearInterval(this.interval); } } private handleScroll() { this.visible = window.pageYOffset > this.visibilityHeight; } private backToTop() { window.scrollTo({ left: 0, top: 0, behavior: 'smooth' }); } } </script> <style scoped> .back-to-ceiling { background-color: rgb(255, 255, 255); box-shadow: 0 0 6px rgba(0, 0, 0, 0.12); background-color: '#f2f6fc'; position: fixed; right: 50px; bottom: 50px; cursor: pointer; } .back-to-ceiling:hover { background-color: #f2f6fc; } .fade-enter-active, .fade-leave-active { display: block; transition: display 0.1s; } .fade-enter, .fade-leave-to { display: none; } </style>
使用:
<back-to-top :custom-style="myBackToTopStyle" :visibility-height="300" :back-position="0"> <i></i> </back-to-top>
custom-style 可以自行定义,返回的图标也可以自由替换。
注意,在 safari 中动画中动画表现不一致,使用 requestAnimationFrame 之后仍然不一致。希望同学们有时间可以自由发挥一下。
总结
永远抱着学习的心态去写代码,尝试多种写法,写出你最优雅的那一种。