本文接上一篇《如何在项目中管理你的图标?》
Iconfont 和 SVG 优缺点对比
在上文中介绍了使用 iconfont 的缺点,以及使用 SVG 的优点,简单归纳为以下几点:
Icon 的缺点
- 当网络不好的时候,会显示方块
- 如只使用一个图标,字体冗余
- 维护依赖 iconfont 平台
- 在组件开发的时候命名冲突
使用 SVG 的优点
- 完全离线化使用,不需要从 CDN 下载字体文件,图标不会因为网络问题呈现方块,也无需字体文件本地部署。
- 在低端设备上 SVG 有更好的清晰度。
- 支持多色图标。
- SVG 可以支持动画
并给出了最终方案,放弃使用字体,使用 SVG 代替 iconfont。
又给出了实践步骤:
- 老项目中的 iconfont, 可以通过 nodejs 脚本将下载的
iconfont.svg
转为多个 SVG 图标 - 新加的图标,可以直接在 iconfont.cn 上下载 SVG
- React 项目中,如果要直接使用 SVG,需要配置 webpack loader —— @svgr/webpack
下面是 webpack.config.js
中要加入的配置
module.exports = {
module: {
rules: [
{
test: /.SVG$/i,
type: 'asset',
resourceQuery: /url/, // *.SVG?url
},
{
test: /.SVG$/i,
issuer: /.[jt]sx?$/,
resourceQuery: { not: [/url/] }, // exclude react component if *.SVG?url
use: ['@SVGr/webpack'],
},
],
},
}
上面这段配置看上去很简单,当我往项目中配置时,却又遇到了困难,有的时候打包配置是在一个单独的包中,比如使用 vite 脚手架创建的 react 项目, 想要在项目中支持直接使用 SVG, 就必须写一个自定义 plugin。
所以我写了一个油猴脚本,可以在 iconfont.cn 上直接复制 React component 代码,如此一来,我们就省去了配置 webpack 的烦恼。
使用
Tampermonkey 是一个 chrome 插件,允许开发者直接在上面发布脚本,相当于是一个简易的 chrome 插件,若要在 chrome 扩展商店中发布插件的话,需要花费 5 美元。
- 第一步: 安装 chrome Tampermonkey 扩展
- 第二步: 安装 IconfontR 插件
名字来源 svgr ,就是 iconfont React component = IconfontR
装完插件后会在原先的下载按钮边上多出一个复制按钮,点击复制按钮复制 react 代码,就可以在 react 项目中粘贴使用了。
实现原理
其实 svgr 可以提供了在 nodejs 中执行的版本 @svgr/core。
安装
代码语言:javascript复制npm install --save-dev @svgr/core
# or use yarn
yarn add --dev @svgr/core
引入 @SVGr/core
这个包,我们就可以直接使用啦!
- source: SVG 源码
- options: SVGr 配置参数
- state: 转变为 react component 的配置参数
使用
代码语言:javascript复制import { transform } from '@SVGr/core'
const SVGCode = `
<SVG xmlns="http://www.w3.org/2000/SVG"
xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="10" y="10" height="100" width="100"
style="stroke:#ff0000; fill: #0000ff"/>
</SVG>
`
const jsCode = await transform(SVGCode, { icon: true }, { componentName: 'MyComponent' })
所以我们可以写一个云函数,直接部署到 vercel 上,下面是 nodejs 云函数代码:
代码语言:javascript复制import { VercelRequest, VercelResponse } from '@vercel/node'
import { transform } from '@SVGr/core'
export default async (request: VercelRequest, response: VercelResponse) => {
const { SVGCode } = request.query
try {
const jsCode = await transform(SVGCode, { icon: true }, { componentName: 'SVGComponent' })
return {
output: SVGCode,
}
} catch (error) {
response.status(200).send(error.message)
}
}
当不是成功后,我们就可以直接使用云函数的部署地址,直接通过 fetch 调用就可以啦,传入 SVG 源码,输入 react component 组件源码,当然你也可以使用国内的云开发平台,腾讯云或阿里云,主要是因为 vercel 是完全免费的。
直接使用 svgr playground 的接口
当我看到 svgr playground 的时候,我就想知道它的实现原理,打开控制台一看,我们连云函数都不用写了,它就是一个部署在 vercel 上的一个接口。
access-control-allow-origin: *
并且允许跨域,所以我们可以直接调用了。
接下来我们只需要通过 Dom api 获得当前点击元素的 SVG 代码
在每个图标的操作覆盖层加入一新图标,用于复制 react component
原先是块级布局,一列显示 3 行
为了减少页面空间, 将覆盖的背景层改成 grid 布局,正好 2 行 2 列。
grid-template-rows: repeat(2, minmax(0, 1fr));
平均分 2 行;grid-template-columns: repeat(2, minmax(0, 1fr));
平均分 2 列,
脚本全部代码
代码语言:javascript复制;(function () {
// 请求接口
async function fetchSVGr(code) {
return await fetch('https://api.react-SVGr.com/api/SVGr', {
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
code: code,
options: {
icon: false,
native: false,
typescript: false,
ref: false,
memo: false,
titleProp: false,
expandProps: 'end',
replaceAttrValues: {},
SVGProps: {},
SVGo: true,
SVGoConfig: {
plugins: [{ name: 'preset-default', params: { overrides: { removeTitle: false } } }],
},
prettier: true,
prettierConfig: { semi: false },
},
}),
method: 'POST',
mode: 'cors',
credentials: 'omit',
}).then((res) => res.json())
}
// 往 head 中插入覆盖样式
const style = `.page-manage-project .project-iconlist .block-icon-list li.cover .icon-cover-unfreeze, .page-manage-project .project-iconlist .block-icon-list li:hover .icon-cover-unfreeze,
.block-icon-list li:hover .icon-cover {
display: grid!important;
}
.page-manage-project .project-iconlist .block-icon-list li .icon-cover {
grid-template-rows: repeat(3, minmax(0, 1fr));
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.block-icon-list li .icon-cover {
grid-template-rows: repeat(2, minmax(0, 1fr));
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.block-icon-list li .icon-cover .cover-item{
width:auto;
}
.page-manage-project .project-iconlist .block-icon-list li .icon-cover .cover-code{
height: auto;
line-height: 40px;
}
.block-icon-list li .icon-cover .cover-item-line {
height: auto;
line-height: 52.5px;
}`
const styleEl = document.createElement('style')
styleEl.textContent = style
document.head.appendChild(styleEl)
function addCopybtn() {
console.log([...document.querySelectorAll('.icon-cover')])
;[...document.querySelectorAll('.icon-cover')].forEach((item) => {
const span = document.createElement('span')
span.title = '复制 React component'
span.className = 'cover-item iconfont cover-item-line icon-fuzhidaima'
span.onclick = async () => {
const SVG = `<SVG width="128" height="128" fill="currentColor" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/SVG">${
item.parentNode.querySelector('SVG').innerHTML
}</SVG>`
console.log('SVG', SVG)
try {
const res = await fetchSVGr(SVG)
navigator.clipboard.writeText(res.output)
console.log('React component 复制成功!')
} catch (error) {
console.log('请求服务出错')
}
}
item.appendChild(span)
})
}
// 监听路
window.onpopstate = function (event) {
addCopybtn()
}
// 调用 `history.pushState()` 或者 `history.replaceState()` 不会触发 `popstate` 事件,所以是点击时,对比 url 判断
let href = window.location.href
document.addEventListener('click', (e) => {
setTimeout(() => {
if (window.location.href !== href) {
addCopybtn()
href = window.location.href
}
}, 500)
})
//由于异步加载,需要延迟执行
setTimeout(() => {
addCopybtn()
}, 1000)
})()
以上就是本文全部内容,希望这篇文章对大家有所帮助,也可以参考我往期的文章或者在评论区交流你的想法和心得,欢迎一起探索前端。