Skip to content

Commit eecd655

Browse files
committed
feat(editor): support move plugin on cawqnvas(zh: 画布上元素支持移动)
1 parent 25dbd43 commit eecd655

File tree

5 files changed

+233
-16
lines changed

5 files changed

+233
-16
lines changed

front-end/h5/src/components/core/editor/canvas/edit.js

+27-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import Shape from '../../support/shape'
12
export default {
2-
props: ['elements', 'handleElementClick'],
3+
props: ['elements', 'editingElement', 'handleClickElementProp', 'handleClickCanvasProp'],
34
methods: {
45
/**
56
* #!zh: renderCanvas 渲染中间画布
@@ -10,17 +11,38 @@ export default {
1011
*/
1112
renderCanvas (h, elements) {
1213
return (
13-
<div style={{ height: '100%' }}>
14+
<div
15+
style={{ height: '100%', position: 'relative' }}
16+
class="canvas-editor-wrapper"
17+
onClick={this.handleClickCanvasProp}
18+
>
1419
{
1520
elements.map((element, index) => {
21+
const style = element.getStyle()
1622
const data = {
17-
style: element.getStyle(),
23+
style,
24+
class: 'element-on-edit-canvas', // TODO 添加为何添加 class 的原因:与 handleClickCanvas 配合
1825
props: element.pluginProps, // #6 #3
1926
nativeOn: {
20-
click: () => this.handleElementClick(element)
27+
// 高亮当前点击的元素
28+
// click: () => this.handleClickElementProp(element)
2129
}
2230
}
23-
return h(element.name, data)
31+
return (
32+
<Shape
33+
element={element}
34+
editingElement={this.editingElement}
35+
active={this.editingElement === element}
36+
handleMousedownProp={() => {
37+
// 在 shape 上面添加 mousedown,而非 plugin 本身添加 onClick 的原因:
38+
// 在 mousedown 的时候,即可激活 editingElement(当前选中元素)
39+
// 这样,就不用等到鼠标抬起的时候,也就是 plugin 的 onClick 生效的时候,才给选中的元素添加边框等选中效果
40+
this.handleClickElementProp(element)
41+
}}
42+
>
43+
{h(element.name, data)}
44+
</Shape>
45+
)
2446
})
2547
}
2648
</div>

front-end/h5/src/components/core/editor/index.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ export default {
5454
},
5555
setCurrentEditingElement (element) {
5656
this.editingElement = element
57+
},
58+
handleClickCanvas (e) {
59+
if (!e.target.classList.contains('element-on-edit-canvas')) {
60+
this.editingElement = null
61+
}
5762
}
5863
},
5964
render (h) {
@@ -104,7 +109,15 @@ export default {
104109
</div>
105110
<div class='canvas-wrapper'>
106111
{/* { this.isPreviewMode ? this.renderPreview(h, this.elements) : this.renderCanvas(h, this.elements) } */}
107-
{ this.isPreviewMode ? <RenderPreviewCanvas elements={this.elements}/> : <RenderEditCanvas elements={this.elements} handleElementClick={this.setCurrentEditingElement} /> }
112+
{ this.isPreviewMode
113+
? <RenderPreviewCanvas elements={this.elements}/>
114+
: <RenderEditCanvas
115+
elements={this.elements}
116+
handleClickElementProp={this.setCurrentEditingElement}
117+
handleClickCanvasProp={this.handleClickCanvas}
118+
editingElement={this.editingElement}
119+
/>
120+
}
108121
</div>
109122
</a-layout-content>
110123
</a-layout>

front-end/h5/src/components/core/models/element.js

+12-9
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ class Element {
1616
this.name = ele.name
1717
this.editorConfig = ele.editorConfig || {}
1818
this.pluginProps = {}
19+
this.commonStyle = {}
1920
this.init()
2021
}
2122

2223
init () {
24+
const commonStyle = this.commonStyle
2325
// init common props
2426
Object.keys(defaultProps).forEach(key => {
25-
this[key] = defaultProps[key]
27+
commonStyle[key] = defaultProps[key]
2628
})
2729

2830
// init prop of plugin
@@ -39,15 +41,16 @@ class Element {
3941

4042
getStyle () {
4143
const pluginProps = this.pluginProps
44+
const commonStyle = this.commonStyle
4245
let style = {
43-
top: `${pluginProps.top || this.top}px`,
44-
left: `${pluginProps.left || this.left}px`,
45-
width: `${pluginProps.width || this.width}px`,
46-
height: `${pluginProps.height || this.height}px`,
47-
fontSize: `${pluginProps.fontSize || this.fontSize}px`,
48-
color: pluginProps.color || this.color,
49-
backgroundColor: pluginProps.backgroundColor || this.backgroundColor,
50-
textAlign: pluginProps.textAlign || this.textAlign
46+
top: `${pluginProps.top || commonStyle.top}px`,
47+
left: `${pluginProps.left || commonStyle.left}px`,
48+
width: `${pluginProps.width || commonStyle.width}px`,
49+
height: `${pluginProps.height || commonStyle.height}px`,
50+
fontSize: `${pluginProps.fontSize || commonStyle.fontSize}px`,
51+
color: pluginProps.color || commonStyle.color,
52+
backgroundColor: pluginProps.backgroundColor || commonStyle.backgroundColor,
53+
textAlign: pluginProps.textAlign || commonStyle.textAlign
5154
}
5255
return style
5356
}

front-end/h5/src/components/core/styles/index.scss

+17-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// background: rgba(255,255,255,.2);
99
margin: 16px 28px 16px 0;
1010
float: left;
11-
11+
1212
line-height: 31px;
1313
text-align: center;
1414
color: white;
@@ -28,6 +28,22 @@
2828
}
2929
}
3030

31+
.canvas-editor-wrapper {
32+
.element--editing {
33+
outline: 1px dashed #70c0ff !important;
34+
}
35+
36+
// !#zh 控制缩放大小的圆点
37+
.shape__scale-point {
38+
position: absolute;
39+
background: #fff;
40+
border: 1px solid rgb(89, 199, 249);
41+
width: 6px;
42+
height: 6px;
43+
z-index: 1;
44+
border-radius: 50%;
45+
}
46+
}
3147
}
3248

3349

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/**
2+
* #!zh: 上下左右 对应的 东南西北
3+
* #!en: top(north)、bottom(south)、left(west)、right(east)
4+
*/
5+
const directionKey = {
6+
t: 'n',
7+
b: 's',
8+
l: 'w',
9+
r: 'e'
10+
}
11+
12+
// #!zh: 四个边角、两条中线上的点
13+
const points = ['lt', 'rt', 'lb', 'rb', 'l', 'r', 't', 'b']
14+
15+
export default {
16+
props: ['element', 'active', 'editingElement', 'handleMousedownProp'],
17+
methods: {
18+
/**
19+
* 通过方位计算样式,主要是 top、left、鼠标样式
20+
*/
21+
getPointStyle (point, isWrapElement = true) {
22+
// const eleStyle = this.numbericElementStyle
23+
const pos = this.element.commonStyle
24+
const top = pos.top - 4 // !#zh 减4是为了让元素能够处于 border 的中间
25+
const left = pos.left - 4
26+
const height = pos.height
27+
const width = pos.width
28+
let hasT = /t/.test(point)
29+
let hasB = /b/.test(point)
30+
let hasL = /l/.test(point)
31+
let hasR = /r/.test(point)
32+
let newLeft = 0
33+
let newTop = 0
34+
if (point.length === 2) {
35+
newLeft = hasL ? 0 : width
36+
newTop = hasT ? 0 : height
37+
} else {
38+
// !#zh 上下点,宽度固定在中间
39+
if (hasT || hasB) {
40+
newLeft = width / 2
41+
newTop = hasT ? 0 : height
42+
}
43+
// !#zh 左右点,高度固定在中间
44+
if (hasL || hasR) {
45+
newLeft = hasL ? 0 : width
46+
newTop = height / 2
47+
}
48+
}
49+
const style = {
50+
left: `${newLeft + (isWrapElement ? 0 : left)}px`,
51+
top: `${newTop + (isWrapElement ? 0 : top)}px`,
52+
cursor: point.split('').reverse().map(m => directionKey[m]).join('') + '-resize'
53+
}
54+
return style
55+
},
56+
/**
57+
* !#zh 主要目的是:阻止冒泡
58+
*/
59+
handleWrapperClick (e) {
60+
e.stopPropagation()
61+
e.preventDefault()
62+
},
63+
mousedownForMark (point, downEvent) {
64+
downEvent.stopPropagation()
65+
downEvent.preventDefault() // Let's stop this event.
66+
// let eleStyle = this.numbericElementStyle
67+
const pos = this.element.commonStyle
68+
let height = pos.height
69+
let width = pos.width
70+
let top = pos.top
71+
let left = pos.left
72+
let startX = downEvent.clientX
73+
let startY = downEvent.clientY
74+
let move = moveEvent => {
75+
let currX = moveEvent.clientX
76+
let currY = moveEvent.clientY
77+
let disY = currY - startY
78+
let disX = currX - startX
79+
let hasT = /t/.test(point)
80+
let hasB = /b/.test(point)
81+
let hasL = /l/.test(point)
82+
let hasR = /r/.test(point)
83+
let newHeight = +height + (hasT ? -disY : hasB ? disY : 0)
84+
let newWidth = +width + (hasL ? -disX : hasR ? disX : 0)
85+
pos.height = newHeight > 0 ? newHeight : 0
86+
pos.width = newWidth > 0 ? newWidth : 0
87+
pos.left = +left + (hasL ? disX : 0)
88+
pos.top = +top + (hasT ? disY : 0)
89+
}
90+
let up = () => {
91+
document.removeEventListener('mousemove', move)
92+
document.removeEventListener('mouseup', up)
93+
}
94+
document.addEventListener('mousemove', move)
95+
document.addEventListener('mouseup', up)
96+
},
97+
/**
98+
* !#zh 给 当前选中元素 添加鼠标移动相关事件
99+
*
100+
* @param {Object} element
101+
* @param {mouseEvent} e
102+
*/
103+
mousedownForElement (e, element) {
104+
const pos = element.commonStyle
105+
let startY = e.clientY
106+
let startX = e.clientX
107+
let startTop = pos.top
108+
let startLeft = pos.left
109+
110+
let move = moveEvent => {
111+
// !#zh 移动的时候,不需要向后代元素传递事件,只需要单纯的移动就OK
112+
moveEvent.stopPropagation()
113+
moveEvent.preventDefault()
114+
115+
let currX = moveEvent.clientX
116+
let currY = moveEvent.clientY
117+
pos.top = currY - startY + startTop
118+
pos.left = currX - startX + startLeft
119+
}
120+
121+
let up = moveEvent => {
122+
document.removeEventListener('mousemove', move, true)
123+
document.removeEventListener('mouseup', up, true)
124+
}
125+
document.addEventListener('mousemove', move, true)
126+
document.addEventListener('mouseup', up, true)
127+
// TODO add comment
128+
return true
129+
},
130+
handleMousedown (e) {
131+
if (this.handleMousedownProp) {
132+
this.handleMousedownProp()
133+
this.mousedownForElement(e, this.element)
134+
}
135+
}
136+
},
137+
render (h) {
138+
return (
139+
<div
140+
onClick={this.handleWrapperClick}
141+
onMousedown={this.handleMousedown}
142+
style={{ ...this.element.getStyle(), position: 'absolute' }}
143+
>
144+
{
145+
this.active &&
146+
points.map(point => {
147+
const pointStyle = this.getPointStyle(point)
148+
return (
149+
<div
150+
key={point}
151+
data-point={point}
152+
style={pointStyle}
153+
class="shape__scale-point"
154+
onMousedown={this.mousedownForMark.bind(this, point)}
155+
></div>
156+
)
157+
})
158+
}
159+
{this.$slots.default}
160+
</div>
161+
)
162+
}
163+
}

0 commit comments

Comments
 (0)