有一个需求是这样的。

一个组件里若干个区块。区块数量不定。

区块里面是一个正六边形组件,而这个用 SVG 和 canvas 都可以。我选择 canvas。

所以就变成了在 react 中使用 canvas 的问题。

canvas 和 SVG 有一个很大的不同。

SVG 是标签,所以HTML怎么整,SVG 就怎么整。

而 canvas 是一套相对独立的 web API,以 canvas 标签为容器(HTML接口)。

所以在 react 中处理 canvas 类似于在 react 中处理第三方DOM库。比如那些需要依赖 jQuery 的各种UI组件库。

关于这个可以看 react 文档中的与第三方库协同

组件文件的结构和上一个文章类似。

  1. import React from 'react'
  2.  
  3. class Polygon extends React.Component {}
  4.  
  5. class polygonContainer extends React.Component {}
  6.  
  7. export default polygonContainer

然后是 canvas 组件。

  1. class Polygon extends React.Component {
  2. constructor(props){
  3. super(props)
  4. this.state = {
  5. }
  6. }
  7.  
  8. componentDidMount() {
  9. console.log("=== componentDidMount Polygon ===")
  10.  
  11. this.init(this.props.data, this.props.sn)
  12. }
  13.  
  14. componentDidUpdate() {
  15. console.log("=== componentDidUpdate Polygon ===")
  16.  
  17. this.init(this.props.data, this.props.sn)
  18. }
  19.  
  20. init = (item, sn) => {
  21.  
  22. const getR = () => {
  23. return Math.min(size.width, size.height) * 0.375
  24. }
  25.  
  26. const getWordCoor = (index, centerCoor, sub, fontSize, fontLength) => {
  27. const getXCoor = (index, centerCoor, fontSize, fontLength) => {
  28. const standand = -1
  29. return (centerCoor.x + fontLength / 2 * (index === 0 ? fontSize : (fontSize / 2)) * standand)
  30. }
  31. const getYCoor = (index, centerCoor, sub) => {
  32. const standand = index === 0 ? -0.3 : 0.6
  33. return (centerCoor.y + sub * standand)
  34. }
  35.  
  36. console.log(getXCoor(index, centerCoor, fontSize, fontLength))
  37.  
  38. return {
  39. x: getXCoor(index, centerCoor, fontSize, fontLength),
  40. y: getYCoor(index, centerCoor, sub)
  41. }
  42. }
  43.  
  44. const getStrokeColor = (sn) => {
  45. return sn === 5 ? 'rgb(255, 114, 0)' : 'rgb(232, 172, 4)'
  46. }
  47.  
  48. const getFillColor = (sn) => {
  49. return sn === 5 ? 'rgb(255, 192, 0)' : 'rgb(4, 154, 79)'
  50. }
  51.  
  52. const canvas = document.getElementById("canvas" + sn);
  53.  
  54. const size = {
  55. width: parseInt(this.props.size.width),
  56. height: parseInt(this.props.size.height),
  57. }
  58.  
  59. canvas.width = size.width;
  60. canvas.height = size.height;
  61. const cc = canvas.getContext("2d");
  62.  
  63. // 多边形
  64. const coorArray = []
  65.  
  66. cc.beginPath();
  67. for (var i = 0 ; i < 6 ; i++) {
  68. var x = Math.cos((i * 60)/180 * Math.PI) * getR() + (size.width / 2) ;
  69. var y = -Math.sin((i * 60)/180 * Math.PI) * getR() + (size.height / 2);
  70.  
  71. coorArray.push({x, y})
  72.  
  73. cc.lineTo(x,y);
  74. }
  75. cc.closePath();
  76. cc.lineWidth = 2;
  77. cc.fillStyle = getFillColor(sn);
  78. cc.fill();
  79. cc.strokeStyle = getStrokeColor(sn);
  80. cc.stroke();
  81.  
  82. // 文字
  83. const centerCoor = {
  84. x: (coorArray[0].x + coorArray[3].x) / 2,
  85. y: coorArray[0].y
  86. }
  87. const sub = coorArray[0].y - coorArray[1].y
  88.  
  89. const wordCoorArray = [
  90. getWordCoor(0, centerCoor, sub, 14, item.name.length),
  91. getWordCoor(1, centerCoor, sub, 20, item.data.toString().length)
  92. ]
  93.  
  94. cc.font="14px Arial"
  95. cc.strokeStyle = "#fff";
  96. cc.fillStyle = "#fff";
  97. cc.fillText(item.name, wordCoorArray[0].x, wordCoorArray[0].y);
  98.  
  99. cc.font="20px Arial"
  100. cc.fillText(item.data, wordCoorArray[1].x, wordCoorArray[1].y);
  101. }
  102.  
  103. render(){
  104. const item = this.props.data
  105. const size = this.props.size
  106. const sn = this.props.sn
  107.  
  108. const getColor = (item) => {
  109. return item.color
  110. }
  111.  
  112. return (
  113. <canvas id={'canvas' + sn}></canvas>
  114. );
  115. }
  116. }

有几点需要说明一下。

  • 因为 componentDidUpdate 钩子中 有 init 方法,所以 init 方法中不能再给 state 赋值,否则会触发无限循环。如果需要存值,则需要想别的办法。
  • getWordCoor 是计算文字位置的方法。六边形里面有文字内容。
  • canvas 对象是通过 document.getElementById 获取的,而一个页面中肯定有多个 canvas ,此时就必须做出区分。我的方法是传一个序列号 sn (index + 1),当然生成 ID 是更好的做法。
  • 响应式的样式对 canvas 是无效的。必须手动赋像素值。也就是说必须手动计算 size 。计算 size 的方法在父组件里面。
  • for 循环是用来绘制路径的,就是个数学问题,Math 对象里有三角函数简化了一些运算。顺便把中心点坐标和六边形各个点的坐标存了一下。
  • canvas 绘制方法不需要说了,百度一下即可。

然后是容器组件。

  1. // 六边形测试
  2.  
  3. import React from 'react'
  4.  
  5. // import Styles from './polygonContainer.less'
  6.  
  7. class Polygon extends React.Component {
  8. constructor(props){
  9. super(props)
  10. this.state = {
  11. }
  12. }
  13.  
  14. componentDidMount() {
  15. console.log("=== componentDidMount Polygon ===")
  16.  
  17. this.init(this.props.data, this.props.sn)
  18. }
  19.  
  20. componentDidUpdate() {
  21. console.log("=== componentDidUpdate Polygon ===")
  22.  
  23. this.init(this.props.data, this.props.sn)
  24. }
  25.  
  26. init = (item, sn) => {
  27. // console.log(item)
  28. // console.log(sn)
  29.  
  30. const getR = () => {
  31. return Math.min(size.width, size.height) * 0.375
  32. }
  33.  
  34. const getWordCoor = (index, centerCoor, sub, fontSize, fontLength) => {
  35. const getXCoor = (index, centerCoor, fontSize, fontLength) => {
  36. const standand = -1
  37. return (centerCoor.x + fontLength / 2 * (index === 0 ? fontSize : (fontSize / 2)) * standand)
  38. }
  39. const getYCoor = (index, centerCoor, sub) => {
  40. const standand = index === 0 ? -0.3 : 0.6
  41. return (centerCoor.y + sub * standand)
  42. }
  43.  
  44. console.log(getXCoor(index, centerCoor, fontSize, fontLength))
  45.  
  46. return {
  47. x: getXCoor(index, centerCoor, fontSize, fontLength),
  48. y: getYCoor(index, centerCoor, sub)
  49. }
  50. }
  51.  
  52. const getStrokeColor = (sn) => {
  53. return sn === 5 ? 'rgb(255, 114, 0)' : 'rgb(232, 172, 4)'
  54. }
  55.  
  56. const getFillColor = (sn) => {
  57. return sn === 5 ? 'rgb(255, 192, 0)' : 'rgb(4, 154, 79)'
  58. }
  59.  
  60. const canvas = document.getElementById("canvas" + sn);
  61.  
  62. const size = {
  63. width: parseInt(this.props.size.width),
  64. height: parseInt(this.props.size.height),
  65. }
  66.  
  67. // console.log(size)
  68.  
  69. canvas.width = size.width;
  70. canvas.height = size.height;
  71. const cc = canvas.getContext("2d");
  72.  
  73. // 多边形
  74. const coorArray = []
  75.  
  76. cc.beginPath();
  77. for (var i = 0 ; i < 6 ; i++) {
  78. var x = Math.cos((i * 60)/180 * Math.PI) * getR() + (size.width / 2) ;
  79. var y = -Math.sin((i * 60)/180 * Math.PI) * getR() + (size.height / 2);
  80.  
  81. coorArray.push({x, y})
  82.  
  83. cc.lineTo(x,y);
  84. }
  85. cc.closePath();
  86. cc.lineWidth = 2;
  87. cc.fillStyle = getFillColor(sn);
  88. cc.fill();
  89. cc.strokeStyle = getStrokeColor(sn);
  90. cc.stroke();
  91.  
  92. // 文字
  93. const centerCoor = {
  94. x: (coorArray[0].x + coorArray[3].x) / 2,
  95. y: coorArray[0].y
  96. }
  97. const sub = coorArray[0].y - coorArray[1].y
  98.  
  99. // console.log(centerCoor)
  100. // console.log(coorArray)
  101.  
  102. const wordCoorArray = [
  103. getWordCoor(0, centerCoor, sub, 14, item.name.length),
  104. getWordCoor(1, centerCoor, sub, 20, item.data.toString().length)
  105. ]
  106. // console.log(wordCoorArray)
  107.  
  108. cc.font="14px Arial"
  109. cc.strokeStyle = "#fff";
  110. cc.fillStyle = "#fff";
  111. cc.fillText(item.name, wordCoorArray[0].x, wordCoorArray[0].y);
  112.  
  113. cc.font="20px Arial"
  114. cc.fillText(item.data, wordCoorArray[1].x, wordCoorArray[1].y);
  115. }
  116.  
  117. render(){
  118. const item = this.props.data
  119. const size = this.props.size
  120. const sn = this.props.sn
  121.  
  122. // console.log("Polygon render === ", size)
  123.  
  124. const getColor = (item) => {
  125. return item.color
  126. }
  127.  
  128. return (
  129. <canvas id={'canvas' + sn}></canvas>
  130. // <div>asd</div>
  131. );
  132. }
  133. }
  134.  
  135. class polygonContainer extends React.Component {
  136. constructor(props){
  137. super(props)
  138. this.state = {
  139. curcity:""
  140. }
  141. }
  142.  
  143. componentDidMount() {
  144. console.log("componentDidMount")
  145. console.log(new Date().getTime())
  146.  
  147. this.setState({
  148. curcity:this.props.curcity
  149. })
  150. }
  151.  
  152. componentDidUpdate(){
  153. console.log("componentDidUpdate")
  154. console.log(new Date().getTime())
  155. }
  156.  
  157. // total 总数 SN 序列号
  158. getSize = () => {
  159. const pc = document.getElementById('pc')
  160. if (!pc) {
  161. return null
  162. } else {
  163. // const length = this.getDataBar().data.sData.length
  164.  
  165. const base = {
  166. width:document.getElementById('pc').offsetWidth,
  167. height:document.getElementById('pc').offsetHeight
  168. }
  169.  
  170. return function (total, SN) {
  171. // console.log(base)
  172.  
  173. const standand = 2
  174. const oneRowStd = 3
  175.  
  176. const ceil = Math.ceil(total / standand)
  177. const floor = Math.floor(total / standand)
  178.  
  179. const basicHeight = (total > oneRowStd) ? (base.height / standand) : (base.height)
  180.  
  181. // console.log(ceil, floor)
  182. // console.log(total, SN)
  183.  
  184. if (SN <= ceil) {
  185. return {
  186. width:(total > oneRowStd) ? (base.width / ceil) : (base.width / total),
  187. height:basicHeight
  188. }
  189. } else {
  190. // console.log(123)
  191. // console.log((total > oneRowStd) ? (base.width / floor) : (base.width / total))
  192.  
  193. return {
  194. width:(total > oneRowStd) ? (base.width / floor) : (base.width / total) ,
  195. height:basicHeight
  196. }
  197. }
  198. }
  199. }
  200. }
  201.  
  202. theStyle = () => {
  203. const baseFlex = {
  204. display: 'flex',
  205. justifyContent: 'center',
  206. alignItems: 'center'
  207. }
  208. return {
  209. main:{
  210. ...baseFlex,
  211. width:'100%',
  212. height:'100%',
  213. color:"#fff"
  214. },
  215. tem:{
  216. ...baseFlex,
  217. flex:"auto",
  218. color:'#fff'
  219. },
  220. shellA:{
  221. ...baseFlex,
  222. width:'100%',
  223. height:'100%'
  224. },
  225. shellB:{
  226. ...baseFlex,
  227. width:'100%',
  228. height:'50%'
  229. }
  230. }
  231. }
  232.  
  233. getDataBar = () => {
  234. if (this.props.curcity && this.props.curcity === 'all') {
  235. return {
  236. data:{
  237. sData:[
  238. { name: 'a', data: 510 },
  239. { name: 'a', data: 46 },
  240. { name: 'a', data: 471 },
  241. { name: 'a', data: 631 },
  242. { name: 'a', data: 924 },
  243. { name: 'a', data: 582 },
  244. ]
  245. }
  246. }
  247. } else {
  248. return {
  249. data:{
  250. sData:[
  251. { name: 'a', data: 50 },
  252. { name: 'a', data: 469 },
  253. { name: 'a', data: 41 },
  254. { name: 'a', data: 31 },
  255. { name: 'a', data: 4 },
  256. { name: 'a', data: 825 },
  257. ]
  258. }
  259. }
  260. }
  261. }
  262.  
  263. getContainer = () => {
  264. const size = this.getSize()
  265.  
  266. if (!size) {
  267. return ""
  268. }
  269.  
  270. const theStyle = this.theStyle()
  271.  
  272. const dataBar = this.getDataBar()
  273.  
  274. const Container = ((dataBar) => {
  275.  
  276. const flexMatrix = [
  277. [0,0],
  278. [1,0],
  279. [2,0],
  280. [3,0],
  281. [2,2],
  282. [3,2],
  283. [3,3],
  284. [4,3],
  285. [4,4],
  286. [5,4],
  287. [5,5],
  288. [6,5],
  289. [6,6]
  290. ]
  291.  
  292. const sData = dataBar.data.sData
  293. const length = sData.length
  294.  
  295. const matrix = flexMatrix[length] ? flexMatrix[length] : flexMatrix[12]
  296.  
  297. if (matrix[0] === 0) {
  298. return ""
  299. }
  300.  
  301. let temShell, temA, temB
  302.  
  303. temA = sData.slice(0, matrix[0]).map((item, index) =>
  304. <div style={theStyle.tem} key={index.toString()}> <Polygon data={item} sn={index + 1} size={size(length, (index + 1))} /> </div>
  305. );
  306.  
  307. if (matrix[1] === 0) {
  308. temB = ""
  309. } else {
  310. temB = sData.slice(matrix[0], (matrix[0] + matrix[1])).map((item, index) =>
  311. <div style={theStyle.tem} key={index.toString()}> <Polygon data={item} sn={index + 1 + matrix[0]} size={size(length, (index + 1 + matrix[0]))} /> </div>
  312. );
  313. }
  314.  
  315. if (matrix[1] === 0) {
  316. temShell = <div style={theStyle.shellA} > {temA} </div>
  317. } else {
  318. temShell = [0,0].map((item, index) =>
  319. <div style={theStyle.shellB} key={"temShell" + index.toString()}> {index === 0 ? temA : temB} </div>
  320. );
  321.  
  322. document.getElementById('pc').style.flexWrap = "wrap"
  323. }
  324.  
  325. return temShell
  326. })(dataBar)
  327.  
  328. return Container
  329. }
  330.  
  331. render(){
  332.  
  333. const theStyle = this.theStyle()
  334. const curcity = this.state.curcity
  335.  
  336. // const dataBar = this.props.dataBar
  337.  
  338. return (
  339. <div style={theStyle.main} id="pc">
  340. { this.getContainer() }
  341. </div>
  342. );
  343. }
  344. }
  345.  
  346. export default polygonContainer

稍微说明一下。

  • getSize 是计算区块大小的方法。这个方法返回一个 size 方法,在 getContainer 方法中输出 JSX 的时候会调用 size 方法得到宽高。
  • 关于布局的问题(为什么写了个双层数组?)之前的文章里写过,不再赘述。
  • 关于数据绑定的机制。通过 props 来绑定。

以上。

react实战 : react 与 canvas的更多相关文章

  1. react实战 : react 与 svg

    有一个需求是这样的. 一个组件里若干个区块.区块数量不定. 区块里面是一个波浪效果组件,而这个一般用 SVG 做. 所以就变成了在 react 中使用 SVG 的问题. 首先是波浪效果需要的样式. . ...

  2. 【视频合集】极客时间 react实战进阶45讲 【更新中】

    https://up2.v.sharedaka.com/video/ochvq0AVfpa71A24bmugS5EewhFM1553702519936.mp4 01 React出现的历史背景及特性介绍 ...

  3. 《React Native 精解与实战》书籍连载「React 与 React Native 简介」

    此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...

  4. RN 实战 & React Native 实战

    RN 实战 & React Native 实战 https://abc.xgqfrms.xyz/react-native-docs/ 0.59 https://github.com/xgqfr ...

  5. easy canvas shape with react antdesign 简单的canvas图形in antd & react

    //show: //code: import React from "react" import {findDOMNode} from 'react-dom' import { B ...

  6. React实战之将数据库返回的时间转换为几分钟前、几小时前、几天前的形式。

    React实战之将数据库返回的时间转换为几分钟前.几小时前.几天前的形式. 不知道大家的时间格式是什么样子的,我先展示下我这里数据库返回的时间格式 ‘2019-05-05T15:52:19Z’ 是这个 ...

  7. React实战之Ant Design—Upload上传_附件上传

    React实战之Ant Design—Upload上传_附件上传 Upload组件大家都在官方文档中看过了,但写的时候还是会遇到许多问题,一些新手看了文档后感觉无从下手,本文过多的简绍就不说了,直接看 ...

  8. React实战之60s倒计时按钮(发送短信验证按钮)

    React实战之60s倒计时按钮——短信验证按钮 导入:(antd组件——Form表单) import { Button, Form, Input } from 'antd'; const FormI ...

  9. 从零开始react实战:云书签-1 react环境搭建

    总览篇:react 实战之云书签 本篇是实战系列的第一篇,主要是搭建 react 开发环境,在create-react-app的基础上加上如下功能: antd 组件库按需引入 ,支持主题定制 支持 l ...

随机推荐

  1. 人声提取工具Spleeter安装教程(linux)

    在安装之前,要确保运行Spleeter的计算机系统是64位,Spleeter不支持32位的系统.如何查看? 因为在linux环境下安装spleeter相对要简单很多,这篇教程先以Ubuntu20.04 ...

  2. LevelDB/Rocksdb 特性分析

    LevelDb是Google开源的嵌入式持久化KV 单机存储引擎.采用LSM(Log Structured Merge)tree的形式组织持久化存储的文件sstable.LSM会造成写放大.读放大的问 ...

  3. 004.OpenShift命令及故障排查

    一 CLI访问OpenShift资源 1.1 资源操作 OCP将OpenShift集群中的为由主节点管理的对象统称为资源,如:node.service.pod.project.deployment.u ...

  4. Java面试必备Springioc上

    配置文件中 Proprety name值必须和 类中的成员变量private IUsedao  userDao一一对应 工程项目的代码为:

  5. ceph SSD HDD分离与openstack调用

    本例子ceph L版本采用的是filestore,而不是bluestore. 一.查看class类型,只有一个hdd,.Luminous 为每个OSD添加了一个新的属性:设备类.默认情况下,OSD将根 ...

  6. Docker搭建Rancher平台

    sudo docker run -d --restart=unless-stopped -p 8080:8080 rancher/server service docker resatrt启动失败   ...

  7. html+css快速入门教程(3)

    练习: 1.画盒子 2.相框 5 基础选择器 5.1 id选择器 ID选择器与类选择器的定义与引用方式类似,只是定义的符号不一样.ID通常表示唯一值,因此,ID选择器在CSS 中通常只出现一次.如果出 ...

  8. MySQL 字符串索引优化方案

    字符串建立索引的优化 1. 建立前缀索引 假设建立一个支持邮箱登录的用户表,对于邮件字段来说,可以有以下几种建立索引的方式: 直接对整个字符串建立索引 alter table SUser add in ...

  9. C# wpf 实现 MD5加密解密 小工具

    源文件: http://pan.baidu.com/share/link?shareid=2038099474&uk=3912660076 MD5 C# 实现代码来源于网络,感谢原系作者! 参 ...

  10. postcss.config.js not found

    https://github.com/ElemeFE/element/issues/10249