What would be the best way of showing all the current visitors cursors live in each browser window?
Is this achievable at all, without too much lag?
This is my attempt with meteor js:
http://meteorpad.com/pad/rCSsroc4G4gYQGkAY/Leaderboard (mind the name, can't change it?)
http://allofthecursors.meteor.com/
cursors.js:
Cursors = new Mongo.Collection("Cursors");
if (Meteor.isClient) {
Meteor.startup(function () {
if(!Session.get('cursorId')) {
// todo: not unique enough!
var cursorId = new Date();
Session.set('cursorId', cursorId);
Meteor.call('initCursor', cursorId);
}
});
Template.cursor.helpers({
cursor: function() {
return Cursors.findOne({ cursorId: Session.get('cursorId') });
},
cursors: function() {
return Cursors.find({ cursorId: { $ne: Session.get('cursorId') } });
}
});
Template.cursor.events({
"mousemove": function(event) {
var x = event.pageX,
y = event.pageY,
cursorId = Session.get('cursorId');
Meteor.call('updateCursor', cursorId, x, y);
}
});
}
if (Meteor.isServer) {
Meteor.methods({
'initCursor': function(cursorId) {
Cursors.insert({
cursorId: cursorId,
x: 0,
y: 0
});
},
'updateCursor': function(cursorId, x, y) {
Cursors.update(
{ cursorId: cursorId },
{ $set: { x: x, y: y }}
);
}
});
}
cursors.html
<head>
<title>cursors</title>
</head>
<body>
{{> cursor}}
</body>
<template name="cursor">
<div class="cursors-area">
{{#each cursors}}
<div class="cursor" style="top: {{y}}px; left: {{x}}px;"></div>
{{/each}}
<div class="my-cursor">
{{cursor.cursorId}}: {{cursor.x}}, {{cursor.y}}
</div>
</div>
</template>
cursors.css
html, body {
margin: 0;
padding: 0;
}
.cursors-area {
height: 100vh;
}
.cursor {
position: fixed;
width: 11px;
height: 17px;
background-image: url(cursor.png);
}
Thanks for the response evrybady. Here's an updated version.
Added the streams package: $ meteor add lepozepo:streams
The client side collection is only used to access the reactive capabilities.
cursors_2.js
Stream = new Meteor.Stream('public');
if( Meteor.isClient ) {
Cursors = new Mongo.Collection(null);
Template.cursor.events({
'mousemove': function(event) {
if (Meteor.status().connected) {
Stream.emit('p', {
_id: Meteor.connection._lastSessionId,
x: event.pageX,
y: event.pageY
});
}
}
});
Stream.on('p', function(p) {
// how can I change this?
if( Cursors.findOne(p._id) === undefined ) {
Cursors.insert({
_id: p._id,
x: p.x,
y: p.y
});
} else {
Cursors.update(p._id, {
$set: {
x: p.x,
y: p.y
}
}
);
}
});
function getCursors() {
return Cursors.find();
}
var throttledGetCursors = _.throttle(getCursors, 50);
Template.cursor.helpers({
cursors: throttledGetCursors
});
}
There are essentially two ways to go about this.
CLIENT
SERVER
As well as obviating the need for session variables, that uses the session id to provide a unique cursor id for each client and removes it from the collection when they disconnect. This is also preferable from a security perspective as it allows you to prevent users from updating cursors which don't share their connection id (via allow/deny) in case that would ever be a concern.
lepozepo:streams
package (docs here) allows you to communicate closer to the raw websocket and, whilst it will involve rather more plumbing, will result in less latency. There was some discussion of this at the London DevShop in Dec 2015, the videos from which should be available soon. It would be great if somebody could write a collection for pure server-client messaging (independent of collections), but I don't think that exists at present.Also, agreed with @sbking that you should be debouncing updates with Underscore (not throttling).