var whiteboard = { canvas: null, ctx: null, drawcolor: "black", tool: "pen", thickness: 4, prevX: null, prevY: null, latestTouchCoods: [], drawFlag: false, oldGCO: null, mouseover: false, lineCap: "round", //butt, square backgroundGrid: null, canvasElement: null, cursorContainer: null, imgContainer: null, svgContainer: null, //For draw prev mouseOverlay: null, ownCursor: null, drawBuffer: [], drawId: 0, //Used for undo function imgDragActive: false, latestActiveTextBoxId: false, //The id of the latest clicked Textbox (for font and color change) pressedKeys: {}, settings: { whiteboardId: "0", username: "unknown", sendFunction: null, canvasWidth: 3000, canvasHeight: 2000, backgroundGridUrl: './images/KtEBa2.png' }, loadWhiteboard: function (whiteboardContainer, newSettings) { var svgns = "http://www.w3.org/2000/svg"; var _this = this; for (var i in newSettings) { this.settings[i] = newSettings[i]; } this.settings["username"] = this.settings["username"].replace(/[^0-9a-z]/gi, ''); this.settings["whiteboardId"] = this.settings["whiteboardId"].replace(/[^0-9a-z]/gi, ''); var startCoords = []; var svgLine = null; var svgRect = null; var svgCirle = null; //background grid (repeating image) and smallest screen indication _this.backgroundGrid = $('
'); // container for background images _this.imgContainer = $(''); // whiteboard canvas _this.canvasElement = $(''); // SVG container holding drawing or moving previews _this.svgContainer = $(''); // drag and drop indicator, hidden by default _this.dropIndicator = $(' ') // container for other users cursors _this.cursorContainer = $(''); // container for texts by users _this.textContainer = $(''); // mouse overlay for draw callbacks _this.mouseOverlay = $(''); $(whiteboardContainer).append(_this.backgroundGrid) .append(_this.imgContainer) .append(_this.canvasElement) .append(_this.svgContainer) .append(_this.dropIndicator) .append(_this.cursorContainer) .append(_this.textContainer) .append(_this.mouseOverlay); this.canvas = $("#whiteboardCanvas")[0]; this.canvas.height = _this.settings.canvasHeight; this.canvas.width = _this.settings.canvasWidth; this.ctx = this.canvas.getContext("2d"); this.oldGCO = this.ctx.globalCompositeOperation; $(_this.mouseOverlay).on("mousedown touchstart", function (e) { if (_this.imgDragActive || _this.drawFlag) { return; } _this.drawFlag = true; _this.prevX = (e.offsetX || e.pageX - $(e.target).offset().left) + 1; _this.prevY = (e.offsetY || e.pageY - $(e.target).offset().top) + 1; if (!_this.prevX || !_this.prevY || (_this.prevX == 1 && _this.prevY == 1)) { var touche = e.touches[0]; _this.prevX = touche.clientX - $(_this.mouseOverlay).offset().left + 1; _this.prevY = touche.clientY - $(_this.mouseOverlay).offset().top + 1; latestTouchCoods = [_this.prevX, _this.prevY]; } if (_this.tool === "pen") { _this.drawPenLine(_this.prevX, _this.prevY, _this.prevX, _this.prevY, _this.drawcolor, _this.thickness); _this.sendFunction({ "t": _this.tool, "d": [_this.prevX, _this.prevY, _this.prevX, _this.prevY], "c": _this.drawcolor, "th": _this.thickness }); } else if (_this.tool === "eraser") { _this.drawEraserLine(_this.prevX, _this.prevY, _this.prevX, _this.prevY, _this.thickness); _this.sendFunction({ "t": _this.tool, "d": [_this.prevX, _this.prevY, _this.prevX, _this.prevY], "th": _this.thickness }); } else if (_this.tool === "line") { startCoords = [_this.prevX, _this.prevY]; svgLine = document.createElementNS(svgns, 'line'); svgLine.setAttribute('stroke', 'gray'); svgLine.setAttribute('stroke-dasharray', '5, 5'); svgLine.setAttribute('x1', _this.prevX); svgLine.setAttribute('y1', _this.prevY); svgLine.setAttribute('x2', _this.prevX); svgLine.setAttribute('y2', _this.prevY); _this.svgContainer.append(svgLine); } else if (_this.tool === "rect" || _this.tool === "recSelect") { _this.svgContainer.find("rect").remove(); svgRect = document.createElementNS(svgns, 'rect'); svgRect.setAttribute('stroke', 'gray'); svgRect.setAttribute('stroke-dasharray', '5, 5'); svgRect.setAttribute('style', 'fill-opacity:0.0;'); svgRect.setAttribute('x', _this.prevX); svgRect.setAttribute('y', _this.prevY); svgRect.setAttribute('width', 0); svgRect.setAttribute('height', 0); _this.svgContainer.append(svgRect); startCoords = [_this.prevX, _this.prevY]; } else if (_this.tool === "circle") { svgCirle = document.createElementNS(svgns, 'circle'); svgCirle.setAttribute('stroke', 'gray'); svgCirle.setAttribute('stroke-dasharray', '5, 5'); svgCirle.setAttribute('style', 'fill-opacity:0.0;'); svgCirle.setAttribute('cx', _this.prevX); svgCirle.setAttribute('cy', _this.prevY); svgCirle.setAttribute('r', 0); _this.svgContainer.append(svgCirle); startCoords = [_this.prevX, _this.prevY]; } }); _this.textContainer.on("mousemove touchmove", function (e) { e.preventDefault(); if (_this.imgDragActive || !$(e.target).hasClass("textcontainer")) { return; } var currX = (e.offsetX || e.pageX - $(e.target).offset().left); var currY = (e.offsetY || e.pageY - $(e.target).offset().top); _this.sendFunction({ "t": "cursor", "event": "move", "d": [currX, currY], "username": _this.settings.username }); }) _this.mouseOverlay.on("mousemove touchmove", function (e) { e.preventDefault(); if (_this.imgDragActive) { return; } var currX = (e.offsetX || e.pageX - $(e.target).offset().left); var currY = (e.offsetY || e.pageY - $(e.target).offset().top); window.requestAnimationFrame(function () { if ((!currX || !currY) && e.touches && e.touches[0]) { var touche = e.touches[0]; currX = touche.clientX - $(_this.mouseOverlay).offset().left; currY = touche.clientY - $(_this.mouseOverlay).offset().top; } _this.latestTouchCoods = [currX, currY]; if (_this.drawFlag) { if (_this.tool === "pen") { _this.drawPenLine(currX, currY, _this.prevX, _this.prevY, _this.drawcolor, _this.thickness); _this.sendFunction({ "t": _this.tool, "d": [currX, currY, _this.prevX, _this.prevY], "c": _this.drawcolor, "th": _this.thickness }); } else if (_this.tool == "eraser") { _this.drawEraserLine(currX, currY, _this.prevX, _this.prevY, _this.thickness); _this.sendFunction({ "t": _this.tool, "d": [currX, currY, _this.prevX, _this.prevY], "th": _this.thickness }); } _this.prevX = currX; _this.prevY = currY; } if (_this.tool === "eraser") { var left = currX - _this.thickness; var top = currY - _this.thickness; if(_this.ownCursor) _this.ownCursor.css({ "top": top + "px", "left": left + "px" }); } else if (_this.tool === "pen") { var left = currX - _this.thickness / 2; var top = currY - _this.thickness / 2; if(_this.ownCursor) _this.ownCursor.css({ "top": top + "px", "left": left + "px" }); } else if (_this.tool === "line") { if (svgLine) { if (_this.pressedKeys.shift) { var angs = getRoundedAngles(currX, currY); currX = angs.x; currY = angs.y; } svgLine.setAttribute('x2', currX); svgLine.setAttribute('y2', currY); } } else if (_this.tool === "rect" || (_this.tool === "recSelect" && _this.drawFlag)) { if (svgRect) { var width = Math.abs(currX - startCoords[0]); var height = Math.abs(currY - startCoords[1]); if (_this.pressedKeys.shift) { height = width; var x = currX < startCoords[0] ? startCoords[0] - width : startCoords[0]; var y = currY < startCoords[1] ? startCoords[1] - width : startCoords[1]; svgRect.setAttribute('x', x); svgRect.setAttribute('y', y); } else { var x = currX < startCoords[0] ? currX : startCoords[0]; var y = currY < startCoords[1] ? currY : startCoords[1]; svgRect.setAttribute('x', x); svgRect.setAttribute('y', y); } svgRect.setAttribute('width', width); svgRect.setAttribute('height', height); } } else if (_this.tool === "circle") { var a = currX - startCoords[0]; var b = currY - startCoords[1]; var r = Math.sqrt(a * a + b * b); if (svgCirle) { svgCirle.setAttribute('r', r); } } }); _this.sendFunction({ "t": "cursor", "event": "move", "d": [currX, currY], "username": _this.settings.username }); }); _this.mouseOverlay.on("mouseup touchend touchcancel", function (e) { if (_this.imgDragActive) { return; } _this.drawFlag = false; _this.drawId++; _this.ctx.globalCompositeOperation = _this.oldGCO; var currX = (e.offsetX || e.pageX - $(e.target).offset().left); var currY = (e.offsetY || e.pageY - $(e.target).offset().top); if (!currX || !currY) { currX = _this.latestTouchCoods[0]; currY = _this.latestTouchCoods[1]; _this.sendFunction({ "t": "cursor", "event": "out", "username": _this.settings.username }); } if (_this.tool === "line") { if (_this.pressedKeys.shift) { var angs = getRoundedAngles(currX, currY); currX = angs.x; currY = angs.y; } _this.drawPenLine(currX, currY, startCoords[0], startCoords[1], _this.drawcolor, _this.thickness); _this.sendFunction({ "t": _this.tool, "d": [currX, currY, startCoords[0], startCoords[1]], "c": _this.drawcolor, "th": _this.thickness }); _this.svgContainer.find("line").remove(); } else if (_this.tool === "rect") { if (_this.pressedKeys.shift) { if ((currY - startCoords[1]) * (currX - startCoords[0]) > 0) { currY = startCoords[1] + (currX - startCoords[0]); } else { currY = startCoords[1] - (currX - startCoords[0]); } } _this.drawRec(startCoords[0], startCoords[1], currX, currY, _this.drawcolor, _this.thickness); _this.sendFunction({ "t": _this.tool, "d": [startCoords[0], startCoords[1], currX, currY], "c": _this.drawcolor, "th": _this.thickness }); _this.svgContainer.find("rect").remove(); } else if (_this.tool === "circle") { var a = currX - startCoords[0]; var b = currY - startCoords[1]; var r = Math.sqrt(a * a + b * b); _this.drawCircle(startCoords[0], startCoords[1], r, _this.drawcolor, _this.thickness); _this.sendFunction({ "t": _this.tool, "d": [startCoords[0], startCoords[1], r], "c": _this.drawcolor, "th": _this.thickness }); _this.svgContainer.find("circle").remove(); } else if (_this.tool === "recSelect") { _this.imgDragActive = true; if (_this.pressedKeys.shift) { if ((currY - startCoords[1]) * (currX - startCoords[0]) > 0) { currY = startCoords[1] + (currX - startCoords[0]); } else { currY = startCoords[1] - (currX - startCoords[0]); } } var width = Math.abs(startCoords[0] - currX); var height = Math.abs(startCoords[1] - currY); var left = startCoords[0] < currX ? startCoords[0] : currX; var top = startCoords[1] < currY ? startCoords[1] : currY; _this.mouseOverlay.css({ "cursor": "default" }); var imgDiv = $(' '); var dragCanvas = $(imgDiv).find("canvas"); var dragOutOverlay = $(''); _this.mouseOverlay.append(dragOutOverlay); _this.mouseOverlay.append(imgDiv); var destCanvasContext = dragCanvas[0].getContext('2d'); destCanvasContext.drawImage(_this.canvas, left, top, width, height, 0, 0, width, height); imgDiv.find(".xCanvasBtn").click(function () { _this.imgDragActive = false; _this.refreshCursorAppearance(); imgDiv.remove(); dragOutOverlay.remove(); }); imgDiv.find(".addToCanvasBtn").click(function () { _this.imgDragActive = false; _this.refreshCursorAppearance(); var p = imgDiv.position(); var leftT = Math.round(p.left * 100) / 100; var topT = Math.round(p.top * 100) / 100; _this.drawId++; _this.sendFunction({ "t": _this.tool, "d": [left, top, leftT, topT, width, height] }); _this.dragCanvasRectContent(left, top, leftT, topT, width, height); imgDiv.remove(); dragOutOverlay.remove(); }); imgDiv.draggable(); _this.svgContainer.find("rect").remove(); } }); _this.mouseOverlay.on("mouseout", function (e) { if (_this.imgDragActive) { return; } _this.drawFlag = false; _this.mouseover = false; _this.ctx.globalCompositeOperation = _this.oldGCO; if(_this.ownCursor) _this.ownCursor.remove(); _this.svgContainer.find("line").remove(); _this.svgContainer.find("rect").remove(); _this.svgContainer.find("circle").remove(); _this.sendFunction({ "t": "cursor", "event": "out" }); }); _this.mouseOverlay.on("mouseover", function (e) { if (_this.imgDragActive) { return; } if (!_this.mouseover) { var color = _this.drawcolor; var widthHeight = _this.thickness; if (_this.tool === "eraser") { color = "#00000000"; widthHeight = widthHeight * 2; } if (_this.tool === "eraser" || _this.tool === "pen") { _this.ownCursor = $(''); _this.cursorContainer.append(_this.ownCursor); } } _this.mouseover = true; }); //On textcontainer click (Add a new textbox) _this.textContainer.on("click", function (e) { currX = (e.offsetX || e.pageX - $(e.target).offset().left); currY = (e.offsetY || e.pageY - $(e.target).offset().top); var fontsize = _this.thickness * 0.5; var txId = 'tx' + (+new Date()); _this.sendFunction({ "t": "addTextBox", "d": [_this.drawcolor, fontsize, currX, currY, txId] }); _this.addTextBox(_this.drawcolor, fontsize, currX, currY, txId, true); }); function getRoundedAngles(currX, currY) { //For drawing lines at 0,45,90° .... var x = currX - startCoords[0]; var y = currY - startCoords[1]; var angle = Math.atan2(x, y) * (180 / Math.PI); var angle45 = Math.round(angle / 45) * 45; if (angle45 % 90 == 0) { if (Math.abs(currX - startCoords[0]) > Math.abs(currY - startCoords[1])) { currY = startCoords[1] } else { currX = startCoords[0] } } else { if ((currY - startCoords[1]) * (currX - startCoords[0]) > 0) { currX = startCoords[0] + (currY - startCoords[1]); } else { currX = startCoords[0] - (currY - startCoords[1]); } } return { "x": currX, "y": currY }; } }, entfKeyAction: function () { var _this = this; $.each(_this.mouseOverlay.find(".dragOutOverlay"), function () { var width = $(this).width(); var height = $(this).height(); var p = $(this).position(); var left = Math.round(p.left * 100) / 100; var top = Math.round(p.top * 100) / 100; _this.drawId++; _this.sendFunction({ "t": "eraseRec", "d": [left, top, width, height] }); _this.eraseRec(left, top, width, height); }); _this.mouseOverlay.find(".xCanvasBtn").click(); //Remove all current drops }, escKeyAction: function () { var _this = this; if (!_this.drawFlag) { _this.svgContainer.empty(); } _this.mouseOverlay.find(".xCanvasBtn").click(); //Remove all current drops }, dragCanvasRectContent: function (xf, yf, xt, yt, width, height) { var tempCanvas = document.createElement('canvas'); tempCanvas.width = width; tempCanvas.height = height; var tempCanvasContext = tempCanvas.getContext('2d'); tempCanvasContext.drawImage(this.canvas, xf, yf, width, height, 0, 0, width, height); this.eraseRec(xf, yf, width, height); this.ctx.drawImage(tempCanvas, xt, yt); }, eraseRec: function (fromX, fromY, width, height) { var _this = this; _this.ctx.beginPath(); _this.ctx.rect(fromX, fromY, width, height); _this.ctx.fillStyle = "rgba(0,0,0,1)"; _this.ctx.globalCompositeOperation = "destination-out"; _this.ctx.fill(); _this.ctx.closePath(); _this.ctx.globalCompositeOperation = _this.oldGCO; }, drawPenLine: function (fromX, fromY, toX, toY, color, thickness) { var _this = this; _this.ctx.beginPath(); _this.ctx.moveTo(fromX, fromY); _this.ctx.lineTo(toX, toY); _this.ctx.strokeStyle = color; _this.ctx.lineWidth = thickness; _this.ctx.lineCap = _this.lineCap; _this.ctx.stroke(); _this.ctx.closePath(); }, drawEraserLine: function (fromX, fromY, toX, toY, thickness) { var _this = this; _this.ctx.beginPath(); _this.ctx.moveTo(fromX, fromY); _this.ctx.lineTo(toX, toY); _this.ctx.strokeStyle = "rgba(0,0,0,1)"; _this.ctx.lineWidth = thickness * 2; _this.ctx.lineCap = _this.lineCap; _this.ctx.globalCompositeOperation = "destination-out"; _this.ctx.stroke(); _this.ctx.closePath(); _this.ctx.globalCompositeOperation = _this.oldGCO; }, drawRec: function (fromX, fromY, toX, toY, color, thickness) { var _this = this; toX = toX - fromX; toY = toY - fromY; _this.ctx.beginPath(); _this.ctx.rect(fromX, fromY, toX, toY); _this.ctx.strokeStyle = color; _this.ctx.lineWidth = thickness; _this.ctx.lineCap = _this.lineCap; _this.ctx.stroke(); _this.ctx.closePath(); }, drawCircle: function (fromX, fromY, radius, color, thickness) { var _this = this; _this.ctx.beginPath(); _this.ctx.arc(fromX, fromY, radius, 0, 2 * Math.PI, false); _this.ctx.lineWidth = thickness; _this.ctx.strokeStyle = color; _this.ctx.stroke(); }, clearWhiteboard: function () { var _this = this; _this.canvas.height = _this.canvas.height; _this.imgContainer.empty(); _this.textContainer.empty(); _this.sendFunction({ "t": "clear" }); _this.drawBuffer = []; _this.drawId = 0; }, setStrokeThickness(thickness) { var _this = this; _this.thickness = thickness; if (_this.tool == "text" && _this.latestActiveTextBoxId) { _this.sendFunction({ "t": "setTextboxFontSize", "d": [_this.latestActiveTextBoxId, thickness] }); _this.setTextboxFontSize(_this.latestActiveTextBoxId, thickness); } }, addImgToCanvasByUrl: function (url) { var _this = this; var wasTextTool = false; if (_this.tool === "text") { wasTextTool = true; _this.setTool("mouse"); //Set to mouse tool while dropping to prevent errors } _this.imgDragActive = true; _this.mouseOverlay.css({ "cursor": "default" }); var imgDiv = $('