Create a drawing app that uses VueJS

Vue JS Draw

Wanted to create a drawing app that uses VueJS for it's interface and data management. :)

Made with

Html
Javascript/VueJS

Html

<html>
<head>
  <title>Bootstrap & Vue Template</title>
</head>
<body>
  <div id="app">
    <div class="container mt-2">
      <form>
        <div class="form-group row">
          <label for="inputName" class="col-sm-2 col-form-label">Name</label>
          <div class="col-sm-10">
            <input type="text" class="form-control" id="inputName" placeholder="Goal name" v-model="currentGoal.name">
          </div>
        </div>
        <div class="form-group row">
          <label for="inputDescription" class="col-sm-2 col-form-label">Description</label>
          <div class="col-sm-10">
            <textarea class="form-control" id="inputDescription" placeholder="Goal description" v-model="currentGoal.description"></textarea>
          </div>
        </div>
        <div class="form-group row">
          <div class="col-sm-2">Favorite</div>
          <div class="col-sm-10">
            <div class="form-check">
              <input class="form-check-input" type="checkbox" id="checkFavorite" v-model="currentGoal.favorite">
            </div>
          </div>
        </div>
        <div class="form-group row">
          <div class="col-sm-6">
            <button type="button" class="btn btn-primary" v-on:click="addGoal">Create Goal</button>
          </div>
        </div>
      </form>
        <table class="table">
          <thead>
            <tr>
              <th scope="col">Name</th>
              <th scope="col">Description</th>
              <th scope="col">Favorite</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="goal in goals">
              <td>{{ goal.name }}</td>
              <td>{{ goal.description }}</td>
              <td>{{ goal.favorite }}</td>
            </tr>
          </tbody>
        </table>
    </div>
  </div> 
</body>
</html>

Javascript

var app = new Vue({
  el: '#draw',
  data: {
    history: [],
		color: '#13c5f7',
		popups: {
			showColor: false,
			showSize: false,
			showWelcome: true,
			showSave: false,
			showOptions: false
		},
		options: {
			restrictY: false,
			restrictX: false
		},
		save: {
			name: '',
			saveItems: []
		},
		size: 12,
		colors: [
			'#d4f713',
			'#13f7ab',
			'#13f3f7',
			'#13c5f7',
			'#138cf7',
			'#1353f7',
			'#2d13f7',
			'#7513f7',
			'#a713f7',
			'#d413f7',
			'#f713e0',
			'#f71397',
			'#f7135b',
			'#f71313',
			'#f76213',
			'#f79413',
			'#f7e013'],
		sizes: [6, 12, 24, 48],
		weights: [ 2, 4, 6 ]
  },
	methods: {
		removeHistoryItem: ()=>{
			app.history.splice(app.history.length-2, 1);
			draw.redraw();
		},
		removeAllHistory: ()=>{
			app.history = [];
			draw.redraw();
		},
		simplify: ()=>{
			var simpleHistory = [];
			app.history.forEach((item, i)=>{
				if(i % 6 !== 1 || item.isDummy){
					simpleHistory.push(item);
				}
			});
			app.history = simpleHistory;
			draw.redraw();
		},
		jumble: ()=>{
			var simpleHistory = [];
			app.history.forEach((item, i)=>{
				item.r += Math.sin(i * 20) * 5;
			});
			app.history = app.shuffle(app.history);
			draw.redraw();
		},
		shuffle: (a)=>{
			var b = [];
			
			a.forEach((item, i)=>{
				if(!item.isDummy){
					var l = b.length;
					var r = Math.floor(l * Math.random());
					b.splice(r, 0, item);
				}
			});
			
			for(var i = 0; i < b.length; i++){
				if(i % 20 === 1){
					b.push(draw.getDummyItem());	
				}
			}
			
			return b;
		},
		saveItem: ()=>{
			if(app.save.name.length > 2){
				var historyItem = {
					history: app.history.slice(),
					name: app.save.name
				};

				app.save.saveItems.push(historyItem);
				app.save.name = "";
			}
		},
		loadSave: (item)=>{
			app.history = item.history.slice();
			draw.redraw();
		}
	}
});

class Draw {
	constructor(){
		this.c = document.getElementById('canvas');
		this.ctx = this.c.getContext('2d');
		
		this.mouseDown = false;
		this.mouseX = 0;
		this.mouseY = 0;
		
		this.tempHistory = [];
		
		this.setSize();
		
		this.listen();
		
		this.redraw();
	}
	
	listen(){
		this.c.addEventListener('mousedown', (e)=>{
			this.mouseDown = true;
			this.mouseX = e.offsetX;
			this.mouseY = e.offsetY;
			this.setDummyPoint();
		});
		
		this.c.addEventListener('mouseup', ()=>{
			if(this.mouseDown){
				this.setDummyPoint();
			}
			this.mouseDown = false;
		});
		
		this.c.addEventListener('mouseleave', ()=>{
			if(this.mouseDown){
				this.setDummyPoint();
			}
			this.mouseDown = false;
		});
		
		this.c.addEventListener('mousemove', (e)=>{
			this.moveMouse(e);
			
			if(this.mouseDown){
				this.mouseX = this.mouseX;
				this.mouseY = this.mouseY;
				
				if(!app.options.restrictX){
					this.mouseX = e.offsetX;
				}
				
				if(!app.options.restrictY){
					this.mouseY = e.offsetY;
				}
								
				var item = {
					isDummy: false,
					x: this.mouseX,
					y: this.mouseY,
					c: app.color,
					r: app.size
				};
				
				app.history.push(item);
				this.draw(item, app.history.length);
			}
		});
		
		window.addEventListener('resize', ()=>{
			this.setSize();
			this.redraw();
		});
	}
	
	setSize(){
		this.c.width = window.innerWidth;
		this.c.height = window.innerHeight - 60;
	}
	
	moveMouse(e){
		let x = e.offsetX;
		let y = e.offsetY;
		
		var cursor = document.getElementById('cursor');
		
		cursor.style.transform = `translate(${x - 10}px, ${y - 10}px)`;
	}
	
	getDummyItem(){
		var lastPoint = app.history[app.history.length-1];
		
		return {
			isDummy: true,
			x: lastPoint.x,
			y: lastPoint.y,
			c: null,
			r: null
		};
	}
	
	setDummyPoint(){
		var item = this.getDummyItem();
		app.history.push(item);
		this.draw(item, app.history.length);
	}
	
	redraw(){
		this.ctx.clearRect(0, 0, this.c.width, this.c.height);
		this.drawBgDots();
		
		if(!app.history.length){
			return true;
		}
		
		app.history.forEach((item, i)=>{
			this.draw(item, i);
		});
	}
	
	drawBgDots(){
		var gridSize = 50;
		this.ctx.fillStyle = 'rgba(0, 0, 0, .2)';
		
		for(var i = 0; i*gridSize < this.c.width; i++){
			for(var j = 0; j*gridSize < this.c.height; j++){
				if(i > 0 && j > 0){
					this.ctx.beginPath();
					this.ctx.rect(i * gridSize, j * gridSize, 2, 2);
					this.ctx.fill();
					this.ctx.closePath();
				}
			}
		}
	}
	
	draw(item, i){
		this.ctx.lineCap = 'round';
		this.ctx.lineJoin="round";
		
		var prevItem = app.history[i-2];
				
		if(i < 2){
			return false;
		}

		if(!item.isDummy && !app.history[i-1].isDummy && !prevItem.isDummy){
			this.ctx.strokeStyle = item.c;
			this.ctx.lineWidth = item.r;
			
			this.ctx.beginPath();
			this.ctx.moveTo(prevItem.x, prevItem.y);
			this.ctx.lineTo(item.x, item.y);
			this.ctx.stroke();
			this.ctx.closePath();
		} else if (!item.isDummy) {			
			this.ctx.strokeStyle = item.c;
			this.ctx.lineWidth = item.r;
			
			this.ctx.beginPath();
			this.ctx.moveTo(item.x, item.y);
			this.ctx.lineTo(item.x, item.y);
			this.ctx.stroke();
			this.ctx.closePath();
		}
	}
}

var draw = new Draw();

Author

Lewi Hussey

Demo

See the Pen Vue JS Draw by Lewi Hussey (@Lewitje) on CodePen.