svg in JS 之图标的解决方案 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
famanoder
V2EX    前端开发

svg in JS 之图标的解决方案

  •  
  •   famanoder
    famanoder 2024-01-19 09:07:28 +08:00 1432 次点击
    这是一个创建于 632 天前的主题,其中的信息可能已经有所发展或是发生改变。

    svgjs: 将网页上所有的图形元素用 svg 来实现

    背景

    svg 是一种使用 XML 定义的可缩放矢量图形,在不同分辨率或者改变其尺寸时,图形质量不会受到影响;在当前高清屏比较普遍的形势下,前端开发需要确保网页上的图形在不同分辨率、不同 dpr 的设备上表现一致,此时不得不说使用 svg 图形绝对是一个非常不错的选择;一方面,常规的做法需要准备几套图片,通过判断设备的分辨率和 dpr 来做切换,实现起来比较复杂,另一方面,基于 svg 天生携带的可缩放 buff ,只需一个图形即可确保显示上的一致性;

    说起前端的图标解决方案,那应该是非常多了,几乎各个组件库都有自己的一个图标组件,开源社区上也有不少实现方式,其中有基于 svg 的、有基于字体的、也有直接对开源图标做一层包装的,那么,为什么还要再实现一套 svg in JS 的图标解决方案呢?咱们先看一看当前图标方案的现状:

    1. 组件库自带的图标组件

    对于一个组件库而言,图标组件必不可少,既需要满足组件库内部场景,同时也能内置一批常见图标供用户使用,而对于项目中需要大量图标,组件库并没有提供的,则需要配置构建工具的插件,比如将 svg 文件转成 vue 组件来使用,需要进行大量的图标组件注册,相当于实现了一种 inline svg 的模式;

    1. 字体图标

    这种方式有个比较大的好处就是,对图标的颜色控制非常方便,就使用常规的 css color 即可,但是字体图标只能支持单色图标,对于颜色比较丰富的图标则需要再寻一种方案;

    1. 包装社区上开源的图标

    这种方式对于开源项目比较友好,拿来即用,但是对于企业项目开发并不推荐,企业项目开发涉及到的图标个性化比较强,开源图标中大概率并未提供,存在大量冗余未使用到的图标;

    1. svgSprite

    比如通过配置 svg-sprite-loader 来将项目中的 svg 转成一个 svgSprite,实现了图标的复用,但是对于图标的引用和颜色控制却还比较原始;

    当然,对于图标组件遍地开花的前端现状,还有很多实现方式就不列举了,但是有个比较明显的特点值得关注:除了字体图标,其他图标实现方式无外乎两种:inline svgsvgSprite

    先来了解一下什么是 inline svg,即将 svg 源码写入 html 的方式,其有两个比较大的好处:一个是避免了 svg 作为独立文件加载时的一次网络请求,另一个是方便对 svg 内部节点进行一些操作,比如颜色设置;但凡事都有两面性,其缺点就是难以复用,对于需要重复使用的图标,其会导致重复渲染多次,内部节点控制时涉及到一些计算逻辑,也难免会降低运行时的效率;而 svgSprite,不用多说,他就是为了解决 svg 可复用的,但也仅仅如此;

    以上,不难看出图标方案还处于百家争鸣的局面:inline svg 模式比较常见,虽实现 svgSprite 也很简单,但对其应用模式还比较原始,颜色控制也稍显复杂,同时也都需要构建工具做额外的支持;

    svg in JS 的图标解决方案

    如图,该方案包括两个部分:一个是 svg2js,一个是封装好的组件;

    在构建工具加持下的现代前端开发,要使用 svg 首先需要配置相应的构建插件,如 webpack 里的 file-loader,其他构建工具也需要对应的插件配置,再比如到了 vite 里,需要配置 vite-plugin-svg-icons;有时候我就在想,为什么使用一个图标还需要构建工具的配置,不同构建工具还不一样,甚至用法都不一样,有没有可能简化一些呢,比如:统一的构建方式?统一的用法?

    svg2js: 统一的 svg 构建方式

    • 是什么?

      一个用来处理项目中 svg 图标的命令行脚本

    • 做什么?

      优化和管理项目中的 svg 图标:

      • 通过命令的方式优化 svg 图标并生成一个 svgSprite
      • 随时预览项目中的 svg 图标,统一管理
    • 好处?

      • 无构建工具的相关配置
      • 可以在需要时执行一个命令,即可将项目中的 svg 图标进行优化处理,一般可大大降低文件体积
      • 随时预览和管理项目中所有的图标
      • 通过脚本的方式,提前优化图标、提取关键信息、颜色处理等,避免 inline svg 模式下在运行时做这些处理,可提高运行效率

    在这里,通过一个通用脚本的方式,脱离了各种构建工具的关联,实现了对项目中 svg 文件的统一处理方式;

    svg2js 的用法

    全局安装即可使用 svg2js 命令,也可以直接使用 npx @svgjs/cli 临时使用,不过更推荐安装在项目里,在 scripts 里配置一个相对固定的命令,在日常开发中可随时使用;目前,svg2js 主要提供两个命令:1. 处理 svg 文件生成 svgSprite; 2. 同样是处理 svg ,但是会生成一个可预览的页面;

    • 安装
    pnpm add -D @svgjs/cli // or npm i -D @svgjs/cli 
    • svg2js build

    package.jsonscripts 里添加一个命令,如:

    { "build:svg": "svg2js build -e src/assets -o src/js" } 

    src/assets 目录下的 svg 图标进行优化处理后生成一个 svg-sprite.js 文件到 src/js 目录下,后续需求迭代如果对图标有进行增删改的操作,同样执行这个命令即可

    • svg2js preview

    package.jsonscripts 里添加一个命令,如:

    { "preview:svg": "svg2js preview -e src/assets -o src/js" } 

    src/assets 目录下的 svg 图标进行优化处理后生成一个 html 页面,打开即可预览项目中的所有图标,项目中图标文件较多时比较方便找图标

    • 预处理:运行时提效
    1. 类似于 vue template 在 compile 时会进行一些代码的优化处理,提高 vue 代码的运行效率一样,咱们通过脚本的方式可对项目的 svg 图标全部进行优化处理,将大大缩小文件体积;

    2. 相较于 inline svg 会对 svg 内容进行一些处理来达到一些效果,通过脚本的方式的好处是,可以在此时提取一些 svg 的关键信息,比如宽、高、viewBox 等,作为元信息在运行时直接取值即可;

    3. svg 图标的颜色处理一般都稍显复杂,默认颜色、激活时颜色、禁用时颜色等,平时我们在项目开发时可能就是定义一个 class 往里面找到 svg 的 path 节点来改颜色,现在有个便捷的方式就是,在脚本里就提取单色图标的颜色值存为元信息,并替换为 currentColor,那么改图标颜色就跟普通的 css 一样操作了,非常方便;当然,inline svg 也完全可以处理,但是假如你的项目中有大量的使用 svg 图标,其运行效率明显低于脚本处理;

    vue-symbol-icon: 统一的 svg 图标组件

    此处以 vue2.x 版本为例,其他框架的支持还未实现,在这也欢迎有兴趣的小伙伴们参与进来,提供宝贵的意见;

    经过上一步的脚本部署,不出意外的话,运行 npm build:svg 将会把您项目中的 svg 文件统一处理并生成一个 svgSprite 文件,而这个文件就是图标组件的主要消费对象;

    • 安装
    pnpm add @svgjs/vue2-symbol-icon // or npm i @svgjs/vue2-symbol-icon 
    • 初始化

    优先推荐使用 vue 插件的方式对图标组件进行初始化

    import Vue from 'vue'; import symbolIconPlugin from '@svgjs/vue2-symbol-icon'; Vue.use(symbolIconPlugin); 

    在使用 vue 插件时也可以配置一些全局参数,支持的参数如下:

    名称 类型 默认值 说明
    name string svg-symbol-icon 图标组件的名称
    color string -- 图标的默认颜色
    disabledColor string -- 图标被禁用时的颜色
    hoverColor string -- 鼠标经过图标时的颜色
    activedColor string -- 鼠标按下图标时的颜色
    placeholderColor string -- 图标作为占位图时的颜色

    比如配置了 color ,那么所有用到的图标默认颜色都是这个,如果有的图标需要不一样的颜色,可通过设置图标的 props color;即优先使用组件的 props color,如果没有设置则使用全局配置的 color ,如果全局没配置 color 则使用默认的颜色;

    • 也可以通过自定义组件的方式
    import VUe from 'vue'; import { SvgSymbolIcon, setGlobalOption } from '@svgjs/vue2-symbol-icon'; Vue.component('symbol-icon', SvgSymbolIcon); // 全局设置,这时不支持设置 name setGlobalOption({ color, disabledColor, hoverColor, activedColor, placeholderColor, }); 
    • 基本用法
    <symbol-icon name="name" /> 
    • 支持下列 props
    名称 类型 默认值 说明
    name string -- 图标的名称,也即 sprite 内的 symbol-id
    disabled boolean fasle 是否禁用
    size number, string, array -- 设置图标的宽高
    sizeUnit string px 图标宽高的单位
    spin boolean false 是否使用旋转动画
    flipV boolean false 是否纵向翻转
    flipH boolean false 是否横向翻转
    color string -- 设置该图标的颜色,会覆盖全局设置的 color
    disabledColor string -- 设置该图标禁用时的颜色,会覆盖全局设置的 disabledColor
    hoverColor string -- 设置鼠标经过时的颜色,会覆盖全局设置的 hoverColor
    activedColor string -- 设置鼠标按下时的颜色,会覆盖全局设置的 activedColor
    tooltip string, object -- 设置 tooltip

    在实际开发时,有个简便的办法就是运行 npm preview:svg 后打开一个预览的页面,在页面上可以快速找到需要的图标,点击直接复制代码即可使用;

    在这个组件内部,默认引入了一个第三方的功能 tippy,但默认未做初始化,假如需要给图标添加 tooltip 功能,可直接配置 tooltip 属性,如果需要自定义 tippy 的主题,参考官方做法:tippyjs themes,同时需要单独引入一个 css 文件:import 'tippy.js/dist/tippy.css'

    • 事件
    事件名 说明
    on-click 点击事件

    对比纯 inline-svg mode

    准确说这个方案属于半个 inline-svgsvg-sprite 的结合,相较于纯 inline-svg mode,在组件代码里会对 svg 内容做一些处理来达到某种效果;比如控制 svg 的某个节点,通过多个正则匹配一些关键的节点信息等;单纯的 inline-svg 不具备可复用性,系统内存在一些场景会多次使用到同一个图标,那么将会导致这个图标内部逻辑处理多次,同时页面上也将同样渲染多次这个 svg ;

    基于一些相对复杂的场景和大量图标需要被使用的情况,svg2js 的做法是在代码进入运行时之前,提取 svg 文件的一些关键信息,比如宽、高、viewBox 等,修改单色图标的占位颜色等可能需要的对 svg 内容的处理,将 inline-svg mode 下的内部处理逻辑通过脚本的方式前置处理,这样能够大大提高图标显示时的运行效率;

    颜色控制方案

    这里的颜色控制只限于单色图标,但是按照同一个思路,多色图标也完全可以支持,只是从实际开发中来看,几乎没有遇到需要控制图标的多个颜色的情况,有可能出现过这个情况,不过切换图标可能相对于改变多个颜色来说更方便,暂时不对多色图标做处理;

    如何判断和识别单色图标呢?

    (path, rect, circle, polygon, line, polyline, ellipse),使用正则匹配以上这些元素上设置的 fillstroke 属性, 如果匹配到的结果为同一个值,那么就认为这个 svg 为单色图标,当然此处还需要排除值为 none 和值为 url 格式的情况;

    识别到单色图标后,将其颜色值重置为 currentColor,那么就可以通过简单的 css color 设置来控制图标的颜色了,接下来处理 hover colordisabled color 就只需要切换 class 即可,至此,svg 的颜色控制就跟普通的元素颜色控制没差别了;

    更新

    • 已将 @svgjs/cli 包内置于 @svgjs/vue2-symbol-icon@svgjs/vue-symbol-icon,无需安装两个包,只需要安装组件包即可使用到 svg2js 命令

    总结

    以上,实现了一套新的 svg in JS 图标解决方案:脚本预处理 + web 组件 = 增强 + 提效;

    通过脚本的方式脱离多种构建工具的关联,用统一的方式来处理 svg 文件;

    通过 web 组件提供常规的交互功能,用统一的方式来使用 svg ;

    最后,如果有同学也对 svg 比较感兴趣,欢迎加入一起开发,或者分享您宝贵的意见,我们的愿景就是:将网页上所有的图形元素用 svg 来实现,仓库地址:svgjs

    8 条回复    2024-01-20 22:58:06 +08:00
    muyeyong
        1
    muyeyong  
       2024-01-19 09:56:21 +08:00
    不错哦
    tool2d
        2
    tool2d  
       2024-01-19 10:17:30 +08:00
    我是在页面启动的时候,注入一大堆 svg symbol 样本,设置隐藏模式。

    等页面里需要引用的时候,直接用<use xlink:href="#svg_email"></use>,超级方便。
    famanoder
        3
    famanoder  
    OP
       2024-01-19 10:59:51 +08:00
    @tool2d 思路差不多,我这就是加了一层运行前的优化,整理了一下 svg 颜色设置的方案和组件化的封装
    codehz
        4
    codehz  
       2024-01-19 11:37:38 +08:00 via iPhone
    字体图标是可以多颜色的,不然你以为彩色 emoji 是怎么实现的
    就是 COLR 表去标记颜色就可以了
    famanoder
        5
    famanoder  
    OP
       2024-01-19 12:16:55 +08:00
    @codehz 多谢提点,我去查了一下,确实有彩色字体图标方案了,我看看我这优化后的 svg 能不能转成字体,但是又不太想加载一个字体文件,导致出现图标闪烁的情况
    codehz
        6
    codehz  
       2024-01-19 12:33:18 +08:00
    @famanoder svg 转字体本身就比较灵车,彩色的更灵车了()
    一般主要是用在 ligature 方面上的(也就是说在图标没加载出的时候显示文字,字体加载好了文字变成图标)在一些可访问性方面会好(屏幕阅读器用户)
    famanoder
        7
    famanoder  
    OP
       2024-01-19 12:50:41 +08:00
    @codehz 了解了,我看看彩色字体能不能跟我这个方案结合一下
    webszy
        8
    webszy  
       2024-01-20 22:58:06 +08:00
    挺好的,支持
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1065 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 17:59 PVG 01:59 LAX 10:59 JFK 13:59
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86