我正在使用精彩的reveal.js库来创建HTML幻灯片。我唯一的问题是我需要它来跨多个设备进行同步。
目前我正在从服务器发出一个AJAX请求,并保留页面的内部时钟。
function syncTime() {
// Set up our time object, synced by the HTTP DATE header
// Fetch the page over JS to get just the headers
console.log("syncing time")
var r = new XMLHttpRequest();
r.open('HEAD', document.location, false);
r.send(null);
var timestring = r.getResponseHeader("DATE");
systemtime = new Date(timestring); // Set the time to the date sent from the server
}
虽然这让我在1秒左右的准确度,但我需要做得更好。幻灯片显示自动前进时,差异非常明显。
代码将在同一平台上运行,因此无需担心跨浏览器兼容性。
Here's what I've managed to put together
有什么想法吗?
答案 0 :(得分:22)
衡量发送请求和取回响应之间经过的时间。然后,将该值除以2.这为您提供了单向延迟的粗略值。如果将其添加到服务器的时间值,您将更接近真实的服务器时间。
这样的事情应该有效:
function syncTime() {
// Set up our time object, synced by the HTTP DATE header
// Fetch the page over JS to get just the headers
console.log("syncing time")
var r = new XMLHttpRequest();
var start = (new Date).getTime();
r.open('HEAD', document.location, false);
r.onreadystatechange = function()
{
if (r.readyState != 4)
{
return;
}
var latency = (new Date).getTime() - start;
var timestring = r.getResponseHeader("DATE");
// Set the time to the **slightly old** date sent from the
// server, then adjust it to a good estimate of what the
// server time is **right now**.
systemtime = new Date(timestring);
systemtime.setMilliseconds(systemtime.getMilliseconds() + (latency / 2))
};
r.send(null);
}
有趣的是:John Resig对Javascript计时的准确性有good article。
在这种情况下它不应该引起问题,因为你只关心你的休息时间约1秒钟。 15毫秒的差异不应该产生太大影响。
答案 1 :(得分:19)
另一种方法怎么样:谁在乎时间? (你不能用JavaScript可靠地同步系统时钟。)
相反,当客户推进幻灯片放映时,使用Node服务器与socket.io同步。而不是客户决定何时前进,服务器告诉他们。
这种方法带来了额外的好处,即能够在幻灯片播放时手动摆弄幻灯片。在下面的示例中,我添加了一个 Next 按钮,该按钮会使所有连接的客户端立即前进到下一张幻灯片。
var express = require('express')
, app = express.createServer()
, io = require('socket.io').listen(app)
, doT = require('dot')
, slide = 0
, slides = [
'http://placekitten.com/700/400?image=13',
'http://placekitten.com/700/400?image=14',
'http://placekitten.com/700/400?image=15',
'http://placekitten.com/700/400?image=16',
'http://placekitten.com/700/400?image=1',
'http://placekitten.com/700/400?image=2',
'http://placekitten.com/700/400?image=3',
'http://placekitten.com/700/400?image=4',
'http://placekitten.com/700/400?image=5',
'http://placekitten.com/700/400?image=6',
'http://placekitten.com/700/400?image=7',
'http://placekitten.com/700/400?image=8',
'http://placekitten.com/700/400?image=9',
'http://placekitten.com/700/400?image=10',
'http://placekitten.com/700/400?image=11',
'http://placekitten.com/700/400?image=12',
];
app.listen(70); // listen on port 70
app.register('.html', doT); // use doT to render templates
app.set('view options', {layout:false}); // keep it simple
doT.templateSettings.strip=false; // don't strip line endings from template file
app.get('/', function(req, res) {
res.render('index.html', { slide: slide, slides: slides });
});
app.post('/next', function(req, res) {
next();
res.send(204); // No Content
});
setInterval(next, 4000); // advance slides every 4 seconds
function next() {
if (++slide >= slides.length) slide = 0;
io.sockets.emit('slide', slide);
}
此文件将作为doT模板处理。
<!DOCTYPE html>
<html>
<head>
<title>Synchronized Slideshow</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
var curslide = {{=it.slide}}; // the slide the server is currently on.
$(function() {
$('#slide' + curslide).css('left',0);
$('#next').click(function() {
$.post('/next');
});
});
var socket = io.connect('http://localhost:70');
socket.on('slide', function(slide) {
$('#slide' + curslide).animate({left:-700}, 400);
$('#slide' + slide).css('left',700).show().animate({left:0}, 400);
curslide = slide;
});
</script>
<style>
#slideshow, .slide { width:700px; height:400px; overflow:hidden; position:relative; }
.slide { position:absolute; top:0px; left:700px; }
</style>
</head>
<body>
<div id="slideshow">
{{~it.slides :url:i}}
<div id="slide{{=i}}" class="slide"><img src="{{=url}}"></div>
{{~}}
</div>
<button id="next">Next ></button>
</body>
</html>
将这两个文件复制到一个文件夹中,然后运行
$ npm install express socket.io dot
$ node app.js
并在几个不同的窗口中导航到http://localhost:70
,然后看看魔术。
答案 2 :(得分:11)
我很高兴你找到了一个满意的答案。我有类似的需要将浏览器与服务器的时钟同步,并决心以超过1秒的精度实现它,就像你一样。我已编写代码来执行此操作,并在此处发布此答案,以防其他人也需要解决方案。
代码名为ServerDate,可免费下载。这是自述文件的一部分。请注意,我在我的示例中实现了108 ms的精度:
您可以像使用ServerDate
函数或其中一个函数一样使用Date
实例,例如:
> ServerDate()
"Mon Aug 13 2012 20:26:34 GMT-0300 (ART)"
> ServerDate.now()
1344900478753
> ServerDate.getMilliseconds()
22
还有一种新方法可以获得ServerDate估计的精确度 服务器的时钟(以毫秒为单位):
> ServerDate.toLocaleString() + " ± " + ServerDate.getPrecision() + " ms"
"Tue Aug 14 01:01:49 2012 ± 108 ms"
您可以看到服务器时钟和浏览器时钟之间的差异,以毫秒为单位:
> ServerDate - new Date()
39
答案 3 :(得分:0)
您无法真正与服务器同步。衡量服务器请求所需的时间(正如MikeWyatt建议的那样)并不是延迟的良好指标。
只有您的服务器知道他何时回复请求。因此,它应该将该信息与答案一起发回。使用Date.now() - new Date(timestringOfServerResponse)
,您可以准确地测量延迟。但我不确定你为什么需要那个价值。
要在多个设备之间同步应用程序,服务器应该向他们发送要执行的操作。 “何时”不应该“一旦得到我的回复”,而是一个确切的时间戳。至于你的设备的系统时钟是准确和同步的(它们通常是),应用程序将同步运行它的方法,因为它知道什么时候发生(或至少:那时应该发生什么,它可以插入什么发生“现在”)。
答案 4 :(得分:0)
我在这里广泛使用COMET模式用于我的实时Web应用程序。
要在您的情况下使用它,您需要客户端向服务器打开AJAX请求并等待答案。一旦它到来,客户必须更换幻灯片。
在服务器上,您必须保留所有答案,直到更改幻灯片为止。 (你可能会更高级,并在客户端上延迟相同的时间,但这很可能没有必要)。我不能在这里向您展示示例代码,因为我不知道您可以使用哪些代码。
因此,您正在有效地创建一个管弦乐队,其中服务器扮演指挥,所有客户都在倾听他。
然后,时间由服务器在(几乎)同时响应请求以及网络延迟的能力决定。
通常客户端应该位于网络的同一部分,因此延迟可能非常相似 - 绝对值在这里不会受到影响,只有变化。
还有一个额外的技巧可以帮助:不要用硬替换来改变幻灯片,混合它们。这将模糊变化,使眼睛无法捕捉到您将永远存在的微小时间差异。
(如果您不能让服务器播放指挥,您可能不得不使用MikeWyatt的解决方案 - 可能需要一些请求并平均结果,具体取决于网络设置。在LAN中,一个请求可能是足够的,超过平均水平超过平均值不会伤害...)