├── 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 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 |
33 | 38 | 39 |
40 | 41 |
42 |
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 | ![截图演示](https://github.com/xiongshuang/konva-palette/blob/master/palette.gif) 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 | --------------------------------------------------------------------------------