You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
12 KiB
282 lines
12 KiB
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>mousetrack PoC</title>
|
|
</head>
|
|
<body>
|
|
<canvas style="pointer-events: none; position: fixed; z-index: 999999999999999; top: 0; left: 0; opacity: 1" id="mousecanvas"></canvas>
|
|
|
|
<h1>HTML Ipsum Presents</h1>
|
|
|
|
<p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p>
|
|
|
|
<h2>Header Level 2</h2>
|
|
|
|
<ol>
|
|
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
|
|
<li>Aliquam tincidunt mauris eu risus.</li>
|
|
</ol>
|
|
|
|
<blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote>
|
|
|
|
<h3>Header Level 3</h3>
|
|
|
|
<ul>
|
|
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
|
|
<li>Aliquam tincidunt mauris eu risus.</li>
|
|
</ul>
|
|
<h1>HTML Ipsum Presents</h1>
|
|
|
|
<p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p>
|
|
|
|
<h2>Header Level 2</h2>
|
|
|
|
<ol>
|
|
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
|
|
<li>Aliquam tincidunt mauris eu risus.</li>
|
|
</ol>
|
|
|
|
<blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote>
|
|
|
|
<h3>Header Level 3</h3>
|
|
|
|
<ul>
|
|
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
|
|
<li>Aliquam tincidunt mauris eu risus.</li>
|
|
</ul>
|
|
<h1>HTML Ipsum Presents</h1>
|
|
|
|
<p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p>
|
|
|
|
<h2>Header Level 2</h2>
|
|
|
|
<ol>
|
|
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
|
|
<li>Aliquam tincidunt mauris eu risus.</li>
|
|
</ol>
|
|
|
|
<blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote>
|
|
|
|
<h3>Header Level 3</h3>
|
|
|
|
<ul>
|
|
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
|
|
<li>Aliquam tincidunt mauris eu risus.</li>
|
|
</ul>
|
|
<h1>HTML Ipsum Presents</h1>
|
|
|
|
<p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p>
|
|
|
|
<h2>Header Level 2</h2>
|
|
|
|
<ol>
|
|
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
|
|
<li>Aliquam tincidunt mauris eu risus.</li>
|
|
</ol>
|
|
|
|
<blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote>
|
|
|
|
<h3>Header Level 3</h3>
|
|
|
|
<ul>
|
|
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
|
|
<li>Aliquam tincidunt mauris eu risus.</li>
|
|
</ul>
|
|
|
|
<pre><code>
|
|
#header h1 a {
|
|
display: block;
|
|
width: 300px;
|
|
height: 80px;
|
|
}
|
|
</code></pre>
|
|
|
|
<script src="https://cdn.socket.io/4.5.3/socket.io.min.js"></script>
|
|
<script>
|
|
const socket = io();
|
|
|
|
let lastX = -1;
|
|
let lastY = -1;
|
|
|
|
let mousePath = [];
|
|
|
|
let scanInterval = 200;
|
|
let lastScan = -1;
|
|
let startTime = Date.now();
|
|
|
|
let uuid = crypto.randomUUID();
|
|
|
|
function shouldScan() {
|
|
let now = Date.now();
|
|
if (now - lastScan > scanInterval) {
|
|
lastScan = now;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// scan mouse position every scanInterval milliseconds and add to path array
|
|
document.addEventListener('mousemove', onMouseMove);
|
|
document.onwheel = onScroll;
|
|
|
|
function onMouseMove(e) {
|
|
if (shouldScan()) {
|
|
let x = e.pageX;
|
|
let y = e.pageY;
|
|
|
|
console.log("scanned mouse, coords are: " + x + ", " + y);
|
|
// important to remember: these coordinates are relative to the PAGE, not the viewport
|
|
|
|
mousePath.push({
|
|
x: x,
|
|
y: y,
|
|
distance: Math.sqrt(Math.pow(x - lastX, 2) + Math.pow(y - lastY, 2)),
|
|
time: Date.now() - startTime,
|
|
path: window.location.origin + window.location.pathname,
|
|
uuid: uuid
|
|
});
|
|
lastX = x;
|
|
lastY = y;
|
|
}
|
|
}
|
|
|
|
// calculate the x and y from the delta values in the scroll event
|
|
function onScroll(e) {
|
|
if (lastX === -1 && lastY === -1) {
|
|
console.log("no mouse position to calculate from, skipping scroll event");
|
|
return;
|
|
}
|
|
|
|
let x = lastX + e.deltaX;
|
|
let y = lastY + e.deltaY;
|
|
|
|
console.log("scanned scroll, coords are: " + x + ", " + y + " (deltaX: " + e.deltaX + ", deltaY: " + e.deltaY + ")");
|
|
|
|
mousePath.push({
|
|
x: x,
|
|
y: y,
|
|
distance: Math.sqrt(Math.pow(x - lastX, 2) + Math.pow(y - lastY, 2)),
|
|
time: Date.now() - startTime,
|
|
uuid: uuid
|
|
});
|
|
lastX = x;
|
|
lastY = y;
|
|
}
|
|
|
|
// send path array to server in regular interval
|
|
setInterval(() => {
|
|
if (mousePath.length > 0) {
|
|
socket.emit('movement', mousePath);
|
|
mousePath = [];
|
|
}
|
|
}, 2000);
|
|
</script>
|
|
<script>
|
|
function linearInterpolate(x1, x2, y1, y2, travel_time, elapsed) {
|
|
if (elapsed > travel_time) {
|
|
return { x: x2, y: y2 };
|
|
}
|
|
|
|
let totalXtoCover = Math.abs(x2 - x1);
|
|
let totalYtoCover = Math.abs(y2 - y1);
|
|
|
|
let interpolatedX, interpolatedY;
|
|
|
|
if (x1 < x2) {
|
|
interpolatedX = x1 + (totalXtoCover / travel_time) * elapsed;
|
|
} else {
|
|
interpolatedX = x1 - (totalXtoCover / travel_time) * elapsed;
|
|
}
|
|
|
|
if (y1 < y2) {
|
|
interpolatedY = y1 + (totalYtoCover / travel_time) * elapsed;
|
|
} else {
|
|
interpolatedY = y1 - (totalYtoCover / travel_time) * elapsed;
|
|
}
|
|
|
|
return { x: interpolatedX, y: interpolatedY };
|
|
}
|
|
|
|
let userPaths = [];
|
|
|
|
let canvas = document.getElementById("mousecanvas");
|
|
canvas.width = document.body.clientWidth;
|
|
canvas.height = document.body.clientHeight;
|
|
|
|
socket.on("pathsync", function(paths) {
|
|
if (paths == null || paths === undefined) {
|
|
return;
|
|
}
|
|
console.log("received pathsync event, path count: " + Object.keys(paths).length);
|
|
|
|
userPaths = paths;
|
|
});
|
|
|
|
let cursorImage = new Image();
|
|
cursorImage.src = "/cursor.svg";
|
|
|
|
let ctx = canvas.getContext("2d");
|
|
|
|
function render() {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
let topOffset = canvas.getBoundingClientRect().top;
|
|
|
|
// iterate over paths, interpolate and build cursor positions for the current time (only one image per cursor)
|
|
let cursorPositions = [];
|
|
for (let key in userPaths) {
|
|
let path = userPaths[key];
|
|
let cursorPosition = { x: 0, y: 0, lastPoint: 0, path: path[0].path, uuid: path[0].uuid };
|
|
|
|
for (let j = 0; j < path.length; j++) {
|
|
let point = path[j];
|
|
let nextPoint = path[j + 1];
|
|
|
|
if (nextPoint && nextPoint.time < Date.now() - startTime) {
|
|
continue;
|
|
}
|
|
|
|
if (nextPoint) {
|
|
// get the elapsed time spent between the two points
|
|
let elapsed = Date.now() - startTime - point.time;
|
|
let travel_time = nextPoint.time - point.time;
|
|
|
|
let interpolated = linearInterpolate(point.x, nextPoint.x, point.y, nextPoint.y, travel_time, elapsed);
|
|
|
|
cursorPosition.x = interpolated.x;
|
|
cursorPosition.y = interpolated.y;
|
|
cursorPosition.lastPoint = nextPoint.time;
|
|
|
|
break
|
|
} else {
|
|
cursorPosition.x = point.x;
|
|
cursorPosition.y = point.y;
|
|
cursorPosition.lastPoint = point.time;
|
|
break
|
|
}
|
|
}
|
|
|
|
cursorPositions.push(cursorPosition);
|
|
}
|
|
|
|
// remove cursors with a timestamp older than 1 second
|
|
let filteredPaths = cursorPositions.filter(cursor => Date.now() - startTime - cursor.lastPoint < 1000);
|
|
|
|
// filter out any paths not matching the current path
|
|
filteredPaths = filteredPaths.filter(path => path.path === window.location.origin + window.location.pathname);
|
|
|
|
// filter out own cursor
|
|
filteredPaths = filteredPaths.filter(path => path.uuid !== uuid);
|
|
|
|
filteredPaths.forEach((cursor) => {
|
|
ctx.drawImage(cursorImage, cursor.x, cursor.y - topOffset);
|
|
});
|
|
}
|
|
|
|
setInterval(render, 1000 / 30);
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|