• react实战 : react 与 canvas


    有一个需求是这样的。

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

    区块里面是一个正六边形组件,而这个用 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 = {
           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 = {
           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 = {
            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 {
                (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 {
                (total > oneRowStd) ? (base.width / floor) : (base.width / total) ,
                height:basicHeight
              }
            }
          }
        }
      }
    
      theStyle = () => {
        const baseFlex = {
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center'
        }
        return {
          main:{
            ...baseFlex,
            '100%',
            height:'100%',
            color:"#fff"
          },
          tem:{
            ...baseFlex,
            flex:"auto",
            color:'#fff'
          },
          shellA:{
            ...baseFlex,
            '100%',
            height:'100%'
          },
          shellB:{
            ...baseFlex,
            '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} id="pc">
            { this.getContainer() }
          </div>
        );
      }
    }
    
    export default polygonContainer

    稍微说明一下。

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

    以上。

  • 相关阅读:
    2017暑期集训Day 1
    17-06-28模拟赛
    17-06-26模拟赛
    平衡树学习笔记
    指针学习笔记
    17-06-14模拟赛
    17-06-11模拟赛
    17-06-02模拟赛
    17-05-31模拟赛
    培训补坑(day1:最短路&two-sat)
  • 原文地址:https://www.cnblogs.com/foxcharon/p/11936567.html
Copyright © 2020-2023  润新知