uni-image-editor 1.0.0
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 +98 -0
- package/components/edit-graffiti-config.vue +151 -0
- package/components/edit-text-config.vue +112 -0
- package/components/image-clipper/image-clipper.vue +1009 -0
- package/components/image-clipper/img/photo.svg +19 -0
- package/components/image-clipper/img/rotate.svg +15 -0
- package/components/image-clipper/index.scss +184 -0
- package/components/image-clipper/utils.js +280 -0
- package/components/input-text-modal.vue +431 -0
- package/image-editor.vue +971 -0
- package/js/const.js +242 -0
- package/js/editor.js +1179 -0
- package/js/utils.js +316 -0
- package/package.json +29 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* @Author: msc 862078729@qq.com
|
|
3
|
+
* @Date: 2024-04-29 22:06:07
|
|
4
|
+
* @LastEditors: msc 862078729@qq.com
|
|
5
|
+
* @LastEditTime: 2024-06-15 18:08:25
|
|
6
|
+
* @FilePath: \code\components\image-editor\components\input-text-modal.vue
|
|
7
|
+
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
|
8
|
+
-->
|
|
9
|
+
<template>
|
|
10
|
+
<view
|
|
11
|
+
v-show="visible"
|
|
12
|
+
class="input-text-modal"
|
|
13
|
+
:style="modalStyle"
|
|
14
|
+
@touchstart.self="handleDragStart"
|
|
15
|
+
@touchmove.self="handleDraging"
|
|
16
|
+
@touchend.self="handleDragEnd"
|
|
17
|
+
>
|
|
18
|
+
<u-input
|
|
19
|
+
type="textarea"
|
|
20
|
+
class="input-text-modal-textarea"
|
|
21
|
+
placeholder=""
|
|
22
|
+
:spellcheck="false"
|
|
23
|
+
:clearable="false"
|
|
24
|
+
:style="{
|
|
25
|
+
width: '100%',
|
|
26
|
+
height: '100%',
|
|
27
|
+
color: color,
|
|
28
|
+
fontSize: size + 'px',
|
|
29
|
+
}"
|
|
30
|
+
:autoHeight="true"
|
|
31
|
+
v-model="inputValue"
|
|
32
|
+
/>
|
|
33
|
+
<i
|
|
34
|
+
v-for="item in strenchIconsConfigs"
|
|
35
|
+
:key="item.name"
|
|
36
|
+
:class="item.icon"
|
|
37
|
+
@touchstart.stop="handleStrenchStart($event, item)"
|
|
38
|
+
@touchmove.stop="handleStrenchMove($event, item)"
|
|
39
|
+
@touchend.stop="handleStrenchEnd($event, item)"
|
|
40
|
+
></i>
|
|
41
|
+
<i class="iconfont icon-strike_out" @tap.stop="cancelInputText" />
|
|
42
|
+
</view>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script>
|
|
46
|
+
import {
|
|
47
|
+
INPUT_TEXT_MODAL_INPUT_DEFAULT_HEIGHT,
|
|
48
|
+
INPUT_TEXT_MODAL_INPUT_DEFAULT_WIDTH,
|
|
49
|
+
INPUT_TEXT_MODAL_STRETCH_ICONS_CONFIGS,
|
|
50
|
+
INPUT_TEXT_MODAL_STRETCH_TYPE,
|
|
51
|
+
} from "../js/const";
|
|
52
|
+
|
|
53
|
+
export default {
|
|
54
|
+
// 组件的props定义,用于接收外部传入的属性值
|
|
55
|
+
props: {
|
|
56
|
+
// value属性,类型为String,表示组件内部的值,默认为空字符串
|
|
57
|
+
value: {
|
|
58
|
+
type: String,
|
|
59
|
+
default: "",
|
|
60
|
+
},
|
|
61
|
+
// color属性,类型为String,表示文本的颜色,默认为白色
|
|
62
|
+
color: {
|
|
63
|
+
type: String,
|
|
64
|
+
default: "#fff",
|
|
65
|
+
},
|
|
66
|
+
// size属性,类型为Number或String,表示文本的大小,默认为12
|
|
67
|
+
size: {
|
|
68
|
+
type: [Number, String],
|
|
69
|
+
default: 12,
|
|
70
|
+
},
|
|
71
|
+
// containerPosition属性,类型为Object,表示容器(如模态框)的位置,默认为null
|
|
72
|
+
containerPosition: {
|
|
73
|
+
type: Object,
|
|
74
|
+
default: null,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// 组件的data函数,用于返回组件的初始数据状态
|
|
79
|
+
data() {
|
|
80
|
+
return {
|
|
81
|
+
// inputValue用于存储和显示当前的输入值
|
|
82
|
+
inputValue: "",
|
|
83
|
+
// visible用于控制模态框的可见性,但在此代码片段中未使用
|
|
84
|
+
visible: false,
|
|
85
|
+
// position对象,用于设置某个元素(如输入框)的位置
|
|
86
|
+
position: {
|
|
87
|
+
left: 0,
|
|
88
|
+
top: 0,
|
|
89
|
+
},
|
|
90
|
+
// 输入框的默认宽度
|
|
91
|
+
width: INPUT_TEXT_MODAL_INPUT_DEFAULT_WIDTH,
|
|
92
|
+
// 输入框的默认高度
|
|
93
|
+
height: INPUT_TEXT_MODAL_INPUT_DEFAULT_HEIGHT,
|
|
94
|
+
// 与可拉伸图标或类似功能相关的配置
|
|
95
|
+
strenchIconsConfigs: INPUT_TEXT_MODAL_STRETCH_ICONS_CONFIGS,
|
|
96
|
+
// 与可拉伸功能相关的点数组
|
|
97
|
+
strenchPoints: [],
|
|
98
|
+
//与拖动功能相关的点数组
|
|
99
|
+
dragPoints: [],
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// 组件的computed计算属性,用于根据组件的状态返回计算后的值
|
|
104
|
+
computed: {
|
|
105
|
+
// modalStyle返回一个样式对象,用于动态设置模态框的样式
|
|
106
|
+
modalStyle() {
|
|
107
|
+
return {
|
|
108
|
+
width: this.width + "px",
|
|
109
|
+
height: this.height + "px",
|
|
110
|
+
left: this.position.left + "px",
|
|
111
|
+
top: this.position.top + "px",
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
// exportState返回一个对象,包含了输入框的各种状态信息,如位置、大小、文本内容等
|
|
115
|
+
exportState() {
|
|
116
|
+
return {
|
|
117
|
+
// 根据position和size属性计算出的文本框绘制位置
|
|
118
|
+
// 给定制定坐标给filltext渲染,渲染出来的文字的x和y坐标相比给定的x,y有差值,需要额外加上差值
|
|
119
|
+
// 5为弹窗的padding,this.size * 0.1为canvas的filltext的水平对齐方式align离x坐标的差值
|
|
120
|
+
left: this.position.left + 10 + this.size * 0.1,
|
|
121
|
+
// 5为弹窗的padding,this.size * 0.1为(行高-当前文字尺寸)/ 2,this.size / 6为canvas的filltext文字对齐方式为top时文字顶端离y坐标的差值
|
|
122
|
+
top: this.position.top + 10 + 0.1 * this.size + this.size / 6,
|
|
123
|
+
// 10 为间距
|
|
124
|
+
width: this.width - 20,
|
|
125
|
+
// 10 为间距
|
|
126
|
+
height: this.height - 20,
|
|
127
|
+
text: this.value,
|
|
128
|
+
color: this.color,
|
|
129
|
+
size: this.size,
|
|
130
|
+
lineHeight: this.size * 1.2,
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// 组件的watch监听器,用于监听data或computed属性的变化,并作出响应
|
|
136
|
+
watch: {
|
|
137
|
+
// 监听inputValue的变化,当变化时触发input事件,并传递新的值
|
|
138
|
+
inputValue: {
|
|
139
|
+
handler(val) {
|
|
140
|
+
this.$emit("input", val);
|
|
141
|
+
},
|
|
142
|
+
// 设置为immediate: true表示在组件创建时就会立即触发一次handler
|
|
143
|
+
immediate: true,
|
|
144
|
+
},
|
|
145
|
+
// 监听value prop的变化,当变化时更新inputValue的值
|
|
146
|
+
value: {
|
|
147
|
+
handler(val) {
|
|
148
|
+
this.inputValue = val;
|
|
149
|
+
},
|
|
150
|
+
// 设置为immediate: true表示在组件创建时就会立即触发一次handler
|
|
151
|
+
immediate: true,
|
|
152
|
+
},
|
|
153
|
+
// 监听exportState计算属性的变化(深度监听),当变化时触发change事件,并传递exportState对象
|
|
154
|
+
exportState: {
|
|
155
|
+
handler(val) {
|
|
156
|
+
this.$emit("change", this.exportState);
|
|
157
|
+
},
|
|
158
|
+
// 设置为deep: true表示深度监听exportState的变化
|
|
159
|
+
deep: true,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
methods: {
|
|
163
|
+
// 处理拉伸开始的逻辑
|
|
164
|
+
handleStrenchStart(e, item) {
|
|
165
|
+
const { x, y } = this.getActualPosition(
|
|
166
|
+
e.touches[0].clientX,
|
|
167
|
+
e.touches[0].clientY
|
|
168
|
+
);
|
|
169
|
+
this.strenchPoints.push({
|
|
170
|
+
x,
|
|
171
|
+
y,
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// 处理拉伸过程中
|
|
176
|
+
handleStrenchMove(e, item) {
|
|
177
|
+
if (!this.strenchPoints.length) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.strenchPoints.length > 1 && this.strenchPoints.shift();
|
|
182
|
+
|
|
183
|
+
const { x, y } = this.getActualPosition(
|
|
184
|
+
e.touches[0].clientX,
|
|
185
|
+
e.touches[0].clientY
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
this.strenchPoints.push({
|
|
189
|
+
x,
|
|
190
|
+
y,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const type = item.name;
|
|
194
|
+
|
|
195
|
+
// 计算两个点之间的移动距离
|
|
196
|
+
const moveX = this.strenchPoints[1].x - this.strenchPoints[0].x;
|
|
197
|
+
|
|
198
|
+
const moveY = this.strenchPoints[1].y - this.strenchPoints[0].y;
|
|
199
|
+
|
|
200
|
+
// 根据拉伸类型更新元素的位置和大小
|
|
201
|
+
switch (type) {
|
|
202
|
+
// 更新左上角拉伸时的位置和大小
|
|
203
|
+
case INPUT_TEXT_MODAL_STRETCH_TYPE.topLeft:
|
|
204
|
+
this.position.top = this.position.top + moveY;
|
|
205
|
+
|
|
206
|
+
this.position.left = this.position.left + moveX;
|
|
207
|
+
|
|
208
|
+
this.width = this.width - moveX;
|
|
209
|
+
|
|
210
|
+
this.height = this.height - moveY;
|
|
211
|
+
break;
|
|
212
|
+
// 更新右上角拉伸时的位置和大小
|
|
213
|
+
case INPUT_TEXT_MODAL_STRETCH_TYPE.topRight:
|
|
214
|
+
this.position.top = this.position.top + moveY;
|
|
215
|
+
this.width = this.width + moveX;
|
|
216
|
+
|
|
217
|
+
this.height = this.height - moveY;
|
|
218
|
+
break;
|
|
219
|
+
// 更新左下角拉伸时的位置和大小
|
|
220
|
+
case INPUT_TEXT_MODAL_STRETCH_TYPE.bottomLeft:
|
|
221
|
+
this.position.left = this.position.left + moveX;
|
|
222
|
+
this.width = this.width - moveX;
|
|
223
|
+
|
|
224
|
+
this.height = this.height + moveY;
|
|
225
|
+
break;
|
|
226
|
+
// 更新右下角拉伸时的位置和大小
|
|
227
|
+
case INPUT_TEXT_MODAL_STRETCH_TYPE.bottomRight:
|
|
228
|
+
this.width = this.width + moveX;
|
|
229
|
+
|
|
230
|
+
this.height = this.height + moveY;
|
|
231
|
+
|
|
232
|
+
break;
|
|
233
|
+
|
|
234
|
+
default:
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
// 处理拉伸结束
|
|
239
|
+
handleStrenchEnd(e, item) {
|
|
240
|
+
this.strenchPoints = [];
|
|
241
|
+
},
|
|
242
|
+
// 当拖动开始时触发的方法
|
|
243
|
+
handleDragStart(e) {
|
|
244
|
+
// 获取触摸点的实际位置(相对于容器)
|
|
245
|
+
const { x, y } = this.getActualPosition(
|
|
246
|
+
e.touches[0].clientX, // 触摸点的X坐标
|
|
247
|
+
e.touches[0].clientY // 触摸点的Y坐标
|
|
248
|
+
);
|
|
249
|
+
// 将触摸点的位置信息添加到dragPoints数组中
|
|
250
|
+
this.dragPoints.push({
|
|
251
|
+
x,
|
|
252
|
+
y,
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
// 当拖动过程中触发的方法
|
|
257
|
+
handleDraging(e) {
|
|
258
|
+
// 如果没有dragPoints(即没有开始拖动),则直接返回
|
|
259
|
+
if (!this.dragPoints.length) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 如果dragPoints中有多个点(通常最多应该只有2个点,用于计算移动距离),则移除第一个点
|
|
264
|
+
this.dragPoints.length > 1 && this.dragPoints.shift();
|
|
265
|
+
|
|
266
|
+
// 获取当前触摸点的实际位置(相对于容器)
|
|
267
|
+
const { x, y } = this.getActualPosition(
|
|
268
|
+
e.touches[0].clientX,
|
|
269
|
+
e.touches[0].clientY
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// 将新的触摸点位置信息添加到dragPoints数组中
|
|
273
|
+
this.dragPoints.push({
|
|
274
|
+
x,
|
|
275
|
+
y,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// 计算移动的距离(即新点与旧点之间的差值)
|
|
279
|
+
const moveX = this.dragPoints[1].x - this.dragPoints[0].x;
|
|
280
|
+
const moveY = this.dragPoints[1].y - this.dragPoints[0].y;
|
|
281
|
+
|
|
282
|
+
// 更新元素的位置
|
|
283
|
+
this.position.left += moveX;
|
|
284
|
+
this.position.top += moveY;
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
// 当拖动结束时触发的方法
|
|
288
|
+
handleDragEnd(e) {
|
|
289
|
+
// 清空dragPoints数组,为下一次拖动做准备
|
|
290
|
+
this.dragPoints = [];
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
// 在指定位置显示元素的方法
|
|
294
|
+
showAt(x, y) {
|
|
295
|
+
// 获取相对于容器的实际位置
|
|
296
|
+
const position = this.getActualPosition(x, y);
|
|
297
|
+
|
|
298
|
+
// 计算元素应该显示的位置(通常使其居中显示)
|
|
299
|
+
this.position.left =
|
|
300
|
+
position.x - INPUT_TEXT_MODAL_INPUT_DEFAULT_WIDTH / 2;
|
|
301
|
+
this.position.top =
|
|
302
|
+
position.y - INPUT_TEXT_MODAL_INPUT_DEFAULT_HEIGHT / 2;
|
|
303
|
+
|
|
304
|
+
// 显示元素
|
|
305
|
+
this.visible = true;
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
// 提交数据并隐藏元素的方法
|
|
309
|
+
submit(noSubmit = false) {
|
|
310
|
+
// 触发submit事件,并传递exportState对象
|
|
311
|
+
!noSubmit && this.$emit("submit", this.exportState);
|
|
312
|
+
if(noSubmit){
|
|
313
|
+
return this.exportState
|
|
314
|
+
}
|
|
315
|
+
// 隐藏元素
|
|
316
|
+
this.hide();
|
|
317
|
+
// 重置元素状态
|
|
318
|
+
this.reset();
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
// 隐藏元素的方法
|
|
324
|
+
hide() {
|
|
325
|
+
this.visible = false;
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
// 显示元素的方法
|
|
329
|
+
show() {
|
|
330
|
+
this.visible = true;
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
cancelInputText() {
|
|
334
|
+
this.$emit("cancel");
|
|
335
|
+
this.hide();
|
|
336
|
+
this.reset();
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// 获取相对于容器的实际位置的方法
|
|
340
|
+
getActualPosition(x, y) {
|
|
341
|
+
// 减去容器位置,得到相对于容器的实际位置
|
|
342
|
+
return {
|
|
343
|
+
x: x - this.containerPosition.x,
|
|
344
|
+
y: y - this.containerPosition.y,
|
|
345
|
+
};
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// 重置元素状态的方法
|
|
349
|
+
reset() {
|
|
350
|
+
// 重置元素的宽度、高度、位置、输入值等属性
|
|
351
|
+
this.width = INPUT_TEXT_MODAL_INPUT_DEFAULT_WIDTH;
|
|
352
|
+
this.height = INPUT_TEXT_MODAL_INPUT_DEFAULT_HEIGHT;
|
|
353
|
+
this.position.left = 0;
|
|
354
|
+
this.position.top = 0;
|
|
355
|
+
this.inputValue = "";
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
</script>
|
|
360
|
+
<style scoped lang="scss">
|
|
361
|
+
.input-text-modal {
|
|
362
|
+
position: absolute;
|
|
363
|
+
padding: 10px;
|
|
364
|
+
box-sizing: border-box;
|
|
365
|
+
background-color: transparent;
|
|
366
|
+
border: 1px solid #fff;
|
|
367
|
+
|
|
368
|
+
.input-text-modal-textarea {
|
|
369
|
+
word-break: break-all;
|
|
370
|
+
overflow: hidden;
|
|
371
|
+
}
|
|
372
|
+
.icon-strike_out {
|
|
373
|
+
color: rgb(61, 104, 232);
|
|
374
|
+
background-color: #fff;
|
|
375
|
+
border-radius: 50%;
|
|
376
|
+
position: absolute;
|
|
377
|
+
top: 0;
|
|
378
|
+
left: 50%;
|
|
379
|
+
translate: -50% -35rpx;
|
|
380
|
+
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.stretch-icon {
|
|
384
|
+
color: rgb(61, 104, 232);
|
|
385
|
+
background-color: #fff;
|
|
386
|
+
border-radius: 50%;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.stretch-icon-top-left {
|
|
390
|
+
position: absolute;
|
|
391
|
+
top: 0;
|
|
392
|
+
left: 0;
|
|
393
|
+
|
|
394
|
+
translate: -50% -50%;
|
|
395
|
+
}
|
|
396
|
+
.stretch-icon-top-right {
|
|
397
|
+
position: absolute;
|
|
398
|
+
top: 0;
|
|
399
|
+
right: 0;
|
|
400
|
+
translate: 50% -50%;
|
|
401
|
+
|
|
402
|
+
transform: rotate(90deg);
|
|
403
|
+
}
|
|
404
|
+
.stretch-icon-bottom-right {
|
|
405
|
+
position: absolute;
|
|
406
|
+
bottom: 0;
|
|
407
|
+
right: 0;
|
|
408
|
+
translate: 50% 50%;
|
|
409
|
+
|
|
410
|
+
transform: rotate(-180deg);
|
|
411
|
+
}
|
|
412
|
+
.stretch-icon-bottom-left {
|
|
413
|
+
position: absolute;
|
|
414
|
+
bottom: 0;
|
|
415
|
+
left: 0;
|
|
416
|
+
translate: -50% 50%;
|
|
417
|
+
|
|
418
|
+
transform: rotate(270deg);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
::v-deep .u-input__input {
|
|
423
|
+
color: inherit !important;
|
|
424
|
+
font-size: inherit !important;
|
|
425
|
+
padding: 0;
|
|
426
|
+
width: 100%;
|
|
427
|
+
height: 100% !important;
|
|
428
|
+
max-width: 100% !important;
|
|
429
|
+
line-height: 1 * 1.2;
|
|
430
|
+
}
|
|
431
|
+
</style>
|