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>
)
}
// index.module.scss 文件
.container {
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
}
2. 使用 hook
获取 canvas
对象
// 这里使用 useRef 钩子,保证函数组件重新渲染后只创建一次对象,返回相同的引用
const canvasRef = useRef<HTMLCanvasElement>(null)
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
})
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()
}
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)
}
画布图像
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)
})
}
画布图像
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>
)
}
画布图像
end …
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
已于2022-6-1 14:45:33修改
赞
11
收藏 2
回复
相关推荐
我看了好久这个typycropt,以为又出了什么新语言了
哈哈,打错了,英语不好
现成的canvas画布组件太多,自己使用canvas画图 还没尝试过,有时间可琢磨下原理 自己画一个~mark
666
不错,听说 react 比 vue 灵活,找时间学学
在大佬面前献丑了
大佬谦虚了
666