我正在制作绘图工具。绘制时如何绘制就可以了,但是在绘制分配后,放开并重新开始绘制,这真的很慢

时间:2018-07-23 23:45:22

标签: javascript canvas html5-canvas

我正在使用HTML5和JS来创建它。当我开始绘图时,它使绘图没有太多滞后。我画了一点后,放开鼠标,然后重新开始,需要花费几秒钟来开始绘制。 编辑:另外,绘图一段时间后,我得到了非常尖的形状。有什么想法吗?

编辑:

我根据规则和注释添加了一个代码段。

class Drawing {
	// So after many get-back-togethers I've finally broke up with JavaScript's arguments keyword which just does not compare to an array.
	// Now you have to provide an Array as args even if you only have one arg.
	// Every Drawing object needs to store the event that created it. This is needed for ctrl-z to be able to revert to the last pointerdown
	constructor(args, ev) {
		for (var arg of args) {
			// Every arg used when creating a Drawing must have a function to be called when that drawing is drawn. Otherwise, there won't be anything to do.
			if (!(arg.func))
				throw Error("A function must be provided!");
			// Make sure the function exists within ctx.
			if (!CanvasRenderingContext2D.prototype.hasOwnProperty(arg["func"]))
				throw Error(`${arg['func']} is not a property of the CanvasRenderingContext2D prototype!`);
		}
		// When a Drawing instance is drawn, it will call all it's functions in this.functions with their appropriate args.
		this.functions = args;
		this.ev = ev;
	}
}
const CSS_COLORS = //["AliceBlue","AntiqueWhite","Aqua","Aquamarine","Azure","Beige","Bisque","Black","BlanchedAlmond","Blue","BlueViolet","Brown","BurlyWood","CadetBlue","Chartreuse","Chocolate","Coral","CornflowerBlue","Cornsilk","Crimson","Cyan","DarkBlue","DarkCyan","DarkGoldenRod","DarkGray","DarkGrey","DarkGreen","DarkKhaki","DarkMagenta","DarkOliveGreen","Darkorange","DarkOrchid","DarkRed","DarkSalmon","DarkSeaGreen","DarkSlateBlue","DarkSlateGray","DarkSlateGrey","DarkTurquoise","DarkViolet","DeepPink","DeepSkyBlue","DimGray","DimGrey","DodgerBlue","FireBrick","FloralWhite","ForestGreen","Fuchsia","Gainsboro","GhostWhite","Gold","GoldenRod","Gray","Grey","Green","GreenYellow","HoneyDew","HotPink","IndianRed","Indigo","Ivory","Khaki","Lavender","LavenderBlush","LawnGreen","LemonChiffon","LightBlue","LightCoral","LightCyan","LightGoldenRodYellow","LightGray","LightGrey","LightGreen","LightPink","LightSalmon","LightSeaGreen","LightSkyBlue","LightSlateGray","LightSlateGrey","LightSteelBlue","LightYellow","Lime","LimeGreen","Linen","Magenta","Maroon","MediumAquaMarine","MediumBlue","MediumOrchid","MediumPurple","MediumSeaGreen","MediumSlateBlue","MediumSpringGreen","MediumTurquoise","MediumVioletRed","MidnightBlue","MintCream","MistyRose","Moccasin","NavajoWhite","Navy","OldLace","Olive","OliveDrab","Orange","OrangeRed","Orchid","PaleGoldenRod","PaleGreen","PaleTurquoise","PaleVioletRed","PapayaWhip","PeachPuff","Peru","Pink","Plum","PowderBlue","Purple","Red","RosyBrown","RoyalBlue","SaddleBrown","Salmon","SandyBrown","SeaGreen","SeaShell","Sienna","Silver","SkyBlue","SlateBlue","SlateGray","SlateGrey","Snow","SpringGreen","SteelBlue","Tan","Teal","Thistle","Tomato","Turquoise","Violet","Wheat","White","WhiteSmoke","Yellow","YellowGreen"];
	["#ff0000", "#ff4000", "#ff8000", "#ffbf00", "#ffff00", "#bfff00", "#80ff00", "#40ff00", "#00ff00", "#00ff40", "#00ff80", "#00ffbf", "#00ffff", "#00bfff", "#0080ff", "#0040ff", "#0000ff", "#4000ff", "#8000ff", "#bf00ff", "#ff00ff", "#ff00bf", "#ff0080", "#ff0040", "#ff0000"];
const canvas = $("canvas");
canvas.locked = false
const colorChanger = $('input[type = color]');
colorChanger.change(function(e) {
	changeColor(colorChanger.val(), e)
})
const colorOptionsDiv = $("#colorOptions");
const select = $("sselect");
const slider = $("#revisionSlider");
let eraserSize = 10;
slider.change(function(ev) {
	console.log(slider.val());
	if (slider.val() > ctx.history.length - 1) {
		ctx.future.push(ctx.history.splice(slider.val(), ctx.history.length));
		ctx.redraw();
	}
});
select.val("brush");
const ctx = canvas[0].getContext("2d");
let selection;
$("body > div > button").click(function() {
	let height = $('#height').val();
	let width = $('#width').val();
	let go = true;
	if (height === "" & width === "") {
		go = confirm("You haven't entered any dimensions. Would you like to go with your screen dimensions?\nThey are:\nWidth: " + window.innerWidth + "\nHeight: " + window.innerHeight);
		width = screen.width //window.innerWidth //- 8 ///1.06;
		height = screen.height //window.innerHeight //- 5 ///1.2;
	}
	else if (height < 300 && width >= 300) {
		go = confirm("Are you sure you don't want your canvas higher?");
	}
	else if (width < 300 && height >= 300) {
		go = confirm("Are you sure you don't want your canvas wider?");
	}
	else if (width < 300 && height < 300) {
		go = confirm("Are you sure you don't want a larger canvas?");
	}
	if (go) {
		updateCanvasSize()

		new ResizeObserver(updateCanvasSize).observe(canvas.parent()[0])
		$(this.parentNode).remove();
		$('#mainDiv, #actions, #paintControls').show();
		document.documentElement.webkitRequestFullScreen()
		canvas.parent().height(height)
		canvas.parent().width(width)
	}
	else return;
});
$("body > div > input").keyup(function(ev) {
	if (ev.keyCode === 13) {
		$("body > div > button").click();
	}
});

function pencilDown(ev) {
	if (ev.buttons - 0 == 1) {
		ctx.redraw();
		let mode = select.val().toLowerCase();
		canvas.pointerIsDown = true;
		if (mode === "brush") {
			canvas.changed = true;
			let drawing = function() {
				ctx.beginPath();
				ctx.moveTo(ev.offsetX, ev.offsetY);
			};
			drawing.ev = ev;
			ctx.draw(drawing);
		}
		else if (mode === "line") {
			ctx.beginPath();
			ctx.moveTo(ev.offsetX, ev.offsetY);
		}
		else if (mode === 'select') {
			ctx.setLineDash([8, 16]);
			ctx.strokeStyle = "black";
		}
		canvas.lastPointerDown = ev;
	}
}
canvas.on('pointerdown', pencilDown);
canvas.on('pointerdown', function() {
	$("#paintControls").css({ top: "-50px" });
	$("#actions").css({ width: 0 });
});
$(window).on('pointerup', function(ev) {
	canvas.pointerIsDown = false;
});

function pencilUp(ev) {
	let mode = select.val().toLowerCase();
	canvas.lastPointerUp = ev;
	if (mode == "line") {
		ctx.fillLine(ctx.lastPointerDown.offsetX, ctx.lastPointerDown.offsetY, ev.offsetX, ev.offsetY);
	}
	else if (mode === "select") {
		ctx.setLineDash([0, 0]);
		selection = [canvas.lastPointerDown.offsetX, canvas.lastPointerDown.offsetY, 0 - (canvas.lastPointerDown.offsetX - canvas.lastPointerUp.offsetX), 0 - (canvas.lastPointerDown.offsetY - ev.offsetY)];
		ctx.redraw();
		ctx.strokeStyle = "#0099ff"
		ctx.strokeRect(...selection);
	}
}
canvas.on('pointerup', pencilUp);
canvas.on('pointerup', function() {
	$("#paintControls").css({ top: "2px" });
	$("#actions").css({ width: "40px" });
});

function pencilMove(ev) {
	if (canvas.pointerIsDown) {
		let endX = ev.offsetX - canvas.lastPointerDown.offsetX;
		let endY = ev.offsetY - canvas.lastPointerDown.offsetY;
		let mode = select.val().toLowerCase();
		if (mode === "brush") {
			let funcs = [
				{ func: "lineTo", args: [ev.offsetX, ev.offsetY] },
				{ func: "stroke", args: [] }
			];
			let drawing = new Drawing([], ev);
			drawing.functions = funcs;
			ctx.draw(drawing);

			// implent this later, it looks very cool! ctx.closePath();
			// and this ctx.fill();
		}
		else if (mode === "line") {
			// try{
			// 	tempLine.clear();
			// } catch (e){}
			// tempLine = ctx.temporaryLine(ctx.lastPointerDown.offsetX, ctx.lastPointerDown.offsetY, ev.offsetX, ev.offsetY, 3);
			ctx.redraw();
			ctx.lineTo(ev.offsetX, ev.offsetY);
			ctx.stroke();
		}
		else if (mode === "select") {
			ctx.redraw();
			ctx.setLineDash([6, 8]);
			ctx.strokeStyle = "black";
			// if (endX<0)endX=0;
			// let drawing = new Drawing([{func:"strokeRect", args: [canvas.lastPointerDown.offsetX, canvas.lastPointerDown.offsetY, endX, endY]}], ev);
			// ctx.draw(drawing, false);
			ctx.strokeRect(canvas.lastPointerDown.offsetX, canvas.lastPointerDown.offsetY, endX, endY);
		}
		else if (mode === "bug1") {
			// ctx.redraw();
			var drawing = new Drawing([{ func: "strokeRect", args: [canvas.lastPointerDown.offsetX, canvas.lastPointerDown.offsetY, endX, endY] }], ev);
			ctx.draw(drawing);
		}
		else if (mode === "bug2") {
			ctx.future.push(ctx.history.pop())
			ctx.redraw();
			var drawing = new Drawing([{ func: "strokeRect", args: [canvas.lastPointerDown.offsetX, canvas.lastPointerDown.offsetY, endX, endY] }], ev);
			ctx.draw(drawing);
		}
		else if (mode === "timeline") {
			ctx.future.push(ctx.history.pop())
			ctx.redraw();
		}
		else if (mode === "dots") {
			let drawing = new Drawing([{ func: "fillRect", args: [ev.offsetX, ev.offsetY, 1, 1] }], ev);
			ctx.draw(drawing);
		}
		else if (mode === "eraser") {
			let drawing = new Drawing([{ func: "clearRect", args: [ev.offsetX - eraserSize / 2, ev.offsetY - eraserSize / 2, eraserSize, eraserSize] }]);
			ctx.draw(drawing);
		}
		canvas.lastPointerMove = ev;
	}
}
canvas.on('pointermove', pencilMove);
$(window).on('keydown', function(ev) {
	if (ev.keyCode === 8 || ev.keyCode === 46) {
		var drawing = new Drawing([{ func: "clearRect", args: selection }], ev);
		ctx.draw(drawing);
		ctx.redraw();
	}
	else if (ev.key.toLowerCase() === 'z' && ev.ctrlKey && ev.shiftKey) {
		ctx.future.push(ctx.history.pop());
		ctx.redraw();
	}
	else if (ev.key.toLowerCase() === 'y' && ev.ctrlKey && ev.shiftKey) {
		ctx.history.push(ctx.future.pop());
		ctx.redraw();
	}
});

function undo() {
	for (var item of ctx.history.slice().reverse()) {
		try {
			if (item.ev.type === "pointerdown") {
				locationInArray = ctx.history.indexOf(item);
				ctx.future = ctx.future.concat(ctx.history.splice(locationInArray, ctx.history.length - locationInArray));
				ctx.redraw();
				return;
			}
		}
		catch (e) { continue; }
	}
}

function redo() {
	for (var item of ctx.future.slice().reverse()) {
		try {
			if (item.ev.type === "pointerdown") {
				locationInArray = ctx.future.indexOf(item);
				ctx.history = ctx.history.concat(ctx.future.splice(locationInArray, ctx.future.length - locationInArray));
				ctx.redraw();
				return;
			}
		}
		catch (e) { continue; }
	}
}
$(window).on('keyup', function(ev) {
	var locationInArray
	if (ev.key.toLowerCase() === 's' && ev.ctrlKey && ev.shiftKey) {
		save();
	}
	if (ev.key.toLowerCase() === 'z' && ev.ctrlKey && !ev.shiftKey) {
		undo()
	}
	if (ev.key.toLowerCase() === 'y' && ev.ctrlKey && !ev.shiftKey) {
		redo()
	}
});
colorChanger.change(function() {
	var drawing = new Drawing([{ func: "set", args: [{ 'strokeStyle': this.value, 'fillStyle': this.value, 'shadowColor': this.value }] }], ev);
	ctx.draw(drawing);
	addNewColor(this.value);
});
CanvasRenderingContext2D.prototype.clear = function(record, ev) {
	ev = ev || {};
	if (record) {
		var drawing = new Drawing([{ func: "clearRect", args: [0, 0, this.canvas.width, this.canvas.height] }], ev);
		this.draw(drawing);
	}
	else
		this.clearRect(0, 0, this.canvas.width, this.canvas.height);
};
CanvasRenderingContext2D.prototype.clearSelection = function(record = true, ev = null) {
	if (selection) {
		x = selection[0];
		y = selection[1];
		w = selection[2];
		h = selection[3]
		selection = null
	}
	else {
		if (!confirm("Are you sure you want to clear the canvas?")) return
		x = 0;
		y = 0;
		w = this.canvas.width;
		h = this.canvas.height;
	}
	if (record) {
		var drawing = new Drawing([{ func: "clearRect", args: [x, y, w, h] }], ev);
		this.draw(drawing);
	}
	else
		this.clearRect(x, y, w, h);
	ctx.redraw()
};
CanvasRenderingContext2D.prototype.draw = function(drawing, record = true) {
	if (drawing instanceof Drawing) {
		for (func of drawing.functions) {
			this[func.func](...func.args);
		}
	}
	else if (drawing instanceof Function) {
		if (drawing.args)
			drawing(...drawing.args);
		else drawing();
	}
	else {
		drawing.toString = drawing.toString || function() { return this; };
		throw TypeError(`${drawing.toString()+"yt"} is not a Drawing or Function!`);
	}
	if (record) {
		this.add(drawing);
		slider.attr("max", Number(slider.attr("max")) + 1);

	}
	this.lastRender = URLToImageEl(canvas.toURL())
};
CanvasRenderingContext2D.prototype.history = [];
CanvasRenderingContext2D.prototype.future = [];
CanvasRenderingContext2D.prototype.add = function(drawing) {
	this.history.push(drawing);
};

CanvasRenderingContext2D.prototype.redraw = function(cache = false) {
	var _ctx = this
	this.clear();
	if (!cache) {
		for (var drawing of this.history) {
			this.draw(drawing, false);
		}
	}
	else {
		this.draw(function() {
			console.log(_ctx.lastRender)
			_ctx.drawImage(_ctx.lastRender, 0, 0)

		})
	}
};
CanvasRenderingContext2D.prototype.set = function(prop, val) {
	if (typeof prop === "string" & typeof val === "string" | typeof val === "number")
		this[prop] = val;
	else if (typeof prop === "object" & !val) {
		for (var key in prop) {
			this[key] = prop[key]
		}
	}
	else throw Error("Invalid arguments")
}
$("#strokeWidthChanger input").change(function() {
	ctx.lineWidth = this.value;
});
for (var color of CSS_COLORS) {
	addNewColor(color);
}

function addNewColor(color) {
	colorOptionsDiv.html(colorOptionsDiv.html() + `<button class="colorOption" style="background-color:${color}" onclick="changeColor('${color}')"></button>`);
}

function changeColor(color, ev) {
	ev = ev || {};
	if (color.startsWith("rgb"))
		colorChanger.val(rgbToHex(color));
	else colorChanger.val(color);
	var colorChange = function() {
		var drawing = new Drawing([{ func: "set", args: [{ 'strokeStyle': color, 'fillStyle': color, 'shadowColor': color }] }], ev);
		ctx.draw(drawing);
	};
	ctx.draw(colorChange);
}
canvas.toURL = function() {
	return this[0].toDataURL()
}

function save() {
	ctx.redraw();
	var dataUrl = canvas.toURL();
	var a = document.createElement("A");
	a.download = $("#downloadName").val();
	a.href = dataUrl;
	a.click();
}

function configCanvas() {
	ctx.draw(function() {
		ctx.strokeStyle = 'black';
		ctx.fillStyle = 'black';
		ctx.setLineDash([0, 0]);
		ctx.lineJoin = "round";
		ctx.lineCap = "round";
		ctx.shadowBlur = 1;
		ctx.shadowColor = ctx.strokeStyle
		ctx.lastRender = new Image()
		ctx.lastRender.src = ""
		this.type = null;
	});
}
configCanvas();
sselectsOptions = $("sselect soption");
sselectsOptions.click(function() {
	$(this).closest("sselect").val(this.getAttribute("value"));
})

function componentToHex(c) {
	var hex = c.toString(16);
	return hex.length == 1 ? "0" + hex : hex;
}

function rgbToHex(color) {
	color = color.replace("rgb(", "").replace(")", "").replace(" ", "").split(",")
	console.log(color)
	r = color[0];
	g = color[1];
	b = color[2];
	return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
let brushes = {
	pencil: function() {

	}
}

function updateCanvasSize() {
	canvas.attr('width', canvas.parent().width() - 6)
	canvas.attr('height', canvas.parent().height() - 6)
	ctx.redraw()
}

function URLToImageEl(URL) {
	var img = new Image()
	img.src = URL
	return img
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="startDiv">
		<h3>Enter a height and width to create your canvas with those dimensions</h3>
		<h5>You can resize it at any point after creation.</h5>
		<input placeholder="Height" id="height" type="number" />
		<br />
		<input placeholder="Width" id="width" type="number" />
		<button>Create canvas</button>
	</div>
	<div id="mainDiv" style="display: none;">
		<div id="canvas-container">
			<canvas></canvas>
		</div>
	</div>
	<div id="paintControls" style="display: none">
		<div id="colorOptions">
			<div id="colorPickerContainer">
				<input type="color" class="field-radio" />
			</div>
			<button class="colorOption" style="background-color:black" onclick="changeColor('black')"></button>
		</div>
	</div>
	<div id="actions" style="display: none">
		<div class="tooltip">
			<span class="tooltiptext">Clear canvas</span>
			<i onclick="ctx.clearSelection()" class="material-icons awesomeButton action">clear</i>
		</div>
		<div class="tooltip">
			<span class="tooltiptext">Download</span>
			<soption class="material-icons awesomeButton action" value="download" id="download" onclick="save();">file_download</soption>
		</div>
		<sselect id="options">
			<div class="tooltip">
				<span class="tooltiptext">Select brush</span>
				<soption class="material-icons awesomeButton action" value="brush" id="brush">
					brush
				</soption>
			</div>
			<div class="tooltip">
				<span class="tooltiptext">Select area</span>
				<soption class="material-icons awesomeButton action" value="select" id="select">crop_free</soption>
			</div>
			<soption id="line"></soption>
			<div class="tooltip">
				<span class="tooltiptext">Eraser</span>
				<soption id="eraser" class="awesomeButton" value="eraser">E</soption>
			</div>
			<div class="tooltip">
				<span class="tooltiptext">RectEffect</span>
				<soption class="awesomeButton material-icons" value="bug1">check_box_outline_blank</soption>
			</div>
			<div class="tooltip">
				<span class="tooltiptext">Tools and colors settings</span>
				<soption class="settings awesomeButton material-icons" onclick="$(document.body).append(`<div style='position: fixed;top: 0;left: 0;z-index:3;text-align:center;background: black;width:100%;height:100%'><input type='range' min='0' max='2'></div>`)">settings</soption>
			</div>
			<div class="tooltip">
				<span class="tooltiptext">Insert image</span>
				<i id="image" class="awesomeButton material-icons" value="insertImage" onclick="$(this).next().click()">image</i>
				<input id="imageFilePicker" style="display: none;" type="file">
			</div>
			<div class="tooltip">
				<span class="tooltiptext">Undo</span>
				<i class="awesomeButton material-icons" onclick="undo()">undo</i>
			</div>
			<div class="tooltip">
				<span class="tooltiptext">Redo</span>
				<i class="awesomeButton material-icons" onclick="redo()">redo</i>
			</div>
		</sselect>
	</div>

Here's a pen with the CSS

0 个答案:

没有答案