1. import React, {createRef, PureComponent} from 'react';
    
  2. 
    
  3. const SPEED = 0.003 / Math.PI;
    
  4. const FRAMES = 10;
    
  5. 
    
  6. export default class Clock extends PureComponent {
    
  7.   faceRef = createRef();
    
  8.   arcGroupRef = createRef();
    
  9.   clockHandRef = createRef();
    
  10.   frame = null;
    
  11.   hitCounter = 0;
    
  12.   rotation = 0;
    
  13.   t0 = Date.now();
    
  14.   arcs = [];
    
  15. 
    
  16.   animate = () => {
    
  17.     const now = Date.now();
    
  18.     const td = now - this.t0;
    
  19.     this.rotation = (this.rotation + SPEED * td) % (2 * Math.PI);
    
  20.     this.t0 = now;
    
  21. 
    
  22.     this.arcs.push({rotation: this.rotation, td});
    
  23. 
    
  24.     let lx, ly, tx, ty;
    
  25.     if (this.arcs.length > FRAMES) {
    
  26.       this.arcs.forEach(({rotation, td}, i) => {
    
  27.         lx = tx;
    
  28.         ly = ty;
    
  29.         const r = 145;
    
  30.         tx = 155 + r * Math.cos(rotation);
    
  31.         ty = 155 + r * Math.sin(rotation);
    
  32.         const bigArc = SPEED * td < Math.PI ? '0' : '1';
    
  33.         const path = `M${tx} ${ty}A${r} ${r} 0 ${bigArc} 0 ${lx} ${ly}L155 155`;
    
  34.         const hue = 120 - Math.min(120, td / 4);
    
  35.         const colour = `hsl(${hue}, 100%, ${60 - i * (30 / FRAMES)}%)`;
    
  36.         if (i !== 0) {
    
  37.           const arcEl = this.arcGroupRef.current.children[i - 1];
    
  38.           arcEl.setAttribute('d', path);
    
  39.           arcEl.setAttribute('fill', colour);
    
  40.         }
    
  41.       });
    
  42.       this.clockHandRef.current.setAttribute('d', `M155 155L${tx} ${ty}`);
    
  43.       this.arcs.shift();
    
  44.     }
    
  45. 
    
  46.     if (this.hitCounter > 0) {
    
  47.       this.faceRef.current.setAttribute(
    
  48.         'fill',
    
  49.         `hsla(0, 0%, ${this.hitCounter}%, 0.95)`
    
  50.       );
    
  51.       this.hitCounter -= 1;
    
  52.     } else {
    
  53.       this.hitCounter = 0;
    
  54.       this.faceRef.current.setAttribute('fill', 'hsla(0, 0%, 5%, 0.95)');
    
  55.     }
    
  56. 
    
  57.     this.frame = requestAnimationFrame(this.animate);
    
  58.   };
    
  59. 
    
  60.   componentDidMount() {
    
  61.     this.frame = requestAnimationFrame(this.animate);
    
  62.     if (this.faceRef.current) {
    
  63.       this.faceRef.current.addEventListener('click', this.handleClick);
    
  64.     }
    
  65.   }
    
  66. 
    
  67.   componentDidUpdate() {
    
  68.     console.log('componentDidUpdate()', this.faceRef.current);
    
  69.   }
    
  70. 
    
  71.   componentWillUnmount() {
    
  72.     this.faceRef.current.removeEventListener('click', this.handleClick);
    
  73.     if (this.frame) {
    
  74.       cancelAnimationFrame(this.frame);
    
  75.     }
    
  76.   }
    
  77. 
    
  78.   handleClick = e => {
    
  79.     e.stopPropagation();
    
  80.     this.hitCounter = 50;
    
  81.   };
    
  82. 
    
  83.   render() {
    
  84.     const paths = new Array(FRAMES);
    
  85.     for (let i = 0; i < FRAMES; i++) {
    
  86.       paths.push(<path className="arcHand" key={i} />);
    
  87.     }
    
  88.     return (
    
  89.       <div className="stutterer">
    
  90.         <svg height="310" width="310">
    
  91.           <circle
    
  92.             className="clockFace"
    
  93.             onClick={this.handleClick}
    
  94.             cx={155}
    
  95.             cy={155}
    
  96.             r={150}
    
  97.             ref={this.faceRef}
    
  98.           />
    
  99.           <g ref={this.arcGroupRef}>{paths}</g>
    
  100.           <path className="clockHand" ref={this.clockHandRef} />
    
  101.         </svg>
    
  102.       </div>
    
  103.     );
    
  104.   }
    
  105. }