index.js 5.5 KB
Newer Older
1
import React, { PureComponent } from 'react';
niko's avatar
niko committed
2
import autoHeight from '../autoHeight';
3 4 5
import styles from './index.less';

/* eslint no-return-assign: 0 */
niko's avatar
niko committed
6
/* eslint no-mixed-operators: 0 */
7 8
// riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90

niko's avatar
niko committed
9
@autoHeight()
afc163's avatar
afc163 committed
10
class WaterWave extends PureComponent {
11 12
  state = {
    radio: 1,
niko's avatar
niko committed
13
  };
14 15

  componentDidMount() {
16 17
    this.renderChart();
    this.resize();
陈帅's avatar
陈帅 committed
18 19 20 21 22 23 24
    window.addEventListener(
      'resize',
      () => {
        requestAnimationFrame(() => this.resize());
      },
      { passive: true }
    );
25 26
  }

陈帅's avatar
陈帅 committed
27 28 29
  componentDidUpdate(props) {
    const { percent } = this.props;
    if (props.percent !== percent) {
陈帅's avatar
陈帅 committed
30 31
      // 不加这个会造成绘制缓慢
      this.renderChart('update');
陈帅's avatar
陈帅 committed
32 33 34
    }
  }

nikogu's avatar
nikogu committed
35 36 37 38 39 40 41 42 43
  componentWillUnmount() {
    cancelAnimationFrame(this.timer);
    if (this.node) {
      this.node.innerHTML = '';
    }
    window.removeEventListener('resize', this.resize);
  }

  resize = () => {
陈帅's avatar
陈帅 committed
44 45 46 47 48 49 50
    if (this.root) {
      const { height } = this.props;
      const { offsetWidth } = this.root.parentNode;
      this.setState({
        radio: offsetWidth < height ? offsetWidth / height : 1,
      });
    }
niko's avatar
niko committed
51
  };
52

陈帅's avatar
陈帅 committed
53
  renderChart(type) {
afc163's avatar
afc163 committed
54
    const { percent, color = '#1890FF' } = this.props;
55
    const data = percent / 100;
nikogu's avatar
nikogu committed
56
    const self = this;
陈帅's avatar
陈帅 committed
57
    cancelAnimationFrame(this.timer);
58

59
    if (!this.node || (data !== 0 && !data)) {
60 61 62 63 64 65 66 67 68
      return;
    }

    const canvas = this.node;
    const ctx = canvas.getContext('2d');
    const canvasWidth = canvas.width;
    const canvasHeight = canvas.height;
    const radius = canvasWidth / 2;
    const lineWidth = 2;
niko's avatar
niko committed
69
    const cR = radius - lineWidth;
70 71

    ctx.beginPath();
afc163's avatar
afc163 committed
72
    ctx.lineWidth = lineWidth * 2;
73

niko's avatar
niko committed
74
    const axisLength = canvasWidth - lineWidth;
75 76 77 78 79 80 81 82 83
    const unit = axisLength / 8;
    const range = 0.2; // 振幅
    let currRange = range;
    const xOffset = lineWidth;
    let sp = 0; // 周期偏移量
    let currData = 0;
    const waveupsp = 0.005; // 水波上涨速度

    let arcStack = [];
niko's avatar
niko committed
84
    const bR = radius - lineWidth;
85 86 87
    const circleOffset = -(Math.PI / 2);
    let circleLock = true;

niko's avatar
niko committed
88 89
    for (let i = circleOffset; i < circleOffset + 2 * Math.PI; i += 1 / (8 * Math.PI)) {
      arcStack.push([radius + bR * Math.cos(i), radius + bR * Math.sin(i)]);
90 91 92 93 94 95 96 97 98 99 100 101
    }

    const cStartPoint = arcStack.shift();
    ctx.strokeStyle = color;
    ctx.moveTo(cStartPoint[0], cStartPoint[1]);

    function drawSin() {
      ctx.beginPath();
      ctx.save();

      const sinStack = [];
      for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) {
niko's avatar
niko committed
102
        const x = sp + (xOffset + i) / unit;
103 104
        const y = Math.sin(x) * currRange;
        const dx = i;
niko's avatar
niko committed
105
        const dy = 2 * cR * (1 - currData) + (radius - cR) - unit * y;
106 107 108 109 110 111 112 113 114 115

        ctx.lineTo(dx, dy);
        sinStack.push([dx, dy]);
      }

      const startPoint = sinStack.shift();

      ctx.lineTo(xOffset + axisLength, canvasHeight);
      ctx.lineTo(xOffset, canvasHeight);
      ctx.lineTo(startPoint[0], startPoint[1]);
afc163's avatar
afc163 committed
116

afc163's avatar
afc163 committed
117
      const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight);
afc163's avatar
afc163 committed
118
      gradient.addColorStop(0, '#ffffff');
陈帅's avatar
陈帅 committed
119
      gradient.addColorStop(1, color);
afc163's avatar
afc163 committed
120
      ctx.fillStyle = gradient;
121 122 123 124 125 126
      ctx.fill();
      ctx.restore();
    }

    function render() {
      ctx.clearRect(0, 0, canvasWidth, canvasHeight);
陈帅's avatar
陈帅 committed
127
      if (circleLock && type !== 'update') {
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
        if (arcStack.length) {
          const temp = arcStack.shift();
          ctx.lineTo(temp[0], temp[1]);
          ctx.stroke();
        } else {
          circleLock = false;
          ctx.lineTo(cStartPoint[0], cStartPoint[1]);
          ctx.stroke();
          arcStack = null;

          ctx.globalCompositeOperation = 'destination-over';
          ctx.beginPath();
          ctx.lineWidth = lineWidth;
          ctx.arc(radius, radius, bR, 0, 2 * Math.PI, 1);

          ctx.beginPath();
          ctx.save();
niko's avatar
niko committed
145
          ctx.arc(radius, radius, radius - 3 * lineWidth, 0, 2 * Math.PI, 1);
146 147 148

          ctx.restore();
          ctx.clip();
陈帅's avatar
陈帅 committed
149
          ctx.fillStyle = color;
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
        }
      } else {
        if (data >= 0.85) {
          if (currRange > range / 4) {
            const t = range * 0.01;
            currRange -= t;
          }
        } else if (data <= 0.1) {
          if (currRange < range * 1.5) {
            const t = range * 0.01;
            currRange += t;
          }
        } else {
          if (currRange <= range) {
            const t = range * 0.01;
            currRange += t;
          }
          if (currRange >= range) {
            const t = range * 0.01;
            currRange -= t;
          }
        }
niko's avatar
niko committed
172
        if (data - currData > 0) {
173 174
          currData += waveupsp;
        }
niko's avatar
niko committed
175
        if (data - currData < 0) {
176 177 178 179 180 181
          currData -= waveupsp;
        }

        sp += 0.07;
        drawSin();
      }
nikogu's avatar
nikogu committed
182
      self.timer = requestAnimationFrame(render);
183 184 185 186 187 188 189 190
    }
    render();
  }

  render() {
    const { radio } = this.state;
    const { percent, title, height } = this.props;
    return (
niko's avatar
niko committed
191 192 193 194 195
      <div
        className={styles.waterWave}
        ref={n => (this.root = n)}
        style={{ transform: `scale(${radio})` }}
      >
afc163's avatar
afc163 committed
196 197 198 199 200 201 202 203
        <div style={{ width: height, height, overflow: 'hidden' }}>
          <canvas
            className={styles.waterWaveCanvasWrapper}
            ref={n => (this.node = n)}
            width={height * 2}
            height={height * 2}
          />
        </div>
204
        <div className={styles.text} style={{ width: height }}>
niko's avatar
niko committed
205
          {title && <span>{title}</span>}
愚道's avatar
愚道 committed
206
          <h4>{percent}%</h4>
207 208 209 210 211
        </div>
      </div>
    );
  }
}
陈帅's avatar
陈帅 committed
212 213

export default WaterWave;