const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') const width = canvas.width; const height = canvas.height; let current; let selection = [] const tools = { graffity: { mousemove(e){ (e.buttons & 1) && new Circle(e.clientX, e.clientY, +size.value, color.value) } }, circle: { mousedown(e){ current = new Circle(e.clientX, e.clientY, 1, color.value) }, mousemove(e){ if (!current) return; current.radius = current.distanceTo(e.clientX, e.clientY) Drawable.drawAll() }, mouseup(e){ current = null } }, line: { mousedown(e){ current = new Line(e.clientX, e.clientY, 0, 0, color.value, +size.value) }, mousemove(e){ if (!current) return; current.width = e.clientX - current.x current.height = e.clientY - current.y Drawable.drawAll() }, mouseup(e){ current = null } }, select: { click(e){ console.log(e) let found = Drawable.instances.filter(c => c.in && c.in(e.clientX, e.clientY)) if (found.length){ if (e.altKey){ selection.push(found.pop()) } else { selection = [found.pop()] } } else { if (!e.altKey) selection = [] } Drawable.drawAll(selection) }, mousedown(e){}, mousemove(e){}, mouseup(e){}, }, rectangle: { mousedown(e){ current = new Rectangle(e.clientX, e.clientY, 0, 0, color.value) }, mousemove(e){ if (!current) return; current.width = e.clientX - current.x current.height = e.clientY - current.y Drawable.drawAll() }, mouseup(e){ current = null } }, ellipse: { mousedown(e){ current = new Ellipse(e.clientX, e.clientY, 0, 0, color.value) }, mousemove(e){ if (!current) return; current.width = e.clientX - current.x current.height = e.clientY - current.y Drawable.drawAll() }, mouseup(e){ current = null } } } function superHandler(evt){ let t = tools[tool.value] if (typeof t[evt.type] === 'function') t[evt.type].call(this, evt) } canvas.onmousemove = superHandler canvas.onmouseup = superHandler canvas.onmousedown = superHandler canvas.onclick = superHandler function Drawable(){ Drawable.addInstance(this); } const distance = (x1, y1, x2, y2) => ((x1-x2)**2 + (y1-y2)**2)**0.5 Drawable.prototype.draw = function(){}; Drawable.prototype.distanceTo = function(x,y){ if (typeof this.x !== 'number' || typeof this.y !== 'number'){ return NaN } return distance(this.x, this.y, x, y) }; Drawable.instances = []; Drawable.addInstance = function(item){ Drawable.instances.push(item); } Drawable.drawAll = function(selection=[]){ ctx.clearRect(0,0,width,height); Drawable.forAll(item => item.draw()) selection.forEach(item => item.draw(true)) } Drawable.forAll = function(callback){ for(var i = 0; i= x && this.x <= x + w && this.y >= y && this.y <= y + h } } class Line extends Drawable { constructor(x,y, width, height, color, lineWidth){ super() this.x = x this.y = y this.width = width this.height = height this.color = color this.lineWidth = lineWidth this.draw() } draw(selected){ ctx.beginPath() ctx.moveTo(this.x, this.y) ctx.lineTo(this.x + this.width, this.y + this.height) ctx.closePath() ctx.strokeStyle = this.color ctx.lineWidth = this.lineWidth if (selected){ ctx.strokeStyle = '#f0f' ctx.stroke() } ctx.stroke() } in(x, y) { let rotateLine = Math.atan2(this.height, this.width) let rotCursor = Math.atan2(y - this.y, x - this.x) let distanceToPoint = distance(x, y, this.x, this.y) let angleLine = rotCursor - rotateLine let rotateX = Math.cos(angleLine) * distanceToPoint let rotateY = Math.sin(angleLine) * distanceToPoint return rotateX >= 0 && rotateX <= this.length && rotateY <= this.lineWidth/2 && rotateY >= -this.lineWidth/2 } get length(){ return this.distanceTo(this.x + this.width, this.y + this.height) } } class Rectangle extends Drawable{ constructor(x, y, width, height, color){ super() this.x = x this.y = y this.width = width this.height = height this.color = color this.draw() } draw(selected){ ctx.beginPath() ctx.moveTo(this.x, this.y) ctx.fillRect(this.x, this.y, this.width, this.height) if (selected){ ctx.rect(this.x, this.y, this.width, this.height) ctx.strokeStyle = '#f0f' ctx.stroke() } ctx.fillStyle = this.color ctx.stroke() } in(x, y){ return x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.height } } class Ellipse extends Drawable{ #width = 0 #height = 0 constructor(x, y, width, height, color){ super() this.x = x this.y = y this.#width = width this.#height = height this.color = color this.draw() } draw(selected){ ctx.beginPath() ctx.moveTo(this.x, this.y) ctx.ellipse(this.x + this.rx, this.y + this.ry, this.rx, this.ry, 0, 0, 2 * Math.PI) ctx.closePath() if (selected){ ctx.lineWidth = 2 ctx.fill() } ctx.fillStyle = this.color ctx.fill() } in(x, y) { let h = this.x + this.rx let k = this.y + this.ry return Math.pow((x - h), 2)/Math.pow(this.rx, 2) + Math.pow((y - k), 2)/Math.pow(this.ry, 2) <= 1 } set width(newWidth){ if (newWidth < 0){ this.#width = -newWidth this.x += newWidth } else this.#width = newWidth } set height(newHeight){ if (newHeight < 0){ this.#height = -newHeight this.y += newHeight } else this.#height = newHeight } get width(){ return this.#width } get height(){ return this.#height } get rx(){ return this.#width/2 } get ry(){ return this.#height/2 } } color.onchange = () => { selection.forEach(c => c.color = color.value) Drawable.drawAll(selection) } document.getElementById('delete').onclick = () =>{ Drawable.instances = [] Drawable.drawAll() } undo.onclick = function(){ Drawable.instances.pop() Drawable.drawAll() }