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

2020 年 6 月 09 日

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

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

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

声明式图形

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

复制代码
const SomeComponent = () => (
<div>
<p>hi!</p>
</div>
);

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

复制代码
const SomeOtherComponent = () => (
<svg viewBox="0 0 100 100">
<circle cx={50} cy={50} r={30} fill="red"/>
</svg>
);

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

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

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

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

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

所有的元素都尽在掌握

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

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

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

Viewport 和 ViewBox

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

复制代码
<svg width="200px" height="200px">...</svg>

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

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

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

复制代码
<svg viewBox="x y width height">...</svg>

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

复制代码
<svg width="10" height="10" viewBox="-100 -100 200 200">...</svg>

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

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

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

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

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

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

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

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

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

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

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

复制代码
import data from './floorplan-data.json';
const App = () => (
<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) 坐标之间的一条线段。

复制代码
<line x1={100} y1={100} x2={200} y2={200} stroke="red"/>

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

复制代码
<line x1={100} y1={100} x2={200} y2={200} stroke="red"/>

房间的视图

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

复制代码
const Floorplan = ({ data: { rooms } }) => (
rooms.map(r => <Room {...r} />)
);

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

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

复制代码
const walls = useMemo(() =>
coords.map((_, i) => {
const a = coords[i];
const b = coords[(i + 1) % coords.length];
return [a, b];
}),
[coords]
);

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

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

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

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

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

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

小结

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

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

英文原文

Graphical UIS with SVG and React part 1 declarative graphics

2020 年 6 月 09 日 14:33 6979
用户头像
小智 InfoQ高级编辑

发布了 385 篇内容, 共 296.4 次阅读, 收获喜欢 1590 次。

关注

评论 4 条评论

发布
用户头像
InfoQ能不能提升一下译文的质量?文章开头第二段代码抄都能抄错?
2020 年 06 月 15 日 12:05
回复
用户头像
简单图形还好,你给我画个肖像画看看,用SVG标签写死你 :P
2020 年 06 月 15 日 09:20
回复
用户头像
这个不错
2020 年 06 月 12 日 11:44
回复
用户头像
标题耸人听闻。举个例子,现在登录页面,我要提交一个 form表单,应该用svg?
2020 年 06 月 10 日 09:44
回复
没有更多评论了
发现更多内容

数据挖掘学习指南(转载)

Jackchang234987

数据挖掘 数据产品经理

开发者的福音,LR.NET模块化代码生成器

Philips

敏捷开发 快速开发 模块化流程 代码质量 .net core

区块链 新基建定位下的新使命 2020新区势

CECBC区块链专委会

区块链 新基建

Flink-状态后端作用-11

小知识点

scala 大数据 flink

面试官想知道都在这里

escray

面试 学习笔记 面试现场

企业网络安全漏洞多,这些等保服务来填坑

华为云开发者社区

Web 安全 防火墙 等保 DDoS

LeetCode题解:155.最小栈,使用两个栈,详细注释

Lee Chen

LeetCode 前端进阶训练营

微服务架构下,DLI的部署和运维有何奥秘?

华为云开发者社区

Docker 大数据 Serverless 数据湖 DLI

【写作群星榜】8.15~8.28 写作平台优秀作者 & 文章排名

InfoQ写作平台

写作平台 排行榜

涵盖多场景区块链与政务结合 应用前景广阔

CECBC区块链专委会

区块链 互联网 数字政务

微前端在民生 APaaS/PSET 平台的探索与实践

亻尔可真木奉

探索与实践 案例分享 微前端

化妆品行业与区块链的融合可减少甚至消除假冒伪劣

CECBC区块链专委会

区块链 化妆品

20年美团架构师一份“架构宝典”竟涵盖了架构设计和实践技巧?

周老师

Java 编程 程序员 架构 面试

甲方日常 3

大橘子

工作 随笔杂谈 日常

消息队列之推还是拉,RocketMQ 和 Kafka 是如何做的?

yes的练级攻略

kafka RocketMQ

【FCC前端教程】44关学习CSS与CSS3基础「二」

三钻

CSS 前端 FCC

架构师训练营0期11周

WW

建设开发者生态:6项华为API管理原则落地

华为云开发者社区

开发者 API 华为云 API Explorer平台 应用技术

抗疫复产,CDN助企业破局发展

华为云开发者社区

CDN 网络 华为云 CDN加速 企业应用

学习Python真的能找到工作吗?

代码制造者

Python 程序员 编程语言 低代码 零代码

mPaaS 客户端证书错误避坑指南

阿里云金融线TAM SRE专家服务团队

屏幕共享接入指南

anyRTC开发者

WebRTC 在线教育 直播 RTC

架构师训练营第 11周作业和感想

tuuezzy

极客大学架构师训练营

软件开发丨关于软件重构的灵魂四问

华为云开发者社区

软件 开发者 软件开发 代码 软件重构

ShardingSphere简介+实战

云淡风轻

ShardingJDBC

最强云硬盘来了,让AI模型迭代从1周缩短到1天

华为云开发者社区

SSD 云存储 All-Flash 云硬盘 擎天架构

穿什么衣服去面试?

escray

面试 学习笔记 面试现场

有为而治:平衡吞噬世界的系统之熵

IT民工大叔

oeasy教您玩转linux010107那啥在哪 whereis

o

初识Druid——实时OLAP系统

justskinny

大数据处理 大数据技术 Apache Druid

分享一个阿里云轻量级开源前端图编排,流程图js组件——butterfly-dag

InfoQ_39ba186c207f

JavaScript 流程图 flow canvas html/css

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