11

前端福音:为什么使用 React 和 SVG 开发图形 UI 是天作之合?

 3 years ago
source link: https://www.infoq.cn/article/f6wtMENlh7QfeVjQ9MOg
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文最初发布于 Data Language 网站,经网站授权由 InfoQ 中文站翻译并分享。

React 和 SVG 是一种强大的组合:声明式 UI 组件库与声明式图形语言堪称绝配,是前端开发人员的福音。

声明式图形

React 开发人员都很满意 JSX 中对 HTML 元素的一流支持:

复制代码

constSomeComponent =()=>(
<div>
<p>hi!</p>
</div>
);

但其实这也适用于内联 SVG 元素:

复制代码

constSomeComponent =()=>(
<div>
<p>hi!</p>
</div>
);

我们可以直接使用组成 SVG 图元的组件来创建复杂的交互式 UI,用法和更典型的基于 HTML 的组件是一样的。

针对 React-Native 开发人员的注释:你需要安装 react-native-svg,我上次用到它时看起来它挺不错的。我在几年前创建了一个简单但功能强大的示例:

https://stackoverflow.com/a/39041997/473338

u63EFfz.png!web

声明式图形组件的简单组合(https://62zqd.csb.app/)

请注意,我们在绘制所有这些图形时都没有写过哪怕一行命令式代码,而且只要你熟悉 React,就可以很容易地阅读并理解我们的声明式标记。如果我们要用 Canvas API 来做的话,结果会相去甚远。

所有的元素都尽在掌握

要使用 SVG 绘制内容,我们首先需要一个 < svg> 元素来定义绘图上下文,在其中将渲染所有后代元素。

我们的页面中可以有很多 < svg> 元素,每个元素描述一个孤立的绘图上下文。每个图标、图表或其他漂亮的图形小部件都搭配一个 < svg>。

另外,我们可以在同一个 < svg> 中创建许多图形组件,并在单个绘图上下文中创建整个 UI。

Viewport 和 ViewBox

< svg> 元素至少应指定它要占据多大的屏幕空间,也就是它的 viewport(视口)。这可以通过 CSS 样式或 width 和 height 属性来完成。

复制代码

<svgwidth="200px"height="200px">...</svg>

我们一般也会指定一个 viewBox,以明确定义绘图的“用户空间”部分,这一部分在 viewport 中是可见的。

可以将 viewport 视为通向由 SVG(“用户空间”)定义的世界的窗口,并将 viewBox 视为缩放和平移的设置,它们决定你可以通过窗口看到用户空间的哪些部分。

viewBox 属性是一个字符串,其按顺序指定用户空间可视部分左上角的 x 和 y 坐标,以及 viewBox 的宽度和高度。

复制代码

<svgviewBox="x y width height">...</svg>

例如,“-100 -100 200 200”就描述了一个正方形的 viewBox,高 200 个单位,宽 200 个单位,以(0,0)的原点为中心。

复制代码

<svgwidth="10"height="10"viewBox="-100 -100 200 200">...</svg>

如果我们没有明确指定,则 viewBox 的宽度和高度会和 viewport 一比一对应,并且用户空间原点(0,0)位于左上角。

重申一下:viewport 描述了 < svg> 元素在 HTML 页面中占据了多少空间。viewBox 描述了在该 viewport 中可见的那部分图像。通过更改 viewBox,我们可以放大或缩小 SVG 图像,或者显示出来完全不同的部分。

YZBZneI.png!web

这两张 SVG 图像的唯一区别是 viewBox 和背景色

如果你的 SVG viewport 的比例(以像素为单位)与 viewBox 的比例不匹配,则默认行为是保留宽高比,居中显示,并使 viewBox 适应可用 viewport。可以通过更改 prepareAspectRatio 属性来控制此行为。

V3Q7bai.png!web

默认情况下,viewBox 将居中并“适应”SVG viewport

viewBox 和 viewport 之间的坐标系是分离的,这意味着在我们的 SVG 中,我们可以针对特定的绘制使用对应的坐标。

例如,如果我们正在绘制一个交互式小部件,则可以使用百分比来控制图像,并将 viewBox 设置为“0 0 100 100”来简化数学运算。

或者,如果我们的小部件是纵向对称的,并且我们想从中心开始绘制,则可以使用一个以原点(0,0)为中心的“-50 -50 100 100”viewBox。

a2yEfaz.png!web

在这个文章系列中我们将绘制的是房间平面图,因此我们可以选择公制、英制或其他合适的度量系统。实际上我们要使用的是毫米单位,这样就可以只涉及整数运算了。

我们将使用一些简单的 JSON 描述平面图,房间的形状用房间各个角的坐标(按顺时针方向)来定义。

zmQzQbf.png!web

现在我们可以设置 viewport 和 viewBox,在平面图周围留一点空隙,以免在 viewport 中太过拥挤。

复制代码

importdatafrom'./floorplan-data.json';
constApp =()=>(
<svg
width="500px"
height="500px"
viewBox="-1000 -1000 14000 11000"
style={{backgroundColor:'blue'}}
>
// ... floorplan components here ...
</svg>
);

搞定 viewport 和 viewBox 后,就可以处理绘图工作了。

声明式图元

SVG 包含许多图元,既有简单的 和 ,也有更灵活的 。Mozilla Developer Network 有 一篇参考资料 介绍了这些可用元素。

形状可以被填充和 / 或描边。填充是在形状定义的边界内应用颜色,描边是对形状的轮廓应用颜色。描边的宽度和其他样式可以通过属性或 CSS 控制。

我们将使用两个简单的图元 < line> 和 < circle> 来绘制初始平面图。

< line> 描述一对 (x,y) 坐标之间的一条线段。

复制代码

<linex1={100}y1={100}x2={200}y2={200}stroke="red"/>

< circle> 描述一个以 (cx,cy) 为中心,半径为 r 的圆。

复制代码

<linex1={100}y1={100}x2={200}y2={200}stroke="red"/>

房间的视图

绘制我们的房间时,我们将数据传递到一个 Floorplan 组件中,该组件将渲染平面图的各种元素——一开始图上只有各个房间。

复制代码

constFloorplan =({ data: { rooms } }) =>(
rooms.map(r=><Room{...r} />)
);

要使用 Room 组件绘制墙壁,我们需要使用成对的连续角坐标,并将它们链接在一起以形成墙。例如,一个具有角 a、b、c 和 d 的矩形房间会有四面墙:(a-b),(b-c), (c-d), (d-a)。

我们可以使用一个简单的函数来提取这些坐标对,还可以把它们打包在一个 useMemo hook 中以实现高效的重渲染。

复制代码

constwalls = useMemo(()=>
coords.map((_, i) =>{
consta = coords[i];
constb = coords[(i +1) % coords.length];
return[a, b];
}),
[coords]
);

现在绘制墙壁时,只需使用 SVG 元素描述各个角之间的线段即可。

63qmy2v.png!web

效果是可以了,但代码不是很漂亮。我们来点创新,提取一个 Wall 组件和一个 Corner 组件。

nia2Eva.png!web

在 Wall 组件中,我们现在给每面墙绘制两条线:首先是一条粗的白线,然后用一条较细的深蓝色虚线覆盖白线。

Corner 非常简单:只画一个圆,描边为白色,深蓝色填充。

mY3MnuQ.png!web

现在,Room 组件里就只有这些更高级别的 Wall 和 Corner 组件,替代之前的一堆 SVG 图元,简洁多了。

eMf2e2U.png!web

你可能注意到了,我们将墙和角嵌套在了一个 SVG 元素中。这是一个逻辑分组元素: 本身不会渲染任何可见内容,但是它为我们提供了一种在 SVG 的整个子结构上执行旋转和平移之类变换的巧妙方法;我们还可以将事件处理程序附加到 元素,进而从这些子结构中捕获事件。

小结

在这篇文章中,我们看到了:

  1. 我们可以使用 SVG 图元和简单明了的声明式 React 代码轻松地绘制任意形状——无需插件、库或命令式代码。
  2. 就像其他 React 应用程序一样,可以将越来越高级的组件组合在一起来构建复杂的图形 UI。
  3. viewBox 和 viewport 的分离使你可以在适合自己需求的坐标空间中绘制图像,然后在适合最终显示需求的 viewport 中渲染结果。

英文原文

Graphical UIS with SVG and React part 1 declarative graphics


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK