{T}

SVG在React中的应用

React 作为一个流行的 JavaScript 库,以其组件化开发模式和高效的虚拟 DOM 机制,在构建用户界面时展现出了强大的能力。当 React 与 SVG 结合使用时,Web 开发者可以创建既美观又动态、互动性强的 Web 应用。通过 React 的组件化思想,SVG 不仅可以作为静态图像被引入,还可以与应用的状态和属性结合,进行动态渲染和交互操作。

在 React 项目中使用 SVG,有多种方法可供选择,每种方法都有其优点和适用场景。例如,可以将 SVG 作为 React组件直接导入,这种方法允许开发者充分利用 React 的属性系统和状态管理来动态控制 SVG 的呈现;也可以使用传统的 <img> 标签来展示 SVG,这种方式简单直观,适用于不需要复杂交互的场景。此外,内联 SVG 也是一种常见的做法,特别是在需要对 SVG 进行细粒度控制和动态操作时。

总的来说,SVG 的灵活性、可扩展性和与 React 的良好兼容性,使其成为现代 Web 开发中不可或缺的一部分。通过合理地在 React 应用中使用 SVG,开发者可以显著提升用户界面的质量和互动体验,从而创造出更加出色的 Web 应用。接下来,将详细探讨在 React 中使用 SVG 的各种方法和技巧,帮助开发者在实际项目中更好地应用这一技术。

为什么在 React 中使用 SVG?

相比之下,你可能更熟悉 JPG、PNG 和 GIF 等传统的图像格式,而对 SVG 了解较少。然而,SVG 有很多优点,足以让你在 Web 应用中优先选择它而不是传统图像格式。正如本文中初级篇的《矢量图和光栅图谁更适合你的项目》所说,以下是你应该在 Web 应用中使用 SVG 的几个主要原因:

  • 可扩展性和清晰度:这是 SVG 相较于传统位图图像格式的最大优势。由于 SVG 使用形状、数字和坐标,而不是像其他图像格一样使用像素网格。这使得 SVG 图形可以在不同尺寸下自由缩放而不会失真。这也赋予了 SVG 无限缩放的能力。使得 SVG 在响应式设计中极其有用,不论是在高分辨率屏幕还是低分辨率屏幕上,都能保证图形的清晰度。
  • 灵活性和可编程性:SVG 本质上是 XML 文件,其语法格式与我们熟悉的 HTML 标记语言极其相似。这使 Web 开发者可以通过 CSS 进行样式控制,通过 JavaScript 进行动态操作。这意味着你可以轻松地控制 SVG 的各个部分,创建丰富的交互和动画效果。
  • 高性能和速度:由于 SVG 是文本文件,通常比位图图像要小,因此 SVG 图像的加载和渲染速度都很快。这就像对比渲染文本和渲染像素和颜色的其他图像格式一样。此外,如果在代码中使用内联 SVG,浏览器甚至不需要额外的请求来获取图像,而是像处理文本文件中的所有其他代码一样渲染它。在这种情况下,没有发出请求。但是在像“蒙娜丽莎”照片这样的复杂图像,我建议使用 JPG 或 PNG,因为这么复杂的图像使用 SVG ,可能会使加载时间和性能急剧下降。
  • 文件大小小:与其他文件格式相比,SVG 文件通常较小,并且易于压缩,从而进一步减少文件大小。这不仅提升了网页加载速度,还减少了带宽消耗。
  • 类似于DOM,可样式化和可编辑:SVG 图像本质上是代码,这意味着它们可以像 DOM 元素一样被导航和样式化。例如,你可以使用 fill 属性来代替 color 属性进行颜色设置。此外,SVG 也可以使用 CSS 进行样式化,甚至通过文本编辑器进行创建、编辑和动画化。
  • 可动画化:SVG可 以通过 Web 动画 API、WebGL、CSS 动画等工具进行动画处理。这使得你可以创建复杂的动画效果,为用户提供更生动的交互体验。
  • 集成的便利性:SVG 可以用于多种场景,包括展示标志、图标、图表、动画和特效等。其灵活性使得它在 Web 开发中具有广泛的应用。
  • 可访问性和 SEO:SVG 包含文本,这不仅提高了可访问性,还可以被搜索引擎索引,提高 SEO 效果。用户可以更容易地搜索和脚本化 SVG 内容,从而提升网页的可发现性。

虽然上面列出的选项都是围绕SVG在Web中的优势,但这些优势同样适用于基于React开发的Web应用。这意味着在使用React开发Web应用时,我们应该根据具体的需求和场景,尽可能地使用SVG图形,而不是一味地依赖传统位图格式。

其次,SVG 的灵活性和可编程性使得它在 React 中更容易进行样式控制和动态操作,可以通过 CSS 和 JavaScript 轻松创建丰富的交互和动画效果。此外,SVG的文件大小通常较小,加载速度快,这对于优化 React 应用的性能非常有帮助。

基于这些优势,在 React 项目中使用 SVG 可以带来更好的开发体验和用户体验。因此,我们有必要进一步了解和学习如何在 React 中高效地使用 SVG,从而充分发挥其潜力,为我们的 Web 应用提供更优质的图形表现和交互效果。

准备工作: 构建 React 开发环境 + SVG 文件

开始了解和学习如何在 React 中高效地使用 SVG 之前,我们需要做一些准备工作:

在构建 React 开发环境时,Create React App (CRA) 和 Vite 是目前最为流行的两种工具。它们各自有优点和适用的场景,都能方便地在项目中使用 SVG。

CRA 内置了对 SVG 文件的良好支持,你可以直接将 SVG 文件作为 React 组件导入使用,操作简单快捷。而 Vite 需要通过安装和配置一些插件才能实现同样的功能,但它的构建速度更快,开发体验更优越。

你可以根据项目需求选择合适的工具,这两种工具都能让你高效地在 React 项目中使用 SVG,从而创建高质量的用户界面。在这里,我们选择使用 Vite 来构建 React 开发环境。因此,我们需要根据 Vite 提供的文档,在你的工作环境中搭建 React 开发环境。

实际上,对于任何一位 Web 开发者来说,使用 Vite 构建 React 开发环境并不困难。不过,我还是想在这里简单地演示一下我在工作环境中创建 React 开发环境的过程。

像我这样,在命令终端执行下面一行命令,然后根据具体的提示,一步一步往下执行,你就拥有了一个 Vite React 应用的开发环境:

code
pnpm create vite svg-in-react

我选择了 React 的 JavaScript 环境。通过上面的几行命令,创建了一个名为 svg-in-react 的项目。然后安装完相关的依赖包之后,在命令终端执行 pnpm run dev ,你就可以在浏览器访问 http://localhost:5173/ 地址。

恭喜你,你已经成功的使用 Vite 工具构建了一个 React 开发环境。

接下来,我们主要是与 SVG 打交道。因此,你可以在 svg-in-react 项目中准备一些 SVG 文件,并将这些 SVG 文件放置在 src/assets/ 目录下。我习惯性将一些 SVG 图标集中放置在 icons 文件夹中,将用于背景、插图或较大的 SVG 文件直接放置在 assets 文件夹中:

与此同时,我删除了 App.jsx 不必要的模板内容和 App.css 不必要的 CSS:

JavaScript
import './App.css'

function App() {
    return (
        <div className='app'>
            <h1>从这里开始...</h1>
        </div>
    )
}

export default App
CSS
/* App.css */
.app {
    width: 100vw;
    min-height: 100vh;
    display: grid;
    place-content: center;
}

现在,你看到的页面非常干净了,页面上只有“从这里开始...”:

做了准备,那么我们就开始吧!

如何在 React 中导入 SVG?

要在 React 开发环境中使用 SVG,首先需要了解如何将 SVG 文件导入到 React 应用中。和在 Vue 开发环境中导入 SVG 类似,在 React 应用中,有多种方式可以将 SVG 整合到项目中。接下来,将逐一解释每种方法,并探讨在项目中使用这些方法的优缺点。

我们先从最简单的方法开始!

将 SVG 作为常规图像使用

在 React 应用中,任何 SVG 都可以像图一样使用,因为将它们视为一种图像资源。也就是说,你可以直接使 S用 <img> 元素的 src 属性加载 SVG。需要注意一点,在 Vite 构建的 React 应用中,需要使用 import 导入 SVG 文件,并将其赋值给一个变量,<img> 元素的 src 才能将 SVG 作为常规图像使用:

JavaScript
// App.jsx
import "./App.css"

import reactIllustration from "./assets/react-Illustration.svg"

function App() {
    return (
        <div className="app">
            <img src={reactIllustration} alt="" />
        </div>
    )
}

export default App

上面的代码从 ./assets 目录中导入了一个名为 react-Illustration 的 SVG 图像,并将其作为一个图像元素(<img>)插入到 React 组件中(App):

有一点需要注意的是,Vite 构建的 React 应用与 CRA 构建的 React 应用在使用 <img>src 引入 .svg 文件有所不同。CRA React 应用中,可以直接通过 src 属性引入 SVG 文件,并且能正常的呈现在 Web 浏览器中。这是因为 CRA 默认配置中已经支持 SVG 文件的导入和使用。但是,在 Vite React 应用中,直接使用 <img> 元素的 src 属性引入 .svg 文件,并无法在 Web 浏览器中得到正常的渲染。这是因为 Vite 默认不会对 SVG 文件进行处理,因此需要进行额外的配置才能正确的加载 SVG 文件。

在 React 中,使用这种方法引入 SVG ,简单易用,但其缺点也很显著,你无法通过 CSS 控制 SVG 样式。例如,你无法在 CSS 中使用 fill 属性来更改 SVG 图形的填充颜色。

将 SVG 作为背景图片使用

前面我们提到,SVG 图像实际上是一种图像资源,可以像使用传统图像一样在 CSS 中使用它。例如,可以将 SVG 用作元素的背景图像:

CSS
/* App.css */
.app {
    background: url('./assets/bg.svg') no-repeat left top / cover, linear-gradient(-45deg, #f36 2%, #000);
}

上面的代码,将 SVG 图像(bg.svg)作为背景应用于 App 组件(App.jsx)中的 .app 元素上。它的使用方式和使用普通图像没有什么不同。这样做不仅能提升页面的视觉效果,还能充分利用 SVG 的可扩展性和清晰度,确认在任何分辨率下,背景图像都是高清无损的:

注意,这种方法不仅局限于 backround-image 属性上,它也适用于 CSS 中可接受 <image> 值类型的其他属性,比如 mask-image

将 SVG 内联到 React 中

众所周知,SVG 是一种基于 XML 的矢量图形格式,实际上是由标签和属性组成的纯文本。这使得它可以很容易地嵌入到 HTML 或 JSX 中,而不需要复杂的解析过程。因此,在 Vite React 应用中,你可以像在 HTML 中嵌入 SVG 代码那样,直接将 SVG 代码内联到 React 的组件中(实际上内联到 JSX 中)。例如:

JavaScript
// App.jsx
import "./App.css";
function App() {
    return (
        <div className="app">
            <svg viewBox="0 0 640 512">
                <path
                    fill="red"
                    stroke="#000"
                    stroke-width="10"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    d="M524.5 69.8a1.5 1.5 0 0 0 -.8-.7A485.1 485.1 0 0 0 404.1 32a1.8 1.8 0 0 0 -1.9 .9 337.5 337.5 0 0 0 -14.9 30.6 447.8 447.8 0 0 0 -134.4 0 309.5 309.5 0 0 0 -15.1-30.6 1.9 1.9 0 0 0 -1.9-.9A483.7 483.7 0 0 0 116.1 69.1a1.7 1.7 0 0 0 -.8 .7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 0 0 .8 1.4A487.7 487.7 0 0 0 176 479.9a1.9 1.9 0 0 0 2.1-.7A348.2 348.2 0 0 0 208.1 430.4a1.9 1.9 0 0 0 -1-2.6 321.2 321.2 0 0 1 -45.9-21.9 1.9 1.9 0 0 1 -.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 0 1 1.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 0 1 1.9 .2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 0 1 -.2 3.1 301.4 301.4 0 0 1 -45.9 21.8 1.9 1.9 0 0 0 -1 2.6 391.1 391.1 0 0 0 30 48.8 1.9 1.9 0 0 0 2.1 .7A486 486 0 0 0 610.7 405.7a1.9 1.9 0 0 0 .8-1.4C623.7 277.6 590.9 167.5 524.5 69.8zM222.5 337.6c-29 0-52.8-26.6-52.8-59.2S193.1 219.1 222.5 219.1c29.7 0 53.3 26.8 52.8 59.2C275.3 311 251.9 337.6 222.5 337.6zm195.4 0c-29 0-52.8-26.6-52.8-59.2S388.4 219.1 417.9 219.1c29.7 0 53.3 26.8 52.8 59.2C470.7 311 447.5 337.6 417.9 337.6z"
                />
            </svg>
        </div>
    );
}

export default App;

你会发现,浏览器会直接渲染内联在 App 组件中的 SVG 代码:

正如你所看到的,SVG 代码很容易地嵌入到 JSX 中,并且不需要复杂的解析过程。这一切都得益于 JSX 是 React 的语法扩展,允许我们在 JavaScript 代码中编写类似 HTML 的标签。这使得在 React 组件中直接嵌入 SVG 代码变得非常自然和直观。因为 JSX 最终会被编译成 JavaScript 函数调用,SVG 代码也会被转换为对应的 React 元素。

需要注意的是,直接将 SVG 代码嵌入到 JSX 中时,虽然浏览器能够正常渲染 SVG 图形,但在渲染过程中可能会将某些 SVG 元素的属性误认为是无效的 DOM 属性。例如,在上述代码中,stroke-width 属性可能会在控制台中触发警告信息。

这种警告信息通常是 React 开发模式下的一种提示,提醒你应该遵循 JSX 属性的命名规范,以确保代码的可读性和一致性。在绝大多数情况下,SVG 仍然能够在 Web 浏览器中正常渲染。然而,如果这些警告被忽视并且在整个项目中广泛存在,可能会导致调试困难或意外行为。

因此,将 SVG 代码直接嵌入到 JSX 中时,最好是将 SVG 代码转换为 JSX 语法。如果你对 JSX 语法规则不是完全了解,那么可以考虑使用诸如 svg2jsx 工具,将 SVG 代码转换为 JSX:

你可以直接将转换后的 JSX 代码替换原来的 SVG 代码:

JavaScript
// App.jsx
import "./App.css";
function App() {
    return (
        <div className="app">
            <svg viewBox="0 0 640 512">
                <path
                    fill="red"
                    stroke="#000"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    strokeWidth="10"
                    d="M524.5 69.8a1.5 1.5 0 00-.8-.7A485.1 485.1 0 00404.1 32a1.8 1.8 0 00-1.9.9 337.5 337.5 0 00-14.9 30.6 447.8 447.8 0 00-134.4 0 309.5 309.5 0 00-15.1-30.6 1.9 1.9 0 00-1.9-.9 483.7 483.7 0 00-119.8 37.1 1.7 1.7 0 00-.8.7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 00.8 1.4A487.7 487.7 0 00176 479.9a1.9 1.9 0 002.1-.7 348.2 348.2 0 0030-48.8 1.9 1.9 0 00-1-2.6 321.2 321.2 0 01-45.9-21.9 1.9 1.9 0 01-.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 011.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 011.9.2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 01-.2 3.1 301.4 301.4 0 01-45.9 21.8 1.9 1.9 0 00-1 2.6 391.1 391.1 0 0030 48.8 1.9 1.9 0 002.1.7 486 486 0 00147.2-74.1 1.9 1.9 0 00.8-1.4c12.2-126.7-20.6-236.8-87-334.5zm-302 267.8c-29 0-52.8-26.6-52.8-59.2s23.4-59.3 52.8-59.3c29.7 0 53.3 26.8 52.8 59.2 0 32.7-23.4 59.3-52.8 59.3zm195.4 0c-29 0-52.8-26.6-52.8-59.2s23.3-59.3 52.8-59.3c29.7 0 53.3 26.8 52.8 59.2 0 32.7-23.2 59.3-52.8 59.3z"
                 />
            </svg>
        </div>
    );
}

export default App;

除了上述方法,在 Vite React 开发环境中,还有一些 Vite 插件可以自动将 SVG 代码转换为 JSX。将在后面的构建工具部分详细介绍这些插件。

你可能已经发现这种方法的缺点了。随着 SVG 图像的复杂性增加,SVG 文件的大小也会增加。由于 SVG 存储在文本中,这意味着我们的代码中会有大量的文本,从而增加了组件的复杂性和大小。这可能会降低代码的可读性和维护性,并且可能会影响到页面加载时间和性能,因为会增加初始 HTML 的大小。

此外,内联 SVG 不会利用浏览器的缓存机制,可能会导致重复加载相同的 SVG 代码。除此之外,内联 SVG 会受到组件和父级样式的影响,可能会导致意想不到的样式冲突和渲染问题。因此,在使用内联 SVG 时,需要特别注意样式的继承和覆盖问题,以避免图像渲染错误。

虽然将 SVG 内联到 React 中有一些缺点,但它也有很多优点。例如,可以使用 React 提供的 props 或者 CSS 样式,轻松地控制 SVG 图像的外观和行为:

JavaScript
import "./App.css";
import  { useState } from 'react';

function App() {
    const [fillColor, setFillColor] = useState('red');

    const handleColorChange = () => {
        setFillColor(fillColor === 'red' ? 'blue' : 'red');
    };

    return (
        <div className="app">
            <svg viewBox="0 0 640 512">
              <path
                  fill={fillColor}
                  stroke="#000"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth="10"
                  d="M524.5 69.8a1.5 1.5 0 00-.8-.7A485.1 485.1 0 00404.1 32a1.8 1.8 0 00-1.9.9 337.5 337.5 0 00-14.9 30.6 447.8 447.8 0 00-134.4 0 309.5 309.5 0 00-15.1-30.6 1.9 1.9 0 00-1.9-.9 483.7 483.7 0 00-119.8 37.1 1.7 1.7 0 00-.8.7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 00.8 1.4A487.7 487.7 0 00176 479.9a1.9 1.9 0 002.1-.7 348.2 348.2 0 0030-48.8 1.9 1.9 0 00-1-2.6 321.2 321.2 0 01-45.9-21.9 1.9 1.9 0 01-.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 011.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 011.9.2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 01-.2 3.1 301.4 301.4 0 01-45.9 21.8 1.9 1.9 0 00-1 2.6 391.1 391.1 0 0030 48.8 1.9 1.9 0 002.1.7 486 486 0 00147.2-74.1 1.9 1.9 0 00.8-1.4c12.2-126.7-20.6-236.8-87-334.5zm-302 267.8c-29 0-52.8-26.6-52.8-59.2s23.4-59.3 52.8-59.3c29.7 0 53.3 26.8 52.8 59.2 0 32.7-23.4 59.3-52.8 59.3zm195.4 0c-29 0-52.8-26.6-52.8-59.2s23.3-59.3 52.8-59.3c29.7 0 53.3 26.8 52.8 59.2 0 32.7-23.2 59.3-52.8 59.3z"
              />
            </svg>
            <button onClick={handleColorChange}>Change Color</button>
        </div>
    );
}

export default App;
CSS
/* App.css */
svg {
    display: block;
    width: 30vh;
    aspect-ratio: 1;
    transform-origin: center;
    transform-box: fill-box;
    animation: pulse 2s ease-in-out infinite alternate;
}

path {
    stroke: lime;
    stroke-width: 20;
}

@keyframes pulse {
    50% {
        scale: 1.5;
    }
}

这种方式的另一个优势是,可以将内联的 SVG 封装成一个 React 组件,方便重复使用。例如,我们在 src/components 目录下创建了一个名为 Heart 的组件,用户点击图标时,会在两个颜色之间进行切换,分别表示已点赞(#f36)和未点赞(gray):

JavaScript
// Heart/index.jsx
import "./heart.css";
import { useState } from "react";

const Heart = () => {
    const [fillColor, setFillColor] = useState("#f36");
    const handleColorChange = () => {
        setFillColor(fillColor === "#f36" ? "gray" : "#f36");
    };

    return (
        <div className="heart" onClick={handleColorChange}>
            <svg viewBox="0 0 640 512" className="icon icon--heart">
                <path
                    fill={fillColor}
                    d="M47.6 300.4l180.7 168.7c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9l180.7-168.7c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141-45.6-7.6-92 7.3-124.6 39.9l-12 12-12-12c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"
                />
            </svg>
        </div>
    );
};

export default Heart;
CSS
/* heart.css */
.icon--heart {
    display: block;
    width: 30vh;
    aspect-ratio: 1;
    transform-origin: center;
    transform-box: fill-box;
    animation: pulse 2s ease-in-out infinite alternate;
}

@keyframes pulse {
    50% {
        scale: 1.5;
    }
}

然后在 App 组件中引用这个 Heart 组件:

JavaScript
import "./App.css";
import Heart from './components/Heart'

function App() {
    return (
        <div className="app">
            <Heart />
        </div>
    );
}

export default App;

你将在浏览器中看到的效果如下:

尽管内联 SVG 在控制和交互方面具有优势,但也存在组件复杂性增加、性能影响、代码重复和维护难度等缺点。在选择是否内联 SVG 时,需要根据项目的具体情况权衡这些优缺点,以确保最佳的开发和用户体验。对于一些简单且频繁使用的 SVG 图像,内联可能是一个不错的选择,但对于复杂或复用频繁的图像,使用外部引用或专门的工具插件可能会更合适。

将 SVG 转换为 React 组件

将 SVG 转换为 React 组件并在 Vite React 应用中使用,是非常普遍且流行的做法。在 Vite React 开发环境中,有多种方法可以完成这个转换,使得你可以在应用中随意重复使用这些组件。

在一些 React 开发环境中,比如由 CRA 构建的 React 应用,在默认情况下,你可以直接导入 SVG 文件并将其作为 React 组件使用。SVG 图像不会作为单独的文件加载;相反,它会与 HTML 一起呈现:

JavaScript
import "./App.css";
import { ReactComponent as ReactLogo} from './assets/react.svg';

const App = () =>{
    return (
        <div className="app">
             <ReactLogo />
        </div>
    );
}

export default App;

不过,在 Vite React 构建的开发环境中,默认是不支持方式。因为 Vite 默认情况下会将 SVG 文件解释为静态资源,而不会将其视为 React 组件。如果你比较喜欢这种方式的话,就需要使用 Vite 相关的插件,例如 vite-plugin-react-svg ,它可以让 Vite 将 SVG 文件作为一个 React 组件。这里暂时不展开介绍,稍后再阐述!

然而,在 Vite React 构建的开发环境中,默认情况下并不支持这种方式。这是因为 Vite 默认会将 SVG 文件解释为静态资源,而不是将其视为 React 组件。如果你更喜欢使用这种方式,你需要使用 Vite 相关的插件,比如 vite-plugin-react-svg。这个插件可以让 Vite 将 SVG 文件作为一个 React 组件处理。但是,关于这一点,将在稍后详细介绍!

将 SVG 转换为 React 组件的另一种方法是创建一个返回 React SVG 的函数式或类组件,像这样:

JavaScript
// components/LeftArrow/index.jsx
import './LeftArrow.css'

const LeftArrow = () => {
    return (
        <svg
            className="icon icon--left-arrow"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
            strokeWidth={2}
        >
            <path
                strokeLinecap="round"
                strokeLinejoin="round"
                d="M11 19l-7-7 7-7m8 14l-7-7 7-7"
            />
        </svg>
    );
};

export default LeftArrow;
CSS
/* LeftArrow.css */
.icon--left-arrow {
    display: block;
    width: 2rem;
    aspect-ratio: 1;
}

现在,你可以像这样导入和渲染 LeftArrow 组件到另一个组件中:

JavaScript
import "./App.css";
import LeftArrow from "./components/LeftArrow";

const App = () =>{
    return (
        <div className="app">
            <button className="button">
                <LeftArrow />
            </button>
        </div>
    );
}

export default App;

你或许已经发现了,这与前面介绍的将 SVG 内联到 React 中是一样的。没错,这种方式实际上就是将每个 SVG 文件内联到 React 中,但每一个 SVG 都被视为一个独立的 React 组件。这些组件可以像其他任何 React 组件一样使用,可以绑定事件、引用、并且随时随地使用 React 的相关特性。

虽然手动将 SVG 转换为 React 组件是可行的,但这一过程耗时且容易出错。幸运的是,有一些技术和工具可以简化这一过程,使其更加高效和可靠。在 Vite React 应用中,你可以通过使用一些插件来轻松地管理 SVG 文件。例如,vite-plugin-svgr 插件就是一个不错的选择,它允许你直接将 SVG 文件转换为 React 组件,省去了手动转换的繁琐步骤,让你的开发过程更加流畅。

如果你还记得的话,本文系列的《SVG 构建工具》中有介绍过 vite-plugin-svgr 插件的用法。为了再次加强大家的印象,快速回顾一下,在 Vite React 应用中如何高效地利用 vite-plugin-svgr 插件来处理 SVG 文件。

要使用 vite-plugin-svgr,你首先需要安装它:

code
pnpm install -D vite-plugin-svgr 

安装完成后,你需要在 Vite 的配置文件 vite.config.js 中配置该插件。以下是一个基本的配置示例:

JavaScript
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import svgr from 'vite-plugin-svgr';

export default defineConfig({
    plugins: [react(),svgr()],
})

配置完成后,你可以在你的 React 组件中直接导入 SVG 文件,并将它们作为 React 组件使用。例如:

JavaScript
// App.jsx
import "./App.css";
import ReactIllustration from './assets/react-Illustration.svg?react'

const App = () =>{
    return (
        <div className="app">
            <ReactIllustration />
        </div>
    );
}

export default App;

在这个例子中,ReactIllustration 是一个通过 vite-plugin-svgr 转换后的 React 组件,你可以像使用其他 React 组件一样使用它。现在,ReactIllustration 组件将对应的 SVG 文件注入到了 DOM 中:

将 SVG 转换为 React 组件的过程中,使用像 vite-plugin-svgr 这样的插件是非常有优势的。它让你能够轻松高效地处理大量的 SVG 图像,为你的项目带来了更大的灵活性和可维护性。然而,这种方法可能需要一些额外的配置,并需要对 SVGR 的功能有一定的了解。此外,编写 React 组件模板时可能不太直观,需要一定的熟悉度。

如何在 React 中使用 SVG?

通过之前的学习,你可能已经了解了在React应用中引入SVG的多种不同方式。接下来,深入探讨在React中如何使用SVG。

在 React 中为 SVG 添加样式

在 React 中,我们有多种方法可以为 SVG 添加样式,使其与 UI 的其余部分相匹配。以内联 SVG 方式创建一个名为 Page404 组件为例:

JavaScript
// components/Page404/index.jsx

import "./page404.css";

const Page404 = () => {
    return (
        <svg
            width="860.131"
            height="571.148"
            viewBox="0 0 860.131 571.148"
        >
            <path
                fill="#f2f2f2"
                d="M435.735 160.527c-7.669-12.684-16.757-26.228-30.99-30.37-16.481-4.796-33.412 4.732-47.774 14.135a1392.157 1392.157 0 00-123.893 91.283l.043.493 92.451-6.376c22.265-1.535 45.296-3.283 64.972-13.816 7.467-3.996 14.745-9.335 23.206-9.707 10.511-.463 19.677 6.879 26.88 14.549 42.607 45.37 54.937 114.754 102.738 154.616a1516.995 1516.995 0 00-107.633-214.807z"
            />
            <path
                fill="#e4e4e4"
                d="M697.636 545.355c-4.711-5.95-6.637-7.343-11.284-13.347q-56.765-73.417-106.708-151.793-33.924-53.23-64.483-108.504-14.549-26.278-28.3-52.969-10.67-20.695-20.864-41.638a841.984 841.984 0 01-5.711-12.009c-4.428-9.442-8.774-18.93-13.44-28.244-5.317-10.616-11.789-21.745-21.552-28.877a29.405 29.405 0 00-15.319-5.895c-7.948-.513-15.282 2.769-22.176 6.353-50.438 26.301-97.659 59.276-140.37 96.798A730.778 730.778 0 00133.39 331.82c-1.009 1.44-3.393.064-2.375-1.384q6.01-8.498 12.257-16.813a734.817 734.817 0 01187.6-174.986q18.248-11.825 37.182-22.542c6.362-3.603 12.752-7.16 19.251-10.497 6.372-3.272 13.137-6.215 20.416-6.325 24.77-.385 37.595 27.667 46.405 46.542q4.153 8.911 8.406 17.767 16.075 33.62 33.388 66.628 10.684 20.379 21.837 40.52 34.707 62.717 73.778 122.896c34.506 53.143 68.737 100.089 108.046 149.785 1.082 1.375-.852 3.337-1.944 1.943zM244.982 191.378c-1.44-1.604-2.87-3.209-4.318-4.813-11.422-12.632-23.679-25.118-39.364-32.36a57.11 57.11 0 00-23.927-5.547c-8.562.028-16.932 2.274-24.843 5.418-3.74 1.494-7.399 3.19-11.001 4.996-4.116 2.072-8.16 4.281-12.183 6.51q-11.332 6.27-22.369 13.09-21.96 13.572-42.545 29.216-10.671 8.113-20.902 16.758-9.516 8.03-18.646 16.492c-1.302 1.201-3.245-.742-1.944-1.943a441.255 441.255 0 014.85-4.446q6.875-6.216 13.971-12.193 12.94-10.918 26.549-20.993 21.162-15.676 43.782-29.226 11.304-6.765 22.919-12.962a198.735 198.735 0 017.095-3.621 113.116 113.116 0 0116.868-6.867 60.006 60.006 0 0125.476-2.502 66.327 66.327 0 0123.505 8.131c15.401 8.608 27.346 21.92 38.97 34.91 1.174 1.32-.76 3.272-1.943 1.952z"
            />
            <path
                fill="#e4e4e4"
                d="M560.542 322.285l36.905-13.498 18.323-6.702c5.968-2.183 11.921-4.667 18.09-6.23a28.539 28.539 0 0116.374.208 37.738 37.738 0 0112.77 7.917 103.64 103.64 0 0110.475 11.186c3.99 4.795 7.92 9.64 11.868 14.467q24.442 29.891 48.563 60.042 24.121 30.15 47.92 60.556 23.857 30.48 47.386 61.216 2.882 3.765 5.76 7.534c1.059 1.388 3.449.02 2.374-1.388q-23.702-31.045-47.735-61.835-24.092-30.864-48.516-61.466-24.425-30.601-49.179-60.937-6.167-7.558-12.354-15.099c-3.48-4.24-6.92-8.527-10.737-12.474-7.005-7.245-15.757-13.648-26.234-13.822-6.16-.102-12.121 1.853-17.844 3.923-6.17 2.232-12.325 4.506-18.486 6.76l-37.163 13.592-9.29 3.398c-1.65.603-.937 3.262.73 2.652z"
            />
            <path
                fill="#f2f2f2"
                d="M196.443 170.1c-18.754-9.639-42.771-7.75-60.005 4.291a855.847 855.847 0 0197.37 22.726c-13.282-7.784-23.672-19.98-37.365-27.017zM136.253 174.358l-3.61 2.935a53.444 53.444 0 013.795-2.902c-.062-.01-.123-.022-.185-.033zM661.615 322.42c-3.633-4.422-7.56-9.052-12.994-10.849l-5.073.2a575.436 575.436 0 00153.267 175.221l-135.2-164.572zM346.15 285.94a37.481 37.481 0 0014.93 20.96c2.82 1.92 6.157 3.761 7.122 7.034a8.379 8.379 0 01-.873 6.15 24.884 24.884 0 01-3.862 5.041l-.136.512c-6.999-4.147-13.657-9.393-17.523-16.551s-4.405-16.539.342-23.146M579.15 488.94a37.481 37.481 0 0014.93 20.96c2.82 1.92 6.157 3.761 7.122 7.034a8.379 8.379 0 01-.873 6.15 24.884 24.884 0 01-3.862 5.041l-.136.512c-6.999-4.147-13.657-9.393-17.523-16.551s-4.405-16.539.342-23.146M114.15 474.94a37.481 37.481 0 0014.93 20.96c2.82 1.92 6.157 3.761 7.122 7.034a8.379 8.379 0 01-.873 6.15 24.884 24.884 0 01-3.862 5.041l-.136.512c-6.999-4.147-13.657-9.393-17.523-16.551s-4.405-16.539.342-23.146"
            />
            <circle cx="649.249" cy="51" r="51" fill="#6c63ff" />
            <path
                fill="#f0f0f0"
                d="M741.284 11.87c-24.717-3.34-52.935 10.02-59.341 34.124a21.597 21.597 0 00-41.094 2.109l2.83 2.026a372.275 372.275 0 00160.659-.726C787.145 31.334 766 15.21 741.284 11.87zM635.284 79.87c-24.717-3.34-52.935 10.02-59.341 34.124a21.597 21.597 0 00-41.094 2.109l2.83 2.026a372.275 372.275 0 00160.659-.726C681.145 99.334 660 83.21 635.284 79.87z"
            />
            <path
                fill="#ccc"
                d="M851.011 92.728a.982.982 0 01-.302-.047C586.303 9.063 353.265 19.998 204.33 43.895a1294.017 1294.017 0 00-60.403 11.161 1196.246 1196.246 0 00-15.597 3.378 1023.104 1023.104 0 00-18.532 4.306q-3.873.917-7.595 1.849a972.21 972.21 0 00-11.66 2.957 930.173 930.173 0 00-13.797 3.671.442.442 0 01-.051.015v.001a926.363 926.363 0 00-15.323 4.325c-2.698.78-5.304 1.548-7.8 2.307-.278.077-.525.151-.776.227l-.536.164c-.31.094-.617.187-.924.275l-.02.006h.001l-.811.253c-.968.293-1.912.579-2.841.864C23.119 87.22 9.626 92.604 9.493 92.656a1 1 0 11-.745-1.856c.135-.053 13.694-5.463 38.328-13.058.932-.286 1.879-.572 2.85-.866l.754-.235c.026-.01.051-.017.078-.025.305-.087.61-.18.92-.273l.536-.164c.268-.08.532-.16.802-.235a593.8 593.8 0 017.797-2.307 932.235 932.235 0 0115.334-4.328c.017-.006.033-.01.05-.014v-.001a941.379 941.379 0 0113.844-3.685 993.766 993.766 0 0111.68-2.962q3.738-.93 7.61-1.852a1026.011 1026.011 0 0118.563-4.313c5.299-1.183 10.555-2.322 15.622-3.383a1295.424 1295.424 0 0160.497-11.178c149.149-23.932 382.52-34.884 647.299 48.854a1 1 0 01-.3 1.953z"
            />
            <path
                fill="#3f3d56"
                d="M262.989 419.84a6.73 6.73 0 00-1.7-2.67 6.43 6.43 0 00-.92-.71c-2.61-1.74-6.51-2.13-8.99 0a5.81 5.81 0 00-.69.71q-1.11 1.365-2.28 2.67a88.226 88.226 0 01-3.96 4.24c-.39.38-.78.77-1.18 1.15-.23.23-.46.45-.69.67-.88.84-1.78 1.65-2.69 2.45-.48.43-.96.85-1.45 1.26-.73.61-1.46 1.22-2.2 1.81-.07.05-.14.1-.21.16-.02.01-.03.03-.05.04-.01 0-.02 0-.03.02a.179.179 0 00-.07.05c-.22.15-.37.25-.48.34.04-.02.08-.05.12-.07-.18.14-.37.28-.55.42a92.853 92.853 0 01-5.37 3.69 99.21 99.21 0 01-14.22 7.55c-.33.13-.67.27-1.01.4a85.97 85.97 0 01-40.85 6.02q-2.13-.165-4.26-.45c-1.64-.24-3.27-.53-4.89-.86a97.932 97.932 0 01-18.02-5.44 118.652 118.652 0 01-20.66-12.12c-1-.71-2.01-1.42-3.02-2.11 1.15-2.82 2.28-5.64 3.38-8.48.55-1.37 1.08-2.74 1.6-4.12 4.09-10.63 7.93-21.36 11.61-32.13q5.58-16.365 10.53-32.92.51-1.68.99-3.36 2.595-8.745 4.98-17.53c.15-.57.31-1.13.45-1.7q.69-2.52 1.35-5.04c1-3.79-1.26-8.32-5.24-9.23a7.634 7.634 0 00-9.22 5.24c-.43 1.62-.86 3.23-1.3 4.85q-3.165 11.745-6.66 23.41l-1.02 3.36q-7.71 25.41-16.93 50.31-1.11 3.015-2.25 6.01c-.37.98-.74 1.96-1.12 2.94-.73 1.93-1.48 3.86-2.23 5.79-.43 1.13-.87 2.26-1.31 3.38-.29.71-.57 1.42-.85 2.12a41.81 41.81 0 00-8.81-2.12l-.48-.06a27.397 27.397 0 00-7.01.06 23.914 23.914 0 00-17.24 10.66c-4.77 7.51-4.71 18.25 1.98 24.63 6.89 6.57 17.32 6.52 25.43 2.41a28.351 28.351 0 0010.52-9.86 50.57 50.57 0 002.74-4.65c.21.14.42.28.63.43.8.56 1.6 1.13 2.39 1.69a111.738 111.738 0 0014.51 8.91 108.359 108.359 0 0034.62 10.47c.27.03.53.07.8.1 1.33.17 2.67.3 4.01.41a103.782 103.782 0 0055.58-11.36q2.175-1.125 4.31-2.36 3.315-1.92 6.48-4.08c1.15-.78 2.27-1.57 3.38-2.4a101.042 101.042 0 0013.51-11.95q2.355-2.475 4.51-5.11a8.061 8.061 0 002.2-5.3 7.564 7.564 0 00-.5-2.64zm-165.59 23.82c.21-.15.42-.31.62-.47-.06.15-.35.32-.62.47zm3.21-3.23c-.23.26-.44.52-.67.78a23.366 23.366 0 01-2.25 2.2c-.11.1-.23.2-.35.29a.01.01 0 00-.01.01 3.804 3.804 0 00-.42.22q-.645.39-1.32.72a17.005 17.005 0 01-2.71.75 16.8 16.8 0 01-2.13.02h-.02a14.823 14.823 0 01-1.45-.4c-.24-.12-.47-.26-.7-.4-.09-.08-.17-.16-.22-.21a2.44 2.44 0 01-.27-.29.01.01 0 00-.01-.01c-.11-.2-.23-.4-.34-.6a.031.031 0 01-.01-.02c-.08-.25-.15-.51-.21-.77a12.51 12.51 0 01.01-1.37 13.467 13.467 0 01.54-1.88 11.068 11.068 0 01.69-1.26c.02-.04.12-.2.23-.38.01-.01.01-.01.01-.02.15-.17.3-.35.46-.51.27-.3.56-.56.85-.83a18.022 18.022 0 011.75-1.01 19.48 19.48 0 012.93-.79 24.99 24.99 0 014.41.04 30.301 30.301 0 014.1 1.01 36.945 36.945 0 01-2.77 4.54c-.04.06-.08.12-.12.17zm-11.12-3.29a2.18 2.18 0 01-.31.39 1.409 1.409 0 01.31-.39z"
            />
            <path
                fill="#3f3d56"
                d="M232.929 317.71l-.27 9.42q-.285 10.455-.59 20.92-.315 11.775-.66 23.54-.165 6.075-.34 12.15-.465 16.365-.92 32.72c-.03 1.13-.07 2.25-.1 3.38l-.45 16.23q-.255 8.805-.5 17.61-.18 6.6-.37 13.21l-2.7 95.79a7.648 7.648 0 01-7.5 7.5 7.561 7.561 0 01-7.5-7.5q.75-26.94 1.52-53.88.675-24.36 1.37-48.72l.45-16.06q.345-12.09.68-24.18c.03-1.13.07-2.25.1-3.38.02-.99.05-1.97.08-2.96l1.32-46.96q.27-9.24.52-18.49l.6-21.08c.09-3.09.17-6.17.26-9.26a7.648 7.648 0 017.5-7.5 7.561 7.561 0 017.5 7.5zM644.357 319.791a893.238 893.238 0 01-28.161 87.941c-3.007 7.947-6.083 15.877-9.372 23.712l.756-1.791a54.583 54.583 0 01-5.59 10.612q-.229.32-.466.636 1.166-1.49.443-.589c-.254.3-.505.602-.768.895a23.664 23.664 0 01-2.249 2.204q-.301.257-.612.504l.938-.73c-.109.258-.873.598-1.11.744a18.254 18.254 0 01-2.405 1.218l1.791-.756a19.086 19.086 0 01-4.23 1.16l1.993-.267a17.02 17.02 0 01-4.298.046l1.994.268a14.002 14.002 0 01-3.405-.917l1.791.756a12.012 12.012 0 01-1.678-.896c-.272-.177-1.106-.809-.015.024 1.133.866.145.075-.088-.155-.194-.192-.37-.4-.56-.595-.882-.905.997 1.556.397.498a18.182 18.182 0 01-.878-1.637l.756 1.792a11.925 11.925 0 01-.728-2.651l.268 1.993a13.651 13.651 0 01-.003-3.404l-.268 1.993a15.964 15.964 0 01.995-3.68l-.756 1.792a16.73 16.73 0 011.178-2.299 6.73 6.73 0 01.728-1.071c.05.016-1.268 1.513-.57.757.184-.198.355-.406.54-.602.296-.314.613-.6.925-.898 1.045-.994-1.461.966-.256.18a19.049 19.049 0 012.75-1.5l-1.792.756a20.311 20.311 0 014.995-1.34l-1.994.268a25.628 25.628 0 016.46.076l-1.993-.267a33.21 33.21 0 017.892 2.22l-1.792-.757c5.39 2.314 10.163 5.75 14.928 9.118a111.95 111.95 0 0014.506 8.907 108.388 108.388 0 0034.622 10.474 103.933 103.933 0 0092.586-36.752 8.078 8.078 0 002.197-5.304 7.632 7.632 0 00-2.197-5.303c-2.752-2.526-7.95-3.239-10.607 0a95.636 95.636 0 01-8.106 8.727q-2.018 1.914-4.143 3.71-1.213 1.026-2.46 2.011c-.394.31-1.62 1.138.263-.197-.432.306-.845.64-1.27.954a99.269 99.269 0 01-20.333 11.565l1.792-.756a96.836 96.836 0 01-24.172 6.623l1.994-.268a97.643 97.643 0 01-25.753-.038l1.993.268a99.8 99.8 0 01-24.857-6.77l1.792.755a116.025 116.025 0 01-21.736-12.59 86.877 86.877 0 00-11.113-6.995 42.824 42.824 0 00-14.438-4.388c-9.44-1.111-19.057 2.565-24.247 10.72-4.775 7.505-4.714 18.244 1.974 24.625 6.888 6.573 17.319 6.517 25.436 2.406 7.817-3.96 12.513-12.186 15.815-19.942 7.43-17.455 14.01-35.314 20.14-53.263q9.096-26.637 16.498-53.813.917-3.366 1.807-6.74c1.001-3.788-1.261-8.32-5.238-9.225a7.633 7.633 0 00-9.226 5.238z"
            />
            <path
                fill="#3f3d56"
                d="M719.19 317.71l-2.7 95.793-2.686 95.294-1.518 53.883a7.565 7.565 0 007.5 7.5 7.65 7.65 0 007.5-7.5l2.7-95.793 2.685-95.294 1.518-53.883a7.565 7.565 0 00-7.5-7.5 7.65 7.65 0 00-7.5 7.5z"
            />
            <path
                d="M459.591 535.935h2.33V429.893h54.328v-2.322H461.92v-44.745h41.956q-.923-1.173-1.899-2.317H461.92v-29.553a65.378 65.378 0 00-2.329-.943v30.496H413.94v-37.865c-.782.036-1.552.09-2.329.155v37.71h-36.42v-28.25a54.63 54.63 0 00-2.317 1.092v27.158h-30.615v2.317h30.615v44.745h-30.615v2.322h30.615v106.042h2.317V429.893a36.413 36.413 0 0136.42 36.42v69.622h2.33V429.893h45.651zm-84.4-108.365v-44.744h36.42v44.745zm38.748 0v-44.744h.914a44.741 44.741 0 0144.738 44.745z"
                opacity="0.2"
            />
            <path
                fill="#3f3d56"
                d="M445.369 504.14a63.059 63.059 0 01-20.05 33.7c-.74.64-1.48 1.26-2.25 1.87q-2.805.255-5.57.52c-1.53.14-3.04.29-4.54.43l-.27.03-.19-1.64-.76-6.64a37.623 37.623 0 01-3.3-32.44c2.64-7.12 7.42-13.41 12.12-19.65 6.49-8.62 12.8-17.14 13.03-27.65a60.544 60.544 0 017.9 13.33 16.432 16.432 0 00-5.12 3.77c-.41.45-.82 1.08-.54 1.62.24.46.84.57 1.36.63l3.76.39c1 .11 2 .21 3 .32a63.99 63.99 0 012.45 12.18 61.189 61.189 0 01-1.03 19.23z"
            />
            <path
                fill="#6c63ff"
                d="M478.569 477.93c-5.9 4.29-9.35 10.46-12.03 17.26a16.628 16.628 0 00-7.17 4.58c-.41.45-.82 1.08-.54 1.62.24.46.84.57 1.36.63l3.76.39c-2.68 8.04-5.14 16.36-9.88 23.15a36.99 36.99 0 01-12.03 10.91 38.492 38.492 0 01-4.02 1.99q-7.62.585-14.95 1.25-2.805.255-5.57.52c-1.53.14-3.04.29-4.54.43q-.015-.825 0-1.65a63.304 63.304 0 0115.25-39.86c.45-.52.91-1.03 1.38-1.54a61.792 61.792 0 0116.81-12.7 62.654 62.654 0 0132.17-6.98z"
            />
            <path
                fill="#6c63ff"
                d="M419.229 535.1l-1.15 3.4-.58 1.73c-1.53.14-3.04.29-4.54.43l-.27.03-4.96.51c-.43-.5-.86-1.01-1.28-1.53a62.03 62.03 0 018.07-87.11c-1.32 6.91.22 13.53 2.75 20.1-.27.11-.53.22-.78.34a16.432 16.432 0 00-5.12 3.77c-.41.45-.82 1.08-.54 1.62.24.46.84.57 1.36.63l3.76.39c1 .11 2 .21 3 .32l1.41.15c.07.15.13.29.2.44 2.85 6.18 5.92 12.39 7.65 18.83a43.666 43.666 0 011.02 4.91 37.604 37.604 0 01-10 31.04z"
            />
            <path
                fill="#3f3d56"
                d="M519.887 390.06c-8.609-16.792-21.946-30.92-37.632-41.303a114.237 114.237 0 00-52.563-18.38q-3.69-.335-7.399-.393c-2.921-.043-46.866 12.632-61.587 22.982a114.295 114.295 0 00-35.333 39.527 102.5 102.5 0 00-12.126 51.634 113.564 113.564 0 0014.703 51.476 110.475 110.475 0 0036.444 38.745c15.338 9.787 30.745 35.736 48.855 36.652 18.246.923 39.054-23.555 55.695-30.987a104.425 104.425 0 0041.725-34.005 110.25 110.25 0 0019.6-48.948c2.573-18.083 1.374-36.733-4.802-54.016a111.86 111.86 0 00-5.58-12.983c-1.78-3.506-6.996-4.796-10.261-2.691a7.68 7.68 0 00-2.691 10.261q1.568 3.088 2.915 6.278l-.756-1.792a101.15 101.15 0 016.877 25.539l-.268-1.994a109.229 109.229 0 01-.066 28.682l.267-1.994a109.734 109.734 0 01-7.554 27.675l.756-1.792a104.212 104.212 0 01-6.672 13.098q-1.923 3.186-4.08 6.222c-.632.888-1.283 1.761-1.94 2.631-.855 1.136 1.168-1.483.283-.37-.15.19-.3.38-.452.57q-.681.852-1.382 1.688a93.613 93.613 0 01-10.176 10.383q-1.366 1.193-2.778 2.331c-.469.379-.932.773-1.42 1.125.018-.013 1.579-1.2.655-.51-.29.216-.579.435-.87.651q-2.91 2.156-5.974 4.092a103.485 103.485 0 01-14.756 7.713l1.792-.756a109.215 109.215 0 01-27.597 7.552l1.994-.268a108.154 108.154 0 01-28.589.05l1.994.268a99.835 99.835 0 01-25.096-6.784l1.792.756a93.643 93.643 0 01-13.416-6.991q-3.174-2-6.184-4.248c-.286-.213-.57-.43-.855-.645-.915-.691.658.51.67.518a19.169 19.169 0 01-1.534-1.225q-1.454-1.184-2.862-2.422a101.99 101.99 0 01-10.493-10.71q-1.213-1.433-2.374-2.91c-.335-.426-.946-1.29.404.53-.177-.24-.362-.475-.541-.713q-.647-.858-1.276-1.728-2.203-3.048-4.188-6.246a109.29 109.29 0 01-7.805-15.108l.756 1.791a106.588 106.588 0 01-7.34-26.837l.267 1.994a97.866 97.866 0 01-.048-25.636l-.268 1.994a94.673 94.673 0 016.595-23.959l-.757 1.792a101.557 101.557 0 017.196-13.857q2.065-3.323 4.377-6.484.526-.719 1.063-1.428c.324-.428 1.215-1.494-.306.388.15-.184.293-.374.44-.56q1.269-1.608 2.6-3.165a107.402 107.402 0 0110.883-11.02q1.474-1.293 2.994-2.53.691-.562 1.391-1.113c.187-.147.376-.29.562-.438-1.998 1.59-.555.432-.102.092q3.134-2.348 6.436-4.46a103.644 103.644 0 0115.386-8.109l-1.791.756c7.76-3.258 42.14-10.949 48.394-10.11l-1.994-.267a106.225 106.225 0 0126.72 7.382l-1.792-.756a110.313 110.313 0 0112.6 6.33q3.044 1.783 5.968 3.762 1.383.936 2.738 1.915.677.489 1.346.989c.248.185.494.372.741.558 1.04.779-1.431-1.129-.342-.267a110.843 110.843 0 0110.368 9.253q2.401 2.445 4.637 5.045 1.147 1.335 2.246 2.708c.365.455 1.605 2.1.085.084.372.493.747.983 1.114 1.48a97.977 97.977 0 018.392 13.537c1.793 3.498 6.987 4.802 10.261 2.691a7.677 7.677 0 002.69-10.261z"
            />
            <path
                fill="#3f3d56"
                d="M432.497 512.456a3.78 3.78 0 01-2.74-6.552l.26-1.03-.103-.247c-3.48-8.297-25.685 14.834-26.645 22.632a30.029 30.029 0 00.527 10.328 120.392 120.392 0 01-10.952-50.003 116.202 116.202 0 01.72-12.963q.598-5.293 1.658-10.51a121.787 121.787 0 0124.151-51.617c6.874.383 12.898-.664 13.48-13.986.103-2.37 1.86-4.421 2.248-6.756a30.72 30.72 0 01-1.98.183l-.623.032-.077.004a3.745 3.745 0 01-3.076-6.101l.85-1.046c.43-.538.872-1.065 1.302-1.603a1.865 1.865 0 00.14-.161c.495-.613.99-1.216 1.485-1.829a10.83 10.83 0 00-3.55-3.432c-4.96-2.904-11.802-.893-15.384 3.593-3.593 4.486-4.271 10.78-3.023 16.385a43.398 43.398 0 006.003 13.383c-.27.344-.549.677-.818 1.022a122.574 122.574 0 00-12.793 20.268c1.016-7.939-11.412-36.608-16.218-42.68-5.773-7.295-17.611-4.112-18.628 5.135l-.03.268q1.072.604 2.097 1.283a5.127 5.127 0 01-2.067 9.33l-.104.016c-9.556 13.644 21.077 49.155 28.745 41.182a125.11 125.11 0 00-6.735 31.692 118.664 118.664 0 00.086 19.16l-.032-.226c-1.704-13.882-30.931-34.522-39.466-32.803-4.917.99-9.76.765-9.013 5.725l.036.237a34.442 34.442 0 013.862 1.861q1.07.605 2.096 1.283a5.127 5.127 0 01-2.067 9.33l-.104.016-.215.033c-4.35 14.966 27.907 39.12 47.517 31.434h.011a125.075 125.075 0 008.402 24.528h30.015c.107-.333.204-.678.301-1.011a34.102 34.102 0 01-8.305-.495c2.227-2.732 4.454-5.486 6.68-8.219a1.861 1.861 0 00.14-.161c1.13-1.399 2.27-2.787 3.4-4.185v-.002a49.952 49.952 0 00-1.463-12.725zm-34.37-67.613l.015-.022-.016.043zm-6.65 59.932l-.257-.58c.01-.42.01-.84 0-1.27 0-.119-.022-.237-.022-.355.097.742.183 1.484.29 2.227z"
            />
            <circle cx="95.249" cy="439" r="11" fill="#3f3d56" />
            <circle cx="227.249" cy="559" r="11" fill="#3f3d56" />
            <circle cx="728.249" cy="559" r="11" fill="#3f3d56" />
            <circle cx="755.249" cy="419" r="11" fill="#3f3d56" />
            <circle cx="723.249" cy="317" r="11" fill="#3f3d56" />
            <path
                fill="#3f3d56"
                d="M264.249 419a10.949 10.949 0 11-.21-2.16 10.992 10.992 0 01.21 2.16z"
            />
            <circle cx="484.249" cy="349" r="11" fill="#3f3d56" />
            <path
                fill="#3f3d56"
                d="M375.249 349a10.949 10.949 0 11-.21-2.16 10.992 10.992 0 01.21 2.16zM233.249 317a10.949 10.949 0 11-.21-2.16 10.992 10.992 0 01.21 2.16z"
            />
            <circle cx="599.249" cy="443" r="11" fill="#3f3d56" />
            <circle cx="426.249" cy="338" r="16" fill="#3f3d56" />
            <path
                fill="#cacaca"
                d="M858.94 570.84l-857.75.308a1.19 1.19 0 110-2.381l857.75-.308a1.19 1.19 0 010 2.382z"
            />
        </svg>
    );
};

export default Page404;
JavaScript
// App.jsx
import "./App.css";
import Page404 from './components/Page404'
const App = () =>{
    return (
        <div className="app">
            <Page404 />
        </div>
    );
}

export default App;

Page404 在浏览器中呈现的效果如下:

接下来,我将以不同的方式向大家阐述,如何给 SVG 添加样式。

首先,你可以通过直接将样式对象传递给 Page404 组件来动态更改 SVG 元素的样式:

JavaScript
const Page404 = () => {
  return (
    <svg
    style={{ width: "100vw", height: "100vh" }}
      width="860.131"
      height="571.148"
      viewBox="0 0 860.131 571.148"
    >
        <!-- 省略其他元素 -->
    </svg>
 )

 export default Page404;

这个时候,<svg> 元素的 width100vwheight100vh<svg> 元素的尺寸与浏览器视窗的尺寸相等。你将看到整个 SVG 铺满了全屏:

如果你像我一样,不太喜欢这种内联的方式来设置 SVG 元素的样式,那么可以通过 CSS 给 SVG 元素设置样式。与 HTML 元素一样,可以使用 CSS 的类或 ID 以及其他 CSS 选择器为 SVG 元素添加样式。例如,给 SVG 中的 “404”图形设置别的填充颜色:

JavaScript
// Page404/index.jsx
import "./page404.css";

const Page404 = () => {
  return (
    <svg
    style={{ width: "100vw", height: "100vh" }}
      width="860.131"
      height="571.148"
      viewBox="0 0 860.131 571.148"
    >
      <path
        fill="#f2f2f2"
        d="M435.735 160.527c-7.669-12.684-16.757-26.228-30.99-30.37-16.481-4.796-33.412 4.732-47.774 14.135a1392.157 1392.157 0 00-123.893 91.283l.043.493 92.451-6.376c22.265-1.535 45.296-3.283 64.972-13.816 7.467-3.996 14.745-9.335 23.206-9.707 10.511-.463 19.677 6.879 26.88 14.549 42.607 45.37 54.937 114.754 102.738 154.616a1516.995 1516.995 0 00-107.633-214.807z"
       />
      <path
        fill="#e4e4e4"
        d="M697.636 545.355c-4.711-5.95-6.637-7.343-11.284-13.347q-56.765-73.417-106.708-151.793-33.924-53.23-64.483-108.504-14.549-26.278-28.3-52.969-10.67-20.695-20.864-41.638a841.984 841.984 0 01-5.711-12.009c-4.428-9.442-8.774-18.93-13.44-28.244-5.317-10.616-11.789-21.745-21.552-28.877a29.405 29.405 0 00-15.319-5.895c-7.948-.513-15.282 2.769-22.176 6.353-50.438 26.301-97.659 59.276-140.37 96.798A730.778 730.778 0 00133.39 331.82c-1.009 1.44-3.393.064-2.375-1.384q6.01-8.498 12.257-16.813a734.817 734.817 0 01187.6-174.986q18.248-11.825 37.182-22.542c6.362-3.603 12.752-7.16 19.251-10.497 6.372-3.272 13.137-6.215 20.416-6.325 24.77-.385 37.595 27.667 46.405 46.542q4.153 8.911 8.406 17.767 16.075 33.62 33.388 66.628 10.684 20.379 21.837 40.52 34.707 62.717 73.778 122.896c34.506 53.143 68.737 100.089 108.046 149.785 1.082 1.375-.852 3.337-1.944 1.943zM244.982 191.378c-1.44-1.604-2.87-3.209-4.318-4.813-11.422-12.632-23.679-25.118-39.364-32.36a57.11 57.11 0 00-23.927-5.547c-8.562.028-16.932 2.274-24.843 5.418-3.74 1.494-7.399 3.19-11.001 4.996-4.116 2.072-8.16 4.281-12.183 6.51q-11.332 6.27-22.369 13.09-21.96 13.572-42.545 29.216-10.671 8.113-20.902 16.758-9.516 8.03-18.646 16.492c-1.302 1.201-3.245-.742-1.944-1.943a441.255 441.255 0 014.85-4.446q6.875-6.216 13.971-12.193 12.94-10.918 26.549-20.993 21.162-15.676 43.782-29.226 11.304-6.765 22.919-12.962a198.735 198.735 0 017.095-3.621 113.116 113.116 0 0116.868-6.867 60.006 60.006 0 0125.476-2.502 66.327 66.327 0 0123.505 8.131c15.401 8.608 27.346 21.92 38.97 34.91 1.174 1.32-.76 3.272-1.943 1.952z"
       />
      <path
        fill="#e4e4e4"
        d="M560.542 322.285l36.905-13.498 18.323-6.702c5.968-2.183 11.921-4.667 18.09-6.23a28.539 28.539 0 0116.374.208 37.738 37.738 0 0112.77 7.917 103.64 103.64 0 0110.475 11.186c3.99 4.795 7.92 9.64 11.868 14.467q24.442 29.891 48.563 60.042 24.121 30.15 47.92 60.556 23.857 30.48 47.386 61.216 2.882 3.765 5.76 7.534c1.059 1.388 3.449.02 2.374-1.388q-23.702-31.045-47.735-61.835-24.092-30.864-48.516-61.466-24.425-30.601-49.179-60.937-6.167-7.558-12.354-15.099c-3.48-4.24-6.92-8.527-10.737-12.474-7.005-7.245-15.757-13.648-26.234-13.822-6.16-.102-12.121 1.853-17.844 3.923-6.17 2.232-12.325 4.506-18.486 6.76l-37.163 13.592-9.29 3.398c-1.65.603-.937 3.262.73 2.652z"
       />
      <path
        fill="#f2f2f2"
        d="M196.443 170.1c-18.754-9.639-42.771-7.75-60.005 4.291a855.847 855.847 0 0197.37 22.726c-13.282-7.784-23.672-19.98-37.365-27.017zM136.253 174.358l-3.61 2.935a53.444 53.444 0 013.795-2.902c-.062-.01-.123-.022-.185-.033zM661.615 322.42c-3.633-4.422-7.56-9.052-12.994-10.849l-5.073.2a575.436 575.436 0 00153.267 175.221l-135.2-164.572zM346.15 285.94a37.481 37.481 0 0014.93 20.96c2.82 1.92 6.157 3.761 7.122 7.034a8.379 8.379 0 01-.873 6.15 24.884 24.884 0 01-3.862 5.041l-.136.512c-6.999-4.147-13.657-9.393-17.523-16.551s-4.405-16.539.342-23.146M579.15 488.94a37.481 37.481 0 0014.93 20.96c2.82 1.92 6.157 3.761 7.122 7.034a8.379 8.379 0 01-.873 6.15 24.884 24.884 0 01-3.862 5.041l-.136.512c-6.999-4.147-13.657-9.393-17.523-16.551s-4.405-16.539.342-23.146M114.15 474.94a37.481 37.481 0 0014.93 20.96c2.82 1.92 6.157 3.761 7.122 7.034a8.379 8.379 0 01-.873 6.15 24.884 24.884 0 01-3.862 5.041l-.136.512c-6.999-4.147-13.657-9.393-17.523-16.551s-4.405-16.539.342-23.146"
       />
      <circle cx="649.249" cy="51" r="51" fill="#6c63ff" />
      <path
        fill="#f0f0f0"
        d="M741.284 11.87c-24.717-3.34-52.935 10.02-59.341 34.124a21.597 21.597 0 00-41.094 2.109l2.83 2.026a372.275 372.275 0 00160.659-.726C787.145 31.334 766 15.21 741.284 11.87zM635.284 79.87c-24.717-3.34-52.935 10.02-59.341 34.124a21.597 21.597 0 00-41.094 2.109l2.83 2.026a372.275 372.275 0 00160.659-.726C681.145 99.334 660 83.21 635.284 79.87z"
       />
      <path
        fill="#ccc"
        d="M851.011 92.728a.982.982 0 01-.302-.047C586.303 9.063 353.265 19.998 204.33 43.895a1294.017 1294.017 0 00-60.403 11.161 1196.246 1196.246 0 00-15.597 3.378 1023.104 1023.104 0 00-18.532 4.306q-3.873.917-7.595 1.849a972.21 972.21 0 00-11.66 2.957 930.173 930.173 0 00-13.797 3.671.442.442 0 01-.051.015v.001a926.363 926.363 0 00-15.323 4.325c-2.698.78-5.304 1.548-7.8 2.307-.278.077-.525.151-.776.227l-.536.164c-.31.094-.617.187-.924.275l-.02.006h.001l-.811.253c-.968.293-1.912.579-2.841.864C23.119 87.22 9.626 92.604 9.493 92.656a1 1 0 11-.745-1.856c.135-.053 13.694-5.463 38.328-13.058.932-.286 1.879-.572 2.85-.866l.754-.235c.026-.01.051-.017.078-.025.305-.087.61-.18.92-.273l.536-.164c.268-.08.532-.16.802-.235a593.8 593.8 0 017.797-2.307 932.235 932.235 0 0115.334-4.328c.017-.006.033-.01.05-.014v-.001a941.379 941.379 0 0113.844-3.685 993.766 993.766 0 0111.68-2.962q3.738-.93 7.61-1.852a1026.011 1026.011 0 0118.563-4.313c5.299-1.183 10.555-2.322 15.622-3.383a1295.424 1295.424 0 0160.497-11.178c149.149-23.932 382.52-34.884 647.299 48.854a1 1 0 01-.3 1.953z"
       />
      <path
        className="number-404"
        fill="#3f3d56"
        d="M262.989 419.84a6.73 6.73 0 00-1.7-2.67 6.43 6.43 0 00-.92-.71c-2.61-1.74-6.51-2.13-8.99 0a5.81 5.81 0 00-.69.71q-1.11 1.365-2.28 2.67a88.226 88.226 0 01-3.96 4.24c-.39.38-.78.77-1.18 1.15-.23.23-.46.45-.69.67-.88.84-1.78 1.65-2.69 2.45-.48.43-.96.85-1.45 1.26-.73.61-1.46 1.22-2.2 1.81-.07.05-.14.1-.21.16-.02.01-.03.03-.05.04-.01 0-.02 0-.03.02a.179.179 0 00-.07.05c-.22.15-.37.25-.48.34.04-.02.08-.05.12-.07-.18.14-.37.28-.55.42a92.853 92.853 0 01-5.37 3.69 99.21 99.21 0 01-14.22 7.55c-.33.13-.67.27-1.01.4a85.97 85.97 0 01-40.85 6.02q-2.13-.165-4.26-.45c-1.64-.24-3.27-.53-4.89-.86a97.932 97.932 0 01-18.02-5.44 118.652 118.652 0 01-20.66-12.12c-1-.71-2.01-1.42-3.02-2.11 1.15-2.82 2.28-5.64 3.38-8.48.55-1.37 1.08-2.74 1.6-4.12 4.09-10.63 7.93-21.36 11.61-32.13q5.58-16.365 10.53-32.92.51-1.68.99-3.36 2.595-8.745 4.98-17.53c.15-.57.31-1.13.45-1.7q.69-2.52 1.35-5.04c1-3.79-1.26-8.32-5.24-9.23a7.634 7.634 0 00-9.22 5.24c-.43 1.62-.86 3.23-1.3 4.85q-3.165 11.745-6.66 23.41l-1.02 3.36q-7.71 25.41-16.93 50.31-1.11 3.015-2.25 6.01c-.37.98-.74 1.96-1.12 2.94-.73 1.93-1.48 3.86-2.23 5.79-.43 1.13-.87 2.26-1.31 3.38-.29.71-.57 1.42-.85 2.12a41.81 41.81 0 00-8.81-2.12l-.48-.06a27.397 27.397 0 00-7.01.06 23.914 23.914 0 00-17.24 10.66c-4.77 7.51-4.71 18.25 1.98 24.63 6.89 6.57 17.32 6.52 25.43 2.41a28.351 28.351 0 0010.52-9.86 50.57 50.57 0 002.74-4.65c.21.14.42.28.63.43.8.56 1.6 1.13 2.39 1.69a111.738 111.738 0 0014.51 8.91 108.359 108.359 0 0034.62 10.47c.27.03.53.07.8.1 1.33.17 2.67.3 4.01.41a103.782 103.782 0 0055.58-11.36q2.175-1.125 4.31-2.36 3.315-1.92 6.48-4.08c1.15-.78 2.27-1.57 3.38-2.4a101.042 101.042 0 0013.51-11.95q2.355-2.475 4.51-5.11a8.061 8.061 0 002.2-5.3 7.564 7.564 0 00-.5-2.64zm-165.59 23.82c.21-.15.42-.31.62-.47-.06.15-.35.32-.62.47zm3.21-3.23c-.23.26-.44.52-.67.78a23.366 23.366 0 01-2.25 2.2c-.11.1-.23.2-.35.29a.01.01 0 00-.01.01 3.804 3.804 0 00-.42.22q-.645.39-1.32.72a17.005 17.005 0 01-2.71.75 16.8 16.8 0 01-2.13.02h-.02a14.823 14.823 0 01-1.45-.4c-.24-.12-.47-.26-.7-.4-.09-.08-.17-.16-.22-.21a2.44 2.44 0 01-.27-.29.01.01 0 00-.01-.01c-.11-.2-.23-.4-.34-.6a.031.031 0 01-.01-.02c-.08-.25-.15-.51-.21-.77a12.51 12.51 0 01.01-1.37 13.467 13.467 0 01.54-1.88 11.068 11.068 0 01.69-1.26c.02-.04.12-.2.23-.38.01-.01.01-.01.01-.02.15-.17.3-.35.46-.51.27-.3.56-.56.85-.83a18.022 18.022 0 011.75-1.01 19.48 19.48 0 012.93-.79 24.99 24.99 0 014.41.04 30.301 30.301 0 014.1 1.01 36.945 36.945 0 01-2.77 4.54c-.04.06-.08.12-.12.17zm-11.12-3.29a2.18 2.18 0 01-.31.39 1.409 1.409 0 01.31-.39z"
       />
      <path
        className="number-404"
        fill="#3f3d56"
        d="M232.929 317.71l-.27 9.42q-.285 10.455-.59 20.92-.315 11.775-.66 23.54-.165 6.075-.34 12.15-.465 16.365-.92 32.72c-.03 1.13-.07 2.25-.1 3.38l-.45 16.23q-.255 8.805-.5 17.61-.18 6.6-.37 13.21l-2.7 95.79a7.648 7.648 0 01-7.5 7.5 7.561 7.561 0 01-7.5-7.5q.75-26.94 1.52-53.88.675-24.36 1.37-48.72l.45-16.06q.345-12.09.68-24.18c.03-1.13.07-2.25.1-3.38.02-.99.05-1.97.08-2.96l1.32-46.96q.27-9.24.52-18.49l.6-21.08c.09-3.09.17-6.17.26-9.26a7.648 7.648 0 017.5-7.5 7.561 7.561 0 017.5 7.5zM644.357 319.791a893.238 893.238 0 01-28.161 87.941c-3.007 7.947-6.083 15.877-9.372 23.712l.756-1.791a54.583 54.583 0 01-5.59 10.612q-.229.32-.466.636 1.166-1.49.443-.589c-.254.3-.505.602-.768.895a23.664 23.664 0 01-2.249 2.204q-.301.257-.612.504l.938-.73c-.109.258-.873.598-1.11.744a18.254 18.254 0 01-2.405 1.218l1.791-.756a19.086 19.086 0 01-4.23 1.16l1.993-.267a17.02 17.02 0 01-4.298.046l1.994.268a14.002 14.002 0 01-3.405-.917l1.791.756a12.012 12.012 0 01-1.678-.896c-.272-.177-1.106-.809-.015.024 1.133.866.145.075-.088-.155-.194-.192-.37-.4-.56-.595-.882-.905.997 1.556.397.498a18.182 18.182 0 01-.878-1.637l.756 1.792a11.925 11.925 0 01-.728-2.651l.268 1.993a13.651 13.651 0 01-.003-3.404l-.268 1.993a15.964 15.964 0 01.995-3.68l-.756 1.792a16.73 16.73 0 011.178-2.299 6.73 6.73 0 01.728-1.071c.05.016-1.268 1.513-.57.757.184-.198.355-.406.54-.602.296-.314.613-.6.925-.898 1.045-.994-1.461.966-.256.18a19.049 19.049 0 012.75-1.5l-1.792.756a20.311 20.311 0 014.995-1.34l-1.994.268a25.628 25.628 0 016.46.076l-1.993-.267a33.21 33.21 0 017.892 2.22l-1.792-.757c5.39 2.314 10.163 5.75 14.928 9.118a111.95 111.95 0 0014.506 8.907 108.388 108.388 0 0034.622 10.474 103.933 103.933 0 0092.586-36.752 8.078 8.078 0 002.197-5.304 7.632 7.632 0 00-2.197-5.303c-2.752-2.526-7.95-3.239-10.607 0a95.636 95.636 0 01-8.106 8.727q-2.018 1.914-4.143 3.71-1.213 1.026-2.46 2.011c-.394.31-1.62 1.138.263-.197-.432.306-.845.64-1.27.954a99.269 99.269 0 01-20.333 11.565l1.792-.756a96.836 96.836 0 01-24.172 6.623l1.994-.268a97.643 97.643 0 01-25.753-.038l1.993.268a99.8 99.8 0 01-24.857-6.77l1.792.755a116.025 116.025 0 01-21.736-12.59 86.877 86.877 0 00-11.113-6.995 42.824 42.824 0 00-14.438-4.388c-9.44-1.111-19.057 2.565-24.247 10.72-4.775 7.505-4.714 18.244 1.974 24.625 6.888 6.573 17.319 6.517 25.436 2.406 7.817-3.96 12.513-12.186 15.815-19.942 7.43-17.455 14.01-35.314 20.14-53.263q9.096-26.637 16.498-53.813.917-3.366 1.807-6.74c1.001-3.788-1.261-8.32-5.238-9.225a7.633 7.633 0 00-9.226 5.238z"
       />
      <path
        className="number-404"
        fill="#3f3d56"
        d="M719.19 317.71l-2.7 95.793-2.686 95.294-1.518 53.883a7.565 7.565 0 007.5 7.5 7.65 7.65 0 007.5-7.5l2.7-95.793 2.685-95.294 1.518-53.883a7.565 7.565 0 00-7.5-7.5 7.65 7.65 0 00-7.5 7.5z"
       />
      <path
        d="M459.591 535.935h2.33V429.893h54.328v-2.322H461.92v-44.745h41.956q-.923-1.173-1.899-2.317H461.92v-29.553a65.378 65.378 0 00-2.329-.943v30.496H413.94v-37.865c-.782.036-1.552.09-2.329.155v37.71h-36.42v-28.25a54.63 54.63 0 00-2.317 1.092v27.158h-30.615v2.317h30.615v44.745h-30.615v2.322h30.615v106.042h2.317V429.893a36.413 36.413 0 0136.42 36.42v69.622h2.33V429.893h45.651zm-84.4-108.365v-44.744h36.42v44.745zm38.748 0v-44.744h.914a44.741 44.741 0 0144.738 44.745z"
        opacity="0.2"
       />
      <path
        fill="#3f3d56"
        d="M445.369 504.14a63.059 63.059 0 01-20.05 33.7c-.74.64-1.48 1.26-2.25 1.87q-2.805.255-5.57.52c-1.53.14-3.04.29-4.54.43l-.27.03-.19-1.64-.76-6.64a37.623 37.623 0 01-3.3-32.44c2.64-7.12 7.42-13.41 12.12-19.65 6.49-8.62 12.8-17.14 13.03-27.65a60.544 60.544 0 017.9 13.33 16.432 16.432 0 00-5.12 3.77c-.41.45-.82 1.08-.54 1.62.24.46.84.57 1.36.63l3.76.39c1 .11 2 .21 3 .32a63.99 63.99 0 012.45 12.18 61.189 61.189 0 01-1.03 19.23z"
       />
      <path
        fill="#6c63ff"
        d="M478.569 477.93c-5.9 4.29-9.35 10.46-12.03 17.26a16.628 16.628 0 00-7.17 4.58c-.41.45-.82 1.08-.54 1.62.24.46.84.57 1.36.63l3.76.39c-2.68 8.04-5.14 16.36-9.88 23.15a36.99 36.99 0 01-12.03 10.91 38.492 38.492 0 01-4.02 1.99q-7.62.585-14.95 1.25-2.805.255-5.57.52c-1.53.14-3.04.29-4.54.43q-.015-.825 0-1.65a63.304 63.304 0 0115.25-39.86c.45-.52.91-1.03 1.38-1.54a61.792 61.792 0 0116.81-12.7 62.654 62.654 0 0132.17-6.98z"
       />
      <path
        fill="#6c63ff"
        d="M419.229 535.1l-1.15 3.4-.58 1.73c-1.53.14-3.04.29-4.54.43l-.27.03-4.96.51c-.43-.5-.86-1.01-1.28-1.53a62.03 62.03 0 018.07-87.11c-1.32 6.91.22 13.53 2.75 20.1-.27.11-.53.22-.78.34a16.432 16.432 0 00-5.12 3.77c-.41.45-.82 1.08-.54 1.62.24.46.84.57 1.36.63l3.76.39c1 .11 2 .21 3 .32l1.41.15c.07.15.13.29.2.44 2.85 6.18 5.92 12.39 7.65 18.83a43.666 43.666 0 011.02 4.91 37.604 37.604 0 01-10 31.04z"
       />
      <path
      className="number-404"
        fill="#3f3d56"
        d="M519.887 390.06c-8.609-16.792-21.946-30.92-37.632-41.303a114.237 114.237 0 00-52.563-18.38q-3.69-.335-7.399-.393c-2.921-.043-46.866 12.632-61.587 22.982a114.295 114.295 0 00-35.333 39.527 102.5 102.5 0 00-12.126 51.634 113.564 113.564 0 0014.703 51.476 110.475 110.475 0 0036.444 38.745c15.338 9.787 30.745 35.736 48.855 36.652 18.246.923 39.054-23.555 55.695-30.987a104.425 104.425 0 0041.725-34.005 110.25 110.25 0 0019.6-48.948c2.573-18.083 1.374-36.733-4.802-54.016a111.86 111.86 0 00-5.58-12.983c-1.78-3.506-6.996-4.796-10.261-2.691a7.68 7.68 0 00-2.691 10.261q1.568 3.088 2.915 6.278l-.756-1.792a101.15 101.15 0 016.877 25.539l-.268-1.994a109.229 109.229 0 01-.066 28.682l.267-1.994a109.734 109.734 0 01-7.554 27.675l.756-1.792a104.212 104.212 0 01-6.672 13.098q-1.923 3.186-4.08 6.222c-.632.888-1.283 1.761-1.94 2.631-.855 1.136 1.168-1.483.283-.37-.15.19-.3.38-.452.57q-.681.852-1.382 1.688a93.613 93.613 0 01-10.176 10.383q-1.366 1.193-2.778 2.331c-.469.379-.932.773-1.42 1.125.018-.013 1.579-1.2.655-.51-.29.216-.579.435-.87.651q-2.91 2.156-5.974 4.092a103.485 103.485 0 01-14.756 7.713l1.792-.756a109.215 109.215 0 01-27.597 7.552l1.994-.268a108.154 108.154 0 01-28.589.05l1.994.268a99.835 99.835 0 01-25.096-6.784l1.792.756a93.643 93.643 0 01-13.416-6.991q-3.174-2-6.184-4.248c-.286-.213-.57-.43-.855-.645-.915-.691.658.51.67.518a19.169 19.169 0 01-1.534-1.225q-1.454-1.184-2.862-2.422a101.99 101.99 0 01-10.493-10.71q-1.213-1.433-2.374-2.91c-.335-.426-.946-1.29.404.53-.177-.24-.362-.475-.541-.713q-.647-.858-1.276-1.728-2.203-3.048-4.188-6.246a109.29 109.29 0 01-7.805-15.108l.756 1.791a106.588 106.588 0 01-7.34-26.837l.267 1.994a97.866 97.866 0 01-.048-25.636l-.268 1.994a94.673 94.673 0 016.595-23.959l-.757 1.792a101.557 101.557 0 017.196-13.857q2.065-3.323 4.377-6.484.526-.719 1.063-1.428c.324-.428 1.215-1.494-.306.388.15-.184.293-.374.44-.56q1.269-1.608 2.6-3.165a107.402 107.402 0 0110.883-11.02q1.474-1.293 2.994-2.53.691-.562 1.391-1.113c.187-.147.376-.29.562-.438-1.998 1.59-.555.432-.102.092q3.134-2.348 6.436-4.46a103.644 103.644 0 0115.386-8.109l-1.791.756c7.76-3.258 42.14-10.949 48.394-10.11l-1.994-.267a106.225 106.225 0 0126.72 7.382l-1.792-.756a110.313 110.313 0 0112.6 6.33q3.044 1.783 5.968 3.762 1.383.936 2.738 1.915.677.489 1.346.989c.248.185.494.372.741.558 1.04.779-1.431-1.129-.342-.267a110.843 110.843 0 0110.368 9.253q2.401 2.445 4.637 5.045 1.147 1.335 2.246 2.708c.365.455 1.605 2.1.085.084.372.493.747.983 1.114 1.48a97.977 97.977 0 018.392 13.537c1.793 3.498 6.987 4.802 10.261 2.691a7.677 7.677 0 002.69-10.261z"
       />
      <path
        fill="#3f3d56"
        d="M432.497 512.456a3.78 3.78 0 01-2.74-6.552l.26-1.03-.103-.247c-3.48-8.297-25.685 14.834-26.645 22.632a30.029 30.029 0 00.527 10.328 120.392 120.392 0 01-10.952-50.003 116.202 116.202 0 01.72-12.963q.598-5.293 1.658-10.51a121.787 121.787 0 0124.151-51.617c6.874.383 12.898-.664 13.48-13.986.103-2.37 1.86-4.421 2.248-6.756a30.72 30.72 0 01-1.98.183l-.623.032-.077.004a3.745 3.745 0 01-3.076-6.101l.85-1.046c.43-.538.872-1.065 1.302-1.603a1.865 1.865 0 00.14-.161c.495-.613.99-1.216 1.485-1.829a10.83 10.83 0 00-3.55-3.432c-4.96-2.904-11.802-.893-15.384 3.593-3.593 4.486-4.271 10.78-3.023 16.385a43.398 43.398 0 006.003 13.383c-.27.344-.549.677-.818 1.022a122.574 122.574 0 00-12.793 20.268c1.016-7.939-11.412-36.608-16.218-42.68-5.773-7.295-17.611-4.112-18.628 5.135l-.03.268q1.072.604 2.097 1.283a5.127 5.127 0 01-2.067 9.33l-.104.016c-9.556 13.644 21.077 49.155 28.745 41.182a125.11 125.11 0 00-6.735 31.692 118.664 118.664 0 00.086 19.16l-.032-.226c-1.704-13.882-30.931-34.522-39.466-32.803-4.917.99-9.76.765-9.013 5.725l.036.237a34.442 34.442 0 013.862 1.861q1.07.605 2.096 1.283a5.127 5.127 0 01-2.067 9.33l-.104.016-.215.033c-4.35 14.966 27.907 39.12 47.517 31.434h.011a125.075 125.075 0 008.402 24.528h30.015c.107-.333.204-.678.301-1.011a34.102 34.102 0 01-8.305-.495c2.227-2.732 4.454-5.486 6.68-8.219a1.861 1.861 0 00.14-.161c1.13-1.399 2.27-2.787 3.4-4.185v-.002a49.952 49.952 0 00-1.463-12.725zm-34.37-67.613l.015-.022-.016.043zm-6.65 59.932l-.257-.58c.01-.42.01-.84 0-1.27 0-.119-.022-.237-.022-.355.097.742.183 1.484.29 2.227z"
       />
      <circle cx="95.249" cy="439" r="11" fill="#3f3d56" className="number-404"/>
      <circle cx="227.249" cy="559" r="11" fill="#3f3d56" className="number-404"/>
      <circle cx="728.249" cy="559" r="11" fill="#3f3d56" className="number-404"/>
      <circle cx="723.249" cy="317" r="11" fill="#3f3d56" className="number-404"/>
      <path
        className="number-404"
        fill="#3f3d56"
        d="M264.249 419a10.949 10.949 0 11-.21-2.16 10.992 10.992 0 01.21 2.16z"
       />
      <circle cx="484.249" cy="349" r="11" fill="#3f3d56" className="number-404"/>
      <path
        className="number-404"
        fill="#3f3d56"
        d="M375.249 349a10.949 10.949 0 11-.21-2.16 10.992 10.992 0 01.21 2.16zM233.249 317a10.949 10.949 0 11-.21-2.16 10.992 10.992 0 01.21 2.16z"
       />
      <circle cx="599.249" cy="443" r="11" fill="#3f3d56" className="number-404"/>
      <circle cx="426.249" cy="338" r="16" fill="#3f3d56" className="number-404"/>
      <path
        fill="#cacaca"
        d="M858.94 570.84l-857.75.308a1.19 1.19 0 110-2.381l857.75-.308a1.19 1.19 0 010 2.382z"
       />
    </svg>
  );
};

export default Page404;
CSS
/* page404.css */
.number-404 {
    fill: #f36;
}

上面这两种方式对于 Web 开发者来说,都非常熟悉,它与给 HTML 元素设置样式没有太多的差异。

在React开发环境中,除了之前提到的传统方法外,还有其他选择,可以根据团队的编码风格和偏好进行调整。例如,Module CSS、Styled Components、Emotion等。尽管它们的编码方式略有不同,但最终的目标是一致的。

此外,每种方法都有其用途。内联样式和操作SVG属性非常适合快速动态样式更改,而CSS样式表、CSS模块和CSS-in-JS库则提供了更多功能和灵活性,特别是当你具有复杂的样式或想要保持样式组织和模块化时。

要是你和我一样,比较喜欢通过原生 CSS 给 SVG 元素设置样式的话,那么下面这个小技巧值得你用于实际项目中。在实际生产过程中,可以考虑与 CSS 的自定义属性结合起来使用。比如 Page404 这个组件,不难发现,它具有多个不同的颜色,与此同时,有多个不同的元素应用相同的一个颜色。在这种情景之下,使用 CSS 自定义属性来替代元素的 fill 属性(同样也适用其他属性):

JavaScript
// Page404/index.jsx
import "./page404.css";

const Page404 = () => {
  return (
    <svg
      style={{ width: "100vw", height: "100vh" }}
      width="860.131"
      height="571.148"
      viewBox="0 0 860.131 571.148"
      className="page404"
    >
      <path
        fill="var(--fill-color-1)"
        d="M435.735 160.527c-7.669-12.684-16.757-26.228-30.99-30.37-16.481-4.796-33.412 4.732-47.774 14.135a1392.157 1392.157 0 00-123.893 91.283l.043.493 92.451-6.376c22.265-1.535 45.296-3.283 64.972-13.816 7.467-3.996 14.745-9.335 23.206-9.707 10.511-.463 19.677 6.879 26.88 14.549 42.607 45.37 54.937 114.754 102.738 154.616a1516.995 1516.995 0 00-107.633-214.807z"
       />
      <path
         fill="var(--fill-color-3)"
        d="M697.636 545.355c-4.711-5.95-6.637-7.343-11.284-13.347q-56.765-73.417-106.708-151.793-33.924-53.23-64.483-108.504-14.549-26.278-28.3-52.969-10.67-20.695-20.864-41.638a841.984 841.984 0 01-5.711-12.009c-4.428-9.442-8.774-18.93-13.44-28.244-5.317-10.616-11.789-21.745-21.552-28.877a29.405 29.405 0 00-15.319-5.895c-7.948-.513-15.282 2.769-22.176 6.353-50.438 26.301-97.659 59.276-140.37 96.798A730.778 730.778 0 00133.39 331.82c-1.009 1.44-3.393.064-2.375-1.384q6.01-8.498 12.257-16.813a734.817 734.817 0 01187.6-174.986q18.248-11.825 37.182-22.542c6.362-3.603 12.752-7.16 19.251-10.497 6.372-3.272 13.137-6.215 20.416-6.325 24.77-.385 37.595 27.667 46.405 46.542q4.153 8.911 8.406 17.767 16.075 33.62 33.388 66.628 10.684 20.379 21.837 40.52 34.707 62.717 73.778 122.896c34.506 53.143 68.737 100.089 108.046 149.785 1.082 1.375-.852 3.337-1.944 1.943zM244.982 191.378c-1.44-1.604-2.87-3.209-4.318-4.813-11.422-12.632-23.679-25.118-39.364-32.36a57.11 57.11 0 00-23.927-5.547c-8.562.028-16.932 2.274-24.843 5.418-3.74 1.494-7.399 3.19-11.001 4.996-4.116 2.072-8.16 4.281-12.183 6.51q-11.332 6.27-22.369 13.09-21.96 13.572-42.545 29.216-10.671 8.113-20.902 16.758-9.516 8.03-18.646 16.492c-1.302 1.201-3.245-.742-1.944-1.943a441.255 441.255 0 014.85-4.446q6.875-6.216 13.971-12.193 12.94-10.918 26.549-20.993 21.162-15.676 43.782-29.226 11.304-6.765 22.919-12.962a198.735 198.735 0 017.095-3.621 113.116 113.116 0 0116.868-6.867 60.006 60.006 0 0125.476-2.502 66.327 66.327 0 0123.505 8.131c15.401 8.608 27.346 21.92 38.97 34.91 1.174 1.32-.76 3.272-1.943 1.952z"
       />
      <path
         fill="var(--fill-color-3)"
        d="M560.542 322.285l36.905-13.498 18.323-6.702c5.968-2.183 11.921-4.667 18.09-6.23a28.539 28.539 0 0116.374.208 37.738 37.738 0 0112.77 7.917 103.64 103.64 0 0110.475 11.186c3.99 4.795 7.92 9.64 11.868 14.467q24.442 29.891 48.563 60.042 24.121 30.15 47.92 60.556 23.857 30.48 47.386 61.216 2.882 3.765 5.76 7.534c1.059 1.388 3.449.02 2.374-1.388q-23.702-31.045-47.735-61.835-24.092-30.864-48.516-61.466-24.425-30.601-49.179-60.937-6.167-7.558-12.354-15.099c-3.48-4.24-6.92-8.527-10.737-12.474-7.005-7.245-15.757-13.648-26.234-13.822-6.16-.102-12.121 1.853-17.844 3.923-6.17 2.232-12.325 4.506-18.486 6.76l-37.163 13.592-9.29 3.398c-1.65.603-.937 3.262.73 2.652z"
       />
      <path
         fill="var(--fill-color-1)"
        d="M196.443 170.1c-18.754-9.639-42.771-7.75-60.005 4.291a855.847 855.847 0 0197.37 22.726c-13.282-7.784-23.672-19.98-37.365-27.017zM136.253 174.358l-3.61 2.935a53.444 53.444 0 013.795-2.902c-.062-.01-.123-.022-.185-.033zM661.615 322.42c-3.633-4.422-7.56-9.052-12.994-10.849l-5.073.2a575.436 575.436 0 00153.267 175.221l-135.2-164.572zM346.15 285.94a37.481 37.481 0 0014.93 20.96c2.82 1.92 6.157 3.761 7.122 7.034a8.379 8.379 0 01-.873 6.15 24.884 24.884 0 01-3.862 5.041l-.136.512c-6.999-4.147-13.657-9.393-17.523-16.551s-4.405-16.539.342-23.146M579.15 488.94a37.481 37.481 0 0014.93 20.96c2.82 1.92 6.157 3.761 7.122 7.034a8.379 8.379 0 01-.873 6.15 24.884 24.884 0 01-3.862 5.041l-.136.512c-6.999-4.147-13.657-9.393-17.523-16.551s-4.405-16.539.342-23.146M114.15 474.94a37.481 37.481 0 0014.93 20.96c2.82 1.92 6.157 3.761 7.122 7.034a8.379 8.379 0 01-.873 6.15 24.884 24.884 0 01-3.862 5.041l-.136.512c-6.999-4.147-13.657-9.393-17.523-16.551s-4.405-16.539.342-23.146"
       />
      <circle cx="649.249" cy="51" r="51"  fill="var(--fill-color-6)" />
      <path
         fill="var(--fill-color-2)"
        d="M741.284 11.87c-24.717-3.34-52.935 10.02-59.341 34.124a21.597 21.597 0 00-41.094 2.109l2.83 2.026a372.275 372.275 0 00160.659-.726C787.145 31.334 766 15.21 741.284 11.87zM635.284 79.87c-24.717-3.34-52.935 10.02-59.341 34.124a21.597 21.597 0 00-41.094 2.109l2.83 2.026a372.275 372.275 0 00160.659-.726C681.145 99.334 660 83.21 635.284 79.87z"
       />
      <path
         fill="var(--fill-color-4)"
        d="M851.011 92.728a.982.982 0 01-.302-.047C586.303 9.063 353.265 19.998 204.33 43.895a1294.017 1294.017 0 00-60.403 11.161 1196.246 1196.246 0 00-15.597 3.378 1023.104 1023.104 0 00-18.532 4.306q-3.873.917-7.595 1.849a972.21 972.21 0 00-11.66 2.957 930.173 930.173 0 00-13.797 3.671.442.442 0 01-.051.015v.001a926.363 926.363 0 00-15.323 4.325c-2.698.78-5.304 1.548-7.8 2.307-.278.077-.525.151-.776.227l-.536.164c-.31.094-.617.187-.924.275l-.02.006h.001l-.811.253c-.968.293-1.912.579-2.841.864C23.119 87.22 9.626 92.604 9.493 92.656a1 1 0 11-.745-1.856c.135-.053 13.694-5.463 38.328-13.058.932-.286 1.879-.572 2.85-.866l.754-.235c.026-.01.051-.017.078-.025.305-.087.61-.18.92-.273l.536-.164c.268-.08.532-.16.802-.235a593.8 593.8 0 017.797-2.307 932.235 932.235 0 0115.334-4.328c.017-.006.033-.01.05-.014v-.001a941.379 941.379 0 0113.844-3.685 993.766 993.766 0 0111.68-2.962q3.738-.93 7.61-1.852a1026.011 1026.011 0 0118.563-4.313c5.299-1.183 10.555-2.322 15.622-3.383a1295.424 1295.424 0 0160.497-11.178c149.149-23.932 382.52-34.884 647.299 48.854a1 1 0 01-.3 1.953z"
       />
      <path

         fill="var(--fill-color-5)"
        d="M262.989 419.84a6.73 6.73 0 00-1.7-2.67 6.43 6.43 0 00-.92-.71c-2.61-1.74-6.51-2.13-8.99 0a5.81 5.81 0 00-.69.71q-1.11 1.365-2.28 2.67a88.226 88.226 0 01-3.96 4.24c-.39.38-.78.77-1.18 1.15-.23.23-.46.45-.69.67-.88.84-1.78 1.65-2.69 2.45-.48.43-.96.85-1.45 1.26-.73.61-1.46 1.22-2.2 1.81-.07.05-.14.1-.21.16-.02.01-.03.03-.05.04-.01 0-.02 0-.03.02a.179.179 0 00-.07.05c-.22.15-.37.25-.48.34.04-.02.08-.05.12-.07-.18.14-.37.28-.55.42a92.853 92.853 0 01-5.37 3.69 99.21 99.21 0 01-14.22 7.55c-.33.13-.67.27-1.01.4a85.97 85.97 0 01-40.85 6.02q-2.13-.165-4.26-.45c-1.64-.24-3.27-.53-4.89-.86a97.932 97.932 0 01-18.02-5.44 118.652 118.652 0 01-20.66-12.12c-1-.71-2.01-1.42-3.02-2.11 1.15-2.82 2.28-5.64 3.38-8.48.55-1.37 1.08-2.74 1.6-4.12 4.09-10.63 7.93-21.36 11.61-32.13q5.58-16.365 10.53-32.92.51-1.68.99-3.36 2.595-8.745 4.98-17.53c.15-.57.31-1.13.45-1.7q.69-2.52 1.35-5.04c1-3.79-1.26-8.32-5.24-9.23a7.634 7.634 0 00-9.22 5.24c-.43 1.62-.86 3.23-1.3 4.85q-3.165 11.745-6.66 23.41l-1.02 3.36q-7.71 25.41-16.93 50.31-1.11 3.015-2.25 6.01c-.37.98-.74 1.96-1.12 2.94-.73 1.93-1.48 3.86-2.23 5.79-.43 1.13-.87 2.26-1.31 3.38-.29.71-.57 1.42-.85 2.12a41.81 41.81 0 00-8.81-2.12l-.48-.06a27.397 27.397 0 00-7.01.06 23.914 23.914 0 00-17.24 10.66c-4.77 7.51-4.71 18.25 1.98 24.63 6.89 6.57 17.32 6.52 25.43 2.41a28.351 28.351 0 0010.52-9.86 50.57 50.57 0 002.74-4.65c.21.14.42.28.63.43.8.56 1.6 1.13 2.39 1.69a111.738 111.738 0 0014.51 8.91 108.359 108.359 0 0034.62 10.47c.27.03.53.07.8.1 1.33.17 2.67.3 4.01.41a103.782 103.782 0 0055.58-11.36q2.175-1.125 4.31-2.36 3.315-1.92 6.48-4.08c1.15-.78 2.27-1.57 3.38-2.4a101.042 101.042 0 0013.51-11.95q2.355-2.475 4.51-5.11a8.061 8.061 0 002.2-5.3 7.564 7.564 0 00-.5-2.64zm-165.59 23.82c.21-.15.42-.31.62-.47-.06.15-.35.32-.62.47zm3.21-3.23c-.23.26-.44.52-.67.78a23.366 23.366 0 01-2.25 2.2c-.11.1-.23.2-.35.29a.01.01 0 00-.01.01 3.804 3.804 0 00-.42.22q-.645.39-1.32.72a17.005 17.005 0 01-2.71.75 16.8 16.8 0 01-2.13.02h-.02a14.823 14.823 0 01-1.45-.4c-.24-.12-.47-.26-.7-.4-.09-.08-.17-.16-.22-.21a2.44 2.44 0 01-.27-.29.01.01 0 00-.01-.01c-.11-.2-.23-.4-.34-.6a.031.031 0 01-.01-.02c-.08-.25-.15-.51-.21-.77a12.51 12.51 0 01.01-1.37 13.467 13.467 0 01.54-1.88 11.068 11.068 0 01.69-1.26c.02-.04.12-.2.23-.38.01-.01.01-.01.01-.02.15-.17.3-.35.46-.51.27-.3.56-.56.85-.83a18.022 18.022 0 011.75-1.01 19.48 19.48 0 012.93-.79 24.99 24.99 0 014.41.04 30.301 30.301 0 014.1 1.01 36.945 36.945 0 01-2.77 4.54c-.04.06-.08.12-.12.17zm-11.12-3.29a2.18 2.18 0 01-.31.39 1.409 1.409 0 01.31-.39z"
       />
      <path

         fill="var(--fill-color-5)"
        d="M232.929 317.71l-.27 9.42q-.285 10.455-.59 20.92-.315 11.775-.66 23.54-.165 6.075-.34 12.15-.465 16.365-.92 32.72c-.03 1.13-.07 2.25-.1 3.38l-.45 16.23q-.255 8.805-.5 17.61-.18 6.6-.37 13.21l-2.7 95.79a7.648 7.648 0 01-7.5 7.5 7.561 7.561 0 01-7.5-7.5q.75-26.94 1.52-53.88.675-24.36 1.37-48.72l.45-16.06q.345-12.09.68-24.18c.03-1.13.07-2.25.1-3.38.02-.99.05-1.97.08-2.96l1.32-46.96q.27-9.24.52-18.49l.6-21.08c.09-3.09.17-6.17.26-9.26a7.648 7.648 0 017.5-7.5 7.561 7.561 0 017.5 7.5zM644.357 319.791a893.238 893.238 0 01-28.161 87.941c-3.007 7.947-6.083 15.877-9.372 23.712l.756-1.791a54.583 54.583 0 01-5.59 10.612q-.229.32-.466.636 1.166-1.49.443-.589c-.254.3-.505.602-.768.895a23.664 23.664 0 01-2.249 2.204q-.301.257-.612.504l.938-.73c-.109.258-.873.598-1.11.744a18.254 18.254 0 01-2.405 1.218l1.791-.756a19.086 19.086 0 01-4.23 1.16l1.993-.267a17.02 17.02 0 01-4.298.046l1.994.268a14.002 14.002 0 01-3.405-.917l1.791.756a12.012 12.012 0 01-1.678-.896c-.272-.177-1.106-.809-.015.024 1.133.866.145.075-.088-.155-.194-.192-.37-.4-.56-.595-.882-.905.997 1.556.397.498a18.182 18.182 0 01-.878-1.637l.756 1.792a11.925 11.925 0 01-.728-2.651l.268 1.993a13.651 13.651 0 01-.003-3.404l-.268 1.993a15.964 15.964 0 01.995-3.68l-.756 1.792a16.73 16.73 0 011.178-2.299 6.73 6.73 0 01.728-1.071c.05.016-1.268 1.513-.57.757.184-.198.355-.406.54-.602.296-.314.613-.6.925-.898 1.045-.994-1.461.966-.256.18a19.049 19.049 0 012.75-1.5l-1.792.756a20.311 20.311 0 014.995-1.34l-1.994.268a25.628 25.628 0 016.46.076l-1.993-.267a33.21 33.21 0 017.892 2.22l-1.792-.757c5.39 2.314 10.163 5.75 14.928 9.118a111.95 111.95 0 0014.506 8.907 108.388 108.388 0 0034.622 10.474 103.933 103.933 0 0092.586-36.752 8.078 8.078 0 002.197-5.304 7.632 7.632 0 00-2.197-5.303c-2.752-2.526-7.95-3.239-10.607 0a95.636 95.636 0 01-8.106 8.727q-2.018 1.914-4.143 3.71-1.213 1.026-2.46 2.011c-.394.31-1.62 1.138.263-.197-.432.306-.845.64-1.27.954a99.269 99.269 0 01-20.333 11.565l1.792-.756a96.836 96.836 0 01-24.172 6.623l1.994-.268a97.643 97.643 0 01-25.753-.038l1.993.268a99.8 99.8 0 01-24.857-6.77l1.792.755a116.025 116.025 0 01-21.736-12.59 86.877 86.877 0 00-11.113-6.995 42.824 42.824 0 00-14.438-4.388c-9.44-1.111-19.057 2.565-24.247 10.72-4.775 7.505-4.714 18.244 1.974 24.625 6.888 6.573 17.319 6.517 25.436 2.406 7.817-3.96 12.513-12.186 15.815-19.942 7.43-17.455 14.01-35.314 20.14-53.263q9.096-26.637 16.498-53.813.917-3.366 1.807-6.74c1.001-3.788-1.261-8.32-5.238-9.225a7.633 7.633 0 00-9.226 5.238z"
       />
      <path

         fill="var(--fill-color-5)"
        d="M719.19 317.71l-2.7 95.793-2.686 95.294-1.518 53.883a7.565 7.565 0 007.5 7.5 7.65 7.65 0 007.5-7.5l2.7-95.793 2.685-95.294 1.518-53.883a7.565 7.565 0 00-7.5-7.5 7.65 7.65 0 00-7.5 7.5z"
       />
      <path
        d="M459.591 535.935h2.33V429.893h54.328v-2.322H461.92v-44.745h41.956q-.923-1.173-1.899-2.317H461.92v-29.553a65.378 65.378 0 00-2.329-.943v30.496H413.94v-37.865c-.782.036-1.552.09-2.329.155v37.71h-36.42v-28.25a54.63 54.63 0 00-2.317 1.092v27.158h-30.615v2.317h30.615v44.745h-30.615v2.322h30.615v106.042h2.317V429.893a36.413 36.413 0 0136.42 36.42v69.622h2.33V429.893h45.651zm-84.4-108.365v-44.744h36.42v44.745zm38.748 0v-44.744h.914a44.741 44.741 0 0144.738 44.745z"
        opacity="0.2"
       />
      <path
         fill="var(--fill-color-5)"
        d="M445.369 504.14a63.059 63.059 0 01-20.05 33.7c-.74.64-1.48 1.26-2.25 1.87q-2.805.255-5.57.52c-1.53.14-3.04.29-4.54.43l-.27.03-.19-1.64-.76-6.64a37.623 37.623 0 01-3.3-32.44c2.64-7.12 7.42-13.41 12.12-19.65 6.49-8.62 12.8-17.14 13.03-27.65a60.544 60.544 0 017.9 13.33 16.432 16.432 0 00-5.12 3.77c-.41.45-.82 1.08-.54 1.62.24.46.84.57 1.36.63l3.76.39c1 .11 2 .21 3 .32a63.99 63.99 0 012.45 12.18 61.189 61.189 0 01-1.03 19.23z"
       />
      <path
         fill="var(--fill-color-6)"
        d="M478.569 477.93c-5.9 4.29-9.35 10.46-12.03 17.26a16.628 16.628 0 00-7.17 4.58c-.41.45-.82 1.08-.54 1.62.24.46.84.57 1.36.63l3.76.39c-2.68 8.04-5.14 16.36-9.88 23.15a36.99 36.99 0 01-12.03 10.91 38.492 38.492 0 01-4.02 1.99q-7.62.585-14.95 1.25-2.805.255-5.57.52c-1.53.14-3.04.29-4.54.43q-.015-.825 0-1.65a63.304 63.304 0 0115.25-39.86c.45-.52.91-1.03 1.38-1.54a61.792 61.792 0 0116.81-12.7 62.654 62.654 0 0132.17-6.98z"
       />
      <path
         fill="var(--fill-color-6)"
        d="M419.229 535.1l-1.15 3.4-.58 1.73c-1.53.14-3.04.29-4.54.43l-.27.03-4.96.51c-.43-.5-.86-1.01-1.28-1.53a62.03 62.03 0 018.07-87.11c-1.32 6.91.22 13.53 2.75 20.1-.27.11-.53.22-.78.34a16.432 16.432 0 00-5.12 3.77c-.41.45-.82 1.08-.54 1.62.24.46.84.57 1.36.63l3.76.39c1 .11 2 .21 3 .32l1.41.15c.07.15.13.29.2.44 2.85 6.18 5.92 12.39 7.65 18.83a43.666 43.666 0 011.02 4.91 37.604 37.604 0 01-10 31.04z"
       />
      <path

         fill="var(--fill-color-5)"
        d="M519.887 390.06c-8.609-16.792-21.946-30.92-37.632-41.303a114.237 114.237 0 00-52.563-18.38q-3.69-.335-7.399-.393c-2.921-.043-46.866 12.632-61.587 22.982a114.295 114.295 0 00-35.333 39.527 102.5 102.5 0 00-12.126 51.634 113.564 113.564 0 0014.703 51.476 110.475 110.475 0 0036.444 38.745c15.338 9.787 30.745 35.736 48.855 36.652 18.246.923 39.054-23.555 55.695-30.987a104.425 104.425 0 0041.725-34.005 110.25 110.25 0 0019.6-48.948c2.573-18.083 1.374-36.733-4.802-54.016a111.86 111.86 0 00-5.58-12.983c-1.78-3.506-6.996-4.796-10.261-2.691a7.68 7.68 0 00-2.691 10.261q1.568 3.088 2.915 6.278l-.756-1.792a101.15 101.15 0 016.877 25.539l-.268-1.994a109.229 109.229 0 01-.066 28.682l.267-1.994a109.734 109.734 0 01-7.554 27.675l.756-1.792a104.212 104.212 0 01-6.672 13.098q-1.923 3.186-4.08 6.222c-.632.888-1.283 1.761-1.94 2.631-.855 1.136 1.168-1.483.283-.37-.15.19-.3.38-.452.57q-.681.852-1.382 1.688a93.613 93.613 0 01-10.176 10.383q-1.366 1.193-2.778 2.331c-.469.379-.932.773-1.42 1.125.018-.013 1.579-1.2.655-.51-.29.216-.579.435-.87.651q-2.91 2.156-5.974 4.092a103.485 103.485 0 01-14.756 7.713l1.792-.756a109.215 109.215 0 01-27.597 7.552l1.994-.268a108.154 108.154 0 01-28.589.05l1.994.268a99.835 99.835 0 01-25.096-6.784l1.792.756a93.643 93.643 0 01-13.416-6.991q-3.174-2-6.184-4.248c-.286-.213-.57-.43-.855-.645-.915-.691.658.51.67.518a19.169 19.169 0 01-1.534-1.225q-1.454-1.184-2.862-2.422a101.99 101.99 0 01-10.493-10.71q-1.213-1.433-2.374-2.91c-.335-.426-.946-1.29.404.53-.177-.24-.362-.475-.541-.713q-.647-.858-1.276-1.728-2.203-3.048-4.188-6.246a109.29 109.29 0 01-7.805-15.108l.756 1.791a106.588 106.588 0 01-7.34-26.837l.267 1.994a97.866 97.866 0 01-.048-25.636l-.268 1.994a94.673 94.673 0 016.595-23.959l-.757 1.792a101.557 101.557 0 017.196-13.857q2.065-3.323 4.377-6.484.526-.719 1.063-1.428c.324-.428 1.215-1.494-.306.388.15-.184.293-.374.44-.56q1.269-1.608 2.6-3.165a107.402 107.402 0 0110.883-11.02q1.474-1.293 2.994-2.53.691-.562 1.391-1.113c.187-.147.376-.29.562-.438-1.998 1.59-.555.432-.102.092q3.134-2.348 6.436-4.46a103.644 103.644 0 0115.386-8.109l-1.791.756c7.76-3.258 42.14-10.949 48.394-10.11l-1.994-.267a106.225 106.225 0 0126.72 7.382l-1.792-.756a110.313 110.313 0 0112.6 6.33q3.044 1.783 5.968 3.762 1.383.936 2.738 1.915.677.489 1.346.989c.248.185.494.372.741.558 1.04.779-1.431-1.129-.342-.267a110.843 110.843 0 0110.368 9.253q2.401 2.445 4.637 5.045 1.147 1.335 2.246 2.708c.365.455 1.605 2.1.085.084.372.493.747.983 1.114 1.48a97.977 97.977 0 018.392 13.537c1.793 3.498 6.987 4.802 10.261 2.691a7.677 7.677 0 002.69-10.261z"
       />
      <path
         fill="var(--fill-color-5)"
        d="M432.497 512.456a3.78 3.78 0 01-2.74-6.552l.26-1.03-.103-.247c-3.48-8.297-25.685 14.834-26.645 22.632a30.029 30.029 0 00.527 10.328 120.392 120.392 0 01-10.952-50.003 116.202 116.202 0 01.72-12.963q.598-5.293 1.658-10.51a121.787 121.787 0 0124.151-51.617c6.874.383 12.898-.664 13.48-13.986.103-2.37 1.86-4.421 2.248-6.756a30.72 30.72 0 01-1.98.183l-.623.032-.077.004a3.745 3.745 0 01-3.076-6.101l.85-1.046c.43-.538.872-1.065 1.302-1.603a1.865 1.865 0 00.14-.161c.495-.613.99-1.216 1.485-1.829a10.83 10.83 0 00-3.55-3.432c-4.96-2.904-11.802-.893-15.384 3.593-3.593 4.486-4.271 10.78-3.023 16.385a43.398 43.398 0 006.003 13.383c-.27.344-.549.677-.818 1.022a122.574 122.574 0 00-12.793 20.268c1.016-7.939-11.412-36.608-16.218-42.68-5.773-7.295-17.611-4.112-18.628 5.135l-.03.268q1.072.604 2.097 1.283a5.127 5.127 0 01-2.067 9.33l-.104.016c-9.556 13.644 21.077 49.155 28.745 41.182a125.11 125.11 0 00-6.735 31.692 118.664 118.664 0 00.086 19.16l-.032-.226c-1.704-13.882-30.931-34.522-39.466-32.803-4.917.99-9.76.765-9.013 5.725l.036.237a34.442 34.442 0 013.862 1.861q1.07.605 2.096 1.283a5.127 5.127 0 01-2.067 9.33l-.104.016-.215.033c-4.35 14.966 27.907 39.12 47.517 31.434h.011a125.075 125.075 0 008.402 24.528h30.015c.107-.333.204-.678.301-1.011a34.102 34.102 0 01-8.305-.495c2.227-2.732 4.454-5.486 6.68-8.219a1.861 1.861 0 00.14-.161c1.13-1.399 2.27-2.787 3.4-4.185v-.002a49.952 49.952 0 00-1.463-12.725zm-34.37-67.613l.015-.022-.016.043zm-6.65 59.932l-.257-.58c.01-.42.01-.84 0-1.27 0-.119-.022-.237-.022-.355.097.742.183 1.484.29 2.227z"
       />
      <circle cx="95.249" cy="439" r="11"  fill="var(--fill-color-5)"  />
      <circle cx="227.249" cy="559" r="11"  fill="var(--fill-color-5)"  />
      <circle cx="728.249" cy="559" r="11"  fill="var(--fill-color-5)"  />
      <circle cx="723.249" cy="317" r="11"  fill="var(--fill-color-5)"  />
      <path

         fill="var(--fill-color-5)"
        d="M264.249 419a10.949 10.949 0 11-.21-2.16 10.992 10.992 0 01.21 2.16z"
       />
      <circle cx="484.249" cy="349" r="11"  fill="var(--fill-color-5)"  />
      <path

         fill="var(--fill-color-5)"
        d="M375.249 349a10.949 10.949 0 11-.21-2.16 10.992 10.992 0 01.21 2.16zM233.249 317a10.949 10.949 0 11-.21-2.16 10.992 10.992 0 01.21 2.16z"
       />
      <circle cx="599.249" cy="443" r="11"  fill="var(--fill-color-5)"  />
      <circle cx="426.249" cy="338" r="16"  fill="var(--fill-color-5)"  />
      <path
        fill="#cacaca"
        d="M858.94 570.84l-857.75.308a1.19 1.19 0 110-2.381l857.75-.308a1.19 1.19 0 010 2.382z"
       />
    </svg>
  );
};

export default Page404;
CSS
/* page404.css */
.page404 {
    --fill-color-1: #f2f2f2;
    --fill-color-2: #f0f0f0;
    --fill-color-3: #e4e4e4;
    --fill-color-4: #ccc;
    --fill-color-5: #3f3d56;
    --fill-color-6: #6c63ff;
    --fill-color-7: #cacaca;
}

这个时候,如果你想改变某个颜色,只需要调整 CSS 自定义属性的值即可:

React 中的动态 SVG

在 React 中,动态 SVG 提供了更加生动和互动的体验。SVG 是一种基于 XML 的图形格式,支持交互和动画。在React中,你可以创建响应用户输入、状态和属性变化的交互式 SVG 元素。比如,你可以动态地改变 SVG 元素的颜色、大小或位置。通过使用 useState 钩子来管理组件内的状态值,并通过 props 传递配置给组件,实现这种交互。例如,前面展示的 Heart 组件:

JavaScript
// Heart/index.jsx

import "./heart.css";
import { useState } from "react";

const Heart = () => {
    const [fillColor, setFillColor] = useState("gray");

    const handleColorChange = () => {
        setFillColor(fillColor === "gray" ? "#f36" : "gray");
    };
    return (
        <div className="heart" onClick={handleColorChange}>
            <svg viewBox="0 0 640 512" className="icon icon--heart">
                <path
                    fill={fillColor}
                    d="M47.6 300.4l180.7 168.7c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9l180.7-168.7c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141-45.6-7.6-92 7.3-124.6 39.9l-12 12-12-12c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"
                 />
            </svg>
        </div>
    );
};

export default Heart;

上面代码中,使用了 React 的 useState 钩子来声明一个名为 fillColor 的状态变量,并提供了一个为名 setFillColor 的函数来更新这个状态变量的值。初始值为 gray (表示未点赞)。这个状态变量用于存储 SVG 的 <path> 元素的填充颜色(fill)。

JavaScript
const [fillColor, setFillColor] = useState("gray");

随后可以通过事件(click)调用 setFillColor 函数来更新 fillColor 的值,从而改变 <path> 元素的 fill 属性值。

JavaScript
const handleColorChange = () => {
    setFillColor(fillColor === "gray" ? "#f36" : "gray");
};

这样就使 SVG 元素具备了交互功能,换句话说,“点赞”图标的填充颜色会随着用户的操作实时变化。

在 React 中,同样可以使用 CSS 动画或 Web 动画 API 来对 SVG 元素进行动画处理。例如,通过 CSS 的 @keyframes 和 animation 可以很容易的给 Heart 组件的“心形”图标添加一个脉搏动画效果:

CSS
/* heart.css */
.icon--heart {
    transform-origin: center;
    transform-box: fill-box;
    animation: pulse 2s ease-in-out infinite alternate;
}

@keyframes pulse {
    50% {
        scale: 1.5;
    }
}

你也可以通过 React 的 useEffect 钩子,使动画在某些状态或属性更改时才被触发。

JavaScript
import "./heart.css"; 
import { useState, useEffect } from "react"; 

const Heart = () => {
    // 使用useState钩子定义fillColor状态,初始值为"gray"
    const [fillColor, setFillColor] = useState("gray");

    // 使用useState钩子定义animate状态,初始值为false
    const [animate, setAnimate] = useState(false);

    // 定义处理颜色变化的函数
    const handleColorChange = () => {
        // 切换fillColor的值,在"gray"和"#f36"之间
        setFillColor(fillColor === "gray" ? "#f36" : "gray");
        // 将animate状态设为true,触发动画
        setAnimate(true);
    };

    // 使用useEffect钩子在animate状态变化时执行副作用
    useEffect(() => {
        if (animate) {
            // 设置一个定时器,在2秒后将animate状态重置为false
            const timer = setTimeout(() => setAnimate(false), 2000);
            // 返回一个清除定时器的函数,在组件卸载时执行
            return () => clearTimeout(timer);
        }
    }, [animate]); // 只有animate状态变化时才执行此副作用

  return (
    <div className="heart" onClick={handleColorChange}>
      <svg viewBox="0 0 640 512" className={`icon icon--heart ${animate ? 'animate' : ''}`}>
        <path
          fill={fillColor} 
          d="M47.6 300.4l180.7 168.7c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9l180.7-168.7c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141-45.6-7.6-92 7.3-124.6 39.9l-12 12-12-12c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"
        ></path>
      </svg>
    </div>
  );
};

export default Heart;
CSS
.icon--heart {
    display: block;
    width: 30vh;
    aspect-ratio: 1;
    transform-origin: center;
    transform-box: fill-box;  
}

.icon--heart.animate {
    animation: pulse 2s ease-in-out  alternate;
}

@keyframes pulse {
    50% {
        scale: 1.5;
    }
}

上面代码使用了 React 的 useState 钩子管理颜色和动画状态,并使用 useEffect 钩子在动画结束后重置动画状态。因此,你会看到,用户每次点击“心形”图标时,图标的填充颜色会发生变化,同时会触发一个持续 2s 的动画效果,使心形图标呈现出放大和缩小的脉动效果。

此外,在 React 中实现更复杂的交互式 SVG 动画时,可以使用像 react-spring 这样的库。这个库可以帮助你高效地构建具有交互性、数据驱动和动画效果的 SVG。

与 Vite Vue 应用一样,在 React 中也可以根据状态变化对 SVG 元素进行条件渲染。例如下面这个 Bars 组件,使用条件渲染 {!isActive && ...} 来控制中间 rect 元素的显示:

JavaScript
// Bars/index.jsx

import "./bars.css";

const Bars = ({ isActive }) => {
    return (
        <svg
            aria-hidden="true"
            viewBox="0 0 24 24"
            className={`bars ${isActive ? "active" : ""}`}
            fill="none"
        >
            <rect className="bars__line bars__line--up" y="6.25" />
            {!isActive && (
                <rect className="bars__line bars__line--middle" y="11.25" />
            )}
            <rect className="bars__line bars__line--down" y="16.25" />
        </svg>
    );
};

export default Bars;
CSS
.bars {
    width: 85%;
}

.bars rect {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    fill: hsl(324, 71%, 4%);
    transition: rotate 0.2s 0s, translate 0.2s 0.2s;
    width: 18px;
    height: 1.5px;
    ry: 0.75;
    x: 3;
}

.bars.active rect {
    transition: translate 0.2s, rotate 0.2s 0.3s;
}

.active .bars__line--up {
    translate: 0 333%;
    rotate: -45deg;
}

.active .bars__line--middle {
    rotate: 45deg;
}

.active .bars__line--down {
    translate: 0 -333%;
    rotate: 45deg;
}

.bars.active {
    rotate: 90deg;
    transition: rotate linear(0,
            0.2178 2.1%,
            1.1144 8.49%,
            1.2959 10.7%,
            1.3463 11.81%,
            1.3705 12.94%,
            1.3726,
            1.3643 14.48%,
            1.3151 16.2%,
            1.0317 21.81%,
            0.941 24.01%,
            0.8912 25.91%,
            0.8694 27.84%,
            0.8698 29.21%,
            0.8824 30.71%,
            1.0122 38.33%,
            1.0357,
            1.046 42.71%,
            1.0416 45.7%,
            0.9961 53.26%,
            0.9839 57.54%,
            0.9853 60.71%,
            1.0012 68.14%,
            1.0056 72.24%,
            0.9981 86.66%,
            1) 1s 0.4s;
}

在调用 Bars 组件时,使用 useState 钩子定义了 isActive 状态变量,并初始化为 false 。然后在 toggleActiveState 函数中使用 setIsActive 来更新 isActive 状态的值。最后,在按钮上绑定 toggleActiveState 函数,当按钮被点击时,isActive 状态会在 truefalse 之间切换。

与此同时,Bars 组件接收 isActive 作为 props 传递过去,并根据该 props 动态改变其外观或行为。

JavaScript
// App.jsx
import "./App.css";
import  { useState } from 'react';

import Bars from "./components/Bars";
const App = () =>{
    const [isActive, setIsActive] = useState(false);

    const toggleActiveState = () => {
        setIsActive(!isActive);
    };

    return (
        <div className="app">
            <button onClick={toggleActiveState} className="button trigger">
                <Bars isActive={isActive}/>
            </button>
        </div>
    );
}

export default App;
CSS
.trigger {
    width: 120px;
    aspect-ratio: 1;
    display: grid;
    place-items: center;
    padding: 0;
    scale: 1;
    border: 2px solid #000;
    border-radius: 10px;
    cursor: pointer;
    place-self: center;
}

.trigger:is(:focus-visible) {
    outline-color: hsl(320 80% 50% / 0.5);
    outline-offset: 1rem;
    outline-width: 4px;
}

最后,你将会看到一个像下面这样的效果:

下面这个示例也是根据条件来渲染 SVG 图形:

JavaScript
import { useRef, useState, useEffect } from "react"; 
import gsap from "gsap"; // 导入 GSAP 动画库
import Flip from "gsap/Flip"; // 导入 GSAP Flip 插件
import "./form.css"; 

gsap.registerPlugin(Flip); // 在 GSAP 中注册 Flip 插件

const STATES = {
    // 定义表单可能的状态
    CLEAN: "clean", // 初始状态
    OPEN: "open", // 打开状态
    SUBMITTED: "submitted", // 提交状态
};

const Form = () => {
    const formRef = useRef(null); // 创建表单的引用
    const inputRef = useRef(null); // 创建输入框的引用
    const buttonRef = useRef(null); // 创建按钮的引用
    const stateRef = useRef(null); // 创建状态的引用,用于保存 GSAP Flip 动画的状态
    const [state, setState] = useState(STATES.CLEAN); // 定义表单的状态,默认为 CLEAN

    // 处理表单状态的转换
    const transitionForm = (event) => {
        event.preventDefault(); // 阻止表单默认提交行为
        if (state === STATES.SUBMITTED) {
            setState(STATES.CLEAN); // 如果当前状态是 SUBMITTED,则将状态设置为 CLEAN
        } else if (state === STATES.CLEAN) {
            if (!document.startViewTransition) {
                // 检查是否存在 startViewTransition 方法
                stateRef.current = Flip.getState(
                    [formRef.current, inputRef.current, buttonRef.current],
                    {
                        scale: true, // 缩放
                        props: "opacity,padding", // 设置动画属性
                    }
                ); // 获取 Flip 动画的初始状态
                setState(STATES.OPEN); // 将状态设置为 OPEN
                Flip.from(stateRef.current, {
                    // 执行 Flip 动画
                    duration: 0.4, // 持续时间
                    nested: true, // 嵌套元素
                    ease: "bounce.out", // 缓动函数
                    absolute: true, // 绝对定位
                    onComplete: () => console.info("gsap: completed this"), // 完成动画时的回调函数
                });
            } else {
                document.startViewTransition(() => {
                    setState(STATES.OPEN); // 将状态设置为 OPEN
                });
            }
        } else if (state === STATES.OPEN) {
            if (!document.startViewTransition) {
                // 检查是否存在 startViewTransition 方法
                stateRef.current = Flip.getState(
                    [formRef.current, inputRef.current, buttonRef.current],
                    {
                        scale: true, // 缩放
                        props: "opacity,padding", // 设置动画属性
                    }
                ); // 获取 Flip 动画的初始状态
                setState(STATES.SUBMITTED); // 将状态设置为 SUBMITTED
                Flip.from(stateRef.current, {
                    // 执行 Flip 动画
                    duration: 0.4, // 持续时间
                    nested: true, // 嵌套元素
                    ease: "bounce.out", // 缓动函数
                    absolute: true, // 绝对定位
                    onComplete: () => console.info("gsap: completed this"), // 完成动画时的回调函数
                });
            } else {
                document.startViewTransition(() => {
                    setState(STATES.SUBMITTED); // 将状态设置为 SUBMITTED
                });
            }
        }
    };

    // useEffect 钩子用于在状态变化时自动将焦点设置到输入框
    useEffect(() => {
        if (state === STATES.OPEN) inputRef.current.focus(); // 如果状态为 OPEN,则将焦点设置到输入框
    }, [state]);

    return (
        <form ref={formRef} data-state={state} onSubmit={transitionForm}>
            <input
                ref={inputRef}
                type="email"
                required
                id="email"
                autoComplete="off"
                placeholder="Email address"
                onFocus={state !== STATES.OPEN ? transitionForm : null} // 在状态不为 OPEN 时触发转换函数
            />
            <button
                ref={buttonRef}
                type="button"
                onClick={transitionForm}
                className="button--singup"
            >
                <svg
                    fill="none"
                    shapeRendering="geometricPrecision"
                    stroke="currentColor"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    strokeWidth="1.5"
                    viewBox="0 0 24 24"
                >
                    {/* 渲染铃铛图形 */}
                    {state === STATES.CLEAN && (
                        <g>
                            <path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9" />
                            <path d="M13.73 21a2 2 0 01-3.46 0" />
                        </g>
                    )}
                    {/* 渲染纸飞机图形 */}
                    {state === STATES.OPEN && (
                        <g>
                            <path d="M22 2l-7 20-4-9-9-4 20-7z" />
                            <path d="M22 2L11 13" />
                        </g>
                    )}
                    {/* 渲染打勾图形 */}
                    {state === STATES.SUBMITTED && (
                        <g>
                            <path d="M8 11.857l2.5 2.5L15.857 9M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10z" />
                        </g>
                    )}
                </svg>
            </button>
        </form>
    );
};

export default Form;
CSS
/* form.css */
:root {
    --ease: linear(
        0, 0.0039, 0.0157, 0.0352, 0.0625 9.09%,
        0.1407, 0.25, 0.3908, 0.5625, 0.7654, 1,
        0.8907, 0.8125 45.45%, 0.7852, 0.7657,
        0.7539, 0.75, 0.7539, 0.7657, 0.7852,
        0.8125 63.64%, 0.8905, 1 72.73%, 0.9727,
        0.9532, 0.9414, 0.9375, 0.9414, 0.9531,
        0.9726, 1, 0.9883, 0.9844, 0.9883, 1
    );
    --ease: ease;
    --speed: 0.4s;
    view-transition-name: none;
}

form {
    background: black;
    background-repeat: no-repeat;
    view-transition-name: form;
    padding: 0.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 1000px;
    margin: 0;
    gap: 0.5rem;
    box-shadow: 0 1px hsl(0 0% 100% / 0.35) inset,  0 -1px hsl(0 0% 0% / 1) inset,  0 10px 20px -5px hsl(0 0% 0% / 1);
}

input {
    view-transition-name: form-input;
    border: 0;
    padding: 1rem 2rem;
    font-weight: 40;
    background: transparent;
    outline: none;
    color: hsl(0 0% 80%);
    font-size: 2rem;
    font-family: "Geist Sans", sans-serif;
}

.button--singup:is(:focus-visible, :hover) {
    color: hsl(0 0% 90%);
}

.button--singup {
    view-transition-name: form-action;
    height: 68px;
    width: 68px;
    border-radius: 1000px;
    display: grid;
    place-items: center;
    padding: 0;
    background: transparent;
    border: 0;
    color: hsl(0 0% 50%);
    cursor: pointer;
    outline: none;
}

.button--singup svg {
    width: 50%;
}

.button--singup svg path {
    stroke: currentColor;
    transition: all 0.25s;
}

[data-state=clean] .button--singup:is(:hover, :focus-visible) svg g {
    --deg: 5;
    transform-box: fill-box;
    transform-origin: 50% 20%;
    animation: ring 0.2s 2;
}

[data-state=clean] .button--singup:is(:hover, :focus-visible) svg g path:nth-of-type(2) {
    --deg: 10;
    transform-box: fill-box;
    transform-origin: 50% -1000%;
    animation: ring 0.2s 3 reverse;
}

@keyframes ring {
    0%, 100% { rotate: 0; }
    25% { rotate: calc(var(--deg) * 1deg); }
    75% { rotate: calc(var(--deg) * -1deg); }
}

[data-state=open] .button--singup:is(:hover, :focus-visible) svg g {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    animation: float 1s infinite;
}

@keyframes float {
    50% {
        translate: 5% -5%;
        scale: 0.85;
        rotate: 5deg;
    }
}

[data-state=submitted] .button--singup:is(:hover, :focus-visible) {
    color: hsl(140 80% 60%);
}

[data-state=clean] input,
[data-state=submitted] input {
    width: 0;
    padding: 0;
    opacity: 0;
}
[data-state=clean],
[data-state=submitted] {
    gap: 0;
}

::view-transition-group(form),
::view-transition-group(form-input),
::view-transition-group(form-action) {
    mix-blend-mode: normal;
    animation-timing-function: var(--ease);
    animation-duration: var(--speed);
}

::view-transition-old(form),
::view-transition-new(form) {
    border-radius: 1000px;
    background-color: black;
    height: 100%;
}

::view-transition-old(form-input),
::view-transition-new(form-input),
::view-transition-old(form-action),
::view-transition-new(form-action) {
    height: 100%;
    width: 100%;
    object-fit: none;
}
::view-transition-old(form-input),
::view-transition-new(form-input) {
    object-position: left center;
    overflow: hidden;
    opacity: 1;
}
::view-transition-old(form-action),
::view-transition-new(form-action) {
    object-position: right center;
}

特别声明,这两个案例在介绍 SVG 在 Vue 中的应用时也有所涉及。从这一点来看,SVG 的使用通常不受 Web 框架限制,只是在不同的 Web 框架中,使用的语法规则略有不同。但无论在哪种框架下,最终的效果都是一样的!

SVG 与 React 实践

接下来,我将与你分享一些利用这种强大的图形媒体的技巧和窍门。

案例一:Loading 动效

当下,应用于 Web 应用的 Loading 动效千奇百怪,纯 CSS 就能实现各式各样的,有创意的 Loading 动效:

URL:https://css-loaders.com/blob/

在这里,我想从头到尾向大家展示一个 SVG Loading 动效又是如何应用于 Web 上。例如下面这个 Loading 效果:

Demo 地址:https://codepen.io/HarleySG/full/wzRyKR (来源于 @Harley Santos Garzón

上面的 Loading 动效是由两个完全相同的 <path> 元素绘制的“无穷”符号组成的。其中一个 <path> 作为动画的轨道(图中颜色较暗的无穷符号),另一个 <path> 作为沿轨道运动的图形(图中白色的符号)。整个效果就像是一个图形沿着无穷符号的路径运动。

HTML
<div class="loading--container">
    <svg class="loading" viewBox="0 0 187.3 93.7" preserveAspectRatio="xMidYMid meet">
        <path class="track" stroke="black" stroke-opacity=".25" fill="none" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M93.9,46.4c9.3,9.5,13.8,17.9,23.5,17.9s17.5-7.8,17.5-17.5s-7.8-17.6-17.5-17.5c-9.7,0.1-13.3,7.2-22.1,17.1 c-8.9,8.8-15.7,17.9-25.4,17.9s-17.5-7.8-17.5-17.5s7.8-17.5,17.5-17.5S86.2,38.6,93.9,46.4z" />
        <path class="animated" stroke="red" fill="none" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M93.9,46.4c9.3,9.5,13.8,17.9,23.5,17.9s17.5-7.8,17.5-17.5s-7.8-17.6-17.5-17.5c-9.7,0.1-13.3,7.2-22.1,17.1 c-8.9,8.8-15.7,17.9-25.4,17.9s-17.5-7.8-17.5-17.5s7.8-17.5,17.5-17.5S86.2,38.6,93.9,46.4z" />
    </svg>
</div>

请注意,到目前为止,CSS 的 z-index 无法控制 SVG 元素在 z 轴的层级,依旧根据元素在 DOM 中出现的先后顺序来决定它在 z 轴的层级。所以,定义轨道的 <path> 要比动画元素先出现。

在将上面的 SVG 代码内联到 React 组件之前,我们先将 SVG 代码做一些清理工作,使其看起来更干净一些。把定义 <path> 样式属性提取到 CSS 中:

CSS
.loading {
    display: block;
    width: 40vw;
    height: auto;

    path {
        fill: none;
        stroke-width:4;
        stroke-linecap: round;
        stroke-linejoin: round;
        stroke-miterlimit: 10;
    }

    .track {
        stroke: black;
        stroke-opacity: .25;
    }

    .animated {
        stroke: red;
    }
}

这样一来,SVG 代码就像下面这样,要比原来干净不少:

HTML
<div class="loading--container">
    <svg class="loading" viewBox="0 0 187.3 93.7">
        <path class="track" d="M93.9,46.4c9.3,9.5,13.8,17.9,23.5,17.9s17.5-7.8,17.5-17.5s-7.8-17.6-17.5-17.5c-9.7,0.1-13.3,7.2-22.1,17.1 c-8.9,8.8-15.7,17.9-25.4,17.9s-17.5-7.8-17.5-17.5s7.8-17.5,17.5-17.5S86.2,38.6,93.9,46.4z" />
        <path class="animated" d="M93.9,46.4c9.3,9.5,13.8,17.9,23.5,17.9s17.5-7.8,17.5-17.5s-7.8-17.6-17.5-17.5c-9.7,0.1-13.3,7.2-22.1,17.1 c-8.9,8.8-15.7,17.9-25.4,17.9s-17.5-7.8-17.5-17.5s7.8-17.5,17.5-17.5S86.2,38.6,93.9,46.4z" />
    </svg>
</div>

示例中的动画效果是 SVG 的线条动画,算是 SVG 中的经典技巧吧!主要奥秘就是调整 .animatedstroke-dasharray 属性值。其实,该属性将 SVG 的描边变成虚线效果,而且它的值将直接影响虚线的展示效果。例如:

这意味着,我们在 CSS 的 @keyframes 中调整 stroke-dasharraystroke-dashoffset 就能实现类似上图所展的动画效果:

CSS
.animated {
    stroke-dasharray: 10 232;
    stroke-dashoffset: 0;
    animation: load 5s linear infinite;
}

@keyframes load {
    10% {
        stroke-dasharray: 30 232;
        stroke-dashoffset: -30;
    }
    50% {
        stroke-dasharray: 80 232;
        stroke-dashoffset: -80;
    }
    100% {
        stroke-dasharray: 10, 232;
        stroke-dashoffset: -230;
   }
}

stroke-dashoffset 设置为负值会将路径推得更远,从而创建循环效果。有关于这方面更详细的介绍,请移步阅读本文系列的《SVG 的描边与填充》。

Demo 地址:https://codepen.io/airen/full/zYQNrXN

注意,我对原 Demo 做了一些优化。感兴趣的同学请对比两个 Demo 的源码,这里就不展开阐述了。

现在,我们从 SVG 和 CSS 的世界中回到 React 的环境中。

假设你想在 Vite React 应用中需要制作一个与其相似的 Loading 动效。首先,可以使用之前介绍的方式,创建一个 React 组件,比如 LoadingAnimation ,然后将上面示例中的 SVG 代码直接内联到 React 中。

JavaScript
// LoadingAnimation/index.jsx
import "./loadingAnimation.css";

const LoadingAnimation = () => {
    return (
        <div className="loading--container">
            <svg className="loading" viewBox="0 0 187.3 93.7">
                <path
                    d="M93.9 46.4c9.3 9.5 13.8 17.9 23.5 17.9s17.5-7.8 17.5-17.5-7.8-17.6-17.5-17.5c-9.7.1-13.3 7.2-22.1 17.1-8.9 8.8-15.7 17.9-25.4 17.9s-17.5-7.8-17.5-17.5 7.8-17.5 17.5-17.5 16.3 9.3 24 17.1z"
                    className="track"
                />
                <path
                    d="M93.9 46.4c9.3 9.5 13.8 17.9 23.5 17.9s17.5-7.8 17.5-17.5-7.8-17.6-17.5-17.5c-9.7.1-13.3 7.2-22.1 17.1-8.9 8.8-15.7 17.9-25.4 17.9s-17.5-7.8-17.5-17.5 7.8-17.5 17.5-17.5 16.3 9.3 24 17.1z"
                    className="animated"
                />
            </svg>
        </div>
   );
};

export default LoadingAnimation;

尝试从头开始编写这个SVG。我们只需要一个圆,所以这就是整个SVG:

JavaScript
const LoadingIcon = () => {
        return (
        <svg
                viewBox="0 0 24 24"
                xmlns="http://www.w3.org/2000/svg"
        >
                <circle
                        cx="12" cy="12" r="8"
                        strokeWidth="4" stroke="tomato"
                        fill="none"
                />

        </svg>
        )
}

将相应的 CSS 代码复制到 loadingAnimation.css 中:

CSS
.loading {
    display: block;
    width: 40vw;
    height: auto;

    path {
        fill: none;
        stroke-width: 4;
        stroke-linecap: round;
        stroke-linejoin: round;
        stroke-miterlimit: 10;
    }

    .track {
        stroke: #fff;
        stroke-opacity: 0.125;
    }

    .animated {
        stroke: lime;
    }
}

.animated {
    stroke-dasharray: 10 232;
    stroke-dashoffset: 0;
    animation: load 5s linear infinite;
}

@keyframes load {
    10% {
        stroke-dasharray: 30 232;
        stroke-dashoffset: -30;
    }

    50% {
        stroke-dasharray: 80 232;
        stroke-dashoffset: -80;
    }

    100% {
        stroke-dasharray: 10, 232;
        stroke-dashoffset: -230;
    }
}

现在,你可以在需要使用加载动画的地方调用 LoadingAnimation 组件:

JavaScript
import "./App.css";
import LoadingAnimation from "./components/LoadingAnimation";
const App = () =>{
    return (
        <div className="app">
            <LoadingAnimation />
        </div>
    );
}

export default App;

这样,你就在浏览器能看到这个加载动画的效果:

正如你所看到的,上面的 LoadingAnimation 组件并没有利用 React 的其他特性,是一个具有最基础能力的 React 组件。事实上,可以利用 React 的一些特性,例如 props 特性,将 LoadingAnimation 组件的几个关键样式通过 props 透传进来。例如:

JavaScript
import "./loadingAnimation.css";

const LoadingAnimation = ({
    trackColor = "black",
    trackOpacity = .125,
    animationColor = "currentColor",
    thickness = 4
}) => {
    return (
        <div className="loading--container">
            <svg className="loading" viewBox="0 0 187.3 93.7">
                <path
                    stroke= {trackColor}
                    strokeOpacity = {trackOpacity}
                    strokeWidth={thickness}
                    d="M93.9 46.4c9.3 9.5 13.8 17.9 23.5 17.9s17.5-7.8 17.5-17.5-7.8-17.6-17.5-17.5c-9.7.1-13.3 7.2-22.1 17.1-8.9 8.8-15.7 17.9-25.4 17.9s-17.5-7.8-17.5-17.5 7.8-17.5 17.5-17.5 16.3 9.3 24 17.1z"
                    className="track"
                />
                <path
                    stroke={animationColor}
                    strokeWidth={thickness}
                    d="M93.9 46.4c9.3 9.5 13.8 17.9 23.5 17.9s17.5-7.8 17.5-17.5-7.8-17.6-17.5-17.5c-9.7.1-13.3 7.2-22.1 17.1-8.9 8.8-15.7 17.9-25.4 17.9s-17.5-7.8-17.5-17.5 7.8-17.5 17.5-17.5 16.3 9.3 24 17.1z"
                    className="animated"
                />
            </svg>
        </div>
    );
};

export default LoadingAnimation;

与此同时,我们需要将 CSS 中设置 <path> 元素的 strokestroke-widthstroke-opacity 相关样式删除,否则它将覆盖用户透传进行的样式:

CSS
.loading {
    display: block;
    width: 40vw;
    height: auto;

    path {
        fill: none;
        stroke-linecap: round;
        stroke-linejoin: round;
        stroke-miterlimit: 10;
    }
}

.animated {
    stroke-dasharray: 10 232;
    stroke-dashoffset: 0;
    animation: load 5s linear infinite;
}

@keyframes load {
    10% {
        stroke-dasharray: 30 232;
        stroke-dashoffset: -30;
    }

    50% {
        stroke-dasharray: 80 232;
        stroke-dashoffset: -80;
    }

    100% {
        stroke-dasharray: 10, 232;
        stroke-dashoffset: -230;
    }
}

这个时候,你可以根据需要,在使用 LoadingAnimation 组件时,调整 trackColoranimationColortrackOpacitythickness 属性的值:

JavaScript
import "./App.css";
import LoadingAnimation from "./components/LoadingAnimation";
const App = () =>{
    return (
        <div className="app">
            <LoadingAnimation />
            <LoadingAnimation animationColor="lime" />
            <LoadingAnimation trackColor="pink" thickness={10} animationColor="#f36"/>
        </div>
    );
}

export default App;

你在浏览器将看到具有不同样式的三个加载动画效果:

到这里,我们使用 SVG 和 React 一起创建了一个看上去效果还不错的加载动画。当然,你可以继续添加一些简单的功能,使 LoadingAnimation 组件变得更友好。例如,添加一些优化 Web 可访问性方面的特性;进一步利用好 React 特性,使组件变得更灵活更强大。这里就不再往下深入了,如果你感兴趣的话,可以挑战一下自己。

尝试使用 SVG 和 React 创建类似上图的 Loading 动画,甚至还可以尝试使用 SVG 和 React 将 css-loaders 中所有 Loading 动效转换为 React SVG 版本。

案例二:文本特效

文本特效常常让许多 Web 开发者感到头疼,尤其是在使用 CSS 时,处理文本特效显得相当棘手。即使是简单的描边文本效果,也可能让人困扰。然而,SVG 在这方面有着显著的优势,它可以轻松实现令人惊叹的文本特效。

Demo 地址:https://codepen.io/collection/DPYwYN

我们从简单的效果着手,例如下面这个探照灯的效果:

与前面的示例类似,先从只有 SVG 开始,然后逐步将其与 React 整合起来。

首行创建一个名为 SpecialTextEffect 的 React 组件,它里面只有最基础的功能,就是使用 SVG 的 <text><tspan> 绘制的文本:

JavaScript
// SpecialTextEffect/index.jsx

import "./specialTextEffect.css";

const SpecialTextEffect = () => {
    return (
        <svg viewBox="0 0 600 337" className="text">
            <text x="20" y="1.25em">
                <tspan>Special Text Effect</tspan>
                <tspan dx="-280" dy="1.25em">with SVG & React</tspan>
            </text>
            <text x="20" dy="3.5em">
                <tspan>SVG & React</tspan>
                <tspan dx="-140" dy="1.25em">Are Awesome!</tspan>
            </text>
        </svg>
    );
};

export default SpecialTextEffect;
CSS
/* specialTextEffect.css */
@import 'https://fonts.googleapis.com/css2?family=Exo:wght@600&display=swap';

.text {
    display: block;
    width: 80vw;
    font-family: "Exo", "Bungee Shade", cursive, Arial, sans-serif;
    font-size: 3.5em;
    font-weight: 900;

    tspan:nth-child(2) {
        font-size: 75%;
    }
}

这个时候,你在浏览器中看到的文本只有最基本的样式效果:

需要知道的是,SVG 中的 <text> 元素中的文本内容不会自动换行,因此,上面的代码中使用多个 <tspan> 将文本调整到满足需求的位置。有关于这方面更详细的介绍,可以阅读《SVG 文本元素》。

接下来,将使用 SVG 的 <linearGradient> 元素来定义渐变效果,并将其应用于文本对象。

JavaScript
import "./specialTextEffect.css";

const SpecialTextEffect = () => {
    return (
        <svg viewBox="0 0 600 337" className="text">
            <defs>
                <linearGradient id="gradient" x1="50%" y1="0%" x2="50%" y2="100%">
                    <stop offset="0" stopColor="plum" />
                    <stop offset="1" stopColor="mediumpurple" />
                </linearGradient>
            </defs>
            <text x="20" y="1.25em" stroke="url(#gradient)" fill="none">
                <tspan>Special Text Effect</tspan>
                <tspan dx="-280" dy="1.25em">with SVG & React</tspan>
            </text>
            <text x="20" dy="3.5em">
                <tspan>SVG & React</tspan>
                <tspan dx="-140" dy="1.25em">Are Awesome!</tspan>
            </text>
        </svg>
    );
};

export default SpecialTextEffect;

请注意,文本效果需要应用渐变描边。为此,可以将 <text> 元素的 stroke 属性设置为 url(#gradient),并将 fill 属性设置为 none。其中,#gradient<linearGradient> 的唯一标识符。这样,你在浏览器中就能看到一个带有渐变效果的描边文本了。

相比与 CSS 的渐变描边,这个效果要更细腻一些,而且我们还可以通过 SVG 的 <animate> 给渐变描边添加动画效果:

JavaScript
import "./specialTextEffect.css";

const SpecialTextEffect = () => {
    return (
        <svg viewBox="0 0 600 337" className="text">
            <defs>
                <linearGradient id="gradient" x1="50%" y1="0%" x2="50%" y2="100%">
                  <stop offset="0" stopColor="plum">
                      <animate
                          attributeName="stop-color"
                          values="plum; violet; plum"
                          dur="1s"
                          repeatCount="indefinite" />
                  </stop>
                  <stop offset="1" stopColor="mediumpurple">
                      <animate
                          attributeName="stop-color"
                          values="mediumpurple; lime; mediumpurple"
                          dur="1s"
                          repeatCount="indefinite" />
                  </stop>
                </linearGradient>
            </defs>

            <text x="20" y="1.25em" stroke="url(#gradient)" fill="none">
                <tspan>Special Text Effect</tspan>
                <tspan dx="-280" dy="1.25em">with SVG & React</tspan>
            </text>
            <text x="20" dy="3.5em">
                <tspan>SVG & React</tspan>
                <tspan dx="-140" dy="1.25em">Are Awesome!</tspan>
            </text>
        </svg>
    );
};

export default SpecialTextEffect;

我们需要的不仅仅是带有动画渐变描边效果的文本,最终目标是实现类似探照灯的效果。为实现这个效果,需要将文本作为剪切路径。接下来,在 React 中,可以通过移动鼠标,将一个圆形剪切到文本内部并进行动画处理。

首先,需要在 <defs> 元素中使用 SVG 的 <clipPath> 创建剪切路径,而这个剪切路径就是用 <text> 元素创建的文本。

JavaScript
import "./specialTextEffect.css";

const SpecialTextEffect = () => {
    return (
        <svg viewBox="0 0 600 337" className="text">
            <defs>
                <linearGradient id="gradient" x1="50%" y1="0%" x2="50%" y2="100%">
                    <stop offset="0" stopColor="plum">
                        <animate
                            attributeName="stop-color"
                            values="plum; violet; plum"
                            dur="1s"
                            repeatCount="indefinite"
                        />
                    </stop>
                    <stop offset="1" stopColor="mediumpurple">
                        <animate
                            attributeName="stop-color"
                            values="mediumpurple; lime; mediumpurple"
                            dur="1s"
                            repeatCount="indefinite"
                        />
                    </stop>
                </linearGradient>
                <clipPath id="text-clip">
                    <text id="text" x="20" y="1.25em">
                        <tspan>Special Text Effect</tspan>
                        <tspan dx="-280" dy="1.25em">with SVG & React</tspan>
                    </text>
                  <text x="20" dy="3.5em">
                      <tspan>SVG & React</tspan>
                      <tspan dx="-140" dy="1.25em">Are Awesome!</tspan>
                  </text>
                </clipPath>
            </defs>
            <use href="#text" stroke="url(#gradient)" fill="none" strokeWidth="0.5" />
        </svg>
    );
};

export default SpecialTextEffect;

<text> 嵌入在 <clipPath> 中,主要有两个目的,其一它可以被当作剪切路径,供其他元素的 clip-path 属性调用;其二,通过使用 <use> 元素引用 #text ,同样可在画布上呈现文本。相当于,同一段代码有多个作用:

你可能会感到疑惑,为什么只有第一个 <text> 绘制的文本在画布上呈现呢?其实很简单,我们把所有文本相关的代码放置在 <clipPath> 元素内,并且都嵌套在 <defs> 元素中。要知道的是,<defs> 元素内的内容是不会在画布上呈现,它的最大优势是,你可以无限次的重复利用于里面的代码功能,比如上面代码中的渐变,剪切,文本等。而第一个文本会在画布上呈现,是因为我们使用 <use> 引用了它。

接下来,就是探照灯的绘制了,这里使用两个 <circle> 来绘制探照灯,其中一个是用户可见的,另一个是用户不可见的,这个圆圈被剪切在文本内部,即应用了前面定义的文本剪切路径:

JavaScript
import "./specialTextEffect.css";

const SpecialTextEffect = () => {
    return (
        <svg viewBox="0 0 600 337" className="text">
            <defs>
                <linearGradient id="gradient" x1="50%" y1="0%" x2="50%" y2="100%">
                    <stop offset="0" stopColor="plum">
                        <animate
                            attributeName="stop-color"
                            values="plum; violet; plum"
                            dur="1s"
                            repeatCount="indefinite"
                        />
                    </stop>
                    <stop offset="1" stopColor="mediumpurple">
                        <animate
                            attributeName="stop-color"
                            values="mediumpurple; lime; mediumpurple"
                            dur="1s"
                            repeatCount="indefinite"
                        />
                    </stop>
                </linearGradient>
                <clipPath id="text-clip">
                    <text id="text" x="20" y="1.25em">
                        <tspan>Special Text Effect</tspan>
                        <tspan dx="-280" dy="1.25em">
                          with SVG & React
                        </tspan>
                    </text>
                    <text x="20" dy="3.5em">
                        <tspan>SVG & React</tspan>
                        <tspan dx="-140" dy="1.25em">
                          Are Awesome!
                        </tspan>
                    </text>
                </clipPath>
            </defs>
            <circle className="dot" cy="30%" cx="30%" r="82" fill="navy" />
            <use href="#text" stroke="url(#gradient)" fill="none" strokeWidth="0.5" />
            <circle
                className="dot"
                r="80"
                cy="50%"
                cx="50%"
                fill="lime"
                clipPath="url(#text-clip)"
            />
        </svg>
    );
};

export default SpecialTextEffect;

正如你所看到的,现在这两个圆在画布上是静态的。我们需要给它们绑定相应的事件,比如 mouseove ,使它能跟随用户的鼠标移动。以往,在 HTML 中,使用 JavaScript 实现鼠标跟随的效果是非常简单的,只需要几行简单的代码即可实现:

JavaScript
window.addEventListener('mousemove', (e) => {
    $circle.setAttribute('cx', e.clientX)
    $circle.setAttribute('cy', e.clientY)
})

在 SVG 的世界中,实现这一点并不那么容易。你会发现,如果使用上面的代码,鼠标位置与 SVG 内部圆圈并不匹配。这是因为圆圈(<circle>)的单位是相对于 <svg> 元素的视口(viewBox)的。

如果你对 SVG 的坐标系统不太了解,那么这简单的任务可能会让你感到头痛。但如果你熟悉它,你就能快速将圆圈的单位转换过来,使其获得正确的位置:

JavaScript
const createTranslator = (svgViewBox, svgDimension) => (a, b) => {
    if (!svgViewBox || !svgDimension) {
        console.warn("No dimension provided.");
      return [a, b];
    }
    const { width: viewBoxWidth, height: viewBoxHeight } = svgViewBox;
    const { top, left, width: svgWidth, height: svgHeight } = svgDimension;

    const x = ((a - left) * viewBoxWidth) / svgWidth;
    const y = ((b - top) * viewBoxHeight) / svgHeight;

    return [x, y];
};

最后,我们使用 React 的其他功能,赋予上面代码更强的能力,最终实现我们所需要的效果:

JavaScript
import { useRef, useEffect } from "react";
import "./specialTextEffect.css";

// 创建一个坐标转换器函数,用于将鼠标位置从窗口坐标转换为 SVG 视口坐标
const createTranslator = (svgViewBox, svgDimension) => (a, b) => {
    if (!svgViewBox || !svgDimension) {
        console.warn("No dimension provided.");
        return [a, b];
    }

    const { width: viewBoxWidth, height: viewBoxHeight } = svgViewBox;
    const { top, left, width: svgWidth, height: svgHeight } = svgDimension;

    const x = ((a - left) * viewBoxWidth) / svgWidth;
    const y = ((b - top) * viewBoxHeight) / svgHeight;

    return [x, y];
};

// 定义一个函数,用于更新圆圈的位置
const followCursor = (el, x, y) => {
    el.setAttribute("cx", x);
    el.setAttribute("cy", y);
};

const SpecialTextEffect = () => {
    // 使用useRef创建一个引用对象,包含SVG容器和两个圆圈的引用
    const combinedRefs = useRef({
        container: null,
        innerDot: null,
        outerDot: null,
    });

    useEffect(() => {
        const { container, innerDot, outerDot } = combinedRefs.current;

        if (!container || !innerDot || !outerDot) {
            return;
        }

        let translateCoords = (...args) => args;

        // 获取SVG容器的尺寸和视口信息
        const getDimensions = () => {
            const svgDimension = container.getBoundingClientRect();
            const svgViewBox = container.viewBox.baseVal;
            translateCoords = createTranslator(svgViewBox, svgDimension);
        };

        getDimensions();

        // 处理鼠标移动事件,更新圆圈的位置
        const handleMouseMove = (e) => {
            const { clientX, clientY } = e;
            const [x, y] = translateCoords(clientX, clientY);
            followCursor(innerDot, x, y);
            followCursor(outerDot, x, y);
        };

        // 添加事件监听器,监听鼠标移动、窗口大小改变和滚动事件
        window.addEventListener("mousemove", handleMouseMove);
        window.addEventListener("resize", getDimensions);
        window.addEventListener("scroll", getDimensions);

        // 清理事件监听器,防止内存泄漏
        return () => {
            window.removeEventListener("mousemove", handleMouseMove);
            window.removeEventListener("resize", getDimensions);
            window.removeEventListener("scroll", getDimensions);
        };
    });

    return (
        <svg viewBox="0 0 600 337" className="text" ref={(el) => (combinedRefs.current.container = el)}>
            <defs>
                {/* 定义渐变效果 */}
                <linearGradient id="gradient" x1="50%" y1="0%" x2="50%" y2="100%">
                    <stop offset="0" stopColor="plum">
                        <animate
                            attributeName="stop-color"
                            values="plum; violet; plum"
                            dur="1s"
                            repeatCount="indefinite"
                        />
                    </stop>
                    <stop offset="1" stopColor="mediumpurple">
                        <animate
                            attributeName="stop-color"
                            values="mediumpurple; lime; mediumpurple"
                            dur="1s"
                            repeatCount="indefinite"
                        />
                    </stop>
                </linearGradient>

                {/* 定义剪切路径 */}
                <clipPath id="text-clip">
                    <text id="text" x="20" y="1.25em">
                        <tspan>Special Text Effect</tspan>
                        <tspan dx="-280" dy="1.25em"> with SVG & React </tspan>
                    </text>
                    <text x="20" dy="3.5em">
                        <tspan>SVG & React</tspan>
                        <tspan dx="-140" dy="1.25em"> Are Awesome! </tspan>
                    </text>
                </clipPath>
            </defs>

            {/* 定义外部圆圈 */}
            <circle 
                className="dot" 
                r="82" 
                fill="navy" 
                ref={(el) => (combinedRefs.current.outerDot = el)} />
            <use href="#text" stroke="url(#gradient)" fill="none" strokeWidth="0.5" />

            {/* 定义内部圆圈,应用剪切路径 */}
            <circle
                ref={(el) => (combinedRefs.current.innerDot = el)}
                className="dot"
                r="80"
                fill="lime"
                clipPath="url(#text-clip)" />
        </svg>
    );
};

export default SpecialTextEffect;

正如你所看到的,带有探照灯的文本效果实现了。

当然,你还可以继续美化一下整个效果,比如给画布添加一个背景、给设置剪切路径的圆定义一个渐变效果:

JavaScript
import { useRef, useEffect } from "react";
import "./specialTextEffect.css";

// 创建一个坐标转换器函数,用于将鼠标位置从窗口坐标转换为SVG视口坐标
const createTranslator = (svgViewBox, svgDimension) => (a, b) => {
  if (!svgViewBox || !svgDimension) {
    console.warn("No dimension provided.");
    return [a, b];
  }
  const { width: viewBoxWidth, height: viewBoxHeight } = svgViewBox;
  const { top, left, width: svgWidth, height: svgHeight } = svgDimension;

  const x = ((a - left) * viewBoxWidth) / svgWidth;
  const y = ((b - top) * viewBoxHeight) / svgHeight;

  return [x, y];
};

// 定义一个函数,用于更新圆圈的位置
const followCursor = (el, x, y) => {
  el.setAttribute("cx", x);
  el.setAttribute("cy", y);
};

const SpecialTextEffect = () => {
  // 使用useRef创建一个引用对象,包含SVG容器和两个圆圈的引用
  const combinedRefs = useRef({
    container: null,
    innerDot: null,
    outerDot: null,
  });

  useEffect(() => {
    const { container, innerDot, outerDot } = combinedRefs.current;
    if (!container || !innerDot || !outerDot) {
      return;
    }

    let translateCoords = (...args) => args;

    // 获取SVG容器的尺寸和视口信息
    const getDimensions = () => {
      const svgDimension = container.getBoundingClientRect();
      const svgViewBox = container.viewBox.baseVal;
      translateCoords = createTranslator(svgViewBox, svgDimension);
    };

    getDimensions();

    // 处理鼠标移动事件,更新圆圈的位置
    const handleMouseMove = (e) => {
      const { clientX, clientY } = e;
      const [x, y] = translateCoords(clientX, clientY);
      followCursor(innerDot, x, y);
      followCursor(outerDot, x, y);
    };

    // 添加事件监听器,监听鼠标移动、窗口大小改变和滚动事件
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("resize", getDimensions);
    window.addEventListener("scroll", getDimensions);

    // 清理事件监听器,防止内存泄漏
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
      window.removeEventListener("resize", getDimensions);
      window.removeEventListener("scroll", getDimensions);
    };
  });

  // 渲染SVG组件,包含文本、渐变效果和两个圆圈
  return (
    <svg viewBox="0 0 600 337" className="text" ref={(el) => (combinedRefs.current.container = el)}>
      <defs>
        {/* 定义渐变效果 */}
        <linearGradient id="gradient" x1="50%" y1="0%" x2="50%" y2="100%">
          <stop offset="0" stopColor="plum">
            <animate
              attributeName="stop-color"
              values="plum; violet; plum"
              dur="1s"
              repeatCount="indefinite"
            />
          </stop>
          <stop offset="1" stopColor="mediumpurple">
            <animate
              attributeName="stop-color"
              values="mediumpurple; lime; mediumpurple"
              dur="1s"
              repeatCount="indefinite"
            />
          </stop>
        </linearGradient>
        <linearGradient id="gradient2" x1="0" y1="100%" x2="100%" y2="0">
            <stop stopColor="#40e0d0" offset="0" />
            <stop stopColor="#9932cc" offset="0.333" />
            <stop stopColor="#ff1493" offset="0.667" />
            <stop stopColor="orange" offset="1" />

        </linearGradient>
        {/* 定义剪切路径 */}
        <clipPath id="text-clip">
          <text id="text" x="20" y="1.25em">
            <tspan>Special Text Effect</tspan>
            <tspan dx="-280" dy="1.25em">
              with SVG & React
            </tspan>
          </text>
          <text x="20" dy="3.5em">
            <tspan>SVG & React</tspan>
            <tspan dx="-140" dy="1.25em">
              Are Awesome!
            </tspan>
          </text>
        </clipPath>
      </defs>
      {/* 定义外部圆圈 */}
      <circle className="dot" r="82" fill="rgb(0 0 0  / .5)" ref={(el) => (combinedRefs.current.outerDot = el)} />
      <use href="#text" stroke="url(#gradient)" fill="none" strokeWidth="2" />
      {/* 定义内部圆圈,应用剪切路径 */}
      <circle
        ref={(el) => (combinedRefs.current.innerDot = el)}
        className="dot"
        r="80"
        fill="url(#gradient2)"
        clipPath="url(#text-clip)"
      />
    </svg>
  );
};

export default SpecialTextEffect;
CSS
@import 'https://fonts.googleapis.com/css2?family=Exo:wght@600&display=swap';

.text {
    display: block;
    width: 80vw;
    font-family: "Exo", "Bungee Shade", cursive, Arial, sans-serif;
    font-size: 3.5em;
    font-weight: 900;
    background-image:
        radial-gradient(farthest-corner circle at 100% 100% in oklab,
            oklch(80% .4 222) 0%, oklch(35% .5 313) 100%);
    border-radius: 20px;
    mix-blend-mode: multiply;

    tspan:nth-child(2) {
        font-size: 75%;
    }
}

.dot {
    opacity: 1;
    transition: opacity 0.3s ease;
}

我尝试着把上面的渐变替换为 SVG 的滤镜:

JavaScript
import { useRef, useEffect } from "react";
import "./specialTextEffect.css";

// 创建一个坐标转换器函数,用于将鼠标位置从窗口坐标转换为SVG视口坐标
const createTranslator = (svgViewBox, svgDimension) => (a, b) => {
  if (!svgViewBox || !svgDimension) {
    console.warn("No dimension provided.");
    return [a, b];
  }
  const { width: viewBoxWidth, height: viewBoxHeight } = svgViewBox;
  const { top, left, width: svgWidth, height: svgHeight } = svgDimension;

  const x = ((a - left) * viewBoxWidth) / svgWidth;
  const y = ((b - top) * viewBoxHeight) / svgHeight;

  return [x, y];
};

// 定义一个函数,用于更新圆圈的位置
const followCursor = (el, x, y) => {
  el.setAttribute("cx", x);
  el.setAttribute("cy", y);
};

const SpecialTextEffect = () => {
  // 使用useRef创建一个引用对象,包含SVG容器和两个圆圈的引用
  const combinedRefs = useRef({
    container: null,
    innerDot: null,
    outerDot: null,
  });

  useEffect(() => {
    const { container, innerDot, outerDot } = combinedRefs.current;
    if (!container || !innerDot || !outerDot) {
      return;
    }

    let translateCoords = (...args) => args;

    // 获取SVG容器的尺寸和视口信息
    const getDimensions = () => {
      const svgDimension = container.getBoundingClientRect();
      const svgViewBox = container.viewBox.baseVal;
      translateCoords = createTranslator(svgViewBox, svgDimension);
    };

    getDimensions();

    // 处理鼠标移动事件,更新圆圈的位置
    const handleMouseMove = (e) => {
      const { clientX, clientY } = e;
      const [x, y] = translateCoords(clientX, clientY);
      followCursor(innerDot, x, y);
      followCursor(outerDot, x, y);
    };

    // 添加事件监听器,监听鼠标移动、窗口大小改变和滚动事件
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("resize", getDimensions);
    window.addEventListener("scroll", getDimensions);

    // 清理事件监听器,防止内存泄漏
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
      window.removeEventListener("resize", getDimensions);
      window.removeEventListener("scroll", getDimensions);
    };
  });

  // 渲染SVG组件,包含文本、渐变效果和两个圆圈
  return (
    <svg
      viewBox="0 0 600 337"
      className="text"
      ref={(el) => (combinedRefs.current.container = el)}
    >
      <defs>
        {/* 定义剪切路径 */}
        <clipPath id="text-clip">
          <text id="text" x="20" y="1.25em">
            <tspan>Special Text Effect</tspan>
            <tspan dx="-280" dy="1.25em">
              with SVG & React
            </tspan>
          </text>
          <text x="20" dy="3.5em">
            <tspan>SVG & React</tspan>
            <tspan dx="-140" dy="1.25em">
              Are Awesome!
            </tspan>
          </text>
        </clipPath>
        <filter id="filter">
          <feMorphology
            operator="dilate"
            radius="4"
            in="SourceAlpha"
            result="morphology"
          />
          <feFlood floodColor="black" floodOpacity="1" result="flood" />
          <feComposite
            in="flood"
            in2="morphology"
            operator="in"
            result="composite"
          />
          <feComposite
            in="composite"
            in2="SourceAlpha"
            operator="out"
            result="composite1"
          />
          <feTurbulence
            type="fractalNoise"
            baseFrequency="0.001 0.02"
            numOctaves="1"
            seed="0"
            stitchTiles="stitch"
            result="turbulence"
          />
          <feDisplacementMap
            in="composite1"
            in2="turbulence"
            scale="17"
            xChannelSelector="A"
            yChannelSelector="A"
            result="displacementMap"
          />
          <feMerge result="merge">
            <feMergeNode in="SourceGraphic" result="mergeNode" />
            <feMergeNode in="displacementMap" result="mergeNode1" />
          </feMerge>
        </filter>
        <filter
          id="filter2"
          x="-20%"
          y="-20%"
          width="140%"
          height="140%"
          filterUnits="objectBoundingBox"
          primitiveUnits="userSpaceOnUse"
        >
          <feGaussianBlur
            stdDeviation="5 5"
            in="SourceGraphic"
            edgeMode="none"
            result="blur"
          />
          <feTurbulence
            type="turbulence"
            baseFrequency="0.04 0.04"
            numOctaves="2"
            seed="2"
            stitchTiles="stitch"
            result="turbulence"
          />
          <feComposite
            in="turbulence"
            in2="blur"
            operator="in"
            result="composite"
          />
          <feColorMatrix
            type="matrix"
            values="1 0 0 0 0
              0 1 0 0 0
              0 0 1 0 0
              0 0 0 40 -4"
            in="composite"
            result="colormatrix"
          />
          <feSpecularLighting
            surfaceScale="40"
            specularConstant="1"
            specularExponent="35"
            kernelUnitLength="10 10"
            lightingColor="#fff"
            in="turbulence"
            result="specularLighting"
          >
            <feDistantLight azimuth="68" elevation="240" />
          </feSpecularLighting>
          <feFlood
            floodColor="hsl(350,100%,73%)
"
            floodOpacity="1"
            result="flood"
          />
          <feComposite
            in="flood"
            in2="colormatrix"
            operator="in"
            result="composite2"
          />
          <feComposite
            in="specularLighting"
            in2="colormatrix"
            operator="in"
            result="composite1"
          />
          <feMerge result="merge1">
            <feMergeNode in="composite2" />
            <feMergeNode in="composite1" />
          </feMerge>
        </filter>
      </defs>
      {/* 定义外部圆圈 */}
      <circle
        className="dot"
        r="82"
        fill="rgb(0 0 0  / .5)"
        ref={(el) => (combinedRefs.current.outerDot = el)}
      />
      <use href="#text" filter="url(#filter)" fill="orange" />
      {/* 定义内部圆圈,应用剪切路径 */}
      <circle
        ref={(el) => (combinedRefs.current.innerDot = el)}
        className="dot"
        r="80"
        filter="url(#filter2)"
        clipPath="url(#text-clip)"
      />
    </svg>
  );
};

export default SpecialTextEffect;

如果你有兴趣,可以挑战一下自己,使用 SVG 和 React 实现类似下面的效果:

Demo 地址:https://tympanus.net/Tutorials/codrops-masked-hero/

Demo 地址:https://tympanus.net/Tutorials/AnimatedCodeBackground/

案例三:Lottie 动画

许多时候,制作各种动画可能会让你感到困扰。然而,通过结合 SVG 和 React,可以利用 Lottie Web 快速创建各种令人惊叹的 SVG 动画:

LottieMarket: https://lottiefiles.com/marketplace/popular

我们一起来看一个简单的示例,在 Vite React 应用中,我创建了一个 Lottie 的组件:

JavaScript
import { memo, useRef, useEffect } from "react";
import lottie from "lottie-web";
import "./lottie.css";

const LOTTIE_PATH = "https://assets.codepen.io/3685267/cute-cat-works.json";

const Lottie = memo(({ className, path = LOTTIE_PATH }) => {
    const elm = useRef(); 
    const animation = useRef(); 

    useEffect(() => {
        animation.current = lottie.loadAnimation({
            path, 
            container: elm.current, 
            loop: true, 
            autoplay: true, 
        });

        return () => {
          animation.current?.destroy();
        };
    }, [path]);

    return <div className={className} ref={elm} />; 
});

export default Lottie;

注意,上面代码中的 LOTTIE_PATH ,它的值是一个 LottieFile,根据 Lottie 相关规范来描述一个动画的 JSON 数据。这个文件你可以通过从 AE 制作动画的软件中导出,也可以在 LottieFiles 中获得。如果你只是要学习一下如何基于 Lottie Web 构建一个 React 组件,那么通过 LottieFile 获得一个演示文件即可;如果你希望将动画效果用于实际生产中,就得使用诸如 AE 这样的软件,将设计好的 Lottie 动画数据导出。

现在,我们在 App 组件中引用已创建好的 Lottie 组件:

JavaScript
import "./App.css";
import Lottie from "./components/Lottie";
const App = () =>{
    return (
      <div className="app">
          <Lottie className="my-lottie-animation" />
      </div>
    );
}

export default App;

你将会在浏览器看到 LOTTIE_PATH 变量引入的 Lottie 动画:

小结

总的来说,SVG 在Vite React 应用中的应用非常广泛,它不仅用于展示图标和图形,还可以实现动画效果、交互式功能以及复杂的视觉效果。通过结合 React 的状态管理和事件处理,以及 SVG 的强大特性,开发人员可以创建出丰富多彩、交互性强的用户界面。因此,SVG 在 Vite React 应用中扮演着重要的角色,为应用的视觉和用户体验增添了更多可能性。