cbvirtua 1.0.47 → 1.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Signature.vue +127 -0
- package/canvas-example-main/canvas-example-main/.github/workflows/main.yml +62 -0
- package/canvas-example-main/canvas-example-main/README.md +13 -0
- package/canvas-example-main/canvas-example-main/curved.html +52 -0
- package/canvas-example-main/canvas-example-main/eslint.config.js +30 -0
- package/canvas-example-main/canvas-example-main/index.html +13 -0
- package/canvas-example-main/canvas-example-main/package.json +51 -0
- package/canvas-example-main/canvas-example-main/pnpm-lock.yaml +4760 -0
- package/canvas-example-main/canvas-example-main/postcss.config.js +6 -0
- package/canvas-example-main/canvas-example-main/public/vite.svg +1 -0
- package/canvas-example-main/canvas-example-main/src/App.tsx +17 -0
- package/canvas-example-main/canvas-example-main/src/assets/github.svg +1 -0
- package/canvas-example-main/canvas-example-main/src/assets/react.svg +1 -0
- package/canvas-example-main/canvas-example-main/src/components/Iconfont/demo.css +539 -0
- package/canvas-example-main/canvas-example-main/src/components/Iconfont/demo_index.html +418 -0
- package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.css +55 -0
- package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.js +1 -0
- package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.json +79 -0
- package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.ttf +0 -0
- package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.woff +0 -0
- package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.woff2 +0 -0
- package/canvas-example-main/canvas-example-main/src/components/Iconfont/index.tsx +39 -0
- package/canvas-example-main/canvas-example-main/src/main.css +9 -0
- package/canvas-example-main/canvas-example-main/src/main.tsx +10 -0
- package/canvas-example-main/canvas-example-main/src/pages/2048/g2048.ts +14 -0
- package/canvas-example-main/canvas-example-main/src/pages/2048/index.tsx +21 -0
- package/canvas-example-main/canvas-example-main/src/pages/clock/index.tsx +103 -0
- package/canvas-example-main/canvas-example-main/src/pages/demo/index.tsx +21 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/components/editor/index.module.less +3 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/components/editor/index.tsx +99 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/components/header/index.module.less +5 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/components/header/index.tsx +5 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/components/material/index.module.less +59 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/components/material/index.tsx +85 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/components/setting/index.module.less +7 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/components/setting/index.tsx +5 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/core/application.ts +35 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/core/cmp/base.ts +17 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/core/cmp/factory.ts +14 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/core/cmp/shape.tsx +43 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/core/editor.ts +61 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/core/type.ts +6 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/index.module.less +7 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/index.tsx +32 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/store/component-config.ts +61 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/store/components.ts +43 -0
- package/canvas-example-main/canvas-example-main/src/pages/editor/store/layout.ts +40 -0
- package/canvas-example-main/canvas-example-main/src/pages/home/index.tsx +59 -0
- package/canvas-example-main/canvas-example-main/src/pages/jigsaw/index.tsx +3 -0
- package/canvas-example-main/canvas-example-main/src/pages/minesweeper/bomber.png +0 -0
- package/canvas-example-main/canvas-example-main/src/pages/minesweeper/index.tsx +138 -0
- package/canvas-example-main/canvas-example-main/src/pages/minesweeper/mark.png +0 -0
- package/canvas-example-main/canvas-example-main/src/pages/minesweeper/minesweeper.ts +345 -0
- package/canvas-example-main/canvas-example-main/src/pages/minesweeper/utils.ts +24 -0
- package/canvas-example-main/canvas-example-main/src/pages/pageflip/index.tsx +200 -0
- package/canvas-example-main/canvas-example-main/src/pages/pageflip/page1.jpg +0 -0
- package/canvas-example-main/canvas-example-main/src/pages/practice/draw/index.ts +367 -0
- package/canvas-example-main/canvas-example-main/src/pages/practice/index.module.less +26 -0
- package/canvas-example-main/canvas-example-main/src/pages/practice/index.tsx +54 -0
- package/canvas-example-main/canvas-example-main/src/pages/shape-editor/control.ts +174 -0
- package/canvas-example-main/canvas-example-main/src/pages/shape-editor/editor.ts +91 -0
- package/canvas-example-main/canvas-example-main/src/pages/shape-editor/index.raw.tsx +159 -0
- package/canvas-example-main/canvas-example-main/src/pages/shape-editor/index.tsx +36 -0
- package/canvas-example-main/canvas-example-main/src/pages/shape-editor/shape.ts +248 -0
- package/canvas-example-main/canvas-example-main/src/router.tsx +53 -0
- package/canvas-example-main/canvas-example-main/src/utils/storage.ts +48 -0
- package/canvas-example-main/canvas-example-main/src/vite-env.d.ts +1 -0
- package/canvas-example-main/canvas-example-main/tailwind.config.js +8 -0
- package/canvas-example-main/canvas-example-main/tsconfig.app.json +30 -0
- package/canvas-example-main/canvas-example-main/tsconfig.json +7 -0
- package/canvas-example-main/canvas-example-main/tsconfig.node.json +22 -0
- package/canvas-example-main/canvas-example-main/vite.config.ts +18 -0
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function create2dimensionArr(x: number, y: number, defaultValue?: any) {
|
|
2
|
+
const arr = new Array(x);
|
|
3
|
+
for (let i = 0; i < y; i++) arr[i] = new Array(y).fill(defaultValue);
|
|
4
|
+
return arr;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getTextColor(num: number) {
|
|
8
|
+
const colors = [
|
|
9
|
+
'#0000FF',
|
|
10
|
+
'#008000',
|
|
11
|
+
'#FF0000',
|
|
12
|
+
'#00008B',
|
|
13
|
+
'#8B0000',
|
|
14
|
+
'#FFA500',
|
|
15
|
+
'#000000',
|
|
16
|
+
' #808080',
|
|
17
|
+
];
|
|
18
|
+
return colors[num - 1];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getDelayTime(step: number) {
|
|
22
|
+
return Math.log(step) * 0.08;
|
|
23
|
+
}
|
|
24
|
+
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import page1 from './page1.jpg';
|
|
3
|
+
|
|
4
|
+
export default function Page() {
|
|
5
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!canvasRef.current) return;
|
|
9
|
+
const ctx = canvasRef.current?.getContext('2d');
|
|
10
|
+
if (!ctx) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const page1Img = new Image();
|
|
14
|
+
page1Img.src = page1;
|
|
15
|
+
|
|
16
|
+
const W = 500,
|
|
17
|
+
H = 500;
|
|
18
|
+
|
|
19
|
+
page1Img.onload = start;
|
|
20
|
+
|
|
21
|
+
function start() {
|
|
22
|
+
if (!canvasRef.current) return;
|
|
23
|
+
const ctx = canvasRef.current?.getContext(
|
|
24
|
+
'2d'
|
|
25
|
+
) as CanvasRenderingContext2D;
|
|
26
|
+
if (!ctx) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
ctx.drawImage(page1Img, 0, 0, W, H);
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
data: Uint8ClampedArray
|
|
33
|
+
图片像素数据,每个像素占4个值
|
|
34
|
+
[R,G,B,A,R,G,B,A...]
|
|
35
|
+
*/
|
|
36
|
+
const imgData = ctx.getImageData(0, 0, W, H);
|
|
37
|
+
const newImgData = ctx.createImageData(W, H);
|
|
38
|
+
|
|
39
|
+
canvasRef.current.addEventListener('mousemove', onMouseMove);
|
|
40
|
+
|
|
41
|
+
function onMouseMove(e) {
|
|
42
|
+
const offsetX = clamp(e.offsetX, 0, W - 1);
|
|
43
|
+
const offsetY = clamp(e.offsetY, 0, H - 1);
|
|
44
|
+
|
|
45
|
+
ctx?.clearRect(0, 0, W, H);
|
|
46
|
+
|
|
47
|
+
// 两点连线的角度
|
|
48
|
+
const beta = Math.atan2(H - offsetY, W - offsetX);
|
|
49
|
+
|
|
50
|
+
const tanBeta = Math.tan(beta);
|
|
51
|
+
const sinBeta = Math.sin(beta);
|
|
52
|
+
|
|
53
|
+
// 两点连线m的长度
|
|
54
|
+
const len = (H - offsetY) / sinBeta;
|
|
55
|
+
|
|
56
|
+
// 过点[offsetX, offsetY]作连线的垂线,交下边于点[bottomX, bottomY],bottomX >=0,过点[bottomX,bottomY]作直线m的垂线n,交上边或右边于点[topX,topY]。
|
|
57
|
+
// 点[bottomX, bottomY]与点[topX,topY]是模拟翻页时的折线
|
|
58
|
+
const bottomY = H;
|
|
59
|
+
let bottomX = offsetX - (bottomY - offsetY) * tanBeta;
|
|
60
|
+
if (bottomX < 0) {
|
|
61
|
+
bottomX = 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let topY = 0;
|
|
65
|
+
let topX = bottomX + H * tanBeta;
|
|
66
|
+
if (topX > W) {
|
|
67
|
+
topX = W;
|
|
68
|
+
topY = bottomY - (W - bottomX) / tanBeta;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// [topX,topY]和[bottomX,bottomY]两点连线l与水平线的夹角
|
|
72
|
+
const theta = Math.atan2(bottomY - topY, bottomX - topX) - Math.PI / 2;
|
|
73
|
+
const sinTheta = Math.sin(theta);
|
|
74
|
+
const cosTheta = Math.cos(theta);
|
|
75
|
+
// 直线l的斜率
|
|
76
|
+
const slope = (bottomX - topX) / (bottomY - topY);
|
|
77
|
+
|
|
78
|
+
drawCurlPage();
|
|
79
|
+
// 画辅助线
|
|
80
|
+
drawHelperLines();
|
|
81
|
+
|
|
82
|
+
function drawCurlPage() {
|
|
83
|
+
for (let y = 0; y < H; y++) {
|
|
84
|
+
// 与直线l的x交点,l左边是没有卷曲的部分,右边是卷曲的部分,分开处理。
|
|
85
|
+
const crossX = bottomX - (bottomY - y) * slope;
|
|
86
|
+
const endX = crossX >> 0;
|
|
87
|
+
|
|
88
|
+
// 直线l左边
|
|
89
|
+
for (let x = 0; x < endX; x++) {
|
|
90
|
+
const pos = (y * W + x) * 4;
|
|
91
|
+
newImgData.data[pos] = imgData.data[pos];
|
|
92
|
+
newImgData.data[pos + 1] = imgData.data[pos + 1];
|
|
93
|
+
newImgData.data[pos + 2] = imgData.data[pos + 2];
|
|
94
|
+
newImgData.data[pos + 3] = imgData.data[pos + 3];
|
|
95
|
+
}
|
|
96
|
+
// 直线l右边
|
|
97
|
+
for (let x = endX; x <= W; x++) {
|
|
98
|
+
const pos = (y * W + x) * 4;
|
|
99
|
+
|
|
100
|
+
const dx = x - crossX;
|
|
101
|
+
const d = dx * cosTheta;
|
|
102
|
+
// 当rate>0.5时,说明当前像素点映射在背面。
|
|
103
|
+
const rate = d / len;
|
|
104
|
+
|
|
105
|
+
// 页面下的阴影
|
|
106
|
+
const opacity = ((len - d) / len) * 255;
|
|
107
|
+
newImgData.data[pos] = 0;
|
|
108
|
+
newImgData.data[pos + 1] = 0;
|
|
109
|
+
newImgData.data[pos + 2] = 0;
|
|
110
|
+
newImgData.data[pos + 3] = opacity;
|
|
111
|
+
|
|
112
|
+
// 算出点在半圆映射中的位移,其中半圆的周长为len
|
|
113
|
+
// y=x 与 y=len/π·sin(πx/len) 相减得到offset。即点映射的位移
|
|
114
|
+
const offset =
|
|
115
|
+
d - Math.sin((Math.PI * d) / len) * (len / Math.PI);
|
|
116
|
+
const targetX = (x - offset * cosTheta + 0.5) >> 0;
|
|
117
|
+
const targetY = (y - offset * sinTheta + 0.5) >> 0;
|
|
118
|
+
|
|
119
|
+
const targetPos = (targetY * W + targetX) * 4;
|
|
120
|
+
|
|
121
|
+
// 页面加上简易的阴影,加强立体效果
|
|
122
|
+
const gray = rate <= 0.5 ? -127 * rate : 127 * (rate - 0.5);
|
|
123
|
+
|
|
124
|
+
newImgData.data[targetPos] = imgData.data[pos] + gray;
|
|
125
|
+
newImgData.data[targetPos + 1] = imgData.data[pos + 1] + gray;
|
|
126
|
+
newImgData.data[targetPos + 2] = imgData.data[pos + 2] + gray;
|
|
127
|
+
newImgData.data[targetPos + 3] = imgData.data[pos + 3];
|
|
128
|
+
|
|
129
|
+
// 在像素点旋转映射的时候会有漏点,做个强行插值
|
|
130
|
+
newImgData.data[targetPos + 4] = imgData.data[pos] + gray;
|
|
131
|
+
newImgData.data[targetPos + 5] = imgData.data[pos + 1] + gray;
|
|
132
|
+
newImgData.data[targetPos + 6] = imgData.data[pos + 2] + gray;
|
|
133
|
+
newImgData.data[targetPos + 7] = imgData.data[pos + 3];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
ctx.putImageData(newImgData, 0, 0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function drawHelperLines() {
|
|
141
|
+
ctx.beginPath();
|
|
142
|
+
ctx.font = `8px sans-serif`;
|
|
143
|
+
ctx.strokeStyle = 'white';
|
|
144
|
+
ctx.moveTo(W, H);
|
|
145
|
+
ctx.lineTo(offsetX, offsetY);
|
|
146
|
+
ctx.stroke();
|
|
147
|
+
|
|
148
|
+
ctx.beginPath();
|
|
149
|
+
ctx.save();
|
|
150
|
+
ctx.fillStyle = 'white';
|
|
151
|
+
ctx.arc(offsetX, offsetY, 2, 0, Math.PI * 2);
|
|
152
|
+
ctx.fill();
|
|
153
|
+
ctx.fillText('[offsetX,offsetY]', offsetX, offsetY);
|
|
154
|
+
ctx.restore();
|
|
155
|
+
|
|
156
|
+
ctx.beginPath();
|
|
157
|
+
ctx.save();
|
|
158
|
+
ctx.fillStyle = 'white';
|
|
159
|
+
ctx.arc(bottomX, bottomY, 2, 0, Math.PI * 2);
|
|
160
|
+
ctx.fill();
|
|
161
|
+
ctx.fillText('[bottomX,bottomY]', bottomX, bottomY);
|
|
162
|
+
ctx.restore();
|
|
163
|
+
|
|
164
|
+
ctx.beginPath();
|
|
165
|
+
ctx.save();
|
|
166
|
+
ctx.fillStyle = 'white';
|
|
167
|
+
ctx.arc(topX, topY, 2, 0, Math.PI * 2);
|
|
168
|
+
ctx.fill();
|
|
169
|
+
const textMetrics = ctx.measureText('[topX,topY]');
|
|
170
|
+
ctx.fillText(
|
|
171
|
+
'[topX,topY]',
|
|
172
|
+
topX - textMetrics.width,
|
|
173
|
+
topY +
|
|
174
|
+
textMetrics.actualBoundingBoxAscent +
|
|
175
|
+
textMetrics.actualBoundingBoxDescent
|
|
176
|
+
);
|
|
177
|
+
ctx.restore();
|
|
178
|
+
|
|
179
|
+
ctx.beginPath();
|
|
180
|
+
ctx.save();
|
|
181
|
+
ctx.strokeStyle = 'white';
|
|
182
|
+
ctx.moveTo(topX, topY);
|
|
183
|
+
ctx.lineTo(bottomX, bottomY);
|
|
184
|
+
ctx.stroke();
|
|
185
|
+
ctx.restore();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function clamp(num: number, min = num, max = num) {
|
|
191
|
+
return Math.min(max, Math.max(min, num));
|
|
192
|
+
}
|
|
193
|
+
}, []);
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<div className="w-[100vw] h-[100vh] flex items-center justify-center">
|
|
197
|
+
<canvas ref={canvasRef} width={300} height={500}></canvas>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
function line(ctx: CanvasRenderingContext2D) {
|
|
2
|
+
// 画个头
|
|
3
|
+
ctx.moveTo(220, 50);
|
|
4
|
+
ctx.lineTo(280, 50);
|
|
5
|
+
ctx.lineTo(280, 100);
|
|
6
|
+
ctx.lineTo(220, 100);
|
|
7
|
+
ctx.lineTo(220, 50);
|
|
8
|
+
ctx.stroke();
|
|
9
|
+
|
|
10
|
+
// 脖子跟左手
|
|
11
|
+
ctx.moveTo(250, 100);
|
|
12
|
+
ctx.lineTo(250, 120);
|
|
13
|
+
ctx.lineTo(200, 150);
|
|
14
|
+
ctx.stroke();
|
|
15
|
+
|
|
16
|
+
// 右手
|
|
17
|
+
ctx.moveTo(250, 120);
|
|
18
|
+
ctx.lineTo(300, 150);
|
|
19
|
+
ctx.stroke();
|
|
20
|
+
|
|
21
|
+
// 身子跟左腿
|
|
22
|
+
ctx.moveTo(250, 120);
|
|
23
|
+
ctx.lineTo(250, 180);
|
|
24
|
+
ctx.lineTo(250, 180);
|
|
25
|
+
ctx.lineTo(200, 230);
|
|
26
|
+
ctx.stroke();
|
|
27
|
+
|
|
28
|
+
// 右腿
|
|
29
|
+
ctx.moveTo(250, 180);
|
|
30
|
+
ctx.lineTo(300, 230);
|
|
31
|
+
ctx.stroke();
|
|
32
|
+
|
|
33
|
+
// 左眼
|
|
34
|
+
ctx.moveTo(235, 70);
|
|
35
|
+
ctx.lineTo(242, 70);
|
|
36
|
+
ctx.stroke();
|
|
37
|
+
|
|
38
|
+
// 右眼
|
|
39
|
+
ctx.moveTo(260, 70);
|
|
40
|
+
ctx.lineTo(267, 70);
|
|
41
|
+
ctx.stroke();
|
|
42
|
+
|
|
43
|
+
// 嘴
|
|
44
|
+
ctx.moveTo(246, 90);
|
|
45
|
+
ctx.lineTo(254, 90);
|
|
46
|
+
ctx.stroke();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function arc(ctx: CanvasRenderingContext2D) {
|
|
50
|
+
// 脸
|
|
51
|
+
ctx.beginPath();
|
|
52
|
+
ctx.arc(200, 200, 50, 0, Math.PI * 2);
|
|
53
|
+
ctx.stroke();
|
|
54
|
+
|
|
55
|
+
// 左眼
|
|
56
|
+
ctx.beginPath();
|
|
57
|
+
ctx.arc(185, 185, 5, Math.PI, Math.PI * 2);
|
|
58
|
+
ctx.stroke();
|
|
59
|
+
|
|
60
|
+
// 右眼
|
|
61
|
+
ctx.beginPath();
|
|
62
|
+
ctx.arc(216, 185, 5, Math.PI, Math.PI * 2);
|
|
63
|
+
ctx.stroke();
|
|
64
|
+
|
|
65
|
+
// 嘴
|
|
66
|
+
ctx.beginPath();
|
|
67
|
+
ctx.arc(200, 215, 10, Math.PI / 8, (Math.PI * 7) / 8);
|
|
68
|
+
ctx.stroke();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function rect(ctx: CanvasRenderingContext2D) {
|
|
72
|
+
// 绘制顶部 tab
|
|
73
|
+
for (let i = 0; i < 7; i++) {
|
|
74
|
+
ctx.strokeRect(i * 80 + (i + 1) * 8, 10, 80, 30);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 绘制四个空心方块
|
|
78
|
+
for (let i = 0; i < 4; i++) {
|
|
79
|
+
ctx.fillRect(i * 30 + (i + 1) * 8, 58, 16, 16);
|
|
80
|
+
ctx.clearRect(i * 30 + (i + 1) * 8 + 4, 62, 8, 8);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 绘制搜索栏
|
|
84
|
+
ctx.strokeRect(160, 50, 400, 30);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function wavy(ctx: CanvasRenderingContext2D) {
|
|
88
|
+
ctx.fillStyle = '#4096ff';
|
|
89
|
+
ctx.beginPath();
|
|
90
|
+
ctx.moveTo(30, 30);
|
|
91
|
+
ctx.bezierCurveTo(80, 90, 180, -30, 220, 30);
|
|
92
|
+
ctx.lineTo(220, 90);
|
|
93
|
+
ctx.lineTo(30, 90);
|
|
94
|
+
ctx.closePath();
|
|
95
|
+
ctx.fill();
|
|
96
|
+
ctx.lineWidth = 4;
|
|
97
|
+
ctx.strokeStyle = '#a0d911';
|
|
98
|
+
ctx.stroke();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function histogram(ctx: CanvasRenderingContext2D) {
|
|
102
|
+
ctx.fillStyle = 'white';
|
|
103
|
+
ctx.fillRect(0, 0, 500, 500);
|
|
104
|
+
|
|
105
|
+
ctx.fillStyle = 'blue';
|
|
106
|
+
|
|
107
|
+
ctx.fillStyle = 'black';
|
|
108
|
+
ctx.lineWidth = 2;
|
|
109
|
+
ctx.beginPath();
|
|
110
|
+
ctx.moveTo(30, 10);
|
|
111
|
+
ctx.lineTo(30, 460);
|
|
112
|
+
ctx.lineTo(490, 460);
|
|
113
|
+
ctx.stroke();
|
|
114
|
+
|
|
115
|
+
ctx.fillStyle = 'black';
|
|
116
|
+
for (let i = 0; i < 6; i++) {
|
|
117
|
+
ctx.fillText(String((5 - i) * 20), 4, i * 80 + 62);
|
|
118
|
+
ctx.beginPath();
|
|
119
|
+
ctx.moveTo(25, i * 80 + 60);
|
|
120
|
+
ctx.lineTo(30, i * 80 + 60);
|
|
121
|
+
ctx.stroke();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const labels = ['JAN', 'FEB', 'MAR', 'APR', 'MAY'];
|
|
125
|
+
for (let i = 0; i < 5; i++) {
|
|
126
|
+
ctx.fillText(labels[i], 50 + i * 100, 475);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ctx.fillStyle = 'blue';
|
|
130
|
+
const data = [16, 68, 20, 30, 54];
|
|
131
|
+
for (let i = 0; i < data.length; i++) {
|
|
132
|
+
ctx.fillRect(40 + i * 100, 460 - data[i] * 5, 45, data[i] * 5);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function pieChart(ctx: CanvasRenderingContext2D) {
|
|
137
|
+
ctx.fillStyle = 'white';
|
|
138
|
+
ctx.fillRect(0, 0, 500, 500);
|
|
139
|
+
|
|
140
|
+
const colors = ['orange', 'green', 'blue', 'yellow', 'teal'];
|
|
141
|
+
let total = 0;
|
|
142
|
+
const data = [100, 68, 20, 30, 100];
|
|
143
|
+
for (let i = 0; i < data.length; i++) {
|
|
144
|
+
total += data[i];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let preAngle = 0;
|
|
148
|
+
for (let i = 0; i < data.length; i++) {
|
|
149
|
+
const fraction = data[i] / total;
|
|
150
|
+
const angle = preAngle + fraction * Math.PI * 2;
|
|
151
|
+
|
|
152
|
+
// ctx.fillStyle = colors[i];
|
|
153
|
+
const grad = ctx.createRadialGradient(250, 250, 5, 250, 250, 100);
|
|
154
|
+
grad.addColorStop(0, 'white');
|
|
155
|
+
grad.addColorStop(1, colors[i]);
|
|
156
|
+
ctx.fillStyle = grad;
|
|
157
|
+
|
|
158
|
+
ctx.beginPath();
|
|
159
|
+
ctx.moveTo(250, 250);
|
|
160
|
+
ctx.arc(250, 250, 100, preAngle, angle, false);
|
|
161
|
+
ctx.closePath();
|
|
162
|
+
ctx.fill();
|
|
163
|
+
ctx.strokeStyle = 'black';
|
|
164
|
+
ctx.stroke();
|
|
165
|
+
|
|
166
|
+
preAngle = angle;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
ctx.fillStyle = 'black';
|
|
170
|
+
ctx.font = '24pt sans-serif';
|
|
171
|
+
|
|
172
|
+
const text = 'Sales Data from 2025';
|
|
173
|
+
const metrics = ctx.measureText(text);
|
|
174
|
+
|
|
175
|
+
ctx.fillText(text, 250 - metrics.width / 2, 400);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function ticTac(ctx: CanvasRenderingContext2D) {
|
|
179
|
+
// 绘制网格
|
|
180
|
+
ctx.lineWidth = 1;
|
|
181
|
+
ctx.rect(50, 50, 300, 300);
|
|
182
|
+
ctx.stroke();
|
|
183
|
+
|
|
184
|
+
ctx.beginPath();
|
|
185
|
+
ctx.moveTo(50, 150);
|
|
186
|
+
ctx.lineTo(350, 150);
|
|
187
|
+
|
|
188
|
+
ctx.moveTo(50, 250);
|
|
189
|
+
ctx.lineTo(350, 250);
|
|
190
|
+
|
|
191
|
+
ctx.moveTo(150, 50);
|
|
192
|
+
ctx.lineTo(150, 350);
|
|
193
|
+
|
|
194
|
+
ctx.moveTo(250, 50);
|
|
195
|
+
ctx.lineTo(250, 350);
|
|
196
|
+
|
|
197
|
+
ctx.stroke();
|
|
198
|
+
|
|
199
|
+
// 绘制 ❌
|
|
200
|
+
const drawX = (x: number, y: number) => {
|
|
201
|
+
ctx.beginPath();
|
|
202
|
+
ctx.moveTo(x, x);
|
|
203
|
+
ctx.lineTo(y, y);
|
|
204
|
+
ctx.moveTo(y, x);
|
|
205
|
+
ctx.lineTo(x, y);
|
|
206
|
+
ctx.stroke();
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// 绘制圆
|
|
210
|
+
const drawArc = (x: number, y: number) => {
|
|
211
|
+
ctx.beginPath();
|
|
212
|
+
ctx.arc(x, y, 30, 0, Math.PI * 2);
|
|
213
|
+
ctx.stroke();
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const steps = [
|
|
217
|
+
{
|
|
218
|
+
type: 'arc',
|
|
219
|
+
x: 100,
|
|
220
|
+
y: 200,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
type: 'arc',
|
|
224
|
+
x: 300,
|
|
225
|
+
y: 100,
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
type: 'x',
|
|
229
|
+
x: 175,
|
|
230
|
+
y: 225,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
type: 'x',
|
|
234
|
+
x: 75,
|
|
235
|
+
y: 125,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
type: 'arc',
|
|
239
|
+
x: 300,
|
|
240
|
+
y: 300,
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
steps.forEach((step) => {
|
|
244
|
+
const { type, x, y } = step;
|
|
245
|
+
if (type === 'arc') {
|
|
246
|
+
drawArc(x, y);
|
|
247
|
+
} else if (type == 'x') {
|
|
248
|
+
drawX(x, y);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function brick(ctx: CanvasRenderingContext2D) {
|
|
254
|
+
const createRgbVal = (r: number, g: number, b: number) => {
|
|
255
|
+
return `rgb(${r},${g},${b})`;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const w = 15;
|
|
259
|
+
|
|
260
|
+
const drawRect = (x: number, y: number) => {
|
|
261
|
+
ctx.beginPath();
|
|
262
|
+
ctx.rect(x * w, y * w, w, w);
|
|
263
|
+
const colorW = w + 5;
|
|
264
|
+
|
|
265
|
+
if (
|
|
266
|
+
(x % 3 === 0 && x !== 0 && (y === 0 || y === n - 1)) ||
|
|
267
|
+
(y % 3 === 0 && y !== 0 && (x === 0 || x === n - 1))
|
|
268
|
+
) {
|
|
269
|
+
ctx.fillStyle = 'white';
|
|
270
|
+
} else if (x % 3 === (y + 2) % 3) {
|
|
271
|
+
ctx.fillStyle = createRgbVal(
|
|
272
|
+
170,
|
|
273
|
+
0 + (x * colorW) / 2 - 8,
|
|
274
|
+
0 + (y * colorW) / 2 - 8
|
|
275
|
+
);
|
|
276
|
+
} else {
|
|
277
|
+
ctx.fillStyle = createRgbVal(
|
|
278
|
+
170,
|
|
279
|
+
0 + (x * colorW) / 2,
|
|
280
|
+
0 + (y * colorW) / 2
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
ctx.fill();
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const n = 25;
|
|
288
|
+
for (let i = 0; i < n; i++) {
|
|
289
|
+
for (let j = 0; j < n; j++) {
|
|
290
|
+
drawRect(i, j);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function test(ctx: CanvasRenderingContext2D) {
|
|
296
|
+
const gradient = ctx.createRadialGradient(200, 300, 10, 200, 0, 100);
|
|
297
|
+
|
|
298
|
+
gradient.addColorStop(0, 'blue');
|
|
299
|
+
gradient.addColorStop(0.25, 'white');
|
|
300
|
+
gradient.addColorStop(0.5, 'purple');
|
|
301
|
+
gradient.addColorStop(0.75, 'red');
|
|
302
|
+
gradient.addColorStop(1, 'yellow');
|
|
303
|
+
|
|
304
|
+
ctx.fillStyle = gradient;
|
|
305
|
+
ctx.rect(0, 0, 400, 300);
|
|
306
|
+
ctx.fill();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function love(ctx: CanvasRenderingContext2D) {
|
|
310
|
+
const drawLeft = () => {
|
|
311
|
+
ctx.beginPath();
|
|
312
|
+
|
|
313
|
+
const p0 = [200, 200];
|
|
314
|
+
const p1 = [110, 170];
|
|
315
|
+
const p2 = [143, 260];
|
|
316
|
+
const p3 = [200, 295];
|
|
317
|
+
ctx.moveTo(p0[0], p0[1]);
|
|
318
|
+
ctx.bezierCurveTo(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);
|
|
319
|
+
ctx.fillStyle = 'red';
|
|
320
|
+
ctx.fill();
|
|
321
|
+
|
|
322
|
+
ctx.beginPath();
|
|
323
|
+
ctx.moveTo(p0[0], p0[1]);
|
|
324
|
+
ctx.lineWidth = 1;
|
|
325
|
+
[p1, p2, p3].forEach((p) => {
|
|
326
|
+
ctx.lineTo(p[0], p[1]);
|
|
327
|
+
});
|
|
328
|
+
ctx.strokeStyle = 'blue';
|
|
329
|
+
ctx.stroke();
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const drawRight = () => {
|
|
333
|
+
ctx.beginPath();
|
|
334
|
+
const p0 = [200, 200];
|
|
335
|
+
const p1 = [290, 165];
|
|
336
|
+
const p2 = [257, 260];
|
|
337
|
+
const p3 = [200, 295];
|
|
338
|
+
ctx.moveTo(p0[0], p0[1]);
|
|
339
|
+
ctx.bezierCurveTo(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);
|
|
340
|
+
ctx.fillStyle = 'red';
|
|
341
|
+
ctx.fill();
|
|
342
|
+
|
|
343
|
+
ctx.beginPath();
|
|
344
|
+
ctx.moveTo(p0[0], p0[1]);
|
|
345
|
+
ctx.lineWidth = 1;
|
|
346
|
+
[p1, p2, p3].forEach((p) => {
|
|
347
|
+
ctx.lineTo(p[0], p[1]);
|
|
348
|
+
});
|
|
349
|
+
ctx.strokeStyle = 'blue';
|
|
350
|
+
ctx.stroke();
|
|
351
|
+
};
|
|
352
|
+
drawLeft();
|
|
353
|
+
drawRight();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export const drawFns = {
|
|
357
|
+
love,
|
|
358
|
+
brick,
|
|
359
|
+
'pie-chart': pieChart,
|
|
360
|
+
histogram,
|
|
361
|
+
wavy,
|
|
362
|
+
line,
|
|
363
|
+
arc,
|
|
364
|
+
rect,
|
|
365
|
+
ticTac,
|
|
366
|
+
test,
|
|
367
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
height: 100vh;
|
|
3
|
+
padding: 30px;
|
|
4
|
+
background-color: #f4f5f5;
|
|
5
|
+
|
|
6
|
+
.card {
|
|
7
|
+
width: 700px;
|
|
8
|
+
|
|
9
|
+
.content {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
|
|
13
|
+
.tab {
|
|
14
|
+
height: 40px;
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: row;
|
|
17
|
+
align-items: center;
|
|
18
|
+
gap: 5px;
|
|
19
|
+
|
|
20
|
+
.tag {
|
|
21
|
+
border: 1px solid rgb(217, 217, 217);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Card, Tag } from 'antd';
|
|
3
|
+
import { drawFns } from './draw';
|
|
4
|
+
import S from './index.module.less';
|
|
5
|
+
|
|
6
|
+
const list = Object.keys(drawFns) as (keyof typeof drawFns)[];
|
|
7
|
+
|
|
8
|
+
export default function Page() {
|
|
9
|
+
const [active, setActive] = useState(list[0]);
|
|
10
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!canvasRef.current) return;
|
|
14
|
+
const ctx = canvasRef.current?.getContext('2d');
|
|
15
|
+
if (!ctx) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
ctx.reset();
|
|
20
|
+
|
|
21
|
+
const fn = drawFns[active];
|
|
22
|
+
fn(ctx);
|
|
23
|
+
}, [active]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className={S.container}>
|
|
27
|
+
<Card title="示例" className={S.card}>
|
|
28
|
+
<div className={S.content}>
|
|
29
|
+
<div className={S.tab}>
|
|
30
|
+
{list.map((label) => (
|
|
31
|
+
<Tag.CheckableTag
|
|
32
|
+
key={label}
|
|
33
|
+
checked={active === label}
|
|
34
|
+
onClick={() => {
|
|
35
|
+
setActive(label);
|
|
36
|
+
}}
|
|
37
|
+
className={S.tag}
|
|
38
|
+
>
|
|
39
|
+
{label}
|
|
40
|
+
</Tag.CheckableTag>
|
|
41
|
+
))}
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<canvas
|
|
45
|
+
ref={canvasRef}
|
|
46
|
+
width="500"
|
|
47
|
+
height="500"
|
|
48
|
+
style={{ border: '1px solid #000000' }}
|
|
49
|
+
></canvas>
|
|
50
|
+
</div>
|
|
51
|
+
</Card>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|