自定义组件 SvgIcon
方案一
提取分散在各组件下的 SVG 到指定目录后,通过 webpack
提供的 include、exclude 分别使用 svg-sprite-loader
和 file-loader
(include: new RegExp(/^(icon--).+\.svg$/g),
正则匹配无效)
添加依赖包
yarn add svg-sprite-loader svgo-loader -D
修改
webpack.config.js
添加配置
{
test: /\.svg$/,
// test: /^(icon--).+\\.svg$/,
include: [resolve('src/icons')], // 仅处理 `src/icons` 下的 svg 文件
use: [
{
loader: 'svg-sprite-loader',
options: {
extract: true, // 提取文件
// spriteFilename: 'icons.svg', // SVG 雪碧图名称(默认 sprite.svg)
outputPath: 'static/assets/' // SVG 雪碧图输出目录
}
},
{
loader: 'svgo-loader',
options: {
// plugins: [
// {removeAttrs: {attrs: 'fill'}} // 移除 fill 属性
// ]
}
}
]
}
...
plugins: [
new SpriteLoaderPlugin({plainSprite: true}) // 添加以启用 svg-sprite-loader 插件
]
...
module: {
rules: base.module.rules.concat([
{
test: /\.(svg|png|wav|gif|jpg)$/,
// test: /^(?!icon--).*\\.(svg|png|wav|gif|jpg)$/, // x 2
exclude: [resolve('src/icons')], // 添加以排除 `src/icons` 目录 x 2
loader: 'file-loader',
options: {
outputPath: 'static/assets/'
}
}
])
},
- 添加
src/components/svg-icon/svg-icon.jsx
组件
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import styles from './svg-icon.css';
// 批量导入 `src/icons/*.svg` 文件
const requireAll = requireContext => requireContext.keys().map(requireContext);
// 查找并引入目录中的 svg 图标(false: 不递归子目录)
requireAll(require.context('../../icons', false, /\.svg$/));
// components 子目录中含有其他 svg 已通过 webpack.config.js 排除,无法递归查找
// requireAll(require.context('../../components', true, /^(icon--).*\\.svg$/));
const SvgIcon = props => {
const {
className,
title,
icon,
onClick,
...componentProps
} = props;
return (
<svg
// xmlns="http://www.w3.org/2000/svg"
// xmlnsXlink="http://www.w3.org/1999/xlink"
className={classNames(styles.icon, className)}
aria-hidden="true"
title={title}
onClick={onClick}
{...componentProps}
>
{/* 通过形如 `/${outputPath}/${spriteFilename}` 指定 SVG 雪碧图资源文件路径 */}
<use xlinkHref={`/static/assets/sprite.svg#icon--${icon}`} />
</svg>
);
};
SvgIcon.propTypes = {
className: PropTypes.string,
title: PropTypes.string,
icon: PropTypes.string.isRequired,
onClick: PropTypes.func
};
export default SvgIcon;
资源预加载 入口模板
index.ejs
添加<link rel="preload" href="<%- htmlWebpackPlugin.options.sprite %>" as="image">
标签-
造型编辑 和 声音编辑 左侧展示列表图标兼容
<img>
元素和<Icon>
组件-
造型编辑:在
src/components/gui/gui.jsx
使用组件src/containers/costume-tab.jsx
构造costumeData
造型数据集,传递给src/components/asset-panel/selector.jsx
组件的props.items
后遍历子组件src/components/sprite-selector-item/sprite-selector-item.jsx
显示img
图片const costumeData = target.costumes ? target.costumes.map(costume => ({ name: costume.name, asset: costume.asset, details: costume.size ? this.formatCostumeDetails(costume.size, costume.bitmapResolution) : null, dragPayload: costume })) : [];
-
声音编辑:在
src/components/gui/gui.jsx
使用组件src/containers/sound-tab.jsx
构造sounds
声音数据集,传递给src/components/asset-panel/selector.jsx
组件的props.items
后遍历子组件src/components/sprite-selector-item/sprite-selector-item.jsx
显示Icon
图标// 声音数据集中有 `url` 属性,后面会用作区分资源的依据(亦可使用 造型数据集 中的 `asset` 属性) const sounds = sprite.sounds ? sprite.sounds.map(sound => ({ url: isRtl ? 'sound-rtl' : 'sound', name: sound.name, details: (sound.sampleCount / sound.rate).toFixed(2), dragPayload: sound })) : [];
-
修改子组件
src/components/sprite-selector-item/sprite-selector-item.jsx
添加isAudio
属性标记资源类型,并根据类型使用<img>
或<Icon>
显示资源:{props.costumeURL && ( <div className={styles.spriteImageOuter}> <div className={styles.spriteImageInner}> {props.isAudio ? ( <Icon className={classNames(styles.spriteImage, styles.spriteIcon)} src={props.costumeURL} /> ) : ( <img className={styles.spriteImage} draggable={false} src={props.costumeURL} /> )} </div> </div> )} ... SpriteSelectorItem.propTypes = { isAudio: PropTypes.bool, ... }
-
在父组件
src/containers/sprite-selector-item.jsx
传递isAudio
(音频资源的costumeURL
为空,this.props.costumeURL && true
将string
转boolean
)return ( <SpriteSelectorItemComponent isAudio={costumeURL && true} costumeURL={this.getCostumeData()} ... /> );
-
方案二:
直接修改 test 匹配规则(是否以 icon--
开头)分别使用不同的 loader 处理。
{
test: /^(icon--).+\\.svg$/`, // 以 `icon--` 开头的 svg 文件
loader: 'svg-sprite-loader',
options: { ... }
}
...
{
test: /^(?!icon--).*\\.(svg|png|wav|gif|jpg)$/, // 非 `icon--`开头的 svg 文件
loader: 'file-loader',
options: { ... }
}
引入时递归查找指定目录下 icon--
开头的 svg 文件: `
const requireAll = requireContext => requireContext.keys().map(requireContext);
requireAll(require.context('../../components', true, new RegExp(/^(icon--).*\\.svg$/)));
SVG 分散在各组件中时,需要大量修改源码,不建议使用!
参考:
SVG 图标在React项目中的优化
React 里使用 svg-sprite-loader
手摸手,带你优雅的使用 icon
How to make react svg use xlinkHref work