<!DOCTYPE html> <html> <head> <style type="text/css"> * { touch-action: none; } body { background-color: red; } button { background-color: #D8D8D8; border: 1px solid #999999; border-radius: 5px; } button.selected { box-shadow: 0 0 2px 2px #0597f8; } #board { touch-action: none; box-shadow: 1px 1px 1px 0.5px rgba(0, 0, 0, 0.1); cursor: crosshair; position: absolute; left: 0px; top: 0px; } #ruler { background-color: #ddddee; opacity: 0.75; width: 770px; height: 150px; border-radius: 8px; box-shadow: 0.5px 0.5px 2px #555555, inset 0px 0px 1px #555555; position: absolute; z-index: 20; touch-action: none; display: none; } #ruler::before { position:absolute; background-color: #ffffff; background: repeating-linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 43px, #777777 46px), repeating-linear-gradient(90deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 751px, #ffffff 751px, #ffffff 769px), repeating-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 30px, #ffffff 30px, #ffffff 50px), repeating-linear-gradient(90deg, #222222, #222222 1px, rgba(0, 0, 0, 0) 1px, rgba(0, 0, 0, 0) 50px), repeating-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 10px, #ffffff 10px, #ffffff 50px), repeating-linear-gradient(90deg, #222222, #222222 1px, rgba(0, 0, 0, 0) 1px, rgba(0, 0, 0, 0) 5px); background-position-x: 10px; opacity: 50%; width: 100%; height: 45px; content: ""; } #ruler::after { position:absolute; background-color: #ffffff; opacity: 20%; width: 100%; height: 10px; bottom: 0px; border-top: 1px solid #555555; filter: blur(1px); content: ""; } #ruler #rulerTopLeft { position: relative; top: 0px; left: 0px; width: 0px; height: 0px; background-color: blue; } #menuTop { position: absolute; top: 0px; left: 0px; margin-left: 30px; } #sidePanelLeft { position: absolute; width: 30%; height: 100%; top: 0px; left: -30%; background-color: #fafafa; box-shadow: #999 0px 0px 12px; padding: 20px; box-sizing: border-box; } #sidePanelLeft::after { content: "Notes"; position: absolute; right: -25px; width: 25px; padding: 20px 0px; font-family: sans-serif; font-size: 20px; top: 0px; background-color: #fafafa; writing-mode: vertical-rl; text-orientation: mixed; box-shadow: #999 0px 0px 12px; border-top-left-radius: 10px; transform: rotate(180deg); } #sidePanelShadowHider { position: absolute; width: 12px; height: 100%; right: 0px; top: 0px; background-color: #fafafa; pointer-events: none; z-index: 2; } #sidePanelLeft button { padding: 20px; margin-bottom: 20px; min-width: 150px; } /* ruler DOM object top/left */ #helper, #helper1, #helper2, #helper3, #helper4 { background-color: fuchsia; width: 10px; height: 10px; position: absolute; z-index: 21; touch-action: none; left: 200px; top: 200px; } #helper1, #helper2 { width: 150px; height: 150px; border-radius: 75px; opacity: 0.8; } /* first touch pointer */ #helper1 { background-color: yellow; } /* second touch pointer */ #helper2{ background-color: orange; } /* transform origin */ #helper3{ background-color: lime; border-radius: 5px; } /* ruler "top/left" */ #helper4{ background-color: red; border-radius: 5px; } </style> <script type="application/javascript" src="js/lib.js"></script> <script type="application/javascript" src="js/Vector.js"></script> <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/PointerManager.js"></script> <script type="application/javascript" src="js/Ruler.js"></script> <script type="application/javascript"> let redrawAllTimeout; function init() { Board.init("board"); Ruler.init(document.getElementById("ruler")); setColor("#000000"); setLineWidth("8"); // Attach event listener var pointerDown = function (e) { //console.log("down"); clearTimeout(redrawAllTimeout); PointerManager.registerPointer(e); if (e.pointerType == "pen") { if (!Pen.erase) { // start new free hand stroke Board.strokeData.push([]); // check if we must follow the ruler Board.collectStrokeInfo(e, Pen); } else { } } else if (e.pointerType == "touch") { gestureStartPoint[e.pointerId] = e; } } var pointerMove = function (e) { if (e.pointerType == "pen") { if (!Pen.erase) { Board.collectStrokeInfo(e, Pen); } else { Board.eraseLine(e, Pen); } } else if (e.pointerType == "touch") { detectGesture(e); } } var pointerCancel = function (e) { //console.log("cancel"); if (e.pointerType == "pen") { if (!Pen.erase) { Board.drawLastCurve(); clearTimeout(redrawAllTimeout); redrawAllTimeout = setTimeout(_ => Board.redrawAll(), 1000); } } else if (e.pointerType == "touch") { if (gestureStartPoint[e.pointerId]) { delete gestureStartPoint[e.pointerId]; } } PointerManager.unregisterPointer(e); } var pointerSwipeInFromLeft = function (e) { var swipeDistance = Math.abs(e.detail.x - e.detail.startX); var width = sidePanelLeft.offsetWidth; var offsetX = e.detail.x; var offsetXSidePanel = -width + offsetX; if (offsetXSidePanel >= 0 || swipeDistance > 100) { showSidePanelLeft(); return; } sidePanelLeft.style.transition = ""; sidePanelLeft.style.left = offsetXSidePanel + "px"; document.getElementById("menuTop").style.transition = ""; document.getElementById("menuTop").style.left = offsetX + "px"; } var pointerSwipeOutToLeft = function (e) { hideSidePanelLeft(); } var pointerSwipeLeft = function (e) { if (e.detail.target == sidePanelLeft) { var swipeDistance = Math.abs(e.detail.x - e.detail.startX); if (swipeDistance > 100) { hideSidePanelLeft(); } } } var hideSidePanelLeft = function () { sidePanelLeft.style.transition = "left 0.5s"; sidePanelLeft.style.left = -sidePanelLeft.offsetWidth + "px"; document.getElementById("menuTop").style.transition = "left 0.5s"; document.getElementById("menuTop").style.left = "0px"; sidePanelLeft.dataset.visible = false; } var showSidePanelLeft = function () { sidePanelLeft.style.transition = ""; sidePanelLeft.style.left = "0px"; document.getElementById("menuTop").style.transition = ""; document.getElementById("menuTop").style.left = sidePanelLeft.offsetWidth + "px"; sidePanelLeft.dataset.visible = true; } var sidePanelLeftOnClick = function () { if (sidePanelLeft.dataset.visible == "false") { showSidePanelLeft(); } } Board.canvas.addEventListener("pointerdown", pointerDown); Board.canvas.addEventListener("pointermove", pointerMove); Board.canvas.addEventListener("pointerup", pointerCancel); Board.canvas.addEventListener("pointerleave", pointerCancel); sidePanelLeft = document.getElementById("sidePanelLeft"); noteList = document.getElementById("noteList"); updateNoteList(); sidePanelLeft.addEventListener("pointerdown", pointerDown); sidePanelLeft.addEventListener("pointermove", pointerMove); sidePanelLeft.addEventListener("pointerup", pointerCancel); sidePanelLeft.addEventListener("pointerleave", pointerCancel); sidePanelLeft.addEventListener("pointerdown", sidePanelLeftOnClick); document.addEventListener("swipeInFromLeft", pointerSwipeInFromLeft); document.addEventListener("swipeOutToLeft", pointerSwipeOutToLeft); document.addEventListener("swipeLeft", pointerSwipeLeft); // set drawing call setInterval(_ => Board.drawLastCurve(), 30); } function updateNoteList() { noteList.innerHTML = ""; for (var key of Object.keys(localStorage)) { if (key.startsWith("strokeData/")) { var noteName = key.replace(/^strokeData\//, ""); var btn = newElement("button", {}, noteName); ((noteName) => btn.addEventListener("click", _ => Board.load(noteName)))(noteName); noteList.appendChild(btn); noteList.appendChild(newElement("br")); } } } var sidePanelLeft; var noteList; var gestureStartPoint = []; function detectGesture(ev) { if (gestureStartPoint[ev.pointerId]) { var gsp = gestureStartPoint[ev.pointerId]; if (gsp.started == "swipeInFromLeft" || (gsp.clientX < ev.clientX && gsp.clientX < 10)) { gsp.started = "swipeInFromLeft"; var swipeInFromLeft = new CustomEvent("swipeInFromLeft", { detail: { target: ev.target, pointerId: ev.pointerId, startX: gsp.clientX, startY: gsp.clientY, x: ev.clientX, y: ev.clientY } }); document.dispatchEvent(swipeInFromLeft); } if (gsp.clientX > ev.clientX && ev.clientX < 10) { var swipeOutToLeft = new CustomEvent("swipeOutToLeft", { detail: { target: ev.target, pointerId: ev.pointerId, startX: gsp.clientX, startY: gsp.clientY, x: ev.clientX, y: ev.clientY } }); document.dispatchEvent(swipeOutToLeft); } if (gsp.clientX > ev.clientX) { var swipeLeft = new CustomEvent("swipeLeft", { detail: { target: ev.target, pointerId: ev.pointerId, startX: gsp.clientX, startY: gsp.clientY, x: ev.clientX, y: ev.clientY } }); document.dispatchEvent(swipeLeft); } } } // sets the color when a color button was pressed function setColor(btn) { Pen.colors.fg = (btn.dataset) ? btn.dataset.color : btn; Pen.erase = Pen.colors.fg.startsWith("erase"); document.querySelectorAll("button[data-color]").forEach(b => b.classList.remove("selected")); document.querySelector("button[data-color='" + Pen.colors.fg + "']").classList.add("selected"); } // sets the line with when a line with button was pressed function setLineWidth(btn) { Pen.lineWidth = (btn.dataset) ? btn.dataset.lineWidth : btn; document.querySelectorAll("button[data-line-width]").forEach(b => b.classList.remove("selected")); document.querySelector("button[data-line-width='" + Pen.lineWidth + "']").classList.add("selected"); } function toggleRuler(btn) { if (Ruler.visible) { Ruler.hide(); btn.classList.remove("selected"); } else { Ruler.show(); btn.classList.add("selected"); } } function btnLoad() { var noteName = prompt("Please enter the name of the note to load:"); if (noteName != "") { Board.load(noteName); } else { alert("Could not find note, loading the lastest saved note!"); Board.load(Board.latestNote); } } </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> <div id="menuTop"> <button style="position:absolute; top:20px; left: 20px; width: 80px; height: 35px;" onclick="Board.save();">save</button> <button style="position:absolute; top:65px; left: 20px; width: 80px; height: 35px;" onclick="btnLoad();">load</button> <button style="position:absolute; top:20px; left: 120px; width: 80px; height: 80px;" onclick="Board.clear();">clear</button> <button data-color="#000000" style="position:absolute; top:20px; left: 220px; width: 35px; height: 35px; background-color: black; color: white" onclick="setColor(this);"> </button> <button data-color="#FF0000" style="position:absolute; top:20px; left: 265px; width: 35px; height: 35px; background-color: red;" onclick="setColor(this);"> </button> <button data-color="#4169E1" style="position:absolute; top:65px; left: 220px; width: 35px; height: 35px; background-color: royalblue;" onclick="setColor(this);"> </button> <button data-color="#32CD32" style="position:absolute; top:65px; left: 265px; width: 35px; height: 35px; background-color: limegreen;" onclick="setColor(this);"> </button> <button data-color="eraseLine" style="position:absolute; top:20px; left: 320px; width: 80px; height: 35px;" onclick="setColor(this);">erase line</button> <button data-color="erase" style="position:absolute; top:65px; left: 320px; width: 80px; height: 35px;" onclick="setColor(this);">erase</button> <button data-line-width="4" style="position:absolute; top:20px; left: 420px; width: 35px; height: 35px;" onclick="setLineWidth(this);">4</button> <button data-line-width="8" style="position:absolute; top:20px; left: 465px; width: 35px; height: 35px;" onclick="setLineWidth(this);">8</button> <button data-line-width="16" style="position:absolute; top:65px; left: 420px; width: 35px; height: 35px;" onclick="setLineWidth(this);">16</button> <button data-line-width="24" style="position:absolute; top:65px; left: 465px; width: 35px; height: 35px;" onclick="setLineWidth(this);">24</button> <button style="position:absolute; top:20px; left: 520px; width: 80px; height: 35px;" onclick="toggleRuler(this);">Ruler</button> </div> <div id="ruler"> <div id="rulerTopLeft">top left</div> <div id="rulerValues"></div> </div> <div id="sidePanelLeft" data-visible="false"> <div id="sidePanelShadowHider"></div> Notes: <br> <br> <div id="noteList"> </div> </div> <div id="helper"></div> <div id="helper1"></div> <div id="helper2"></div> <div id="helper3"></div> <div id="helper4"></div> </body> </html>