@ray-js/graffiti 1.0.0 → 1.1.3
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/README.md +4 -2
- package/lib/components/index.js +43 -4
- package/lib/components/index.less +6 -0
- package/lib/components/index.rjs +125 -7
- package/lib/components/index.tyml +9 -6
- package/lib/index.js +6 -0
- package/lib/props.d.ts +25 -2
- package/lib/props.js +2 -0
- package/package.json +6 -12
package/README.md
CHANGED
|
@@ -31,8 +31,10 @@ yarn start:tuya
|
|
|
31
31
|
- By changing the operation type, you can switch to pencil mode, eraser mode, paint bucket mode
|
|
32
32
|
- By changing `penColor`, you can pass in different pen colors
|
|
33
33
|
- `needStroke` is `true`, start monitoring each brush data, and get the x, y coordinate path data of each brush stroke through `onStrokeChange`
|
|
34
|
-
-
|
|
35
|
-
-
|
|
34
|
+
- Updating the `saveTrigger` value triggers canvas saving, returning the canvas's base64 data and grid color data.
|
|
35
|
+
- Updating the `clearTrigger` value triggers canvas clearing.
|
|
36
|
+
- The `scale` function allows scaling the canvas.
|
|
37
|
+
- `isDragging` disables drawing; the canvas can be dragged when it exceeds the limit.
|
|
36
38
|
|
|
37
39
|
```tsx
|
|
38
40
|
import React, { useState } from 'react';
|
package/lib/components/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Render from './index.rjs';
|
|
2
|
-
import { getSystemInfoSync } from '@ray-js/ray';
|
|
2
|
+
import { getSystemInfoSync, getElementById, getBoundingClientRect } from '@ray-js/ray';
|
|
3
3
|
let _systemInfoResult = null;
|
|
4
4
|
export const getSystemInfoResult = () => {
|
|
5
5
|
if (_systemInfoResult) {
|
|
@@ -127,6 +127,35 @@ Component({
|
|
|
127
127
|
this.render.clear();
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
+
},
|
|
131
|
+
scale: {
|
|
132
|
+
// 画布缩放比例
|
|
133
|
+
type: Number,
|
|
134
|
+
value: 1,
|
|
135
|
+
observer(newValue) {
|
|
136
|
+
if (newValue && this.render) {
|
|
137
|
+
this.render.updateScale(newValue);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
isDragging: {
|
|
142
|
+
// 可拖动画布, 同时禁止绘制
|
|
143
|
+
type: Boolean,
|
|
144
|
+
value: false,
|
|
145
|
+
observer(newValue) {
|
|
146
|
+
if (this.render) {
|
|
147
|
+
this.render.setDragging(newValue);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
drawData: {
|
|
152
|
+
// 初始绘制数据
|
|
153
|
+
type: null,
|
|
154
|
+
observer(newValue) {
|
|
155
|
+
if (newValue && this.render) {
|
|
156
|
+
this.render.drawData(newValue);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
130
159
|
}
|
|
131
160
|
},
|
|
132
161
|
data: {
|
|
@@ -137,7 +166,7 @@ Component({
|
|
|
137
166
|
created() {
|
|
138
167
|
this.render = new Render(this);
|
|
139
168
|
},
|
|
140
|
-
ready() {
|
|
169
|
+
async ready() {
|
|
141
170
|
let {
|
|
142
171
|
canvasIdPrefix,
|
|
143
172
|
width,
|
|
@@ -147,7 +176,9 @@ Component({
|
|
|
147
176
|
pixelSizeX,
|
|
148
177
|
pixelSizeY,
|
|
149
178
|
pixelGap,
|
|
150
|
-
mode
|
|
179
|
+
mode,
|
|
180
|
+
scale,
|
|
181
|
+
isDragging
|
|
151
182
|
} = this.data;
|
|
152
183
|
width = getDeviceRealPx(width);
|
|
153
184
|
height = getDeviceRealPx(height);
|
|
@@ -171,6 +202,8 @@ Component({
|
|
|
171
202
|
realHeight: height
|
|
172
203
|
});
|
|
173
204
|
}
|
|
205
|
+
const ele = await getElementById(`${canvasIdPrefix}-container`);
|
|
206
|
+
const rect = await getBoundingClientRect(ele);
|
|
174
207
|
this.render.initPanel({
|
|
175
208
|
canvasIdPrefix: canvasIdPrefix,
|
|
176
209
|
width: width,
|
|
@@ -183,8 +216,14 @@ Component({
|
|
|
183
216
|
pixelGap: pixelGap,
|
|
184
217
|
pixelShape: this.data.pixelShape,
|
|
185
218
|
pixelColor: this.data.pixelColor,
|
|
186
|
-
penColor: this.data.penColor
|
|
219
|
+
penColor: this.data.penColor,
|
|
220
|
+
scale: scale,
|
|
221
|
+
isDragging: isDragging,
|
|
222
|
+
boxRect: rect
|
|
187
223
|
});
|
|
224
|
+
if (this.data.drawData) {
|
|
225
|
+
this.render.drawData(this.data.drawData);
|
|
226
|
+
}
|
|
188
227
|
}
|
|
189
228
|
},
|
|
190
229
|
methods: {
|
package/lib/components/index.rjs
CHANGED
|
@@ -14,7 +14,13 @@ export default Render({
|
|
|
14
14
|
pixelShape,
|
|
15
15
|
pixelColor,
|
|
16
16
|
penColor,
|
|
17
|
+
scale,
|
|
18
|
+
isDragging,
|
|
19
|
+
boxRect,
|
|
17
20
|
}) {
|
|
21
|
+
this.scale = scale || 1;
|
|
22
|
+
this.isDragging = isDragging || false;
|
|
23
|
+
this.boxRect = boxRect || null;
|
|
18
24
|
let canvas = await getCanvasById(`${canvasIdPrefix}-sourceCanvas`);
|
|
19
25
|
|
|
20
26
|
// 根据屏幕分辨率动态计算canvas尺寸
|
|
@@ -54,9 +60,23 @@ export default Render({
|
|
|
54
60
|
// 记录触摸是否开始
|
|
55
61
|
this.isTouchStarted = false;
|
|
56
62
|
|
|
63
|
+
this.gridData = {};
|
|
64
|
+
|
|
65
|
+
// 缩放拖动相关
|
|
66
|
+
this.offsetX = 0;
|
|
67
|
+
this.offsetY = 0;
|
|
68
|
+
this.lastX = 0;
|
|
69
|
+
this.lastY = 0;
|
|
70
|
+
// 计算缩放后格子尺寸
|
|
71
|
+
this.scalePixelSizeX = this.pixelSizeX * scale;
|
|
72
|
+
this.scalePixelSizeY = this.pixelSizeY * scale;
|
|
73
|
+
this.scalePixelGap = this.pixelGap * scale;
|
|
74
|
+
|
|
57
75
|
// 初始化画布, 绘制像素点
|
|
58
76
|
this.createPixel(pixelColor);
|
|
59
77
|
|
|
78
|
+
this.updateScale(scale);
|
|
79
|
+
|
|
60
80
|
canvas.addEventListener('touchstart', this.handleTouchstart, false);
|
|
61
81
|
canvas.addEventListener('touchmove', this.handleTouchmove, false);
|
|
62
82
|
canvas.addEventListener('touchend', this.handleTouchend, false);
|
|
@@ -65,13 +85,32 @@ export default Render({
|
|
|
65
85
|
ctx.imageSmoothingQuality = 'high'; // 高质量抗锯齿
|
|
66
86
|
},
|
|
67
87
|
handleTouchstart(e) {
|
|
68
|
-
const {
|
|
88
|
+
const {
|
|
89
|
+
canvas,
|
|
90
|
+
pixelGap,
|
|
91
|
+
pixelSizeX,
|
|
92
|
+
pixelSizeY,
|
|
93
|
+
isDragging,
|
|
94
|
+
scalePixelSizeX,
|
|
95
|
+
scalePixelSizeY,
|
|
96
|
+
scalePixelGap,
|
|
97
|
+
} = this;
|
|
98
|
+
if (isDragging) {
|
|
99
|
+
const touch = e.changedTouches[0];
|
|
100
|
+
this.lastX = touch.pageX;
|
|
101
|
+
this.lastY = touch.pageY;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
69
104
|
this.touchedSquaresSet.clear();
|
|
70
105
|
this.isTouchStarted = true;
|
|
71
106
|
const touch = e.changedTouches[0];
|
|
72
107
|
const rect = canvas.getBoundingClientRect();
|
|
73
|
-
const x = Math.floor(
|
|
74
|
-
|
|
108
|
+
const x = Math.floor(
|
|
109
|
+
(touch.pageX - rect.left - scalePixelGap) / (scalePixelSizeX + scalePixelGap)
|
|
110
|
+
);
|
|
111
|
+
const y = Math.floor(
|
|
112
|
+
(touch.pageY - rect.top - scalePixelGap) / (scalePixelSizeY + scalePixelGap)
|
|
113
|
+
);
|
|
75
114
|
const coordinate = `${x},${y}`;
|
|
76
115
|
if (!this.touchedSquaresSet.has(coordinate)) {
|
|
77
116
|
this.touchedSquaresSet.add(coordinate);
|
|
@@ -79,13 +118,58 @@ export default Render({
|
|
|
79
118
|
}
|
|
80
119
|
},
|
|
81
120
|
handleTouchmove(e) {
|
|
82
|
-
const {
|
|
121
|
+
const {
|
|
122
|
+
canvas,
|
|
123
|
+
pixelGap,
|
|
124
|
+
pixelSizeX,
|
|
125
|
+
pixelSizeY,
|
|
126
|
+
scalePixelGap,
|
|
127
|
+
scalePixelSizeX,
|
|
128
|
+
scalePixelSizeY,
|
|
129
|
+
} = this;
|
|
83
130
|
e.preventDefault();
|
|
131
|
+
if (this.isDragging) {
|
|
132
|
+
const touch = e.changedTouches[0];
|
|
133
|
+
const rect = canvas.getBoundingClientRect();
|
|
134
|
+
const dx = touch.pageX - this.lastX;
|
|
135
|
+
const dy = touch.pageY - this.lastY;
|
|
136
|
+
this.offsetX += dx;
|
|
137
|
+
this.offsetY += dy;
|
|
138
|
+
this.lastX = touch.pageX;
|
|
139
|
+
this.lastY = touch.pageY;
|
|
140
|
+
|
|
141
|
+
// 获取容器大小
|
|
142
|
+
const boxWidth = this.boxRect?.width;
|
|
143
|
+
const boxHeight = this.boxRect?.height;
|
|
144
|
+
|
|
145
|
+
// 缩放后的画布内容尺寸
|
|
146
|
+
const displayWidth = rect.width;
|
|
147
|
+
const displayHeight = rect.height;
|
|
148
|
+
// 边界限制
|
|
149
|
+
if (displayWidth <= boxWidth) {
|
|
150
|
+
this.offsetX = 0;
|
|
151
|
+
} else {
|
|
152
|
+
const maxOffsetX = (displayWidth - boxWidth) / 2;
|
|
153
|
+
this.offsetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, this.offsetX));
|
|
154
|
+
}
|
|
155
|
+
if (displayHeight <= boxHeight) {
|
|
156
|
+
this.offsetY = 0;
|
|
157
|
+
} else {
|
|
158
|
+
const maxOffsetY = (displayHeight - boxHeight) / 2;
|
|
159
|
+
this.offsetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, this.offsetY));
|
|
160
|
+
}
|
|
161
|
+
canvas.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(${this.scale})`;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
84
164
|
if (this.isTouchStarted) {
|
|
85
165
|
const touch = e.changedTouches[0];
|
|
86
166
|
const rect = canvas.getBoundingClientRect();
|
|
87
|
-
const x = Math.floor(
|
|
88
|
-
|
|
167
|
+
const x = Math.floor(
|
|
168
|
+
(touch.pageX - rect.left - scalePixelGap) / (scalePixelSizeX + scalePixelGap)
|
|
169
|
+
);
|
|
170
|
+
const y = Math.floor(
|
|
171
|
+
(touch.pageY - rect.top - scalePixelGap) / (scalePixelSizeY + scalePixelGap)
|
|
172
|
+
);
|
|
89
173
|
const coordinate = `${x},${y}`;
|
|
90
174
|
if (!this.touchedSquaresSet.has(coordinate)) {
|
|
91
175
|
this.touchedSquaresSet.add(coordinate);
|
|
@@ -94,6 +178,9 @@ export default Render({
|
|
|
94
178
|
}
|
|
95
179
|
},
|
|
96
180
|
handleTouchend() {
|
|
181
|
+
if (this.isDragging) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
97
184
|
this.isTouchStarted = false;
|
|
98
185
|
const touchedSquares = [];
|
|
99
186
|
for (const coordinateStr of this.touchedSquaresSet) {
|
|
@@ -143,6 +230,7 @@ export default Render({
|
|
|
143
230
|
// 执行填充操作,将圆形内部填充为设定的颜色
|
|
144
231
|
ctx.fill();
|
|
145
232
|
}
|
|
233
|
+
this.gridData[`${x}_${y}`] = color;
|
|
146
234
|
},
|
|
147
235
|
// 改变画笔颜色
|
|
148
236
|
updateColor(color) {
|
|
@@ -153,13 +241,43 @@ export default Render({
|
|
|
153
241
|
this.penColor = color;
|
|
154
242
|
this.createPixel(color);
|
|
155
243
|
},
|
|
244
|
+
updateScale(scale) {
|
|
245
|
+
const { canvas } = this;
|
|
246
|
+
if (canvas && scale) {
|
|
247
|
+
canvas.style.transform = `scale(${scale})`;
|
|
248
|
+
this.scale = scale;
|
|
249
|
+
this.scalePixelSizeX = this.pixelSizeX * scale;
|
|
250
|
+
this.scalePixelSizeY = this.pixelSizeY * scale;
|
|
251
|
+
this.scalePixelGap = this.pixelGap * scale;
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
setDragging(isDragging) {
|
|
255
|
+
this.isDragging = isDragging;
|
|
256
|
+
const { canvas } = this;
|
|
257
|
+
if (canvas) {
|
|
258
|
+
if (isDragging) {
|
|
259
|
+
canvas.style.touchAction = 'none'; // 禁用默认的触摸行为,防止页面滚动
|
|
260
|
+
} else {
|
|
261
|
+
canvas.style.touchAction = 'auto'; // 恢复默认的触摸行为
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
},
|
|
156
265
|
// 清除画布
|
|
157
266
|
clear() {
|
|
158
267
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
159
268
|
this.createPixel(this.pixelColor);
|
|
160
269
|
},
|
|
161
270
|
save() {
|
|
271
|
+
const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
|
272
|
+
console.log('render save imageData', imageData);
|
|
162
273
|
const base64 = this.canvas.toDataURL('image/png');
|
|
163
|
-
this.callMethod('genImageData', { base64 });
|
|
274
|
+
this.callMethod('genImageData', { base64, gridData: this.gridData });
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
drawData(list) {
|
|
278
|
+
console.log('render drawData', list);
|
|
279
|
+
for (const item of list) {
|
|
280
|
+
this.fillPixel(item.x, item.y, item.color);
|
|
281
|
+
}
|
|
164
282
|
},
|
|
165
283
|
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<view class="pixelGraffiti_container" id="{{`${canvasIdPrefix}-container`}}" style="width: {{width}}px; height: {{height}}px">
|
|
2
|
+
<view class="smear-wrap">
|
|
3
|
+
<canvas
|
|
4
|
+
type="2d"
|
|
5
|
+
canvas-id="{{`${canvasIdPrefix}-sourceCanvas`}}"
|
|
6
|
+
style="width: {{realWidth}}px; height: {{realHeight}}px"
|
|
7
|
+
/>
|
|
8
|
+
</view>
|
|
9
|
+
</view>
|
package/lib/index.js
CHANGED
|
@@ -27,6 +27,9 @@ const RayGraffiti = props => {
|
|
|
27
27
|
needStroke,
|
|
28
28
|
saveTrigger,
|
|
29
29
|
clearTrigger,
|
|
30
|
+
scale,
|
|
31
|
+
isDragging,
|
|
32
|
+
drawData,
|
|
30
33
|
onStrokeChange,
|
|
31
34
|
onSaveData
|
|
32
35
|
} = props;
|
|
@@ -50,6 +53,9 @@ const RayGraffiti = props => {
|
|
|
50
53
|
needStroke: needStroke,
|
|
51
54
|
saveTrigger: saveTrigger,
|
|
52
55
|
clearTrigger: clearTrigger,
|
|
56
|
+
scale: scale,
|
|
57
|
+
isDragging: isDragging,
|
|
58
|
+
drawData: drawData,
|
|
53
59
|
bindstrokeChange: e => onStrokeChange(e.detail),
|
|
54
60
|
bindsaveData: e => onSaveData(e.detail)
|
|
55
61
|
}));
|
package/lib/props.d.ts
CHANGED
|
@@ -102,6 +102,28 @@ export interface IProps {
|
|
|
102
102
|
* @default 0
|
|
103
103
|
*/
|
|
104
104
|
clearTrigger?: number;
|
|
105
|
+
/**
|
|
106
|
+
* @description.zh 画布缩放比例
|
|
107
|
+
* @description.en Canvas scaling ratio
|
|
108
|
+
* @default 1
|
|
109
|
+
*/
|
|
110
|
+
scale?: number;
|
|
111
|
+
/**
|
|
112
|
+
* @description.zh 禁止绘制, 画布超出时可拖动画布
|
|
113
|
+
* @description.en Draggable canvas, while disabling brush drawing
|
|
114
|
+
* @default false
|
|
115
|
+
*/
|
|
116
|
+
isDragging?: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* @description.zh 初始绘制数据
|
|
119
|
+
* @description.en Initial drawing data
|
|
120
|
+
* @default undefined
|
|
121
|
+
*/
|
|
122
|
+
drawData?: Array<{
|
|
123
|
+
x: number;
|
|
124
|
+
y: number;
|
|
125
|
+
color: string;
|
|
126
|
+
}>;
|
|
105
127
|
/**
|
|
106
128
|
* @description.zh 画笔数据更新时触发, 返回画笔经过的x,y坐标路径数据
|
|
107
129
|
* @description.en Triggered when the brush data is updated, returns the x,y coordinate path data of the brush.
|
|
@@ -109,8 +131,8 @@ export interface IProps {
|
|
|
109
131
|
*/
|
|
110
132
|
onStrokeChange?: (data: IStrokeData) => void;
|
|
111
133
|
/**
|
|
112
|
-
* @description.zh 保存数据时触发, 返回画布的base64
|
|
113
|
-
* @description.en Triggered when saving data, return canvas base64 data
|
|
134
|
+
* @description.zh 保存数据时触发, 返回画布的 base64 数据和格子颜色数据(gridData, key 为 格子坐标 "x_y",value 为 颜色值)
|
|
135
|
+
* @description.en Triggered when saving data, return canvas base64 data and grid color data
|
|
114
136
|
* @default () => null
|
|
115
137
|
*/
|
|
116
138
|
onSaveData?: (data: IData) => void;
|
|
@@ -123,5 +145,6 @@ export type IStrokeData = {
|
|
|
123
145
|
};
|
|
124
146
|
export type IData = {
|
|
125
147
|
base64: string;
|
|
148
|
+
gridData: Record<string, string>;
|
|
126
149
|
};
|
|
127
150
|
export declare const defaultProps: IProps;
|
package/lib/props.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ray-js/graffiti",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "画布涂鸦组件",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"files": [
|
|
@@ -31,21 +31,18 @@
|
|
|
31
31
|
"prepublishOnly": "yarn build",
|
|
32
32
|
"release-it": "standard-version"
|
|
33
33
|
},
|
|
34
|
-
"
|
|
35
|
-
"@ray-js/ray": "^1.4.9"
|
|
36
|
-
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"clsx": "^1.2.1"
|
|
39
|
-
},
|
|
34
|
+
"dependencies": {},
|
|
40
35
|
"devDependencies": {
|
|
41
36
|
"@commitlint/cli": "^7.2.1",
|
|
42
37
|
"@commitlint/config-conventional": "^9.0.1",
|
|
43
|
-
"@ray-js/cli": "^1.
|
|
38
|
+
"@ray-js/cli": "^1.7.58",
|
|
39
|
+
"@ray-js/code-sandbox": "0.0.7-beta-10",
|
|
44
40
|
"@ray-js/components-ty-lamp": "^2.0.2",
|
|
45
41
|
"@ray-js/lamp-saturation-slider": "^1.1.7",
|
|
46
42
|
"@ray-js/panel-sdk": "^1.13.0-storage.7",
|
|
47
|
-
"@ray-js/ray": "^1.4.9",
|
|
48
43
|
"@vant/stylelint-config": "^1.4.2",
|
|
44
|
+
"@ray-js/ray": "^1.7.58",
|
|
45
|
+
"clsx": "^1.2.1",
|
|
49
46
|
"core-js": "^3.19.1",
|
|
50
47
|
"eslint-config-tuya-panel": "^0.4.2",
|
|
51
48
|
"husky": "^1.2.0",
|
|
@@ -53,9 +50,6 @@
|
|
|
53
50
|
"standard-version": "9.3.2",
|
|
54
51
|
"stylelint": "^13.0.0"
|
|
55
52
|
},
|
|
56
|
-
"resolutions": {
|
|
57
|
-
"@ray-js/builder-mp": "1.4.15"
|
|
58
|
-
},
|
|
59
53
|
"husky": {
|
|
60
54
|
"hooks": {
|
|
61
55
|
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS --config commitlint.config.js",
|