cbvirtua 1.0.48 → 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.
Files changed (72) hide show
  1. package/canvas-example-main/canvas-example-main/.github/workflows/main.yml +62 -0
  2. package/canvas-example-main/canvas-example-main/README.md +13 -0
  3. package/canvas-example-main/canvas-example-main/curved.html +52 -0
  4. package/canvas-example-main/canvas-example-main/eslint.config.js +30 -0
  5. package/canvas-example-main/canvas-example-main/index.html +13 -0
  6. package/canvas-example-main/canvas-example-main/package.json +51 -0
  7. package/canvas-example-main/canvas-example-main/pnpm-lock.yaml +4760 -0
  8. package/canvas-example-main/canvas-example-main/postcss.config.js +6 -0
  9. package/canvas-example-main/canvas-example-main/public/vite.svg +1 -0
  10. package/canvas-example-main/canvas-example-main/src/App.tsx +17 -0
  11. package/canvas-example-main/canvas-example-main/src/assets/github.svg +1 -0
  12. package/canvas-example-main/canvas-example-main/src/assets/react.svg +1 -0
  13. package/canvas-example-main/canvas-example-main/src/components/Iconfont/demo.css +539 -0
  14. package/canvas-example-main/canvas-example-main/src/components/Iconfont/demo_index.html +418 -0
  15. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.css +55 -0
  16. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.js +1 -0
  17. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.json +79 -0
  18. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.ttf +0 -0
  19. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.woff +0 -0
  20. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.woff2 +0 -0
  21. package/canvas-example-main/canvas-example-main/src/components/Iconfont/index.tsx +39 -0
  22. package/canvas-example-main/canvas-example-main/src/main.css +9 -0
  23. package/canvas-example-main/canvas-example-main/src/main.tsx +10 -0
  24. package/canvas-example-main/canvas-example-main/src/pages/2048/g2048.ts +14 -0
  25. package/canvas-example-main/canvas-example-main/src/pages/2048/index.tsx +21 -0
  26. package/canvas-example-main/canvas-example-main/src/pages/clock/index.tsx +103 -0
  27. package/canvas-example-main/canvas-example-main/src/pages/demo/index.tsx +21 -0
  28. package/canvas-example-main/canvas-example-main/src/pages/editor/components/editor/index.module.less +3 -0
  29. package/canvas-example-main/canvas-example-main/src/pages/editor/components/editor/index.tsx +99 -0
  30. package/canvas-example-main/canvas-example-main/src/pages/editor/components/header/index.module.less +5 -0
  31. package/canvas-example-main/canvas-example-main/src/pages/editor/components/header/index.tsx +5 -0
  32. package/canvas-example-main/canvas-example-main/src/pages/editor/components/material/index.module.less +59 -0
  33. package/canvas-example-main/canvas-example-main/src/pages/editor/components/material/index.tsx +85 -0
  34. package/canvas-example-main/canvas-example-main/src/pages/editor/components/setting/index.module.less +7 -0
  35. package/canvas-example-main/canvas-example-main/src/pages/editor/components/setting/index.tsx +5 -0
  36. package/canvas-example-main/canvas-example-main/src/pages/editor/core/application.ts +35 -0
  37. package/canvas-example-main/canvas-example-main/src/pages/editor/core/cmp/base.ts +17 -0
  38. package/canvas-example-main/canvas-example-main/src/pages/editor/core/cmp/factory.ts +14 -0
  39. package/canvas-example-main/canvas-example-main/src/pages/editor/core/cmp/shape.tsx +43 -0
  40. package/canvas-example-main/canvas-example-main/src/pages/editor/core/editor.ts +61 -0
  41. package/canvas-example-main/canvas-example-main/src/pages/editor/core/type.ts +6 -0
  42. package/canvas-example-main/canvas-example-main/src/pages/editor/index.module.less +7 -0
  43. package/canvas-example-main/canvas-example-main/src/pages/editor/index.tsx +32 -0
  44. package/canvas-example-main/canvas-example-main/src/pages/editor/store/component-config.ts +61 -0
  45. package/canvas-example-main/canvas-example-main/src/pages/editor/store/components.ts +43 -0
  46. package/canvas-example-main/canvas-example-main/src/pages/editor/store/layout.ts +40 -0
  47. package/canvas-example-main/canvas-example-main/src/pages/home/index.tsx +59 -0
  48. package/canvas-example-main/canvas-example-main/src/pages/jigsaw/index.tsx +3 -0
  49. package/canvas-example-main/canvas-example-main/src/pages/minesweeper/bomber.png +0 -0
  50. package/canvas-example-main/canvas-example-main/src/pages/minesweeper/index.tsx +138 -0
  51. package/canvas-example-main/canvas-example-main/src/pages/minesweeper/mark.png +0 -0
  52. package/canvas-example-main/canvas-example-main/src/pages/minesweeper/minesweeper.ts +345 -0
  53. package/canvas-example-main/canvas-example-main/src/pages/minesweeper/utils.ts +24 -0
  54. package/canvas-example-main/canvas-example-main/src/pages/pageflip/index.tsx +200 -0
  55. package/canvas-example-main/canvas-example-main/src/pages/pageflip/page1.jpg +0 -0
  56. package/canvas-example-main/canvas-example-main/src/pages/practice/draw/index.ts +367 -0
  57. package/canvas-example-main/canvas-example-main/src/pages/practice/index.module.less +26 -0
  58. package/canvas-example-main/canvas-example-main/src/pages/practice/index.tsx +54 -0
  59. package/canvas-example-main/canvas-example-main/src/pages/shape-editor/control.ts +174 -0
  60. package/canvas-example-main/canvas-example-main/src/pages/shape-editor/editor.ts +91 -0
  61. package/canvas-example-main/canvas-example-main/src/pages/shape-editor/index.raw.tsx +159 -0
  62. package/canvas-example-main/canvas-example-main/src/pages/shape-editor/index.tsx +36 -0
  63. package/canvas-example-main/canvas-example-main/src/pages/shape-editor/shape.ts +248 -0
  64. package/canvas-example-main/canvas-example-main/src/router.tsx +53 -0
  65. package/canvas-example-main/canvas-example-main/src/utils/storage.ts +48 -0
  66. package/canvas-example-main/canvas-example-main/src/vite-env.d.ts +1 -0
  67. package/canvas-example-main/canvas-example-main/tailwind.config.js +8 -0
  68. package/canvas-example-main/canvas-example-main/tsconfig.app.json +30 -0
  69. package/canvas-example-main/canvas-example-main/tsconfig.json +7 -0
  70. package/canvas-example-main/canvas-example-main/tsconfig.node.json +22 -0
  71. package/canvas-example-main/canvas-example-main/vite.config.ts +18 -0
  72. 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
+ }