专栏首页杰的记事本SVG 图标在React项目中的优化

SVG 图标在React项目中的优化

一、在 webpack 中有各种 loader 可以加载 SVG:

1、url-loader

官方文档:https://webpack.docschina.org/loaders/url-loader/

可以把 svg 当作普通 jpg、png 图片来使用。

安装:

npm install url-loader --save-dev

webpack 配置:

test: /\.(png|jpg|gif|svg)$/i,
use: [
    {
        loader: 'url-loader',
    }
]

在 React 组件中的引入方式:

import test from './test.svg'
export default class App extends Component {
  render() {
    return (
       <img src={test} />
    );
  }
};

经过 url-loader 处理后,test.svg 文件被处理为 “data:image/svg+xml;base64,PHN2ZyBpZ…..” 这样的 base64 编码。

1
module.exports = "data:image/svg+xml;base64,PHN2ZyBpZ..

我们用 webpack-bundle-analyzer 插件测试看看 svg 文件被打包后的大小:

此外 file-loader 也可以。

这种方式的优点: SVG 资源可被缓存,SVG 资源可单独加载。

缺点:加载多个 SVG 文件时,会产生多个 http 请求,影响页面加载性能。

2、svg-react-loader

安装:

npm install svg-react-loader --save-dev

webpack 配置:

{
    test: /\.svg$/,
    loader: 'svg-react-loader',
}

在 React 组件中的引入方式:

import Test from "./svg/test.svg";
export default class App extends Component {
    render() {
        return <Test className={iconType} />
    }
}

svg-react-loader 会将 svg 文件处理成 React 组件,最后会以 svg 标签的形式渲染到 html 中。从最后渲染到 html 中的代码来看,svg-react-loader 是有对 svg 原文件进行优化的。从打包后的文件大小可以看出来文件有被压缩:

这种方式的缺点:SVG 资源不可被缓存。且会将 svg 资源的处理逻辑与页面的交互逻辑一起打包。

最好的方式是:SVG 资源与 JS 资源分离,图片的加载不影响页面的主要执行逻辑。并且所有的 SVG 资源希望能一次 HTTP 请求就能搞定。

3、svg-inline-loader

官方文档:https://webpack.js.org/loaders/svg-inline-loader/

svg-inline-loader 会根据配置项去除 SVG 中冗余的代码或者不必要的代码,以减少 SVG 的文件大小。

安装:

npm install svg-inline-loader --save-dev

webpack 配置:

{
    test: /\.svg$/,
    loader: 'svg-inline-loader'
}

在 React 组件中的引入方式:

import test from "./test.svg";
 
export default class App extends Component {
  render() {
    return (
        <span dangerouslySetInnerHTML={{__html: test}} />
    );
  }
};

经过 svg-inline-loader 处理后 svg 文件被处理为这样的字符串:

module.exports = \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\">....

看下打包大小,相对比,这个体积优化的效果还不错。

此外还有 raw-loader 等都可以处理 SVG 文件。

二、SVG 优化

1. SVG 文件压缩

一般设计师会使用 Adobe suite 或者 Sketch 等工具导出 SVG 文件,但这样的 SVG 一般是有属性冗余的,所以需要对 SVG 进行一定的压缩。

手动压缩:当然,也不需要手动压缩,但是可以看看哪些属性是有冗余的。

工具压缩:推荐使用 SVGO。

webpack 项目中引入 SVGO:

安装:

npm install svgo svgo-loader --save-dev

webpack 配置:

{
    test: /\.svg$/,
    exclude: /node_modules/,
    loaders: [
        'url-loader',
        'svgo-loader'
    ],
},

在单独使用 url-loader 时,文件 test.svg 打包后的大小是:17.82 KB。在使用 svgo-loader 后,我们看下打包大小,确实是有很大幅度的压缩。

2. SVG 雪碧图

当项目需要加载多个 SVG 文件时,上述加载方式就需要优化了。我们考虑以下几种情况:

1)使用 url-loader 加载多个 svg 文件

这种方式会产生多次 http 请求,而浏览器对并发请求数目是有限制的,请求太多会影响页面加载性能。这种情况就需要引入雪碧图,将多个 svg 文件合成一个 svg 文件。

2)使用 svg-react-loader ,当一个组件需要加载多个 svg 文件时,所有的 svg 文件都会被打包到 index.js 文件中。如果 svg 文件过多,就会增大 index.js 文件体积。而 SVG 作为图片资源,最好是作为一个单独文件异步加载,与页面主要执行逻辑分离。当然这里也同样需要引入雪碧图。

第一种方法

第一种方法是把所有的图标通过<symbol> 元素定义在 SVG 代码中,嵌入在<symbol> 元素中的图标是不会被直接显示的。通过使用<use> 元素的 xlink:href=”#id” 来引用单个图标。

也就是说合成后的大 SVG 会是这样:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="__SVG_SPRITE_NODE__">
    <symbol class="icon" viewBox="0 0 1024 1024" id="icon名1">{{省略的icon path}}</symbol>
    <symbol class="icon" viewBox="0 0 1024 1024" id="icon名2">{{省略的icon path}}</symbol>
</svg>

在需要引入单个图标的时候:

<use xlink:href="#symbolId"></use>

这里我们使用 svg-sprite-loader 来自动生成 svg 雪碧图。

安装:

npm install svg-sprite-loader --save-dev

loader 配置:

{
     test: /\.svg$/,
     exclude: /node_modules/,
     use: [{
         loader: 'svg-sprite-loader',
     }],
}

react 组件中引入:

import "./svg/node.svg";
import "./svg/test.svg";
import "./svg/logo.svg";
export default class App extends Component {
    render() {
        return (
            <div>
                <svg><use xlinkHref="#node" /></svg>
                <svg><use xlinkHref="#test" /></svg>
                <svg><use xlinkHref="#logo" /></svg>
            </div>
        );
    }
}

这样配置打包后,body 下会自动注入一个大的 SVG 元素,达到了雪碧图的效果。可是这样还是有两个缺点:1)当需要使用这些 svg 的时候,需要在组件中单独 import。2)这个大的 SVG 资源并没有与 JS 资源分离。

为了解决第一个缺点:在组件加载前,增加自动 import 所有 svg 的功能:

// requires and returns all modules that match
 
const requireAll = requireContext => requireContext.keys().map(requireContext);
 
// import all svg
 
const req = require.context('./svg', true, /\.svg$/);
 
requireAll(req);

现在可以自动导入了,不用到每个组件中去单独 import 了。可是还需要解决第二个问题:SVG 资源与 JS 资源分离。

从 svg-sprite-loader 文档中看到可以利用 spriteloaderplugin 插件将所有 SVG 文件打包输出为一个大的 SVG:

这里的 loader 配置:

{
    test: /\.svg$/,
    exclude: /node_modules/,
    loaders: [
        {
            loader: 'svg-sprite-loader',
            options: {
                  extract: true,
                  spriteFilename:  'sprite.svg', // 生成的 SVG 雪碧图资源路径
             }
        },
        'svgo-loader'
   ],
},

插件配置:

plugins: [
    new SpriteLoaderPlugin({
        plainSprite: true
    })
]

这种方式会在打包目录下生成 sprite.svg 文件,我们可以通过 ajax 请求的方式获取到该 svg 资源,然后将其注入到 html 中去,这样就将 svg 资源与 js 资源分离了。

fetch(`sprite.svg`).then(res => {
    return res.text();
}).then(data => {
    document.getElementById('svg-sprite').innerHTML = data;
});

这样的方式是我们自己手动去请求的,思考下能否使用 webpack 配置或者一些插件就能达到 svg 与 js 资源分离的目的呢。于是想到了 webpack 的 SplitChunksPlugin 插件提供的抽取公共代码的能力。

于是尝试把 import 所有 svg 的逻辑抽离出来成为一个单独的 js 文件。配置好 SplitChunks 后,webpack 就会把这部分逻辑抽离出来,单独打包成一个 js 文件。这样就实现了 svg 与 js 资源分离了。

webpack 配置:

optimization: {
        splitChunks: {
          cacheGroups: {
            commons: {
              name: 'commons',
              chunks: 'initial',
              minChunks: 2
            }
          }
        }
     },
第二种方法

第二种方法是使用 SVG 的 viewbox 属性来指定显示 SVG 画布的区域,跟 background-position 原理差不多。待探索~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Source Code Pro 字体其实并不完美

    事情的起因是这样的,前两天我在服务器上看到一个莫名其妙的文件夹 ‐p,所以决定删了它,于是顺手就敲了 rm -rf -- -p。

    poslua
  • Flutter版本玩Android(3)——文章详情页

    pushXXX表示跳转到下一页面,pop表示跳出当前页面,可以携带参数跳转。具体可以参考路由管理。

    用户1108631
  • Go 语言并发编程系列(一)—— 多进程、多线程与协程的引入

    在原生 PHP 中并没有并发的概念,所有的操作都是串行执行的、同步阻塞的,这也是很多人诟病 PHP 性能的原因,但是不支持并发编程的好处也是显而易见的:保证了 ...

    学院君
  • WordPress的Kyma plugin HTML发送的connect请求是怎么投递到PHP的

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    Jerry Wang
  • Flutter版本玩Android客户端(5)——微信公众号tab点击跳转

    在Flutter版本玩Android客户端(4)——知识体系tab点击跳转中,完成了主页面知识体系tab的点击跳转,本文主要完成微信公众号tab的跳转。效果如下...

    用户1108631
  • 第三方应用如何在SAP Kyma上进行服务注册

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    Jerry Wang
  • 16-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案微信小程序篇

    https://www.cnblogs.com/yangfengwu/p/11200767.html

    杨奉武
  • 使用WordPress的Kyma plugin同Kyma断开连接的实现

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    Jerry Wang
  • 如何把css'content的操作跟价值发挥到最大💢

    content属性需要与before及after伪元素配合使用,作用是可以定义伪元素所显示的内容,本文主要列举content的可选值及实用的案例与技巧?

    聪明的汤姆
  • Go 语言错误及异常处理篇(三):panic 和 recover

    前面学院君介绍了 Go 语言通过 error 接口统一进行错误处理,但这些错误都是我们在编写代码时就已经预见并返回的,对于某些运行时错误,比如数组越界、除数为0...

    学院君

扫码关注云+社区

领取腾讯云代金券