使用jQuery创建交互式街道视图

  • 知识需要:中级JavaScript,基本HTML和CSS
  • 需要:jQuery,现代浏览器(适用于演示1到3)
  • 项目时间:大约四周

今年早些时候,我们被要求为瑞士电视纪录片创建一个网站,关于苏黎世着名的“Langstrasse”及其居民。我们想出了让用户能够穿过街道并看到与纪录片相关的有趣热点的想法。访问360°Langstrasse网站所以看看它是如何工作的(小心,它用德语!)。

我们的计划是采用共同的用户体验并以令人耳目一新的方式使用它。无论是通过拖动滚动条,使用鼠标滚轮还是用手指滑动,每个人都知道如何滚动文档。您希望页面内容能够上下移动,但通过这种方式移动街道会让您感到惊讶。

在本教程中,我们将向您展示该项目背后的一些想法和技巧。

基本方法

在最简单的形式中,您只需要一系列照片(通常称为视频)来显示街道上的移动,以及列出热点的页面。在HTML中可能如下所示:



滚动教程 - #1







#1


#2


#3


#4


#5

热点绝对位于具有固定高度的容器内(我们稍后将需要它)。现在开始运行我们的第一个演示非常简单:

  • 添加我们心爱的jQuery库
  • 收听调整大小事件以测量和存储窗口的尺寸和最大滚动高度,并调整视频大小以使其始终填充窗口
  • 收听滚动事件以移至视频中的相应时间

以下是如何实现这一目标:

我们将文档和窗口存储在变量中,因此我们不必每次都创建jQuery对象:

Var $ Doc = $(document);
Var $ Win = $(窗口);
Var $ VideoContainer = $('。street-view');
var video = $('。street-view> video')[0];

定义变量以缓存窗口宽度和高度,文档高度和滚动高度(请参阅后面的内容)CalculateDimensions())。

Var WindowHeight,windowWidth;
Var FullHeight,scrollHeight;

我们最好将图像比例存储在常量中(否则我们必须等到图像或视频加载后再实际调整容器大小)。

Var StreetImgWidth = 1024,streetImgHeight = 640;

我们希望在变量中保持当前滚动位置(介于0和1之间)。

Var CurrentPosition = 0;

每次调整窗口大小时,我们都需要重新计算尺寸(我们将在变量中缓存),调整背景图像/视频的大小并调用滚动处理程序(因为在调整大小时,滚动可能会更改而不会调度事件)。

Function CalculateDimensions(){
WindowWidth = $ Win.width();
WindowHeight = $ Win.height();
FullHeight = $('#main')。height();
scrollHeight = fullHeight - windowHeight;
}
Function HandleResize(){
CalculateDimensions();
ResizeBackgroundImage();
HandleScroll();
}
Function ResizeBackgroundImage(){
//获取图像容器大小
var scale = Math.max(windowHeight / streetImgHeight,windowWidth / streetImgWidth);
var width = scale * streetImgWidth,height = scale * streetImgHeight;
var left =(windowWidth-width)/ 2,top =(windowHeight-height)/ 2;
$ VideoContainer
.WIDTH(宽度).height(高度)
的CSS(“位置”,“固定”)
的CSS(“左”,左+“像素”)
的CSS( '顶部',顶部+ '像素');
}

现在剩下的就是确保每次滚动文档时,我们都会移动到视频中的相应位置(并希望此位置已经加载):

Function HandleScroll(){
currentPosition = $ win.scrollTop()/ scrollHeight;
Render(currentPosition);
}
Function Render(position){
If(video.duration){
video.currentTime = position * video.duration;
}
}

现在一切都准备就绪,让我们听听事件并打电话给“HandleResize()“确保一切都按照正确的尺寸布置。

$ Win.resize(handleResize);
$ Win.scroll(handleScroll);

HandleResize();

看到演示1

进行滚动控制

我们不是通过静态内联样式绝对定位热点,而是将必要的信息放入数据属性中。一旦用户滚动,我们只需遍历热点元素并将它们放在我们的渲染函数中。

HTML需要对我们的热点定义进行一些更改:


#1


#2


#3


#4


#5

必要的定位代码将添加到渲染例程中:

Function Render(position){
var minY = -windowHeight,maxY = windowHeight;
$。每个($ HotspotElements,函数(指数,元素){
Var $ Hotspot = $(element);
var elemPosition = Number($ hotspot.attr('data-position'));
var elemSpeed = Number($ hotspot.attr('data-speed'));
var elemY = windowHeight / 2 + elemSpeed *(elemPosition-position)* scrollHeight;
if(elemY <minY || elemY> maxY){
$ hotspot.css({'visiblity':'none',top:' - 1000px','webkitTransform':'none'});
} Else {
$ hotspot.css({'visiblity':'visible',top:elemY,position:'fixed'});
}
});
RenderVideo(position);
}

看到演示2

您可能想知道这有什么好处,因为它似乎在不实际改变游戏的情况下增加了不必要的复杂性。那么,有充分的理由接管控制权:

  • 我们可以自由地将这个应用程序改编为不支持位置的浏览器:固定(除了IE6之外只有一个浏览器,我们喜欢它运行的设备:iOS上的Safari)
  • 我们可以添加一些额外的运动行为。在演示中,我们希望热点以不同的速度移动,因此它们能够适应视频的速度(它是模拟的,因此不是线性的)。在我们的360°Langstrasse项目中,您可能会看到Twitter气球在波浪中移动,热点在离开屏幕时水平移动到边界。最重要的是,他们必须能够适应日夜电影,每个电影都有不同的位置。

让事情顺利

当我们谈论滚动时,用户体验在浏览器和操作系统与操作系统之间变化很大。在某些系统上,我们会有一个平滑的滚动动作,而在其他系统上 - 特别是当使用鼠标的滚轮时 - 它更像是一个梯子的波涛汹涌的攀爬,而不是连续滑动。为了保证所有用户的平滑性,我们将不得不简化一般动作。并猜测:我们准备示例的方式,只需几行:

//将目标位置设置为相对(0..1)滚动位置
Function HandleScroll(){
targetPosition = $ win.scrollTop()/ scrollHeight;
}

//在每个循环上:近似到目标位置的电流
Function Animloop(){
if(Math.floor(currentPosition * 5000)!= Math.floor(targetPosition * 5000)){
currentPosition + =(targetPosition - currentPosition)/ 5;
渲染(currentPosition);
}
}

//启动渲染循环
SetInterval(animloop,1000/60);

的setInterval现在打电话Animloop()大约60fps。这很好,但我们喜欢现代优化,所以我们将使用RequestAnimationFrame大多数现代浏览器提供的功能。这有两个好处:

  • 我们可能会得到一个更流畅的动画,真正与显示频率同步(这取决于浏览器 - 现在我们只看到Chrome中的那种行为,如果我们幸运的话。
  • 仅当我们的站点实际位于可见选项卡中时才会调用该循环。现在这很酷,特别是对于无限的渲染循环 - 我们不会浪费用户的CPU。在我们的情况下,当然,它并不是那么大的交易,但是嘿,不管怎样,习惯它是好的

//主渲染循环
Window.requestAnimFrame =(function(){
Return Window.requestAnimationFrame ||
Window.webkitRequestAnimationFrame ||
Window.mozRequestAnimationFrame ||
Window.oRequestAnimationFrame ||
Window.msRequestAnimationFrame ||
function(/ * function * / callback,/ * DOMElement * / element){
Window.setTimeout(callback,1000/60);
};
})();

//在每个循环上:近似到目标位置的电流
Function Animloop(){
if(Math.floor(currentPosition * 5000)!= Math.floor(targetPosition * 5000)){
currentPosition + =(targetPosition - currentPosition)/ 5;
渲染(currentPosition);
}
RequestAnimFrame(animloop);
}
Animloop();

如您所见,我们使用包装函数来确保浏览器兼容性。

要查看我们的努力结果,请单击此处演示3(已经相当不错了,不是吗?)。

接口循环

现在一切都是如此紧凑,美观和HTML5-ish - 并猜测是什么,我们会膨胀一点,做一些真正的老派。我们将摆脱我们的甜蜜视频并将其替换为图像。是的,我们将为每个视频帧交换一个简单的图像。你可能会认为我们已经疯了,但放松了:我们这样做是为了更好的用户体验。

虽然使用视频非常简单(至少使用H.264,这是硬件加速,也是高性能的),但它对我们的应用程序有一些不利之处:

  • 我们需要一个预加载的视频来立即渲染任何帧(毕竟你可以快速滚动)。现在视频很好一旦它实际加载。在那之前我们可以访问的是加载的帧 - 视频线性加载,这意味着要跳到我们街道的尽头,我们必须等到整个视频加载。对于每个帧都关键帧(性能所需)的视频,这可能需要一段时间。让用户等待那么久并不酷。 (你知道那些拥有真正很酷的预加载器的Flash网站,可以让你不得不像往常一样等待获得两点信息。我们认为如果可能的话,根本就没有预装载器会更酷。)
  • 兼容性。对。不仅适用于IE8-,也适用于不那么新的现代浏览器,如Firefox,当然也适用于我们的iOS游乐场。什么?! iOS完全支持HTML5视频标签!嗯...到一定限度,是的。但是这个限制是禁止的:你将无法通过脚本启动/访问视频。在我们可以做任何事情之前,用户实际上必须单击这个精心设置的播放按钮。没有Video.play(),真的。最重要的是,在iPhone中,视频只能全屏播放:与您的用户界面无任何组合。 (并不是说我们真的想在iPhone上运行,但你知道..)

为了克服这些限制,我们将使用简单的单个图像而不是视频。首先是关于图像交换:我们对以下方法进行了一些性能测试:

  • 将所有图像保存到DOM中,并通过display:none / block进行交换
  • 将所有图像保存到DOM中,并通过可见性进行交换:隐藏/可见
  • 将所有图像保存在逻辑集合中并替换DOM中仅一个图像的src

Function RenderVideo(position){
var index = Math.round(currentPosition *(imageSeqLoader.length-1));
var img = imageSeqLoader.getNearest(index);
var nearestIndex = imageSeqLoader.nearestIndex;
if(nearestIndex <0)nearestIndex = 0;
Var $ Img = $(img);
Var Src;
If(!! Img){
Src = Img.src;
If(src!= CurrentSrc){
Video.src = Src;
CurrentSrc = Src;
}
}
}

事实证明,所有方法的表现都完全相同。惊人。所以我们选择了第三个,因为它似乎对内存占用的影响最小。我们也希望尽可能保持DOM的清洁(如果不是DOM选择)。通过这种方式,我们实现了Firefox 3和IE7的浏览器兼容性以及我们自己的加载算法的可能性。

我们现在就谈谈。而不是加载第一个图像,然后是第二个图像,等等,我们想要更聪明的东西。首先,我们加载一个非常粗略的步骤(像每第16帧一样),每一轮我们收紧间隙,直到没有间隙为止。这样做的好处是,当图像仍在加载时,我们可以开始穿过街道。

在我们的演示中,我们只有100个图像,所以让我们首先加载第一个图像,然后是第1个,第16个,第32个,第48个,第64个,第80个,第96个,现在是差距:第8个,第24个,第40个......在下一个圆形指数4,12,20,28,......每一轮我们都会有更好的分辨率。现在让我们假设在加载第一轮之后我们滚动到25%。这将是第25帧。现在它没有加载,对吧?让我们选择最近的加载帧,然后,它将是32,并显示一个。当然,它不是25,但是,嘿,你不愿意看到街道只能精确到几米而不是再等一分钟直到你看到街道吗?我们会。

渐进式隔行扫描加载由我们称之为“ProgressiveImageSequence”。看看我们的演示下载,看看它的整个荣耀课程。不要向您展示一些代码,而是让我们看看加载实际上是如何工作的。

看到演示4

关于浏览器的一些注意事项:对于IE7,我们只会像第四张图像一样加载。无论如何,交换图像太慢了。最重要的是,使用上个世纪的浏览器的人通常拥有上个世纪的计算机,其网络连接速度与没有腿的青蛙一样慢 - 所以切割一些东西真的没问题。 Firefox似乎有一个网络堆栈,使处理器非常繁忙。因此,我们在加载周期之间稍微休息,以便在后台仍然加载时不会太慢地减慢用户体验。

塞弗林克劳斯

曾担任瑞士代理商Hinderling Volkart的首席互动开发人员。他对ActionScript有着丰富的经验,但现在WebKit是他选择的游乐场。他喜欢他的iPad,阿尔卑斯山和挑战简约。和奶牛。



翻译字数超限