react实战 : react 与 canvas

react

有一个需求是这样的。

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

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

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

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

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

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

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

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

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

import React from 'react'

class Polygon extends React.Component {}

class polygonContainer extends React.Component {}

export default polygonContainer

然后是 canvas 组件。

class Polygon extends React.Component {

constructor(props){

super(props)

this.state = {

}

}

componentDidMount() {

console.log("=== componentDidMount Polygon ===")

this.init(this.props.data, this.props.sn)

}

componentDidUpdate() {

console.log("=== componentDidUpdate Polygon ===")

this.init(this.props.data, this.props.sn)

}

init = (item, sn) => {

const getR = () => {

return Math.min(size.width, size.height) * 0.375

}

const getWordCoor = (index, centerCoor, sub, fontSize, fontLength) => {

const getXCoor = (index, centerCoor, fontSize, fontLength) => {

const standand = -1

return (centerCoor.x + fontLength / 2 * (index === 0 ? fontSize : (fontSize / 2)) * standand)

}

const getYCoor = (index, centerCoor, sub) => {

const standand = index === 0 ? -0.3 : 0.6

return (centerCoor.y + sub * standand)

}

console.log(getXCoor(index, centerCoor, fontSize, fontLength))

return {

x: getXCoor(index, centerCoor, fontSize, fontLength),

y: getYCoor(index, centerCoor, sub)

}

}

const getStrokeColor = (sn) => {

return sn === 5 ? 'rgb(255, 114, 0)' : 'rgb(232, 172, 4)'

}

const getFillColor = (sn) => {

return sn === 5 ? 'rgb(255, 192, 0)' : 'rgb(4, 154, 79)'

}

const canvas = document.getElementById("canvas" + sn);

const size = {

width: parseInt(this.props.size.width),

height: parseInt(this.props.size.height),

}

canvas.width = size.width;

canvas.height = size.height;

const cc = canvas.getContext("2d");

// 多边形

const coorArray = []

cc.beginPath();

for (var i = 0 ; i < 6 ; i++) {

var x = Math.cos((i * 60)/180 * Math.PI) * getR() + (size.width / 2) ;

var y = -Math.sin((i * 60)/180 * Math.PI) * getR() + (size.height / 2);

coorArray.push({x, y})

cc.lineTo(x,y);

}

cc.closePath();

cc.lineWidth = 2;

cc.fillStyle = getFillColor(sn);

cc.fill();

cc.strokeStyle = getStrokeColor(sn);

cc.stroke();

// 文字

const centerCoor = {

x: (coorArray[0].x + coorArray[3].x) / 2,

y: coorArray[0].y

}

const sub = coorArray[0].y - coorArray[1].y

const wordCoorArray = [

getWordCoor(0, centerCoor, sub, 14, item.name.length),

getWordCoor(1, centerCoor, sub, 20, item.data.toString().length)

]

cc.font="14px Arial"

cc.strokeStyle = "#fff";

cc.fillStyle = "#fff";

cc.fillText(item.name, wordCoorArray[0].x, wordCoorArray[0].y);

cc.font="20px Arial"

cc.fillText(item.data, wordCoorArray[1].x, wordCoorArray[1].y);

}

render(){

const item = this.props.data

const size = this.props.size

const sn = this.props.sn

const getColor = (item) => {

return item.color

}

return (

<canvas id={'canvas' + sn}></canvas>

);

}

}

有几点需要说明一下。

  • 因为 componentDidUpdate 钩子中 有 init 方法,所以 init 方法中不能再给 state 赋值,否则会触发无限循环。如果需要存值,则需要想别的办法。
  • getWordCoor 是计算文字位置的方法。六边形里面有文字内容。

  • canvas 对象是通过 document.getElementById 获取的,而一个页面中肯定有多个 canvas ,此时就必须做出区分。我的方法是传一个序列号 sn (index + 1),当然生成 ID 是更好的做法。
  • 响应式的样式对 canvas 是无效的。必须手动赋像素值。也就是说必须手动计算 size 。计算 size 的方法在父组件里面。
  • for 循环是用来绘制路径的,就是个数学问题,Math 对象里有三角函数简化了一些运算。顺便把中心点坐标和六边形各个点的坐标存了一下。
  • canvas 绘制方法不需要说了,百度一下即可。

然后是容器组件。

// 六边形测试

import React from 'react'

// import Styles from './polygonContainer.less'

class Polygon extends React.Component {

constructor(props){

super(props)

this.state = {

}

}

componentDidMount() {

console.log("=== componentDidMount Polygon ===")

this.init(this.props.data, this.props.sn)

}

componentDidUpdate() {

console.log("=== componentDidUpdate Polygon ===")

this.init(this.props.data, this.props.sn)

}

init = (item, sn) => {

// console.log(item)

// console.log(sn)

const getR = () => {

return Math.min(size.width, size.height) * 0.375

}

const getWordCoor = (index, centerCoor, sub, fontSize, fontLength) => {

const getXCoor = (index, centerCoor, fontSize, fontLength) => {

const standand = -1

return (centerCoor.x + fontLength / 2 * (index === 0 ? fontSize : (fontSize / 2)) * standand)

}

const getYCoor = (index, centerCoor, sub) => {

const standand = index === 0 ? -0.3 : 0.6

return (centerCoor.y + sub * standand)

}

console.log(getXCoor(index, centerCoor, fontSize, fontLength))

return {

x: getXCoor(index, centerCoor, fontSize, fontLength),

y: getYCoor(index, centerCoor, sub)

}

}

const getStrokeColor = (sn) => {

return sn === 5 ? 'rgb(255, 114, 0)' : 'rgb(232, 172, 4)'

}

const getFillColor = (sn) => {

return sn === 5 ? 'rgb(255, 192, 0)' : 'rgb(4, 154, 79)'

}

const canvas = document.getElementById("canvas" + sn);

const size = {

width: parseInt(this.props.size.width),

height: parseInt(this.props.size.height),

}

// console.log(size)

canvas.width = size.width;

canvas.height = size.height;

const cc = canvas.getContext("2d");

// 多边形

const coorArray = []

cc.beginPath();

for (var i = 0 ; i < 6 ; i++) {

var x = Math.cos((i * 60)/180 * Math.PI) * getR() + (size.width / 2) ;

var y = -Math.sin((i * 60)/180 * Math.PI) * getR() + (size.height / 2);

coorArray.push({x, y})

cc.lineTo(x,y);

}

cc.closePath();

cc.lineWidth = 2;

cc.fillStyle = getFillColor(sn);

cc.fill();

cc.strokeStyle = getStrokeColor(sn);

cc.stroke();

// 文字

const centerCoor = {

x: (coorArray[0].x + coorArray[3].x) / 2,

y: coorArray[0].y

}

const sub = coorArray[0].y - coorArray[1].y

// console.log(centerCoor)

// console.log(coorArray)

const wordCoorArray = [

getWordCoor(0, centerCoor, sub, 14, item.name.length),

getWordCoor(1, centerCoor, sub, 20, item.data.toString().length)

]

// console.log(wordCoorArray)

cc.font="14px Arial"

cc.strokeStyle = "#fff";

cc.fillStyle = "#fff";

cc.fillText(item.name, wordCoorArray[0].x, wordCoorArray[0].y);

cc.font="20px Arial"

cc.fillText(item.data, wordCoorArray[1].x, wordCoorArray[1].y);

}

render(){

const item = this.props.data

const size = this.props.size

const sn = this.props.sn

// console.log("Polygon render === ", size)

const getColor = (item) => {

return item.color

}

return (

<canvas id={'canvas' + sn}></canvas>

// <div>asd</div>

);

}

}

class polygonContainer extends React.Component {

constructor(props){

super(props)

this.state = {

curcity:""

}

}

componentDidMount() {

console.log("componentDidMount")

console.log(new Date().getTime())

this.setState({

curcity:this.props.curcity

})

}

componentDidUpdate(){

console.log("componentDidUpdate")

console.log(new Date().getTime())

}

// total 总数 SN 序列号

getSize = () => {

const pc = document.getElementById('pc')

if (!pc) {

return null

} else {

// const length = this.getDataBar().data.sData.length

const base = {

width:document.getElementById('pc').offsetWidth,

height:document.getElementById('pc').offsetHeight

}

return function (total, SN) {

// console.log(base)

const standand = 2

const oneRowStd = 3

const ceil = Math.ceil(total / standand)

const floor = Math.floor(total / standand)

const basicHeight = (total > oneRowStd) ? (base.height / standand) : (base.height)

// console.log(ceil, floor)

// console.log(total, SN)

if (SN <= ceil) {

return {

width:(total > oneRowStd) ? (base.width / ceil) : (base.width / total),

height:basicHeight

}

} else {

// console.log(123)

// console.log((total > oneRowStd) ? (base.width / floor) : (base.width / total))

return {

width:(total > oneRowStd) ? (base.width / floor) : (base.width / total) ,

height:basicHeight

}

}

}

}

}

theStyle = () => {

const baseFlex = {

display: 'flex',

justifyContent: 'center',

alignItems: 'center'

}

return {

main:{

...baseFlex,

width:'100%',

height:'100%',

color:"#fff"

},

tem:{

...baseFlex,

flex:"auto",

color:'#fff'

},

shellA:{

...baseFlex,

width:'100%',

height:'100%'

},

shellB:{

...baseFlex,

width:'100%',

height:'50%'

}

}

}

getDataBar = () => {

if (this.props.curcity && this.props.curcity === 'all') {

return {

data:{

sData:[

{ name: 'a', data: 510 },

{ name: 'a', data: 46 },

{ name: 'a', data: 471 },

{ name: 'a', data: 631 },

{ name: 'a', data: 924 },

{ name: 'a', data: 582 },

]

}

}

} else {

return {

data:{

sData:[

{ name: 'a', data: 50 },

{ name: 'a', data: 469 },

{ name: 'a', data: 41 },

{ name: 'a', data: 31 },

{ name: 'a', data: 4 },

{ name: 'a', data: 825 },

]

}

}

}

}

getContainer = () => {

const size = this.getSize()

if (!size) {

return ""

}

const theStyle = this.theStyle()

const dataBar = this.getDataBar()

const Container = ((dataBar) => {

const flexMatrix = [

[0,0],

[1,0],

[2,0],

[3,0],

[2,2],

[3,2],

[3,3],

[4,3],

[4,4],

[5,4],

[5,5],

[6,5],

[6,6]

]

const sData = dataBar.data.sData

const length = sData.length

const matrix = flexMatrix[length] ? flexMatrix[length] : flexMatrix[12]

if (matrix[0] === 0) {

return ""

}

let temShell, temA, temB

temA = sData.slice(0, matrix[0]).map((item, index) =>

<div style={theStyle.tem} key={index.toString()}> <Polygon data={item} sn={index + 1} size={size(length, (index + 1))} /> </div>

);

if (matrix[1] === 0) {

temB = ""

} else {

temB = sData.slice(matrix[0], (matrix[0] + matrix[1])).map((item, index) =>

<div style={theStyle.tem} key={index.toString()}> <Polygon data={item} sn={index + 1 + matrix[0]} size={size(length, (index + 1 + matrix[0]))} /> </div>

);

}

if (matrix[1] === 0) {

temShell = <div style={theStyle.shellA} > {temA} </div>

} else {

temShell = [0,0].map((item, index) =>

<div style={theStyle.shellB} key={"temShell" + index.toString()}> {index === 0 ? temA : temB} </div>

);

document.getElementById('pc').style.flexWrap = "wrap"

}

return temShell

})(dataBar)

return Container

}

render(){

const theStyle = this.theStyle()

const curcity = this.state.curcity

// const dataBar = this.props.dataBar

return (

<div style={theStyle.main} >

{ this.getContainer() }

</div>

);

}

}

export default polygonContainer

稍微说明一下。

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

以上。

以上是 react实战 : react 与 canvas 的全部内容, 来源链接: utcz.com/z/383276.html

回到顶部