<!DOCTYPE html> <html> <head> <style type="text/css"> #board { touch-action: none; border: 0.5px solid rgba(0, 0, 0, 0.1); box-shadow: 1px 1px 1px 0.5px rgba(0, 0, 0, 0.1); cursor: crosshair; } </style> <script type="application/javascript" src="js/main.js"></script> <script type="application/javascript" src="js/Board.js"></script> <script type="application/javascript" src="js/Pen.js"></script> <script type="application/javascript" src="js/Pointer.js"></script> <script type="application/javascript"> let strokeData = []; let canvasStore; let redrawAllTimeout; function init() { Board.init("board"); Pen.init(Board.ctx); // Attach event listener var pointerDown = function (e) { //console.log("down"); clearTimeout(redrawAllTimeout); if (e.pointerType == "pen") { // Initialise pointer var pointer = new Pointer(e.pointerId); pointer.set(Board.getPointerPos(e)); strokeData.push([]); // Get function type Pen.setFuncType(e); if (Pen.funcType === Pen.funcTypes.menu) Board.clearMemory(); else collectStrokeInfo(e, pointer, Pen); } } var pointerMove = function (e) { //console.log("move"); if (Pen.funcType && (Pen.funcType.indexOf(Pen.funcTypes.draw) !== -1)) { var pointer = Pointer.get(e.pointerId); collectStrokeInfo(e, pointer, Pen); } } var pointerCancel = function (e) { //console.log("cancel"); drawCurve(Board.ctx, strokeData[strokeData.length - 1]); clearTimeout(redrawAllTimeout); redrawAllTimeout = setTimeout(redrawAll, 1000); Pointer.destruct(e.pointerId); } Board.dom.addEventListener('pointerdown', pointerDown); Board.dom.addEventListener('pointermove', pointerMove); Board.dom.addEventListener('pointerup', pointerCancel); Board.dom.addEventListener('pointerleave', pointerCancel); setInterval(_ => { if (strokeData[strokeData.length - 1] && strokeData[strokeData.length - 1].length) { drawCurve(Board.ctx, strokeData[strokeData.length - 1]) } }, 30); } function redrawAll() { Board.ctx.clearRect(0, 0, Board.dom.width, Board.dom.height); for (var stroke of strokeData) { drawCurve(Board.ctx, stroke); } } // Draw method function collectStrokeInfo(e, pointerObj, Pen) { if (pointerObj) { pointerObj.set(Board.getPointerPos(e)); Pen.setPen(Board.ctx, e); if (pointerObj.pos0.x < 0) { pointerObj.pos0.x = pointerObj.pos1.x - 1; pointerObj.pos0.y = pointerObj.pos1.y - 1; } // save stroke info strokeData[strokeData.length - 1].push({ x: pointerObj.pos1.x, y: pointerObj.pos1.y, width: getLineWidth(e) }); pointerObj.pos0.x = pointerObj.pos1.x; pointerObj.pos0.y = pointerObj.pos1.y; } } function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) { ctx.beginPath(); drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments)); if (showPoints) { ctx.beginPath(); for (var i = 0; i < ptsa.length - 1; i += 2) ctx.rect(ptsa[i] - 2, ptsa[i + 1] - 2, 4, 4); } ctx.stroke(); } function getCurvePoints(pts, tension, isClosed, numOfSegments) { // use input value if provided, or use a default value tension = (typeof tension != 'undefined') ? tension : 0.5; isClosed = isClosed ? isClosed : false; numOfSegments = numOfSegments ? numOfSegments : 16; var _pts = [], res = [], // clone array x, y, // our x,y coords t1x, t2x, t1y, t2y, // tension vectors c1, c2, c3, c4, // cardinal points st, t, i; // steps based on num. of segments // clone array so we don't change the original _pts = pts.slice(0); // The algorithm require a previous and next point to the actual point array. // Check if we will draw closed or open curve. // If closed, copy end points to beginning and first points to end // If open, duplicate first points to befinning, end points to end if (isClosed) { _pts.unshift(pts[pts.length - 1]); _pts.unshift(pts[pts.length - 1]); _pts.push(pts[0]); } else { _pts.unshift(pts[0]); //copy 1. point and insert at beginning _pts.push(pts[pts.length - 1]); //copy last point and append } // ok, lets start.. // 1. loop goes through point array // 2. loop goes through each segment between the 2 pts + 1e point before and after for (var i = 1; i < (_pts.length - 2); i++) { for (var t = 0; t <= numOfSegments; t++) { // calc tension vectors t1x = (_pts[i + 1].x - _pts[i - 1].x) * tension; t2x = (_pts[i + 2].x - _pts[i].x) * tension; t1y = (_pts[i + 1].y - _pts[i - 1].y) * tension; t2y = (_pts[i + 2].y - _pts[i].y) * tension; // calc step st = t / numOfSegments; // calc cardinals c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1; c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2); c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st; c4 = Math.pow(st, 3) - Math.pow(st, 2); // calc x and y cords with common control vectors x = c1 * _pts[i].x + c2 * _pts[i + 1].x + c3 * t1x + c4 * t2x; y = c1 * _pts[i].y + c2 * _pts[i + 1].y + c3 * t1y + c4 * t2y; //store points in array res.push({ x: x, y: y, width: lerp(_pts[i].width, _pts[i + 1].width, t / numOfSegments)}); } } return res; } function lerp (start, end, pos){ return (1 - pos) * start + pos * end; } function drawLines(ctx, pts) { for (i = 0; i < pts.length - 2; i++) { ctx.beginPath(); ctx.lineWidth = pts[i].width; ctx.moveTo(pts[i].x, pts[i].y); ctx.lineTo(pts[i + 1].x, pts[i + 1].y); ctx.stroke(); } } function getLineWidth(e) { switch (e.pointerType) { case 'touch': if (e.width < 10 && e.height < 10) { return (e.width + e.height) * 2 + 10; } else { return (e.width + e.height - 40) / 2; } case 'pen': return e.pressure * 8; default: return (e.pressure) ? e.pressure * 8 : 4; } } </script> </head> <body onload="init();"> <canvas touch-action="none" id="board" width="640" height="640" style="width:320px; height:320px;"> Opps, you cannot play draw N guess with this browser! </canvas> <button style="z-index: 9001; position:absolute; top:100px; left: 100px; width: 200px;" onclick="Board.storeMemory();">save</button> <button style="z-index: 9001; position:absolute; top:100px; left: 320px; width: 200px;" onclick="Board.clearMemory();">clear</button> </body> </html>