├── test
├── _config.yml
├── palette.gif
├── palette
├── 画板palette.webm
├── index.html
└── js
│ └── palette.js
└── README.md
/test:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-slate
--------------------------------------------------------------------------------
/palette.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acejsnb/konva-palette/HEAD/palette.gif
--------------------------------------------------------------------------------
/palette/画板palette.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acejsnb/konva-palette/HEAD/palette/画板palette.webm
--------------------------------------------------------------------------------
/palette/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | palette
6 |
22 |
23 |
24 |
43 |
44 |
45 |
46 |
146 |
147 |
--------------------------------------------------------------------------------
/palette/js/palette.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 铅笔
3 | * @param points 点数组
4 | * @param stroke 颜色
5 | * @param strokeWidth 线粗细
6 | */
7 | function drawPencil(points, stroke, strokeWidth) {
8 | const line = new Konva.Line({
9 | name: 'line',
10 | points: points,
11 | stroke: stroke,
12 | strokeWidth: strokeWidth,
13 | lineCap: 'round',
14 | lineJoin: 'round',
15 | tension: 0.5,
16 | draggable: true
17 | });
18 | graphNow=line;
19 | layer.add(line);
20 | layer.draw();
21 |
22 | line.on('mouseenter', function() {
23 | stage.container().style.cursor = 'move';
24 | });
25 |
26 | line.on('mouseleave', function() {
27 | stage.container().style.cursor = 'default';
28 | });
29 |
30 | line.on('dblclick', function() {
31 | // 双击删除自己
32 | this.remove();
33 | stage.find('Transformer').destroy();
34 | layer.draw();
35 | });
36 | }
37 |
38 | /**
39 | * 椭圆
40 | * @param x x坐标
41 | * @param y y坐标
42 | * @param rx x半径
43 | * @param ry y半径
44 | * @param stroke 描边颜色
45 | * @param strokeWidth 描边大小
46 | */
47 | function drawEllipse(x, y, rx, ry, stroke, strokeWidth) {
48 | const ellipse=new Konva.Ellipse({
49 | name: 'ellipse',
50 | x: x,
51 | y: y,
52 | radiusX: rx,
53 | radiusY: ry,
54 | stroke: stroke,
55 | strokeWidth: strokeWidth,
56 | draggable: true
57 | });
58 | graphNow=ellipse;
59 | layer.add(ellipse);
60 | layer.draw();
61 |
62 | ellipse.on('mouseenter', function() {
63 | stage.container().style.cursor = 'move';
64 | });
65 |
66 | ellipse.on('mouseleave', function() {
67 | stage.container().style.cursor = 'default';
68 | });
69 |
70 | ellipse.on('dblclick', function() {
71 | // 双击删除自己
72 | this.remove();
73 | stage.find('Transformer').destroy();
74 | layer.draw();
75 | });
76 | }
77 |
78 | /**
79 | * 矩形
80 | * @param x x坐标
81 | * @param y y坐标
82 | * @param w 宽
83 | * @param h 高
84 | * @param c 颜色
85 | * @param sw 该值大于0-表示空心矩形(描边宽),等于0-表示实心矩形
86 | */
87 | function drawRect(x, y, w, h, c, sw) {
88 | const rect = new Konva.Rect({
89 | name: 'rect',
90 | x: x,
91 | y: y,
92 | width: w,
93 | height: h,
94 | fill: sw===0?c:null,
95 | stroke: sw>0?c:null,
96 | strokeWidth: sw,
97 | opacity: sw===0?0.5:1,
98 | draggable: true
99 | });
100 | graphNow=rect;
101 | layer.add(rect);
102 | layer.draw();
103 |
104 | rect.on('mouseenter', function() {
105 | stage.container().style.cursor = 'move';
106 | });
107 |
108 | rect.on('mouseleave', function() {
109 | stage.container().style.cursor = 'default';
110 | });
111 |
112 | rect.on('dblclick', function() {
113 | // 双击删除自己
114 | this.remove();
115 | stage.find('Transformer').destroy();
116 | layer.draw();
117 | });
118 | }
119 |
120 | /**
121 | * 输入文字
122 | * @param x x坐标
123 | * @param y y坐标
124 | * @param fill 文字颜色
125 | * @param fs 文字大小
126 | */
127 | function drawText(x, y, fill, fs) {
128 | var text = new Konva.Text({
129 | text: '双击编辑文字',
130 | x: x,
131 | y: y,
132 | fill: fill,
133 | fontSize: fs,
134 | width: 300,
135 | draggable: true
136 | });
137 | graphNow=text;
138 | layer.add(text);
139 | layer.draw();
140 |
141 | text.on('mouseenter', function() {
142 | stage.container().style.cursor = 'move';
143 | });
144 |
145 | text.on('mouseleave', function() {
146 | stage.container().style.cursor = 'default';
147 | });
148 |
149 | text.on('dblclick', function() {
150 | // 在画布上创建具有绝对位置的textarea
151 |
152 | // 首先,我们需要为textarea找到位置
153 |
154 | // 首先,让我们找到文本节点相对于舞台的位置:
155 | let textPosition = this.getAbsolutePosition();
156 |
157 | // 然后让我们在页面上找到stage容器的位置
158 | let stageBox = stage.container().getBoundingClientRect();
159 |
160 | // 因此textarea的位置将是上面位置的和
161 | let areaPosition = {
162 | x: stageBox.left + textPosition.x,
163 | y: stageBox.top + textPosition.y
164 | };
165 |
166 | // 创建textarea并设置它的样式
167 | let textarea = document.createElement('textarea');
168 | document.body.appendChild(textarea);
169 |
170 | let T=this.text();
171 | if (T === '双击编辑文字') {
172 | textarea.value = '';
173 | textarea.setAttribute('placeholder','请输入文字')
174 | } else {
175 | textarea.value = T;
176 | }
177 |
178 | textarea.style.position = 'absolute';
179 | textarea.style.top = areaPosition.y + 'px';
180 | textarea.style.left = areaPosition.x + 'px';
181 | textarea.style.background = 'none';
182 | textarea.style.border = '1px dashed #000';
183 | textarea.style.outline = 'none';
184 | textarea.style.color = this.fill();
185 | textarea.style.width = this.width();
186 |
187 | textarea.focus();
188 |
189 | this.setAttr('text', '');
190 | layer.draw();
191 |
192 | // 确定输入的文字
193 | let confirm=(val) => {
194 | this.text(val?val:'双击编辑文字');
195 | layer.draw();
196 | // 隐藏在输入
197 | if (textarea) document.body.removeChild(textarea);
198 | };
199 | // 回车键
200 | let keydown=(e) => {
201 | if (e.keyCode === 13) {
202 | textarea.removeEventListener('blur', blur);
203 | confirm(textarea.value)
204 | }
205 | };
206 | // 鼠标失去焦点
207 | let blur=() => {
208 | textarea.removeEventListener('keydown', keydown);
209 | confirm(textarea.value);
210 | };
211 |
212 | textarea.addEventListener('keydown', keydown);
213 | textarea.addEventListener('blur', blur);
214 | });
215 | }
216 |
217 | /**
218 | * stage鼠标按下
219 | * @param flag 是否可绘制
220 | * @param ev 传入的event对象
221 | */
222 | function stageMousedown(flag, ev) {
223 | if (flag) {
224 | let x=ev.evt.offsetX, y=ev.evt.offsetY;
225 | pointStart=[x, y];
226 |
227 | switch (flag) {
228 | case 'pencil':
229 | drawPencil(pointStart, graphColor, 2);
230 | break;
231 | case 'ellipse':
232 | // 椭圆
233 | drawEllipse(x, y, 0, 0, graphColor, 2);
234 | break;
235 | case 'rect':
236 | drawRect(x, y, 0, 0, graphColor, 0);
237 | break;
238 | case 'rectH':
239 | drawRect(x, y, 0, 0, graphColor, 2);
240 | break;
241 | case 'text':
242 | drawText(x, y, graphColor, 16);
243 | break;
244 | default:
245 | break;
246 | }
247 | drawing=true;
248 | }
249 | }
250 |
251 | /**
252 | * stage鼠标移动
253 | * @param flag 是否可绘制
254 | * @param ev 传入的event对象
255 | */
256 | function stageMousemove(flag, ev) {
257 | switch (flag) {
258 | case 'pencil':
259 | // 铅笔
260 | pointStart.push(ev.evt.offsetX, ev.evt.offsetY);
261 | graphNow.setAttrs({
262 | points: pointStart
263 | });
264 | break;
265 | case 'ellipse':
266 | // 椭圆
267 | graphNow.setAttrs({
268 | radiusX: Math.abs(ev.evt.offsetX-pointStart[0]),
269 | radiusY: Math.abs(ev.evt.offsetY-pointStart[1])
270 | });
271 | break;
272 | case 'rect':
273 | case 'rectH':
274 | graphNow.setAttrs({
275 | width: ev.evt.offsetX-pointStart[0],
276 | height: ev.evt.offsetY-pointStart[1]
277 | });
278 | break;
279 | default:
280 | break;
281 | }
282 | layer.draw();
283 | }
284 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # canvas2D库konva konvajs制作画板功能 类似QQ截图 可拖动
2 | [demo演示](https://xiongshuang.github.io/konva-palette/palette/index.html)
3 | [demo2](https://xiongshuang.github.io/painting/)
4 |
5 | 
6 |
7 | ## 一、 变量申明
8 |
9 | let draw=[], // 绘制的图形数组
10 | graphNow=null, // 当前图形
11 | flag=null, // 激活绘制-铅笔 pencil:铅笔 ellipse:椭圆 rect:矩形 rectH:矩形-空心
12 | drawing=false, // 绘制中
13 | graphColor='red', // 默认颜色
14 | pointStart=[]; // 初始坐标
15 |
16 | ## 二、 获得Konva对象
17 |
18 | // 1 create stage
19 | const stage=new Konva.Stage({
20 | container: 'container',
21 | width: 1200,
22 | height: 800
23 | });
24 |
25 | // 2 create layer
26 | const layer=new Konva.Layer();
27 | stage.add(layer);
28 |
29 | // 3 create our shape
30 |
31 | // 移除改变大小事件
32 | stage.on('mousedown', function(e) {
33 | // 如果点击空白处 移除图形选择框
34 | // console.log(e);
35 |
36 | if (e.target === stage) {
37 | stageMousedown(flag, e);
38 |
39 | // 移除图形选择框
40 | stage.find('Transformer').destroy();
41 | layer.draw();
42 | return;
43 | }
44 | // 如果没有匹配到就终止往下执行
45 | if (!e.target.hasName('line') && !e.target.hasName('ellipse') && !e.target.hasName('rect') && !e.target.hasName('circle')) {
46 | return;
47 | }
48 | // 移除图形选择框
49 | stage.find('Transformer').destroy();
50 |
51 | // 当前点击的对象赋值给graphNow
52 | graphNow=e.target;
53 | // 创建图形选框事件
54 | const tr = new Konva.Transformer({
55 | borderStroke: '#000', // 虚线颜色
56 | borderStrokeWidth: 1, //虚线大小
57 | borderDash: [5], // 虚线间距
58 | keepRatio: false // 不等比缩放
59 | });
60 | layer.add(tr);
61 | tr.attachTo(e.target);
62 | layer.draw();
63 | });
64 |
65 | // 鼠标移动
66 | stage.on('mousemove', function (e) {
67 | if (graphNow && flag && drawing) {
68 | stageMousemove(flag, e);
69 | }
70 | });
71 |
72 | // 鼠标放开
73 | stage.on('mouseup', function () {
74 | drawing=false;
75 | if (flag === 'text') flag=null;
76 | });
77 |
78 | ## 三、 绘制
79 | ### 1.铅笔
80 |
81 | // 铅笔
82 | // @param points 点数组
83 | // @param stroke 颜色
84 | // @param strokeWidth 线粗细
85 |
86 | function drawPencil(points, stroke, strokeWidth) {
87 | const line = new Konva.Line({
88 | name: 'line',
89 | points: points,
90 | stroke: stroke,
91 | strokeWidth: strokeWidth,
92 | lineCap: 'round',
93 | lineJoin: 'round',
94 | tension: 0.5,
95 | draggable: true
96 | });
97 | graphNow=line;
98 | layer.add(line);
99 | layer.draw();
100 |
101 | line.on('mouseenter', function() {
102 | stage.container().style.cursor = 'move';
103 | });
104 |
105 | line.on('mouseleave', function() {
106 | stage.container().style.cursor = 'default';
107 | });
108 |
109 | line.on('dblclick', function() {
110 | // 双击删除自己
111 | this.remove();
112 | stage.find('Transformer').destroy();
113 | layer.draw();
114 | });
115 | }
116 |
117 |
118 |
119 | ### 2.椭圆
120 |
121 | // 椭圆
122 | // @param x x坐标
123 | // @param y y坐标
124 | // @param rx x半径
125 | // @param ry y半径
126 | // @param stroke 描边颜色
127 | // @param strokeWidth 描边大小
128 |
129 | function drawEllipse(x, y, rx, ry, stroke, strokeWidth) {
130 | const ellipse=new Konva.Ellipse({
131 | name: 'ellipse',
132 | x: x,
133 | y: y,
134 | radiusX: rx,
135 | radiusY: ry,
136 | stroke: stroke,
137 | strokeWidth: strokeWidth,
138 | draggable: true
139 | });
140 | graphNow=ellipse;
141 | layer.add(ellipse);
142 | layer.draw();
143 |
144 | ellipse.on('mouseenter', function() {
145 | stage.container().style.cursor = 'move';
146 | });
147 |
148 | ellipse.on('mouseleave', function() {
149 | stage.container().style.cursor = 'default';
150 | });
151 |
152 | ellipse.on('dblclick', function() {
153 | // 双击删除自己
154 | this.remove();
155 | stage.find('Transformer').destroy();
156 | layer.draw();
157 | });
158 | }
159 |
160 |
161 | ### 3.绘制矩形
162 |
163 | /**
164 | * 矩形
165 | * @param x x坐标
166 | * @param y y坐标
167 | * @param w 宽
168 | * @param h 高
169 | * @param c 颜色
170 | * @param sw 该值大于0-表示空心矩形(描边宽),等于0-表示实心矩形
171 | */
172 | function drawRect(x, y, w, h, c, sw) {
173 | const rect = new Konva.Rect({
174 | name: 'rect',
175 | x: x,
176 | y: y,
177 | width: w,
178 | height: h,
179 | fill: sw===0?c:null,
180 | stroke: sw>0?c:null,
181 | strokeWidth: sw,
182 | opacity: sw===0?0.5:1,
183 | draggable: true
184 | });
185 | graphNow=rect;
186 | layer.add(rect);
187 | layer.draw();
188 |
189 | rect.on('mouseenter', function() {
190 | stage.container().style.cursor = 'move';
191 | });
192 |
193 | rect.on('mouseleave', function() {
194 | stage.container().style.cursor = 'default';
195 | });
196 |
197 | rect.on('dblclick', function() {
198 | // 双击删除自己
199 | this.remove();
200 | stage.find('Transformer').destroy();
201 | layer.draw();
202 | });
203 | }
204 |
205 |
206 | ### 4.文字
207 |
208 | /**
209 | * 输入文字
210 | * @param x x坐标
211 | * @param y y坐标
212 | * @param fill 文字颜色
213 | * @param fs 文字大小
214 | */
215 | function drawText(x, y, fill, fs) {
216 | var text = new Konva.Text({
217 | text: '双击编辑文字',
218 | x: x,
219 | y: y,
220 | fill: fill,
221 | fontSize: fs,
222 | width: 300,
223 | draggable: true
224 | });
225 | graphNow=text;
226 | layer.add(text);
227 | layer.draw();
228 |
229 | text.on('mouseenter', function() {
230 | stage.container().style.cursor = 'move';
231 | });
232 |
233 | text.on('mouseleave', function() {
234 | stage.container().style.cursor = 'default';
235 | });
236 |
237 | text.on('dblclick', function() {
238 | // 在画布上创建具有绝对位置的textarea
239 |
240 | // 首先,我们需要为textarea找到位置
241 |
242 | // 首先,让我们找到文本节点相对于舞台的位置:
243 | let textPosition = this.getAbsolutePosition();
244 |
245 | // 然后让我们在页面上找到stage容器的位置
246 | let stageBox = stage.container().getBoundingClientRect();
247 |
248 | // 因此textarea的位置将是上面位置的和
249 | let areaPosition = {
250 | x: stageBox.left + textPosition.x,
251 | y: stageBox.top + textPosition.y
252 | };
253 |
254 | // 创建textarea并设置它的样式
255 | let textarea = document.createElement('textarea');
256 | document.body.appendChild(textarea);
257 |
258 | let T=this.text();
259 | if (T === '双击编辑文字') {
260 | textarea.value = '';
261 | textarea.setAttribute('placeholder','请输入文字')
262 | } else {
263 | textarea.value = T;
264 | }
265 |
266 | textarea.style.position = 'absolute';
267 | textarea.style.top = areaPosition.y + 'px';
268 | textarea.style.left = areaPosition.x + 'px';
269 | textarea.style.background = 'none';
270 | textarea.style.border = '1px dashed #000';
271 | textarea.style.outline = 'none';
272 | textarea.style.color = this.fill();
273 | textarea.style.width = this.width();
274 |
275 | textarea.focus();
276 |
277 | this.setAttr('text', '');
278 | layer.draw();
279 |
280 | // 确定输入的文字
281 | let confirm=(val) => {
282 | this.text(val?val:'双击编辑文字');
283 | layer.draw();
284 | // 隐藏在输入
285 | if (textarea) document.body.removeChild(textarea);
286 | };
287 | // 回车键
288 | let keydown=(e) => {
289 | if (e.keyCode === 13) {
290 | textarea.removeEventListener('blur', blur);
291 | confirm(textarea.value)
292 | }
293 | };
294 | // 鼠标失去焦点
295 | let blur=() => {
296 | textarea.removeEventListener('keydown', keydown);
297 | confirm(textarea.value);
298 | };
299 |
300 | textarea.addEventListener('keydown', keydown);
301 | textarea.addEventListener('blur', blur);
302 | });
303 | }
304 |
305 |
306 |
307 | ### 5.鼠标按下
308 |
309 | /**
310 | * stage鼠标按下
311 | * @param flag 是否可绘制
312 | * @param ev 传入的event对象
313 | */
314 | function stageMousedown(flag, ev) {
315 | if (flag) {
316 | let x=ev.evt.offsetX, y=ev.evt.offsetY;
317 | pointStart=[x, y];
318 |
319 | switch (flag) {
320 | case 'pencil':
321 | drawPencil(pointStart, graphColor, 2);
322 | break;
323 | case 'ellipse':
324 | // 椭圆
325 | drawEllipse(x, y, 0, 0, graphColor, 2);
326 | break;
327 | case 'rect':
328 | drawRect(x, y, 0, 0, graphColor, 0);
329 | break;
330 | case 'rectH':
331 | drawRect(x, y, 0, 0, graphColor, 2);
332 | break;
333 | case 'text':
334 | drawText(x, y, graphColor, 16);
335 | break;
336 | default:
337 | break;
338 | }
339 | drawing=true;
340 | }
341 | }
342 |
343 |
344 | ### 6.鼠标移动
345 |
346 | /**
347 | * stage鼠标移动
348 | * @param flag 是否可绘制
349 | * @param ev 传入的event对象
350 | */
351 | function stageMousemove(flag, ev) {
352 | switch (flag) {
353 | case 'pencil':
354 | // 铅笔
355 | pointStart.push(ev.evt.offsetX, ev.evt.offsetY);
356 | graphNow.setAttrs({
357 | points: pointStart
358 | });
359 | break;
360 | case 'ellipse':
361 | // 椭圆
362 | graphNow.setAttrs({
363 | radiusX: Math.abs(ev.evt.offsetX-pointStart[0]),
364 | radiusY: Math.abs(ev.evt.offsetY-pointStart[1])
365 | });
366 | break;
367 | case 'rect':
368 | case 'rectH':
369 | graphNow.setAttrs({
370 | width: ev.evt.offsetX-pointStart[0],
371 | height: ev.evt.offsetY-pointStart[1]
372 | });
373 | break;
374 | default:
375 | break;
376 | }
377 | layer.draw();
378 | }
379 |
380 |
381 | ### 7.选择颜色
382 |
383 | // 选择颜色
384 | function selectColorFn(t) {
385 | graphColor=t.value;
386 | }
387 |
388 |
389 | ### 8.删除
390 |
391 | // 移除图形
392 | function removeFn() {
393 | if (graphNow) {
394 | graphNow.remove();
395 | stage.find('Transformer').destroy();
396 | layer.draw();
397 | graphNow=null;
398 | } else {
399 | alert('请选择图形')
400 | }
401 | }
402 |
403 |
404 |
--------------------------------------------------------------------------------