fullscreen drawing
This commit is contained in:
committed by
Juraj Kubrican
parent
0505964854
commit
7ed413648c
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
.env
|
.env
|
||||||
tmp
|
tmp
|
||||||
|
|
||||||
|
node_modules
|
||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
|
}
|
||||||
144
js/draw.js
144
js/draw.js
@@ -1,101 +1,101 @@
|
|||||||
|
"use strict";
|
||||||
(() => {
|
(() => {
|
||||||
const canvas = document.getElementById("drawCanvas");
|
const drawElement = document.getElementById("drawDiv");
|
||||||
const ctx = canvas.getContext("2d");
|
if (!drawElement) {
|
||||||
|
throw new Error("canvas not found");
|
||||||
ctx.lineWidth = 2;
|
}
|
||||||
ctx.lineCap = "round";
|
const drawEmitter = mitt();
|
||||||
ctx.strokeStyle = "white";
|
|
||||||
|
|
||||||
let drawing = false;
|
let drawing = false;
|
||||||
let lineId = 0;
|
const socketUrl = "/draw/ws";
|
||||||
|
|
||||||
const socketUrl =
|
|
||||||
(window.location.protocol.startsWith("https") ? "wss://" : "ws://") +
|
|
||||||
window.location.host +
|
|
||||||
"/draw/ws";
|
|
||||||
|
|
||||||
let socket = new WebSocket(socketUrl);
|
let socket = new WebSocket(socketUrl);
|
||||||
|
|
||||||
socket.onerror = console.error;
|
socket.onerror = console.error;
|
||||||
socket.onclose = console.warn;
|
socket.onclose = console.warn;
|
||||||
|
const serializeDraw = ({ from, to }) => `${from.x},${from.y},${to.x},${to.y}`;
|
||||||
setTimeout(() => socket.send("ping"), 200);
|
const deserializeDraw = (message) => {
|
||||||
|
const parts = message.split(",");
|
||||||
// const queue = []
|
return {
|
||||||
|
from: {
|
||||||
const sendMessage = (id, x, y) => {
|
x: Number(parts[0]),
|
||||||
// queue.push(`${id},${x},${y}`)
|
y: Number(parts[1]),
|
||||||
|
},
|
||||||
// console.log(queue)
|
to: {
|
||||||
|
x: Number(parts[2]),
|
||||||
|
y: Number(parts[3]),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
drawEmitter.on("draw", (e) => sendMessage(e));
|
||||||
|
const sendMessage = (e) => {
|
||||||
if (socket.readyState !== socket.OPEN) {
|
if (socket.readyState !== socket.OPEN) {
|
||||||
socket = new WebSocket(socketUrl);
|
socket = new WebSocket(socketUrl);
|
||||||
}
|
}
|
||||||
|
socket.send(serializeDraw(e));
|
||||||
socket.send(id + "," + x + "," + y);
|
|
||||||
};
|
};
|
||||||
|
const mouseDown = () => {
|
||||||
const drawStart = () => {
|
|
||||||
ctx.beginPath();
|
|
||||||
lineId++;
|
|
||||||
drawing = true;
|
drawing = true;
|
||||||
|
previous = null;
|
||||||
};
|
};
|
||||||
|
const mouseUp = () => {
|
||||||
const drawStop = () => {
|
|
||||||
drawing = false;
|
drawing = false;
|
||||||
ctx.closePath();
|
|
||||||
};
|
};
|
||||||
|
let previous = null;
|
||||||
const drawMove = (e) => {
|
const mouseMove = (e) => {
|
||||||
if (!drawing) return;
|
if (!drawing)
|
||||||
ctx.lineTo(e.offsetX, e.offsetY);
|
return;
|
||||||
sendMessage(lineId, e.offsetX, e.offsetY);
|
if (previous) {
|
||||||
ctx.stroke();
|
drawEmitter.emit("draw", {
|
||||||
};
|
from: previous,
|
||||||
|
to: { x: e.offsetX, y: e.offsetY },
|
||||||
const touchStart = (e) => drawStart(e.touches[0]);
|
|
||||||
const touchMove = (e) => {
|
|
||||||
drawMove({
|
|
||||||
offsetX: e.touches[0].clientX - canvas.offsetLeft,
|
|
||||||
offsetY: e.touches[0].clientY - canvas.offsetTop,
|
|
||||||
});
|
});
|
||||||
e.preventDefault();
|
}
|
||||||
|
previous = { x: e.offsetX, y: e.offsetY };
|
||||||
};
|
};
|
||||||
const touchEnd = (e) => drawStop(e.changedTouches[0]);
|
const touchMove = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
canvas.addEventListener("mousedown", drawStart);
|
mouseMove({
|
||||||
canvas.addEventListener("mouseup", drawStop);
|
offsetX: e.touches[0].clientX - drawElement.offsetLeft,
|
||||||
canvas.addEventListener("mousemove", drawMove);
|
offsetY: e.touches[0].clientY - drawElement.offsetTop,
|
||||||
|
});
|
||||||
canvas.addEventListener("touchstart", touchStart);
|
};
|
||||||
canvas.addEventListener("touchend", touchEnd);
|
drawElement.addEventListener("mousedown", mouseDown);
|
||||||
canvas.addEventListener("touchmove", touchMove);
|
drawElement.addEventListener("mouseup", mouseUp);
|
||||||
|
drawElement.addEventListener("mousemove", mouseMove);
|
||||||
|
drawElement.addEventListener("touchstart", mouseDown);
|
||||||
|
drawElement.addEventListener("touchend", mouseUp);
|
||||||
|
drawElement.addEventListener("touchmove", touchMove);
|
||||||
const receiveCanvas = document.getElementById("receiveCanvas");
|
const receiveCanvas = document.getElementById("receiveCanvas");
|
||||||
const recCtx = receiveCanvas.getContext("2d");
|
const recCtx = receiveCanvas?.getContext("2d");
|
||||||
|
if (!recCtx || !receiveCanvas) {
|
||||||
|
throw new Error("canvas not found");
|
||||||
|
}
|
||||||
|
//@ts-expect-error
|
||||||
|
window.ctx = recCtx;
|
||||||
|
setTimeout(() => {
|
||||||
recCtx.lineWidth = 2;
|
recCtx.lineWidth = 2;
|
||||||
recCtx.lineCap = "round";
|
recCtx.lineCap = "round";
|
||||||
recCtx.strokeStyle = "white";
|
recCtx.strokeStyle = "white";
|
||||||
|
}, 10);
|
||||||
let receiveLineId = 0;
|
socket.addEventListener("message", (event) => {
|
||||||
|
if (event.data === "ping" || event.data.startsWith('pong'))
|
||||||
socket.addEventListener("message", function (event) {
|
return;
|
||||||
const parts = event.data.split(",");
|
const { from, to } = deserializeDraw(event.data);
|
||||||
if (parts[0] != receiveLineId) {
|
|
||||||
recCtx.closePath();
|
|
||||||
recCtx.beginPath();
|
recCtx.beginPath();
|
||||||
receiveLineId = parts[0];
|
recCtx.moveTo(from.x, from.y);
|
||||||
recCtx.moveTo(parts[1], parts[2]);
|
recCtx.lineTo(to.x, to.y);
|
||||||
} else {
|
recCtx.closePath();
|
||||||
recCtx.lineTo(parts[1], parts[2]);
|
|
||||||
recCtx.stroke();
|
recCtx.stroke();
|
||||||
}
|
console.log(from, to);
|
||||||
});
|
});
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (socket.readyState !== socket.OPEN) {
|
if (socket.readyState !== socket.OPEN) {
|
||||||
socket = new WebSocket(socketUrl);
|
socket = new WebSocket(socketUrl);
|
||||||
}
|
}
|
||||||
socket.send("ping");
|
socket.send("ping");
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
const entry = entries.at(0);
|
||||||
|
receiveCanvas.height = entry?.contentRect.height ?? 500;
|
||||||
|
receiveCanvas.width = entry?.contentRect.width ?? 500;
|
||||||
|
});
|
||||||
|
resizeObserver.observe(drawElement);
|
||||||
})();
|
})();
|
||||||
|
|||||||
138
js/draw.ts
Normal file
138
js/draw.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
(() => {
|
||||||
|
const drawElement = document.getElementById(
|
||||||
|
"drawDiv"
|
||||||
|
) as HTMLDivElement | null;
|
||||||
|
if (!drawElement) {
|
||||||
|
throw new Error("canvas not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
type DrawPoint = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
type DrawEvent = {
|
||||||
|
from: DrawPoint;
|
||||||
|
to: DrawPoint;
|
||||||
|
};
|
||||||
|
type Events = {
|
||||||
|
draw: DrawEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawEmitter = mitt<Events>();
|
||||||
|
|
||||||
|
let drawing = false;
|
||||||
|
const socketUrl = "/draw/ws";
|
||||||
|
|
||||||
|
let socket = new WebSocket(socketUrl);
|
||||||
|
|
||||||
|
socket.onerror = console.error;
|
||||||
|
socket.onclose = console.warn;
|
||||||
|
|
||||||
|
const serializeDraw = ({from, to }: DrawEvent) =>
|
||||||
|
`${from.x},${from.y},${to.x},${to.y}`;
|
||||||
|
|
||||||
|
const deserializeDraw = (message: string): DrawEvent => {
|
||||||
|
const parts = message.split(",");
|
||||||
|
return {
|
||||||
|
from: {
|
||||||
|
x: Number(parts[0]),
|
||||||
|
y: Number(parts[1]),
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
x: Number(parts[2]),
|
||||||
|
y: Number(parts[3]),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
drawEmitter.on("draw", (e) => sendMessage(e));
|
||||||
|
const sendMessage = (e: DrawEvent) => {
|
||||||
|
if (socket.readyState !== socket.OPEN) {
|
||||||
|
socket = new WebSocket(socketUrl);
|
||||||
|
}
|
||||||
|
socket.send(serializeDraw(e));
|
||||||
|
};
|
||||||
|
|
||||||
|
const mouseDown = () => {
|
||||||
|
drawing = true;
|
||||||
|
previous=null
|
||||||
|
};
|
||||||
|
|
||||||
|
const mouseUp = () => {
|
||||||
|
drawing = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let previous: DrawPoint | null = null;
|
||||||
|
const mouseMove = (e: Pick<MouseEvent, "offsetX" | "offsetY">) => {
|
||||||
|
if (!drawing) return;
|
||||||
|
if (previous) {
|
||||||
|
drawEmitter.emit("draw", {
|
||||||
|
from: previous,
|
||||||
|
to: { x: e.offsetX, y: e.offsetY },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
previous = { x: e.offsetX, y: e.offsetY };
|
||||||
|
};
|
||||||
|
|
||||||
|
const touchMove = (e: TouchEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
mouseMove({
|
||||||
|
offsetX: e.touches[0].clientX - drawElement.offsetLeft,
|
||||||
|
offsetY: e.touches[0].clientY - drawElement.offsetTop,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
drawElement.addEventListener("mousedown", mouseDown);
|
||||||
|
drawElement.addEventListener("mouseup", mouseUp);
|
||||||
|
drawElement.addEventListener("mousemove", mouseMove);
|
||||||
|
|
||||||
|
drawElement.addEventListener("touchstart", mouseDown);
|
||||||
|
drawElement.addEventListener("touchend", mouseUp);
|
||||||
|
drawElement.addEventListener("touchmove", touchMove);
|
||||||
|
|
||||||
|
const receiveCanvas = document.getElementById(
|
||||||
|
"receiveCanvas"
|
||||||
|
) as HTMLCanvasElement | null;
|
||||||
|
const recCtx = receiveCanvas?.getContext("2d");
|
||||||
|
|
||||||
|
if (!recCtx || !receiveCanvas) {
|
||||||
|
throw new Error("canvas not found");
|
||||||
|
}
|
||||||
|
//@ts-expect-error
|
||||||
|
window.ctx = recCtx;
|
||||||
|
|
||||||
|
setTimeout(()=>{
|
||||||
|
recCtx.lineWidth = 2;
|
||||||
|
recCtx.lineCap = "round";
|
||||||
|
recCtx.strokeStyle = "white";
|
||||||
|
},10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
socket.addEventListener("message", (event) => {
|
||||||
|
if(event.data==="ping" || event.data.startsWith('pong')) return
|
||||||
|
const { from, to } = deserializeDraw(event.data);
|
||||||
|
|
||||||
|
recCtx.beginPath();
|
||||||
|
recCtx.moveTo(from.x, from.y);
|
||||||
|
recCtx.lineTo(to.x, to.y);
|
||||||
|
recCtx.closePath();
|
||||||
|
recCtx.stroke();
|
||||||
|
console.log(from, to);
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if (socket.readyState !== socket.OPEN) {
|
||||||
|
socket = new WebSocket(socketUrl);
|
||||||
|
}
|
||||||
|
socket.send("ping");
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
const entry = entries.at(0);
|
||||||
|
receiveCanvas.height = entry?.contentRect.height ?? 500;
|
||||||
|
receiveCanvas.width = entry?.contentRect.width ?? 500;
|
||||||
|
});
|
||||||
|
resizeObserver.observe(drawElement);
|
||||||
|
})();
|
||||||
4
js/global.d.ts
vendored
Normal file
4
js/global.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare const mitt: {
|
||||||
|
//@ts-expect-error
|
||||||
|
<T = any>(): import("mitt").Emitter<T>;
|
||||||
|
};
|
||||||
2
js/mitt/mitt.js
Normal file
2
js/mitt/mitt.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
function mitt (n){return{all:n=n||new Map,on:function(e,t){var i=n.get(e);i?i.push(t):n.set(e,[t])},off:function(e,t){var i=n.get(e);i&&(t?i.splice(i.indexOf(t)>>>0,1):n.set(e,[]))},emit:function(e,t){var i=n.get(e);i&&i.slice().map(function(n){n(t)}),(i=n.get("*"))&&i.slice().map(function(n){n(e,t)})}}};
|
||||||
|
//# sourceMappingURL=mitt.js.map
|
||||||
27
package.json
Normal file
27
package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "knet",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"homepage": "https://github.com/JurajKubrican/knet#readme",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/JurajKubrican/knet/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/JurajKubrican/knet.git"
|
||||||
|
},
|
||||||
|
"license": "ISC",
|
||||||
|
"author": "",
|
||||||
|
"type": "commonjs",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"dev": "tsc --project tsconfig.json --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.7.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"mitt": "^3.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
33
pnpm-lock.yaml
generated
Normal file
33
pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
mitt:
|
||||||
|
specifier: ^3.0.1
|
||||||
|
version: 3.0.1
|
||||||
|
devDependencies:
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.7.3
|
||||||
|
version: 5.7.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
mitt@3.0.1:
|
||||||
|
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||||
|
|
||||||
|
typescript@5.7.3:
|
||||||
|
resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
mitt@3.0.1: {}
|
||||||
|
|
||||||
|
typescript@5.7.3: {}
|
||||||
10
tsconfig.json
Normal file
10
tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"types": ["./js/global.d.ts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,30 @@
|
|||||||
{{block "draw" .}}
|
{{block "draw" .}}
|
||||||
<div style="width:500px; height:500px">
|
|
||||||
<canvas
|
<canvas
|
||||||
id="receiveCanvas"
|
id="receiveCanvas"
|
||||||
width="500"
|
width="500"
|
||||||
height="500"
|
height="500"
|
||||||
style="position:absolute; border: 1px solid black ;background-color: transparent"
|
style="
|
||||||
></canvas>
|
position: absolute;
|
||||||
<canvas
|
background-color: transparent;
|
||||||
id="drawCanvas"
|
top: 0;
|
||||||
width="500"
|
left: 0;
|
||||||
height="500"
|
right: 0;
|
||||||
style="position:absolute; border: 1px solid black; background-color: transparent;"
|
bottom: 0;
|
||||||
></canvas>
|
"
|
||||||
|
></canvas>
|
||||||
<div>
|
<div
|
||||||
|
id="drawDiv"
|
||||||
<script src="/js/draw.js">
|
style="
|
||||||
|
position: absolute;
|
||||||
</script>
|
background-color: transparent;
|
||||||
{{end}}
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
"
|
||||||
|
></div>
|
||||||
|
<script src="/js/mitt/mitt.js"></script>
|
||||||
|
<script src="/js/draw.js"></script>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -8,36 +8,11 @@
|
|||||||
<link rel="stylesheet" href="/css/main.css" />
|
<link rel="stylesheet" href="/css/main.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<h1><a href="/">K</a></h1>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<section>
|
||||||
{{template "draw" }}
|
{{template "draw" }}
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/JurajKubrican">Github</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://www.linkedin.com/in/juraj-kubri%C4%8Dan-614b3274/"
|
|
||||||
>LinkedIn</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</footer>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
Reference in New Issue
Block a user