react + typescript+ canvas 绘制圆环表 原创

xjj_xnkl
发布于 2022-6-1 10:17
浏览
2收藏

绘制一个圆环表

​ 经过上章介绍,接下来我们尝试在 react + typescript 项目中,做一个简单的圆环表

1. 首先我们在项目中建起一个 tsx 文件, 并引入它的外部样式(使用 css 模块化)

// cricleClock.tsx 文件
import styles from './index.module.scss'
export default function CricleClock() {
  return (
    <div className={styles.container}>
      <canvas></canvas>
    </div>
  )
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
// index.module.scss 文件
.container {
  width: 100%;
  height: 100vh;
  background-color: rgba(0, 0, 0, 0.8);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

2. 使用 hook 获取 canvas 对象

// 这里使用 useRef 钩子,保证函数组件重新渲染后只创建一次对象,返回相同的引用
const canvasRef = useRef<HTMLCanvasElement>(null)
  • 1.
  • 2.

3. 使用 hook 获取,获取画布上下文

// 函数组件中执行副作用操作
//例如数据请求、直接手动修改 DOM 节点、直接操作页面「修改页面标题等」、记录日志等都是副作用操作
// 第一个参数就是副作用函数 effect
// 第二个参数表示依赖项,是一个可选参数。当不传入该参数时,每次 UI 渲染 effect 函数都会执行
useEffect(() => {
  const canvas = initCanvas()
  canvasWidth = canvas.width
  canvasHeight = canvas.height
  // 强转 canvas.getContext 返回值,过滤null 值的返回可能
  ctx = canvas.getContext('2d') as CanvasRenderingContext2D
})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

4. 使用 arc 分别画出时分秒画对应的背景圆环

// 申明一个类型别名
type Circle = {
  x: number
  y: number
  r: number
  sAngle: number
  eAngle: number
  color?: string // ?: 表示属性可选
}
function drawCircle({ x, y, r, sAngle, eAngle, color = 'red' }: Circle) {
  ctx.beginPath()
  ctx.arc(x, y, r, sAngle, eAngle)
  // 线宽
  ctx.lineWidth = 8
  // 线帽类型
  ctx.lineCap = 'round'
  // 颜色样式
  ctx.strokeStyle = color
  ctx.stroke()
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

5. 获取时分秒,并使用 fillText 绘制文本

// 获取当前时间,并返回时分秒
function getCurrentTime() {
  const date = new Date()
  return [date.getHours(), date.getMinutes(), date.getSeconds()]
}
function drawText(text: string, x: number, y: number) {
  // 设置文本样式
  ctx.font = '60px 宋体'
  // 文字水平居中
  ctx.textAlign = 'center'
  // 文字垂直居中
  ctx.textBaseline = 'middle'
  ctx.fillText(text, x, y)
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

画布图像

react + typescript+ canvas 绘制圆环表-鸿蒙开发者社区

6. 绘制时分秒弧度

// 角度转换弧度
function convertToArc(angle: number) {
  return (angle * Math.PI) / 180
}
function draw() {
  // 绘制文本
  const [hh, mm, ss] = getCurrentTime()
  const x = canvasWidth / 2
  const y = canvasHeight / 2
  drawText(`${repairZero(hh)}:${repairZero(mm)}:${repairZero(ss)}`, x, y)
  const radiusArray = [200, 175, 150]
  const colorArray = ['#2ecc71', '#e67e22', '#a29bfe']
  // 60 秒 = 一圈 = 360°
  const secondsArc = convertToArc(ss * (360 / 60)) - Math.PI / 2
  // 60 分 = 一圈 = 360°
  const minutesArc = convertToArc(mm * (360 / 60)) - Math.PI / 2 + secondsArc / 60
  // 12 小时 = 一圈 = 360°,24小时 = 两圈 所以要对 12 进行取余操作
  const hoursArc = convertToArc((hh % 12) * (360 / 12)) - Math.PI / 2 + minutesArc / 60
  const arcArray = [secondsArc, minutesArc, hoursArc]
  let circle: Circle
  // 绘制圆
  radiusArray.forEach((r, i) => {
    circle = {
      x,
      y,
      r,
      sAngle: 0,
      eAngle: Math.PI * 2,
      color: '#999'
    }
    // 画时分秒 圆环背景
    drawCircle(circle)
    // 通过更改开始和结束角度画出对应圆弧
    circle.sAngle = -Math.PI / 2
    circle.eAngle = arcArray[i]
    circle.color = colorArray[i]
    drawCircle(circle)
  })
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

画布图像

react + typescript+ canvas 绘制圆环表-鸿蒙开发者社区

7. 使用程序帧 requestAnimationFrame 让画布动起来,完整代码

import { useEffect, useRef } from 'react'
import styles from './index.module.scss'

type Circle = {
  x: number
  y: number
  r: number
  sAngle: number
  eAngle: number
  color?: string
}

export default function CricleClock() {
  // 这里使用 useRef 钩子,保证函数组件重新渲染后只创建一次对象,返回相同的引用
  const wrapRef = useRef<HTMLDivElement>(null)
  const canvasRef = useRef<HTMLCanvasElement>(null)
  let canvasHeight: number
  let canvasWidth: number
  let ctx: CanvasRenderingContext2D

  // 函数组件中执行副作用操作
  //例如数据请求、直接手动修改 DOM 节点、直接操作页面「修改页面标题等」、记录日志等都是副作用操作
  // 第一个参数就是副作用函数 effect
  // 第二个参数表示依赖项,是一个可选参数。当不传入该参数时,每次 UI 渲染 effect 函数都会执行
  useEffect(() => {
    const canvas = initCanvas()
    canvasWidth = canvas.width
    canvasHeight = canvas.height
    // 强转 canvas.getContext 返回值,过滤null 值的返回可能
    ctx = canvas.getContext('2d') as CanvasRenderingContext2D
    animate()
  })
  function drawText(text: string, x: number, y: number) {
    // 设置文本样式
    ctx.font = '60px 宋体'
    // 文字水平居中
    ctx.textAlign = 'center'
    // 文字垂直居中
    ctx.textBaseline = 'middle'
    ctx.fillText(text, x, y)
  }
  function drawCircle({ x, y, r, sAngle, eAngle, color = 'red' }: Circle) {
    ctx.beginPath()
    ctx.arc(x, y, r, sAngle, eAngle)
    // 线宽
    ctx.lineWidth = 8
    // 线帽类型
    ctx.lineCap = 'round'
    // 颜色
    ctx.strokeStyle = color
    ctx.stroke()
  }
  // 角度转换弧度
  function convertToArc(angle: number) {
    return (angle * Math.PI) / 180
  }

  /**
   * 补零,传入一个number 或者 string, 这里使用泛型 T 表示
   * @param text
   * @returns 通过类型转换成string, 提取字符串的某一部分
   */
  function repairZero<T>(text: T) {
    return ('0' + text).slice(-2)
  }
  // 获取当前时间,并返回时分秒
  function getCurrentTime() {
    const date = new Date()
    return [date.getHours(), date.getMinutes(), date.getSeconds()]
  }
  function draw() {
    // 多次绘制,清除画布
    ctx.clearRect(0, 0, canvasWidth, canvasHeight)
    // 绘制文本
    const [hh, mm, ss] = getCurrentTime()
    const x = canvasWidth / 2
    const y = canvasHeight / 2
    drawText(`${repairZero(hh)}:${repairZero(mm)}:${repairZero(ss)}`, x, y)
    const radiusArray = [200, 175, 150]
    const colorArray = ['#2ecc71', '#e67e22', '#a29bfe']
    // milliseconds 能让弧度变化更自然
    const milliseconds = new Date().getMilliseconds() / 1000
    // 60 秒 = 一圈 = 360°
    const secondsArc = convertToArc((ss + milliseconds) * (360 / 60)) - Math.PI / 2
    // 60 分 = 一圈 = 360°
    const minutesArc = convertToArc(mm * (360 / 60)) - Math.PI / 2 + secondsArc / 60
    // 12 小时 = 一圈 = 360°,24小时 = 两圈 所以要对 12 进行取余操作
    const hoursArc = convertToArc((hh % 12) * (360 / 12)) - Math.PI / 2 + minutesArc / 60

    const arcArray = [secondsArc, minutesArc, hoursArc]
    let circle: Circle
    // 绘制圆
    radiusArray.forEach((r, i) => {
      circle = {
        x,
        y,
        r,
        sAngle: 0,
        eAngle: Math.PI * 2,
        color: '#999'
      }
      // 画时分秒 圆环背景
      drawCircle(circle)
      // 通过更改开始和结束角度画出对应圆弧
      circle.sAngle = -Math.PI / 2
      circle.eAngle = arcArray[i]
      circle.color = colorArray[i]
      drawCircle(circle)
    })
  }
  function animate() {
    draw()
    window.requestAnimationFrame(animate)
  }
  function initCanvas() {
    // 强转类型
    const wrap = wrapRef.current as HTMLDivElement
    const canvas = canvasRef.current as HTMLCanvasElement
    canvas.width = wrap.clientWidth
    canvas.height = wrap.clientHeight
    return canvas
  }

  return (
    <div className={styles.container} ref={wrapRef}>
      <canvas ref={canvasRef}></canvas>
    </div>
  )
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.

画布图像

react + typescript+ canvas 绘制圆环表-鸿蒙开发者社区

end …

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
已于2022-6-1 14:45:33修改
11
收藏 2
回复
举报
11
8
2
8条回复
按时间正序
/
按时间倒序
物联风景
物联风景

我看了好久这个typycropt,以为又出了什么新语言了

回复
2022-6-1 10:57:13
xjj_xnkl
xjj_xnkl 回复了 物联风景
我看了好久这个typycropt,以为又出了什么新语言了

哈哈,打错了,英语不好

回复
2022-6-1 14:46:31
xjj前端妹_雅
xjj前端妹_雅

现成的canvas画布组件太多,自己使用canvas画图 还没尝试过,有时间可琢磨下原理 自己画一个~mark

 

回复
2022-6-1 15:04:14
xjj_snntH
xjj_snntH

666

回复
2022-6-1 15:06:31
xjjFeRookie
xjjFeRookie

不错,听说 react 比 vue 灵活,找时间学学

回复
2022-6-1 15:07:27
xjj_xnkl
xjj_xnkl 回复了 xjj前端妹_雅
现成的canvas画布组件太多,自己使用canvas画图 还没尝试过,有时间可琢磨下原理 自己画一个~mark

在大佬面前献丑了

 

回复
2022-6-1 19:03:14
xjj前端妹_雅
xjj前端妹_雅 回复了 xjj_xnkl
在大佬面前献丑了

大佬谦虚了

回复
2022-6-2 08:47:31
mb6297759536c8f
mb6297759536c8f

666

回复
2022-6-2 08:56:09


回复
    相关推荐