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

<!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>