A drawing app that was given to me as a coding challenge for a potential job as a web developer.
Developed using HTML5 Canvas .
Github link: www.github.com/seangeleno/drawingApp
A Pen by Sean Esteva on CodePen.
A drawing app that was given to me as a coding challenge for a potential job as a web developer.
Developed using HTML5 Canvas .
Github link: www.github.com/seangeleno/drawingApp
A Pen by Sean Esteva on CodePen.
| <body> | |
| <div class="controls"> | |
| <h3>Shape:</h3> | |
| <div class='lightBorder' > | |
| <p><input type="radio" name="shape" value="line" checked="checked"> Line</p> | |
| <p><input type="radio" name="shape" value="rect"> Rectangle</p> | |
| <p><input type="radio" name="shape" value="circle"> Circle</p> | |
| <p><input type="radio" name="shape" value="square"> Square</p> | |
| <p><input type="radio" name="shape" value="ellipse"> Ellipse</p> | |
| <p><input type="radio" name="shape" value="polygon"> Polygon</p> | |
| </div> | |
| <h3>Line Type:</h3> | |
| <div class='lightBorder'> | |
| <p><label>Line Width: <input id='lineWidth' type='range' step='1' min="1" max="200" value="4"></label></p> | |
| Line End: | |
| <p><input name="lineCap" value="round" type='radio' checked="checked">round</p> | |
| <p><input name="lineCap" value="square" type='radio'>square</p> | |
| <p><input name="lineCap" value="butt" type='radio'>butt</p> | |
| </div> | |
| <h3>Colors:</h3> | |
| <div class='lightBorder'> | |
| <!-- color --> | |
| <p><label>Fill Color: <input id='fillColor' type='color' step='1' value="#24B0D5"></label></p> | |
| <p><label>Stroke Color: <input id='strokeColor' type='color' step='1'> </label></p> | |
| <p><label>Background Color:<input id='backgroundColor' type='color' step='1' value="#ffffff"></label></p> | |
| *changing background will delete image! | |
| </div> | |
| <div class='lightBorder'> | |
| <!-- polygon sides --> | |
| <p><label>Polygon Sides: <input id='polygonSides' type='range' step='1' min="3" max="12"></label></p> | |
| </div> | |
| <form> | |
| <h3>Input Text:</h3> <input type="text" id="textInput"/> | |
| </form> | |
| <!-- fill or no fill, you decide --> | |
| <p><label>Fill: <input id='fillBox' type='checkbox' checked='checked'></label></p> | |
| <p><label style="font-size:12px;">XOR: <input id='xor' type='checkbox'></label></p> | |
| <button type="button" class="button-error pure-button" id="clearCanvas" name="button">Clear Canvas</button> | |
| </div> | |
| <canvas id="canvas" width="650" height="800"></canvas> | |
| </body> |
| var canvas, | |
| context, | |
| dragging = false, //this will be dragging if mouse move is followed by mouse down | |
| dragStartLocation, | |
| snapshot; | |
| //getBoundingClientRect | |
| //The returned value is a DOMRect object, which contains read-only left, top, right and | |
| //bottom properties describing the border-box in pixels. top and left are relative to the top-left of the viewport. | |
| // rect is a DOMRect object with six properties: left, top, right, bottom, width, height | |
| //var rect = obj.getBoundingClientRect(); | |
| //define mouse coordinates | |
| function getCanvasCoordinates(event) { | |
| var x = event.clientX - canvas.getBoundingClientRect().left; | |
| var y = event.clientY - canvas.getBoundingClientRect().top; | |
| //return object where x is x and y is y | |
| return {x: x, y: y}; | |
| } | |
| //this avoids dragging the image | |
| //The getImageData() method returns an ImageData object that copies the pixel data for the specified rectangle on a canvas. | |
| function takeSnapShot() { | |
| snapshot = context.getImageData(0, 0, canvas.width, canvas.height); | |
| } | |
| //These must be added to dragStart() | |
| function restoreSnapShot() { | |
| context.putImageData(snapshot, 0, 0); | |
| } | |
| function drawLine(position) { | |
| context.beginPath(); | |
| context.moveTo(dragStartLocation.x, dragStartLocation.y);//this will be the first point and during mouse up the line will be drawn | |
| context.lineTo(position.x, position.y); //current position of x and y during mouseMove event | |
| context.stroke(); // The stroke() method actually draws the path you have defined with all those moveTo() and lineTo() methods. So Cool! | |
| } | |
| //The arc() method creates an arc/curve (used to create circles, or parts of circles). To create a circles set start angle to 0 and end angle to 2*Math.PI | |
| function drawCircle(position) { //takes position during mouse up | |
| var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2)); | |
| context.beginPath(); | |
| context.arc(dragStartLocation.x, dragStartLocation.y, radius, 0, 2 * Math.PI); | |
| // context.fill(); | |
| } | |
| function drawEllipse(position) { | |
| var w = position.x - dragStartLocation.x ; | |
| var h = position.y - dragStartLocation.y ; | |
| var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2)); | |
| context.beginPath(); | |
| //Used the .ellipse method instead of arc to give an extra radius, radius x and radius | |
| var cw = (dragStartLocation.x > position.x) ? true : false; | |
| console.log(cw); | |
| context.ellipse(dragStartLocation.x, dragStartLocation.y, Math.abs(w), Math.abs(h), 0, 2 * Math.PI, false); | |
| } | |
| function drawRect(position) { | |
| console.log(position.x, dragStartLocation.x); | |
| var w = position.x - dragStartLocation.x ; | |
| var h = position.y - dragStartLocation.y ; | |
| context.beginPath(); | |
| context.rect(dragStartLocation.x, dragStartLocation.y, w, h); | |
| } | |
| function drawPolygon(position, sides, angle) { | |
| var coordinates = [], | |
| radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2)), | |
| index = 0; | |
| for (index; index < sides; index++) { | |
| coordinates.push({ | |
| x: dragStartLocation.x + radius * Math.cos(angle), | |
| y: dragStartLocation.y - radius * Math.sin(angle) | |
| }) | |
| angle += (2 * Math.PI) / sides; | |
| } | |
| context.beginPath(); | |
| context.moveTo(coordinates[0].x, coordinates[0].y); | |
| for (index = 0; index < sides; index++) { | |
| context.lineTo(coordinates[index].x, coordinates[index].y); | |
| } | |
| context.closePath(); | |
| // context.fill(); | |
| } | |
| function draw(position) { | |
| var fillBox = document.getElementById("fillBox") | |
| , shape = document.querySelector('input[type="radio"][name="shape"]:checked').value | |
| , polygonSides = document.getElementById('polygonSides').value | |
| // , polygonAngle = document.getElementById('polygonAngle').value | |
| , polygonAngle = calculateAngle(dragStartLocation, position) | |
| , lineCap = document.querySelector('input[type="radio"][name="lineCap"]:checked').value | |
| , writeCanvas = document.getElementById('textInput').value | |
| , xor = document.getElementById('xor'); | |
| //global context | |
| context.lineCap = lineCap; | |
| //we don't need even't handlers because before drawing we are jsut taking a default value | |
| if (shape === "circle") { | |
| drawCircle(position); | |
| } | |
| if (shape === "square") { | |
| drawPolygon(position, 4, Math.PI / 4); | |
| } | |
| if (shape === "line") { | |
| drawLine(position); | |
| } | |
| if (shape === "ellipse") { | |
| drawEllipse(position); | |
| } | |
| if (shape === "rect") { | |
| drawRect(position); | |
| } | |
| if (shape === "polygon") { | |
| drawPolygon(position, polygonSides, polygonAngle * (Math.PI / 180)); | |
| } | |
| if (xor.checked){ | |
| context.globalCompositeOperation = "xor"; | |
| } else { | |
| context.globalCompositeOperation = "source-over"; | |
| } | |
| if (fillBox.checked) { | |
| context.fill(); | |
| } else { | |
| context.stroke(); | |
| } | |
| } | |
| //define dragstart, drag and dragStop | |
| function dragStart(event) { | |
| dragging = true; | |
| dragStartLocation = getCanvasCoordinates(event); | |
| takeSnapShot(); | |
| } | |
| function calculateAngle(start, current) { | |
| var angle = 360 - Math.atan2(current.y - start.y, current.x - start.x) * 180 / Math.PI; | |
| return angle; | |
| } | |
| function drag(event) { | |
| var position; | |
| if (dragging === true) { | |
| restoreSnapShot(); | |
| position = getCanvasCoordinates(event); | |
| //generic | |
| draw(position); | |
| } | |
| } | |
| //Drag Stop | |
| function dragStop(event) { | |
| dragging = false; //dragging stops here | |
| restoreSnapShot(); | |
| var position = getCanvasCoordinates(event); | |
| //generic | |
| draw(position); | |
| } | |
| //Line Width Here | |
| function changeLineWidth() { | |
| context.lineWidth = this.value; | |
| //**important** | |
| //event.stopPropagation() prevents the vent from bubblim up the DOM tree, preventing any parent handlers from being notified of the event. | |
| event.stopPropagation(); | |
| } | |
| //Fill Color | |
| function changeFillStyle() { | |
| context.fillStyle = this.value; | |
| event.stopPropagation(); | |
| } | |
| //Stroke Color | |
| function changeStrokeStyle() { | |
| context.strokeStyle = this.value; | |
| event.stopPropagation(); | |
| } | |
| //backgroundColor | |
| function changeBackgroundColor() { | |
| context.save(); | |
| context.fillStyle = document.getElementById('backgroundColor').value; | |
| context.fillRect(0, 0, canvas.width, canvas.height); | |
| context.restore(); | |
| //more info on save() and resote() www.html5.litten.com/understanding-save-and-restore-for-the-canvas-context/ | |
| } | |
| function eraseCanvas() { | |
| context.clearRect(0, 0, canvas.width, canvas.height); | |
| } | |
| //write on canvas | |
| function writeCanvas() { | |
| context.font = '55px Impact'; | |
| context.fillText(textInput.value, 25, 175); | |
| console.log(textInput.value); | |
| } | |
| //function invoked when document is fully loaded | |
| function init() { | |
| canvas = document.getElementById('canvas'); | |
| context = canvas.getContext('2d'); | |
| var lineWidth = document.getElementById('lineWidth'), | |
| fillColor = document.getElementById('fillColor'), | |
| strokeColor = document.getElementById('strokeColor'), | |
| canvasColor = document.getElementById('backgroundColor'), | |
| clearCanvas = document.getElementById('clearCanvas'), | |
| textInput = document.getElementById('textInput'); | |
| // context.strokeStyle = 'rebeccapurple'; | |
| // context.font = '25px Impact'; | |
| // context.fillText(writeCanvas, 50, 150); | |
| // var imgElement = document.getElementById("logo"); | |
| // context.fillStyle = context.createPattern(imgElement, 'repeat'); | |
| context.lineWidth = lineWidth.value; | |
| // no need for linecap will be defined directly by user | |
| // context.lineCap = 'square'; | |
| context.fillStyle = fillColor.value; | |
| //shapes made transparent by overlapping shapes | |
| // context.globalCompositeOperation = 'xor'; | |
| //event listeners below | |
| canvas.addEventListener('mousedown', dragStart, false); | |
| canvas.addEventListener('mousemove', drag, false); | |
| canvas.addEventListener('mouseup', dragStop, false); | |
| lineWidth.addEventListener('input', changeLineWidth, false); | |
| fillColor.addEventListener('input', changeFillStyle, false); | |
| strokeColor.addEventListener('input', changeStrokeStyle, false); | |
| canvasColor.addEventListener('input', changeBackgroundColor, false); | |
| clearCanvas.addEventListener('click', eraseCanvas, false); | |
| textInput.addEventListener('input', writeCanvas, false); | |
| } | |
| window.addEventListener('load', init, false); |
| canvas { | |
| float: left; | |
| position: relative; | |
| border-radius: 10px; | |
| margin: 10px; | |
| border: 1px solid gray; | |
| -webkit-box-sizing: border-box; | |
| -moz-box-sizing: border-box; | |
| box-sizing: border-box; | |
| } | |
| #canvas1 { | |
| margin-left: 1150px; | |
| margin-top: 30px; | |
| } | |
| .controls { | |
| width: 200px; | |
| padding: 12px; | |
| border: 1px solid gray; | |
| border-radius: 10px; | |
| background: #D9D9D9; | |
| transition: all .5s ease; | |
| float: left; | |
| position: relative; | |
| margin: 10px; | |
| height: 800px; | |
| -webkit-box-sizing: border-box; | |
| -moz-box-sizing: border-box; | |
| box-sizing: border-box; | |
| font-size: 13px; | |
| font-family: Arial; | |
| } | |
| .controls h3 { | |
| color: #3D3D3D; | |
| text-transform: uppercase; | |
| font-size: 11px; | |
| line-height: 10px; | |
| } | |
| .lightBorder { | |
| border: 1px solid #B6B6B6; | |
| margin-top: 10px; | |
| margin-bottom: 18px; | |
| padding: 5px; | |
| border-radius: 7px; | |
| background: #EEEEEE; | |
| box-shadow: inset 1px 1px 10px rgba(0,0,0,0.1); | |
| } | |
| } | |
| h1 { | |
| margin-left: 300px; | |
| } | |
| p { | |
| margin: 4px; | |
| } | |
| /*body { | |
| background: #FF4E50; | |
| background: linear-gradient(to left, #FF4E50 , #F9D423); | |
| transition: all .5s ease; | |
| }*/ | |
| .button-success, | |
| .button-error, | |
| .button-warning, | |
| .button-secondary { | |
| color: white; | |
| border-radius: 4px; | |
| text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); | |
| } | |
| .button-success { | |
| background: rgb(28, 184, 65); /* this is a green */ | |
| } | |
| .button-error { | |
| background: rgb(202, 60, 60); /* this is a maroon */ | |
| } | |
| .button-warning { | |
| background: rgb(223, 117, 20); /* this is an orange */ | |
| } | |
| .button-secondary { | |
| background: rgb(66, 184, 221); /* this is a light blue */ | |
| } |
| <link href="https://yui.yahooapis.com/pure/0.6.0/pure-min.css" rel="stylesheet" /> |