我最近参加了 Michael Jackson 和 Ryan Florence 的 ReactJS 训练营。我非常兴奋地参加了这次训练营,部分原因是我有很多关于 SVG 和 React 的问题。使用 React 和 SVG,尤其是对 SVG 进行操作,有很多功能目前还不太支持。对我来说,一个主要的差距是 <use>
元素,因为大多数 SVG 图标系统都是使用 <use>
构建的。
我问 Michael 他是否认为这些功能中的一些可能会得到更好的支持,但他向我展示了一种更好的使用方式,完全绕过了这种方法。我们将讨论这种技术,以便您可以开始在 React 中编写可扩展的 SVG 图标系统,以及一些我认为可以很好地工作的技巧。
注意:值得一提的是,use 支持 最近有所改进,但我注意到它充其量只是零星存在,而且存在其他路由和 XML 问题。我们将在这里向您展示另一种更简洁的方法。
<use>
?
什么是 对于那些不熟悉 SVG 图标系统通常如何构建的人来说,它的工作方式有点像这样。<use>
元素克隆了任何其他 SVG 形状元素的副本,该元素的 ID 在 xlink:href
属性中引用,并且仍然可以对其进行操作而无需重复所有路径数据。您可能想知道为什么人们不会直接使用 SVG 作为 <img>
标签。您可以这样做,但每个图标都会是一个单独的请求,而且您将无法访问更改 SVG 的部分,例如 fill
颜色。
使用 <use>
使我们能够将图标的路径数据和基本外观定义在一个地方,这样它们就可以更新一次并在所有地方更改,同时仍然让我们能够动态更新它们。
Joni Trythall 有一篇关于 use 和 SVG 图标的 很棒的文章,Chris Coyier 也在 CSS-Tricks 上写了另一篇 很棒的文章。
如果您想看看标记是什么样的,这里有一个小例子
查看 CodePen 中 Sarah Drasner (@sdras) 的 bc5441283414ae5085f3c19e2fd3f7f2。
为什么要使用 SVG 图标?
有些人可能想知道为什么我们要使用 SVG 图标系统,而不是一开始就使用图标字体。我们有 关于这个问题的比较。另外还有 大量的人 写文章 以及 发言 关于这个问题。
在我看来,以下是一些更令人信服的原因
- 图标字体很难做到无障碍。SVG 可以添加标题和 ARIA 标签,这极大地提高了无障碍性,尤其是在图标单独存在且是信息导航的唯一来源的情况下。想想:盲人、阅读障碍者、老年人(您迟早也会成为老年人,所以,如果您不是那种关心这部分人群的开发者,就为了积累善业而做吧!但说真的,关心老年人。)
- 图标字体在某些显示器上不够清晰。您可以通过在 CSS 中进行一些精密的字体平滑来避免这种情况,但我注意到有一点:很难覆盖字体平滑,而不会完全关闭字体平滑。总的来说,SVG 更清晰,因为它们是专门用来绘制的。
- 图标字体经常出现故障。我认识的大多数开发人员都遇到过在框中缺少字形 X 的情况,图标字体有很多种方式会导致故障,而 SVG 则不会。无论是 CORS 问题还是 Opera mini,它都是一个让人头疼的问题。
- 图标字体很难定位。它们是一种使用字体样式来定位的图像。没什么好说的。您无法对它们的部分进行动画,除非使用技巧进行堆叠。SVG 提供了一个可导航的 DOM,用于对图标的各个部分进行动画或对部分进行着色。并不是每个人都想这样做,但拥有这种选择总是很不错的。
如果您像我一样,需要更新一个巨大的代码库,而为了从图标字体迁移到 SVG,您必须更新数百个标记实例,我理解您的感受。在这种情况下,可能不值得花时间。但如果您要重写视图并使用 React 更新它们,那么值得重新考虑这个机会。
<use>
简而言之:您在 React 中不需要 在 Michael 耐心地听我解释了我们如何使用 <use>
以及让我展示一个示例图标系统后,他的解决方案很简单:它实际上没有必要。
考虑一下:我们定义图标以便之后重用它们(通常是在 <defs>
中作为 <symbol>
),唯一的原因是为了避免重复,并且只需要在一个地方更新 SVG 路径即可。但 React 已经允许这样做。我们只需创建组件
// Icon
const IconUmbrella = React.createClass({
render() {
return (
<svg className="umbrella" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" aria-labelledby="title">
<title id="title">Umbrella Icon</title>
<path d="M27 14h5c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2v0zM27 14c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2v0 14c0 1.112-0.895 2-2 2-1.112 0-2-0.896-2-2.001v-1.494c0-0.291 0.224-0.505 0.5-0.505 0.268 0 0.5 0.226 0.5 0.505v1.505c0 0.547 0.444 0.991 1 0.991 0.552 0 1-0.451 1-0.991v-14.009c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-5.415 6.671-9.825 15-9.995v-1.506c0-0.283 0.224-0.499 0.5-0.499 0.268 0 0.5 0.224 0.5 0.499v1.506c8.329 0.17 15 4.58 15 9.995h-5z"/>
</svg>
)
}
});
// which makes this reusable component for other views
<IconUmbrella />
查看 CodePen 中 Sarah Drasner (@sdras) 的 SVG 图标在 React 中。
我们可以反复使用它,但与旧的 <use>
方式不同,我们没有额外的 HTTP 请求。
您可能会从上面的示例中注意到两件与 SVG 相关的事情。首先,我没有这种输出
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
或者甚至在 SVG 标签本身也不会有这个
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" …
这是因为我在将标记添加到任何地方之前,确保使用 SVGOMG 或 SVGO 对我的 SVG 进行了优化。我强烈建议您也这样做,因为您可以将 SVG 的大小减小到相当大的程度。我通常看到大约 30% 的百分比,但可以高达 60% 或更高。
您可能还会注意到我添加了一个标题和 ARIA 标签。这将有助于屏幕阅读器为使用辅助技术的人朗读图标。
<svg className="umbrella" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" aria-labelledby="title">
<title id="title">Umbrella Icon</title>
由于此 ID 必须是唯一的,因此我们可以对图标的实例传递属性,它将像这样传播到标题和 aria 标签
// App
const App = React.createClass({
render() {
return (
<div>
<div className="switcher">
<IconOffice iconTitle="animatedOffice" />
</div>
<IconOffice iconTitle="orangeBook" bookfill="orange" bookside="#39B39B" bookfront="#76CEBD"/>
<IconOffice iconTitle="biggerOffice" width="200" height="200"/>
</div>
)
}
});
// Icon
const IconOffice = React.createClass({
...
render() {
return (
<svg className="office" xmlns="http://www.w3.org/2000/svg" width={this.props.width} height={this.props.height} viewBox="0 0 188.5 188.5" aria-labelledby={this.props.iconTitle}>
<title id={this.props.iconTitle}>Office With a Lamp</title>
...
</svg>
)
}
});
ReactDOM.render(<App/>, document.querySelector("#main"));
也许最好的部分
整个事情中真正酷的一点是:除了不需要额外的 HTTP 请求之外,我还可以在将来完全更新 SVG 的形状,而无需进行任何标记更改,因为组件是自包含的。更棒的是,我无需在每个页面上加载整个图标字体(或 SVG 精灵)。由于所有图标都被组件化了,我可以使用 webpack 之类的工具来“选择”我需要的任何图标,用于特定视图。考虑到字体本身的重量,尤其是重量很大的图标字体字形,这为性能优化带来了巨大的可能性。
除此之外,我们还可以通过 SVG 和属性以非常简单的方式动态修改图标的部分,使其呈现不同的颜色或进行动画。
动态修改
您可能注意到这里我们还没有动态调整它,而这正是我们使用 SVG 的原因之一,对吧?我们可以为图标声明一些默认属性,然后更改它们,如下所示
// App
const App = React.createClass({
render() {
return (
<div>
<IconOffice />
<IconOffice width="200" height="200"/>
</div>
)
}
});
// Icon
const IconOffice = React.createClass({
getDefaultProps() {
return {
width: '100',
height: '200'
};
},
render() {
return (
<svg className="office" width={this.props.width} height={this.props.height} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 188.5 188.5" aria-labelledby="title">
<title id="title">Office Icon</title>
...
</svg>
)
}
});
ReactDOM.render(<App />, document.querySelector("#main"));
查看 CodePen 中 Sarah Drasner (@sdras) 的 SVG 图标在 React 中带有默认属性。
让我们更进一步,根据实例更改一些外观。我们可以为此使用 props
,并声明一些默认属性。
我喜欢 SVG,因为我们现在拥有一个可导航的 DOM,所以在下面,让我们使用 fill
动态更改多个形状的颜色。请记住,如果您习惯于处理图标字体,那么您不再使用 color
更改颜色,而是使用 fill
。您可以查看下面的第二个示例,看看它如何工作,书已经改变了颜色。我还喜欢能够动态地为这些部分制作动画,在下面,我们将其包装在一个 div 中,以便使用 CSS 很容易地对其制作动画(您可能需要点击重新运行才能看到动画播放)
查看 CodePen 中 Sarah Drasner (@sdras) 的 SVG 图标在 React 中带有默认属性和动画。
// App
const App = React.createClass({
render() {
return (
<div>
<div className="switcher">
<IconOffice />
</div>
<IconOffice bookfill="orange" bookside="#39B39B" bookfront="#76CEBD" />
<IconOffice width="200" height="200" />
</div>
)
}
});
// Icon
const IconOffice = React.createClass({
getDefaultProps() {
return {
width: '100',
height: '200',
bookfill: '#f77b55',
bookside: '#353f49',
bookfront: '#474f59'
};
},
render() {
return (
<svg className="office" xmlns="http://www.w3.org/2000/svg" width={this.props.width} height={this.props.height} viewBox="0 0 188.5 188.5" aria-labelledby="title">
<title id="title">Office Icon</title>
<g className="cls-2">
<circle id="background" className="cls-3" cx="94.2" cy="94.2" r="94.2"/>
<path className="cls-4" d="M50.3 69.8h10.4v72.51H50.3z"/>
<path fill={this.props.bookside} d="M50.3 77.5h10.4v57.18H50.3z"/>
<path fill={this.props.bookfront} d="M60.7 77.5h38.9v57.19H60.7z"/>
<path className="cls-7" d="M60.7 69.8h38.9v7.66H60.7z"/>
<path className="cls-5" d="M60.7 134.7h38.9v7.66H60.7z"/>
...
</svg>
)
}
});
ReactDOM.render(<App />, document.querySelector("#main"));
.switcher .office {
#bulb { animation: switch 3s 4 ease both; }
#background { animation: fillChange 3s 4 ease both; }
}
@keyframes switch {
50% {
opacity: 1;
}
}
@keyframes fillChange {
50% {
fill: #FFDB79;
}
}
我 Trulia 的一位很棒的同事,Mattia Toso,还推荐了一种非常不错、更简洁的声明所有这些属性的方式。我们可以通过为所有使用情况声明常量来减少 this.props
的重复,然后只需简单地应用该变量即可
render() {
const { height, width, bookfill, bookside, bookfront } = this.props;
return (
<svg className="office" xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 188.5 188.5" aria-labelledby="title">
<title id="title">Office Icon</title>
<g className="cls-2">
<circle id="background" className="cls-3" cx="94.2" cy="94.2" r="94.2"/>
<path className="cls-4" d="M50.3 69.8h10.4v72.51H50.3z"/>
<path fill={bookside} d="M50.3 77.5h10.4v57.18H50.3z"/>
<path fill={bookfront} d="M60.7 77.5h38.9v57.19H60.7z"/>
我们还可以通过为我们使用的属性声明 propTypes
来使它更加出色。PropTypes 非常有用,因为它们就像我们正在重用属性的文档。
propTypes: {
width: string,
height: string,
bookfill: string,
bookside: string,
bookfront: string
},
这样,如果我们使用不当,比如下面的例子,我们会得到一个控制台错误,不会阻止代码运行,但会提醒可能与我们协作的其他人(或我们自己)我们正在错误地使用道具。这里,我使用的是数字而不是字符串作为我的道具。
<IconOffice bookfill={200} bookside="#39B39B" bookfront="#76CEBD" />
然后我得到以下错误

查看 Pen 带有错误的 React 中的 SVG 图标使用扩展运算符 by Sarah Drasner (@sdras) on CodePen.
React 0.14+ 变得更加精简
在 React 的新版本中,我们可以减少一些这些冗余,并进一步简化我们的代码,但前提是它是一个非常“愚蠢”的组件,例如,它不使用生命周期方法。图标是一个很好的用例,因为我们主要只是渲染,所以让我们试一试。我们可以摆脱 React.createClass
并将我们的组件写成简单的函数。如果您使用 JavaScript 很长时间,但不太熟悉 React 本身,这会很不错 - 它读起来就像我们都习惯使用的函数。让我们进一步清理我们的道具,并像在网站上一样重复使用雨伞图标。
// App
function App() {
return (
<div>
<Header />
<IconUmbrella />
<IconUmbrella umbrellafill="#333" />
<IconUmbrella umbrellafill="#ccc" />
</div>
)
}
// Header
function Header() {
return (
<h3>Hello, world!</h3>
)
}
// Icon
function IconUmbrella(props) {
const umbrellafill = props.umbrellafill || 'orangered'
return (
<svg className="umbrella" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" aria-labelledby="title">
<title id="title">Umbrella</title>
<path fill={umbrellafill} d="M27 14h5c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2v0zM27 14c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2v0 14c0 1.112-0.895 2-2 2-1.112 0-2-0.896-2-2.001v-1.494c0-0.291 0.224-0.505 0.5-0.505 0.268 0 0.5 0.226 0.5 0.505v1.505c0 0.547 0.444 0.991 1 0.991 0.552 0 1-0.451 1-0.991v-14.009c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-5.415 6.671-9.825 15-9.995v-1.506c0-0.283 0.224-0.499 0.5-0.499 0.268 0 0.5 0.224 0.5 0.499v1.506c8.329 0.17 15 4.58 15 9.995h-5z"/>
</svg>
)
}
ReactDOM.render(<App />, document.querySelector("#main"));
查看 Pen React 中的 SVG 图标 by Sarah Drasner (@sdras) on CodePen.
SVG 图标系统在 React 中非常简单,易于扩展,减少 HTTP 请求,并且由于我们可以将来完全更新输出,而无需进行任何重复的标记更改,因此易于维护。我们可以通过选择我们需要的来提高性能。我们可以使用道具动态地更改颜色,甚至添加 CSS 动画。所有这些,我们还可以使其对屏幕阅读器可访问,这使得 React 和 SVG 图标系统成为在 Web 上添加图标的非常好的方法。
呃,所以你建议使用 JavaScript 在每次使用图标时都插入整个 SVG?因此,如果你有一个包含 100 条记录的表格,每条记录都有一个铅笔图标、一个垃圾桶图标和一个状态图标,你打算让 JavaScript 插入 300 个 SVG,每个 SVG 都包含该图标的所有路径信息?
这究竟比
<use>
(让浏览器在内部进行复制,而不是用 JS 模拟它)或者甚至老式背景图像 SVG 雪碧图(忘记复制任何东西,只需渲染一个已经在内存中的图像,只是在不同的偏移量)更好吗?我的意思是,我明白了,它使雪碧易于定制,但我就是不认为这能扩展到任何实际应用。我希望我永远不必维护一个决定将所有单独的雪碧包装在 JavaScript 组件中的代码库。
不,这不是我建议的。我向您展示了如何在您已经使用 React 的情况下使用 SVG 图标系统。不是将一个不在 React 中的视图转换为 React 才能使用 SVG 图标。
我还在文章中解释了为什么我们不使用 use,以及为什么如果你按照我描述的方式使用 React,你就无需将每个 SVG 图标的每个实例加载到页面中。
在重新阅读了 Agop 提出的批评大约 15 次之后,我不确定 Sarah 实际上是否解决了它。
当我阅读时,Agop 似乎在询问这种机制在真实网站中的有效性。提供的例子,我敢肯定我们都设计过,是一组带有图标的表格记录,用于指示操作(编辑、删除等)。
这是一个雪碧甚至字体图标大放异彩的用例。本文中的解决方案在这里能发挥什么作用?它似乎需要为每个迭代独特地渲染 SVG… 因此,如果您的网站/应用程序/任何东西都严重依赖于 SVG 生成的图标作为 UI/UX 元素,似乎会存在可扩展性问题。
或者说,争论点是,对于 Agop 提出的情况,人们根本不会使用 React 吗?
只是在这里提供一些想法,GitHub 最近切换 到 SVG 图标
另外,我有点担心这里的气氛。有一些方法可以在没有粗鲁语气的情况下提出问题。我有一些朋友说使用 阳光台灯 对他们很有帮助,因为它可以帮助他们在一天中保持心情舒畅。
这实际上是关于
<svg><use>
vs<svg><path>
创建一个测试页面会很有趣!300 个图标听起来像是一个合理的测试数量。也许 10 个独特的图标,每个图标使用 30 次。
页面 1:使用
<symbol>
的 SVG 雪碧图,用于每个独特的图标,并使用<use>
来绘制所有 300 次页面 2:所有 300 个图标都使用
<svg><path> ...
单独绘制但是问题是什么呢?也许…
呃,为什么有人会建议这样做?
好的,假设您有不同的图标用于编辑、发布、删除。如果您在 SVG 图标中使用 use,即使使用 use,您仍然会在 shadow DOM 中渲染路径数据。如果您使用图标字体,无论您是否需要,您都会将整个集合的所有字形渲染到该视图中。这通常比您通常需要渲染该表格的图标要多得多。
每种技术都有其正确的用途和开销,开发人员需要选择合适的工具。这取决于您的网站有多少图标,以及图标字体的大小。它还取决于您在一个给定视图中使用图标的次数。我想说,与在菜单或视图中使用图标相比,表格实际上在 Web 中的用例更少,而每种用例都需要不同的技术。在表格的情况下,如果您不需要更改颜色,可能 SVG 雪碧图更好。如果您需要更改颜色或对其进行动画处理,那就不再是一个很好的用例了。作为 Web 开发人员,我们应该使用正确的工具来完成工作,但我们应该在决定之前了解每个工具的参数。
Agop 提到:“这究竟比 use(让浏览器在内部进行复制,而不是用 JS 模拟它)更好吗?” - 这在文章中首先得到解决,这就是为什么我没有在这里详细说明 - 它已经涵盖了。我还解释说,使用 webpack 之类的东西,您无需将所有 svg 和 js 数据加载到文章中的每个视图中。
这里要理解的一点是,这篇文章并不是在规定我们抛弃图标字体或 SVG use 来在 React 中创建新的 SVG - 它提出了一种解决方案,以解决 React 中缺乏对 use 的支持,以及如何克服这种问题以创建图标系统。
这不再有效。我认为从 0.14 或类似版本开始就是这样。请查看我在下面的 jsperf 测试中使用
<use>
在 React 中的用法。Ago,我理解您对使用 JavaScript 创建本来可以在 HTML 中创建的视图的担忧。JS(和 React)的一个好处是,所有这些都可以在服务器上使用 JS 渲染。这是可重复使用代码和快速服务器渲染的胜利。使用本文中解释的技术并不会阻止您进行服务器端渲染。
我最近做了一些类似的事情。但没有将 SVG 包裹在 JS 中,而是让 React 导入 SVG 文件。这需要更多“webpack”设置(SVG-inline-loader),但这意味着 SVG 保持在 .svg 文件中。
这种组合意味着我们获得了内联 SVG(样式和过渡更改)、服务器端渲染和客户端缓存的优势,我认为之前没有这么优雅地实现过。
如果您使用
<svg><use>
与 svg4everybody 从 CDN 中以 ajax 方式获取您的 svg 雪碧图并绕过 CORS 问题(如这里建议的 https://github.com/jonathantneal/svg4everybody/issues/21),您最终还是会将一堆 SVG 写入 DOM。我喜欢将其构建到 JS 负载中的建议,并从等式中删除 svg4everybody。如果您碰巧使用的是 browserify… https://github.com/coma/svg-reactify
我有点同意 Agop 的观点,这似乎是一个针对更复杂、独立插图的更好解决方案,而不是用于整个页面的图标。
在我正在进行的一个 React 项目中,我们使用了一个更通用的图标组件,它接受图标名称和大小作为属性(如下所示:
<Icon name="umbrella" size="big" />
),并在内部使用use
标签,这在 React 0.14(以及之前的dangerouslySetInnerHTML
中)没有问题。对于需要比这更多控制的情况,你的解决方案看起来是一个很棒的方法。另外,一个小提示:你在
getDefaultProps()
函数中为宽度和高度设置的默认值包含“px” CSS 单位,如果你要在 HTML 属性中使用它,则无效。嗨,diondiondion,
关于 px 的建议很棒!谢谢,已更新。
所以,除了任何使用问题之外,这里的重点是 use vs path,即 use 是否真的必要。在我看来,如果你仍然使用 use 渲染影子 DOM,那么判断它是否比同样的事情做了更多工作。考虑一下:如果你加载了一个 SVG 精灵图,然后也描述了 SVG 图标,你可能会加载比必要的多。如果你只加载 SVG 图标组件,你可以在理论上选择你需要的图标,用于给定的视图。
如果你好奇其他人是否以这种方式内联使用 SVG,GitHub 最近发布了一篇关于他们如何做到这一点的文章:https://github.com/blog/2112-delivering-octicons-with-svg。
我当然认为有些情况下这种方法并不理想,但我不知道这里提到的原因是否考虑了常见的优缺点,我仍然认为,在 React 和你的典型网站情况下,这可以提供性能和工作流程方面的优势。但是,最重要的是测试你的用例!测试所有东西!
嗨,Sarah,
确实,在某些情况下,通过直接包含路径,你最终可能会更有效地加载资源。在实践中,我认为这两种方法在性能方面都差不多。
use
的一个潜在优势是,现代浏览器可以缓存外部 SVG 精灵图,以供将来页面加载。在不支持该功能的浏览器(最值得注意的是 IE 和早期的 Edge)中,它们可以使用 svgxuse 之类的 polyfill 插入页面。但这实际上让我想起使用
path
可能最大的优势:它的简单性。它将在所有功能合理的浏览器中“正常工作”,无需任何 polyfill 或其他变通方法,这非常棒。使用
use
和外部精灵图还有一个特定于我正在开发的产品的优势,但无论如何我都会提一下:能够简单地指向另一个 svg 文件来更改所有图标,而无需重新编译应用程序。我们的应用程序为每个客户提供不同的主题、徽标,有时还会提供图标集,因此将这些东西分开并“抽象出来”对我们来说很有意义,但显然,这不是许多产品共有的需求。这似乎主要是一个关于工作流程和你想将图标和 React 组件结合得有多紧密的个人偏好问题。
嘿,diondiondion,
当然,缓存可能是这里使用 use 的最有力论据。这绝对是值得一提的。它比每个视图只加载几个内联图标(通常大于 1kb)更好吗?我不确定。看来,这完全取决于手头的系统。
你的用例听起来很有趣!如果一切条件都相同,在加载整套不同的图标时,工作流程和抽象方面的优势似乎完全合适。我可以想象这样一种场景:你也可以为每个客户打包组件,但这可能需要比有意义的更多配置。在你的情况下,精灵图可能需要更少的维护,所以同意,它将是更好的选择。
我非常喜欢这篇文章,Sarah!
我一直对 SVG 图标如此看好,原因之一就是它们的通用性。我认为对于开发人员来说,拥有丰富的选择(客户端通过
use
,服务器端(如GitHub 最新的 octicons 版本),或集成到项目JavaScript 框架中(如你详细描述的那样))是件好事(而且非常健康)。 Heck,图标需求适中的项目可能使用img
就足够了!很容易从各种原因中找到这些解决方案的漏洞。像这样的文章帮助像我们这样的团队确定对任何给定项目最适合什么。SVG 太复杂、太强大,而且太有趣了,以至于不能满足于任何“一刀切”的解决方案!
谢谢 Tyler!我完全同意。
我认为这就是你想要达成的效果。
id
属性丢失了;很确定它是必需的(https://www.paciellogroup.com/blog/2013/12/using-aria-enhance-svg-accessibility/)。哦,有趣。谢谢,我不知道这一点。已更新。
很棒的文章,但可访问性部分现在有点乱七八糟。
你使用的是
aria-labelledby
,但它不是必需的,而且你使用的方式有误:你使用了一个id
值(“title”),而 React 不会为组件的每次使用更改它,所以你最终会在整个页面上得到重复的id
(如果你多次使用该图标组件,或者如果你在其他图标组件中使用了相同的 id)。我的建议:完全删除
id
属性和aria-labelledby
属性,因为当前的屏幕阅读器不需要它来读取内联 SVG 的<title>
元素。此外,在 SVG 图标中添加
<title>
元素并不会为可访问性提供帮助。你的图标组件应该能够做两件事完全隐藏图标,因为含义已经在相邻文本中说明。这将是:
<svg aria-hidden="true"><path /></svg>
为图标提供可访问文本,并且能够为每次使用更改该文本,并允许国际化:
<svg><title>[此文本不应始终相同]</title></svg>
。这可以通过有条件地输出
aria-hidden
和<title>
元素来实现,具体取决于使用该组件的代码是否提供可访问文本(未提供可访问文本:aria-hidden="true"
)。一般来说,可访问文本不是图形的描述,所以“雨伞图标”作为相关可访问文本的效果并不好。它应该概括你试图传达的意思。例如,如果你有
<button><IconUmbrella/></button>
,那么这个只有图标的按钮的作用是什么?它显示天气预报吗?那么你应该有:<button aria-label="显示天气预报"><IconUmbrella/></button>
或<button><IconUmbrella customthingforalttext="显示天气预报" /></button>
。你希望屏幕阅读器说“显示天气预报,按钮”,而不是“雨伞图标,按钮”。另外,由于你从图形元素中删除了 alt 文本,所以你可以在需要时翻译它,或者如果你在不同的地方使用同一个图形元素来传达不同的含义,你也可以更改它。你不会希望屏幕阅读器读出“雨伞图标”,而实际上你需要传达“Afficher les prévisions météo”。
@fvsch 所说的大部分内容都是正确的。不幸的是,情况并非如此(尚未)
这就是为什么大多数面向可访问性的 SVG 示例使用冗余的
aria-labelledby
属性。该属性的值必须是一个或多个指向其他元素的有效 ID 引用,并且当然每个元素的 ID 必须在页面中是唯一的。对于没有有意义的子内容的图标,你通常还需要为它指定一个明确的role="img"
,以便浏览器将它视为一个块,隐藏子标记。有关其他内容的更多信息
SVG 2 允许你通过多个用
lang
属性区分的<title>
元素,在 SVG 代码中包含国际化。但是,据我所知,这在任何浏览器中都还没有实现,因此作者仍然需要自己选择正确的语言变体。即使支持多语言 SVG 替代文本,它也不会解决 fvsch 提出的另一个问题:图标的正确可访问文本取决于该图标在页面中的使用方式。如果图标位于按钮/链接内,那么描述该元素的功能比描述图标本身更重要。
谢谢你们两位!我从这里学到了很多。好的,我要做的是 - 我已经使用 title 和 aria-labelledby 的唯一属性更新了 codepen,并将更新文章。我可能会很快关闭这篇文章的评论区,然后写一篇指向这篇新文章的新文章,其中包含我通过这篇文章的评论中学到的所有内容。
我非常感谢你们的反馈。
Amelia,你知道哪些屏幕阅读器需要
aria-labelledby
属性才能读取<title>
元素吗?我在 2015 年秋季的 JAWS、NVDA 和 VoiceOver 中进行的测试中,它不需要?Windows-Eyes 或 ZoomText 也许需要,或者 JAWS、NVDA 或 VoiceOver 的旧版本?由于
<title>
支持在我的测试中对于页面内 SVG 元素(独立 SVG 文档是另外一回事)来说很好,所以我倾向于避免添加额外的aria-labelledby
,因为它们在实践中很容易获得错误或过时的值。如果想添加额外的
aria-labelledby
和id
,最好在componentWillMount
中生成一个随机 UUID,并使用它,以便每个实例都有一个有效的aria-labelledby
。萨拉,请继续分享关于 React 的文章!你的文章“一周内学会 React 生产力,你也行!”让我开始学习这个库。
谢谢马克!很高兴它对你有用 :)
伙计们,让我们退一步,真正看看我们在做什么。
如果你直接在页面上渲染 SVG,比如,服务器端(像 GitHub,耶!),那么是的,可能没有太大区别。浏览器可能更有效地处理它自己的影子 DOM 和
<use>
,或者它可能比已经就位的完整 SVG 更快。我赌它在<use>
上更有效率,因为它会知道 SVG 的结构每次都一样,从而可以重复使用与解析和渲染相关的现有资源。但让我们不要偏离主题…这篇文章是关于使用 React 中的 SVG,这就是为什么我之前的评论这样开头
使用 JavaScript才是问题所在。React 恰好是使用 JavaScript 执行此操作的一种方式。事实上,这是一种非常糟糕的方式。为什么它是一种糟糕的方式呢?
这是一种糟糕的方式,因为将 React 用作某种 SVG 模板系统非常缓慢,特别是对于复杂的 SVG!在这里,我在 jsperf 上快速构建了一个小测试:http://jsperf.com/react-icons-vs-use/2
注意,
<use>
方法快得多,快很多(比如,快 500%)。这并不奇怪。<use>
方法创建了一个元素。完整的 SVG 方法创建了,嗯,很多。再次强调,这不是关于在编写 HTML 时使用完整的 SVG 与使用
<use>
,而是关于使用 React来构建 SVG。当你需要一个高度自定义的 SVG 时这样做是有意义的(把它当作组件对待,duh!),但当你使用 SVG 图标时,无论你是否也使用 React,这都是不必要的。这主要是对文章的这部分的回应,这似乎暗示“嘿,如果你使用 React,就不用
<use>
了,直接使用 React 组件吧!”如果我的评论语气显得粗鲁,请见谅。我不是有意写得粗鲁,只是强有力地提醒大家
你可以,并不意味着你应该。
:)
很棒的测试,感谢你的辛苦工作,这些确实很有趣!
好吧,这样比我之前认为你说的话更有意义了。并且它提出一个体面且有见地的观点。
这确实快很多。但我在这里有一个顾虑,我将谈谈我遇到的用例,这样你就能理解我的出发点。假设你管理着一个拥有 50 个左右图标的巨大网站。假设你的视图只需要其中的 3 个图标。我看到一个例子,在这里,不加载所有 50 个图标(无论是 SVG 精灵图还是图标字体)会很方便,这样节省的成本可能会使其值得。
我也看到一个例子,比如上面,我有一个更复杂的 SVG,我想对其进行更改,无论是添加动画还是只是更新其部分内容。假设我的所有视图都在 React 中。我认为,在这种情况下,这种方法仍然可以很好地工作。
不过,你的测试确实很好地展示了为什么我们不应该这样做。我认为你在这里提出一个很好的观点。
我从两方面都能看到,并且仍然会说,选择适合工作任务的工具。
萨拉,我认为我们基本上在同一页上 :)
就像你说的,如果你有一个特殊的 SVG——不是一个即用即忘的图标,它可能在页面上出现数百次——当然,把它做成一个组件。我完全支持这样做。
至于另一个用例
是的,这当然是有道理的。请记住,一个包含 50 个左右图标的 SVG 精灵图在 gzip 压缩后可能只有几 KB。而且,使用指纹,你只需使用远期到期标头服务一次这个精灵图。浏览器基本上会永远缓存它,或者直到你更改它(因此,文件名也会更改)。然后问题就变成了:哪种更快,服务器渲染完整的内联 SVG 和浏览器解析每一个 SVG,还是使用浏览器已经缓存的单个 SVG?
再次强调,就像你说的:使用适合工作任务的工具 :)
我认为你在缓存方面提出了很强的论据,我们可能永远能就这个问题来回讨论,但如果你也对页面和视图进行适当的缓存和 gzip 压缩,我仍然没有被说服。很难将包含 50 个 SVG 的精灵图描绘成只有几 KB,而不也提醒大家,一个典型的内联 SVG 图标,即使使用 React 渲染,也可能不到 1 KB。如果你有一个在搜索视图和详细信息视图之间切换的网站,通常不需要所有其他图标,我仍然不确定 :) 在这一点上,我们可能还在讨论性能方面非常小的差异。
@萨拉
我有一个懒惰的网络问题。你能推荐任何自动生成单个图标组件的工具吗?(最好使用 webpack)
我一直避免尝试在 React 中解决这个问题,所以这篇文章太完美了!谢谢。
https://github.com/morlay/svg-simplify
https://github.com/morlay/svg2jsx
最近,我找到了一种方法来实现这一点。
演示:https://github.com/morlay/svg2jsx/blob/master/examples/entry.js
svgo-loader
或svg-simplify
可以在使用转换器之前清理 svg。对于单色图标,使用起来会很简单。
但对于像这篇文章中显示的多色图标,可能需要你自己组合形状。
太棒了…… :)
很棒的信息!
这是我读过的关于 React 的最佳文章!
我这么说,是因为我是一名前端开发人员,对 React 感兴趣,但还没有尝试过太多。虽然我可能不会像这样设置我的图标系统,但我认为这篇文章在传达一些基本的 React 概念方面非常出色,这些概念确实让我对 React 感到兴奋。这篇文章打开了我的思维,让我了解了这个库…… 谢谢萨拉;)
嘿,请查看我们由同事创建的 React SVG 图标实时生成器https://react-svg-icon-live-generator.herokuapp.com/。欢迎任何反馈。
前面提到的GitHub 文章引发了我们团队的讨论,让我们也考虑使用 SVG。
根据我的初步测试,直接渲染 SVG 比图标字体好多了。在我当前的情况下,我仅仅在页面上包含了完整的 SVG,没有任何 JavaScript。我们在使用的模板语言(Jade)中使用“包含”,因此在语法上,我们在页面上看不到完整的 SVG 代码。它看起来有点像这样
我对这种方式感到满意,因为如果你从 Sketch 中导出一个 16×16 的 SVG,并且你确保它使用 CSS 渲染成一个 16×16 的块,那么你就能 100% 确定它是清晰的。而如果你导出一个 16×16 的 SVG,然后用图标字体生成器处理它,你就永远无法真正确定它是清晰的。
我认为我和我的团队花了几个小时才搞定图标字体。这包括一些任务,比如调试图标字体的输出,告诉人们如何设置他们的系统以包含 fontforge/fontcustom,以及调整 SVG 代码以使其作为图标字体很好地渲染。我希望这些麻烦现在随着这种新解决方案的出现而结束。
一些建议:最好以特定尺寸导出你的 SVG 图标,并明确定义它
然后你可以使用 CSS 来“着色”SVG,如下所示
萨拉,写得很好!我在一些 Rails 项目中使用了类似的方法,使用https://github.com/jamesmartin/inline_svg
我最近做了一个 SVG 图标系统,它必须与 React 和普通的 JavaScript 兼容(React 用于嵌入在页面中的多个 SPA,还有遗留的 JS 代码)。我最终使用 webpack-svgstore-plugin 生成 SVG 包,然后像这样从 React 中使用它
<SvgSymbol name={‘icon-name-is-the-src-file-name’} />
或者从服务器端模板(基于 Jinja2)中使用它,像这样
{% svg_symbol(‘icon-name’) %}
辅助标签只是发出
<svg><use…>
内容并引用图标,比如“/path/to/bundle.svg#icon-name”。这可以通过可选参数 bundle={‘bundle-name’} 轻松扩展以支持多个包。
直接在 React 中导入 SVG 并依赖于 Webpack 或其他东西将它们捆绑到 JS 中的一个问题是,生成的图标系统只能在 React 环境中使用。我认为,对于像图标这样微不足道的事情,供应商锁定比上面提到的性能问题更严重。
我认为我所有的纯组件在被你称为“笨”后,都感到很受伤 ;(
嗨,Sarah,
感谢你的文章和与 Agop 的讨论。
关于 #a11y 的一个快速说明。在描述的场景中,你引入了多个 SVG 元素,它们都包含一个 id 为“title”的 title 元素。
我刚刚用 VoiceOver 试了一下。假设你在一个页面中包含了多个不同的图标,并且它们都包含
VoiceOver 将为所有图标读取第一个出现的图标的标题,因为 ID 应该在一个文档中只包含一次。要使
aria-labelledby
正确工作,需要设置唯一的 ID。谢谢。:)
嗨,Stephan,
是的,如果你查看评论和帖子,这个问题已经解决,并且帖子也更新了。不过,我可以更新所有其他 codepens,以避免将来出现混淆。我只更新了作为可访问性参考的那一个。
感谢你的关注!
嗨,Sarah,感谢你的分享。