我正在使用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>