doesn't kill your computer, can draw lines
This commit is contained in:
parent
1f8a250c24
commit
ca51abb958
2 changed files with 102 additions and 96 deletions
197
public/client.js
197
public/client.js
|
@ -1,113 +1,125 @@
|
||||||
const $ = e => document.getElementById(e);
|
const $ = e => document.getElementById(e);
|
||||||
const setStatus = str => $("status").innerText = str;
|
const setStatus = str => $("status").innerText = str;
|
||||||
const socket = io();
|
const socket = io();
|
||||||
const size = 15000;
|
|
||||||
|
|
||||||
// someone's (or your) pen
|
// someone's (or your) pen
|
||||||
class Pen {
|
class Stroke {
|
||||||
constructor({ x, y, color = "black", stroke = 5 }) {
|
constructor({ x, y, color = "black", stroke = 5 }) {
|
||||||
this.startx = x;
|
|
||||||
this.starty = y;
|
|
||||||
this.color = color;
|
this.color = color;
|
||||||
this.stroke = stroke;
|
this.stroke = stroke;
|
||||||
this.path = new Path2D();
|
this.path = new Path2D();
|
||||||
}
|
this.reset(x, y);
|
||||||
}
|
|
||||||
|
|
||||||
// take in events and process them
|
|
||||||
class Drawer {
|
|
||||||
constructor() {
|
|
||||||
this.canvas = new OffscreenCanvas(size, size);
|
|
||||||
this.ctx = this.canvas.getContext("2d");
|
|
||||||
this.history = [];
|
|
||||||
|
|
||||||
// a temporary "staging area" before being drawn to the main canvas
|
|
||||||
this.tmpcanvas = new OffscreenCanvas(size, size);
|
|
||||||
this.tmpctx = this.canvas.getContext("2d");
|
|
||||||
this.tmphistory = [];
|
|
||||||
|
|
||||||
this.pens = new Map();
|
|
||||||
this.ctx.lineCap = "round";
|
|
||||||
this.ctx.lineJoin = "round";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chain(event) {
|
reset(x, y) {
|
||||||
const { pens, ctx, tmpctx } = this;
|
for(let i of ["start", "min", "max", "end"]) {
|
||||||
const pen = pens.get(event.id);
|
this[i + "X"] = x;
|
||||||
if(event.type === "drawstart") {
|
this[i + "Y"] = y;
|
||||||
const pen = new Pen(event);
|
|
||||||
pen.path.moveTo(pen.startx, pen.starty);
|
|
||||||
pens.set(event.id, pen);
|
|
||||||
} else if(!pens.has(event.id)) {
|
|
||||||
return;
|
|
||||||
} else if(event.type === "drawline") {
|
|
||||||
// pen.path.lineTo(event.x, event.y);
|
|
||||||
// pen.path.moveTo(pen.startx, pen.starty);
|
|
||||||
// tmpctx.strokeStyle = pen.color;
|
|
||||||
// tmpctx.lineWidth = pen.stroke;
|
|
||||||
// tmpctx.stroke(pen.path);
|
|
||||||
} else if(event.type === "drawmove" || event.type === "drawend") {
|
|
||||||
pen.path.lineTo(event.x, event.y);
|
|
||||||
ctx.strokeStyle = pen.color;
|
|
||||||
ctx.lineWidth = pen.stroke;
|
|
||||||
ctx.stroke(pen.path);
|
|
||||||
}
|
}
|
||||||
|
this.path.moveTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmprender() {
|
add(x, y) {
|
||||||
// this.tmpctx.clearRect(0, 0, size, size);
|
this.path.lineTo(x, y);
|
||||||
// }
|
if(x < this.minX) this.minX = x;
|
||||||
|
if(y < this.minY) this.minY = y;
|
||||||
// *very* expensive
|
if(x > this.maxX) this.maxX = x;
|
||||||
render() {
|
if(y > this.maxY) this.maxY = y;
|
||||||
this.ctx.clearRect(0, 0, size, size);
|
this.endX = x;
|
||||||
for(let event of this.history) this.chain(event);
|
this.endY = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(event) {
|
line(x, y) {
|
||||||
this.history.push(event);
|
this.path = new Path2D();
|
||||||
this.chain(event);
|
this.reset(this.startX, this.startY);
|
||||||
|
this.add(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
intersect(rect) {
|
||||||
|
if(this.minX > rect.x + rect.width) return false;
|
||||||
|
if(this.minY > rect.y + rect.height) return false;
|
||||||
|
if(this.maxX < rect.x) return false;
|
||||||
|
if(this.maxY < rect.y) return false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// render a drawer to canvas
|
// render a drawer to canvas
|
||||||
class Renderer {
|
class Renderer {
|
||||||
constructor(canvas) {
|
constructor(canvas) {
|
||||||
this.drawer = new Drawer();
|
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.ctx = canvas.getContext("2d");
|
this.ctx = canvas.getContext("2d");
|
||||||
this.pan = [size/-2, size/-2];
|
this.pan = [0, 0];
|
||||||
|
this.scale = 1;
|
||||||
|
|
||||||
|
// drawing
|
||||||
|
this.strokes = new Map();
|
||||||
|
this.history = [];
|
||||||
|
|
||||||
|
// init canvas
|
||||||
|
this.ctx.lineCap = "round";
|
||||||
|
this.ctx.lineJoin = "round";
|
||||||
this.resize();
|
this.resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
panBy(x, y) {
|
||||||
|
this.pan[0] += x;
|
||||||
|
this.pan[1] += y;
|
||||||
|
this.ctx.translate(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
resize() {
|
resize() {
|
||||||
this.canvas.height = window.innerHeight;
|
const width = window.innerWidth;
|
||||||
this.canvas.width = window.innerWidth;
|
const height = window.innerHeight;
|
||||||
this.ctx.height = window.innerHeight;
|
this.canvas.height = height;
|
||||||
this.ctx.width = window.innerWidth;
|
this.canvas.width = width;
|
||||||
this.ctx.clearRect(0, 0, size, size);
|
this.ctx.height = height;
|
||||||
this.render();
|
this.ctx.width = width;
|
||||||
|
this.ctx.clearRect(0, 0, width, height);
|
||||||
|
this.redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
stroke(stroke) {
|
||||||
|
const { ctx } = this;
|
||||||
|
ctx.strokeStyle = stroke.color;
|
||||||
|
ctx.lineWidth = stroke.stroke;
|
||||||
|
ctx.stroke(stroke.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
add(event) {
|
add(event) {
|
||||||
this.drawer.add(event);
|
const { strokes } = this;
|
||||||
this.render();
|
const stroke = strokes.get(event.id);
|
||||||
|
if(event.type === "drawstart") {
|
||||||
|
const stroke = new Stroke(event);
|
||||||
|
strokes.set(event.id, stroke);
|
||||||
|
} else if(!strokes.has(event.id)) {
|
||||||
|
return;
|
||||||
|
} else if(event.type === "drawline") {
|
||||||
|
this.redraw();
|
||||||
|
stroke.line(event.x, event.y);
|
||||||
|
this.stroke(stroke);
|
||||||
|
} else if(event.type === "drawmove" || event.type === "drawend") {
|
||||||
|
stroke.add(event.x, event.y);
|
||||||
|
this.stroke(stroke);
|
||||||
|
if(event.type === "drawend") this.history.push(stroke);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addAll(events) {
|
addAll(events) {
|
||||||
this.drawer.history.push(...events);
|
for(let event of events) this.add(event);
|
||||||
this.drawer.render();
|
|
||||||
this.render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
redraw() {
|
||||||
|
const { ctx } = this;
|
||||||
const width = window.innerWidth;
|
const width = window.innerWidth;
|
||||||
const height = window.innerHeight;
|
const height = window.innerHeight;
|
||||||
this.ctx.clearRect(0, 0, size, size);
|
const rect = { x: -this.pan[0], y: -this.pan[1], width, height };
|
||||||
this.ctx.drawImage(this.drawer.canvas, -this.pan[0], -this.pan[1], width, height, 0, 0, width, height);
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
// this.ctx.globalAlpha = 0.4;
|
ctx.clearRect(0, 0, width, height);
|
||||||
// this.ctx.drawImage(this.drawer.tmpcanvas, -this.pan[0], -this.pan[1], width, height, 0, 0, width, height);
|
ctx.translate(this.pan[0], this.pan[1]);
|
||||||
// this.ctx.globalAlpha = 1;
|
for(let stroke of this.history) {
|
||||||
|
if(stroke.intersect(rect)) this.stroke(stroke);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,13 +159,14 @@ const cursor = { pressed: false, x: 0, y: 0 };
|
||||||
const renderer = new Renderer($("canvas"));
|
const renderer = new Renderer($("canvas"));
|
||||||
const colors = new ColorSelection($("picker"));
|
const colors = new ColorSelection($("picker"));
|
||||||
colors.addAll([
|
colors.addAll([
|
||||||
"#F45B69",
|
"#F45B69", // red
|
||||||
"#FE7F2D",
|
"#FE7F2D", // orange
|
||||||
"#FCCA46",
|
"#FCCA46", // yellow
|
||||||
"#87FF65",
|
"#87FF65", // green
|
||||||
"#00A5CF",
|
"#00A5CF", // blue
|
||||||
"#7D5BA6",
|
"#7D5BA6", // purple
|
||||||
"#312F2F",
|
"#312F2F", // black
|
||||||
|
"#424B54", // gray
|
||||||
]);
|
]);
|
||||||
colors.random();
|
colors.random();
|
||||||
colors.add("white");
|
colors.add("white");
|
||||||
|
@ -199,24 +212,16 @@ function handle(e) {
|
||||||
if(!coords) return;
|
if(!coords) return;
|
||||||
const type = parseType(e);
|
const type = parseType(e);
|
||||||
if(e.which === 1) {
|
if(e.which === 1) {
|
||||||
if(e.shiftKey) {
|
setStatus(e.shiftKey ? "line" : "drawing");
|
||||||
setStatus("line");
|
const event = parseDrawEvent(coords, type);
|
||||||
const event = parseDrawEvent(coords, type, e);
|
if(!event) return;
|
||||||
if(!event) return;
|
if(e.shiftKey && type === "drawmove") event.type = "drawline";
|
||||||
renderer.add(event);
|
renderer.add(event);
|
||||||
socket.emit("draw", event);
|
socket.emit("draw", event);
|
||||||
} else {
|
} else if(e.which === 2) {
|
||||||
setStatus("drawing");
|
|
||||||
const event = parseDrawEvent(coords, type, e);
|
|
||||||
if(!event) return;
|
|
||||||
renderer.add(event);
|
|
||||||
socket.emit("draw", event);
|
|
||||||
}
|
|
||||||
} else if(cursor.pressed && e.which === 2) {
|
|
||||||
setStatus("panning");
|
setStatus("panning");
|
||||||
renderer.pan[0] += coords.x - cursor.x;
|
renderer.panBy(coords.x - cursor.x, coords.y - cursor.y);
|
||||||
renderer.pan[1] += coords.y - cursor.y;
|
renderer.redraw();
|
||||||
renderer.render();
|
|
||||||
}
|
}
|
||||||
if(type === "drawstart") cursor.pressed = true;
|
if(type === "drawstart") cursor.pressed = true;
|
||||||
if(type === "drawend") {
|
if(type === "drawend") {
|
||||||
|
|
|
@ -6,6 +6,7 @@ body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-family: "Trebuchet MS", Arial, sans-serif;
|
font-family: "Trebuchet MS", Arial, sans-serif;
|
||||||
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#bottom {
|
#bottom {
|
||||||
|
|
Loading…
Reference in a new issue