几年前,Ajax的兴起给互联网带来了新的生机,同时也使用户体验有了质的飞跃,用户无需刷新页面即可获取新的数据,而页面也以一种更具有交互性的形式为用户展现视图,可以说这种变化对互联网发展的贡献是史无前例的。

但随着Ajax大规模应用,越来越多的开发人员开始注意到其中存在的问题,因为Ajax的视图展现是在页面无刷新情况下进行的,这也就意味着在用户做了一系列操作之后,页面的URL是没有任何变化的,这些操作所产生的结果自然也就无法保留,当用户再次访问时,想要展现的数据也就无法重现。

举个例如来说,我们在一个摄影网站浏览一个相册,点击第一张图片会动态弹出一个大图的相框,可以更好地浏览图片,当看到一个自己喜欢的图片时,急于分享这张图片,于是把当前的URL发布出去了,但是我们的好友点击进去后发现,看到的只是原来的那个相册,并没有弹出的大图相框,更别说我们要分享的那张图片了,是不是很失望。也正是因为如此,这个页面无法被搜索引擎精确地抓取,SEO无法做到优化,用户的可访问性也大打折扣。

为了解决这个问题,开发者开始尝试使用URL中的hash来提高可访问性(hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分)),部分浏览器开始提供hash相关的事件支持,有些知名站点也和搜索引擎约定使用某种规则来对站点页面进行抓取,但这毕竟不是一个标准的技术,还是不可避免的存在很多问题,开发者也期待出现一个标准化的完美解决方案,彻底解决这个难题。

幸运的是,HTML5中的History API给开发者带了新的希望,它很好的支持了基于URL的页面无刷新操作,也使得SEO优化得到完美的解决,毫无疑问,History API将会成为Web领域未来的标准,越来越多的开发者也将会使用它来开发自己的应用程序。

那么今天,我们就来讲解一下与History相关的技术。

为了能够更好的阐述这项新技术,我们选择使用一个示例程序来开始。如下图所示,我们展示三张小图,点击任意一张,将会弹出大的预览图,然后可以前后切换预览图片:

这个程序对于大家来说应该说是比较简单的,当点击小图时,将放置大图的页面元素显示出来,图片和简介显示出与小图对应的信息即可,切换预览图片的操作也是非常容易做到的。

这就是传统的Ajax操作,而这种操作存在一个极其严重的缺陷,我们无法记录当前浏览的图片信息,假如后面的小图有几百张,当我们浏览到某一张时,觉得非常不错,想把它发送给朋友,也有可能想自己存下来,这时候我们会发现,URL中没有任何这张图片的信息,复制地址栏也就没有任何效果了。

所以我们极为迫切的需要在URL中加入用户操作所产生的信息,这个时候History API就派上用场了,他提供的方法可以在页面不刷新的情况下对URL进行更新操作。下面就介绍一下主要的两个方法:

history.pushState(stateObj, title, url);

这个方法会往当前会话的历史栈中放入一条记录,stateObj是用户自定义的对象,用于记录一些有用的数据,title顾名思义就是标题信息,第三个参数是要放入的url信息。例如我们点击第二张图片时可以执行history.pushState({id: 2}, 'img: 2', '?preview=2');

history.replaceState(stateObj, title, url);

这个方法于pushState类似,唯一不同的是,执行这个操作后,浏览器会对会话历史栈内的记录进行替换而不是添加,这在特定场景下是比较适用的。

我们这里就来结合实例来讲解一下如何操作历史记录。

正如上图看到的那样,页面初始化时展现三张小图,点击任意一张小图时,会弹出预览图,页面的基本框架如下:

然后页面初始化时,我们需要为一些DOM元素绑定事件,就像下面这样:

可以看到几个绑定的事件,点击小图会触发预览模式;然后在预览时点击关闭按钮,会隐藏预览框,同时调用了history的back函数,我们稍后会介绍;最后是两个切换按钮的事件,分别会切换到上一张或下一张大图。我们注意到,当小图被点击时,调用了一个叫preview的函数,这个函数正是我们重点要讲的,代码如下:

上面的preview函数主要负责几个重要的工作,首先显示预览框,然后根据数据ID获取数据信息,最后操作历史记录对象。在实际开发中,getPreviewDataById函数大多是以Ajax方式进行的,这个时候只需将后面的逻辑放到回调函数内部即可。在preview函数的最后,我们往历史记录里面放入或替换一条包含ID信息的对象,同时改变URL,放入还是替换,取决于是不是前后切换预览图,我们稍后会详细解释一下。注意,这个时候虽然URL改变了,但页面并不会刷新。下面我们就来对比一下点击前后的效果,以点击第二张小图为例:

可以明显看到,地址栏已经变化了,这其实就是我们调用pushState函数的结果:

window.history.pushState({id: 2}, 'img: 2', '?preview=2');  

需要额外注意的一点是,第三个参数是新的URL地址,根据官方文档介绍,它可以是相对地址,也可以是绝对地址,所以我们也可以选择使用绝对地址,就像下面这样,效果是一样的:

window.history.pushState({id: 2}, 'img: 2','http://localhost/history/?preview=2');  

在上面的preview函数参数列表中,存在一个isSwitch参数,如果函数被调用时指定这个参数,则会调用replaceState,而不是pushState,这是为什么呢,下面就来解释一下。

还记得我们我们为预览框右上角关闭按钮绑定的事件吗,除了隐藏预览图,还调用了history的back函数,其实我们是要利用这个函数从历史栈中弹出之前放入的历史记录,进而将URL恢复到预览前的状态。那么,如果我们在预览的时候频繁的切换到下一张或者上一张,历史记录栈中就会存在很多记录,要回到初始状态显然不易,我们也没有必要保留这么多的历史记录,所以当用户前后切换预览图时,只需要调用replaceState替换当前历史栈中那条记录即可,假如我们点击右边的切换按钮,应该是这样的:

为了调用replaceState函数,我们需要在执行preview函数时,传入一个isSwitch参数,我们来看看是如何调用的:

下面就以两个示意图来讲解一下切换前后的栈结构:

这样我们就能保证历史栈中只有一条记录,当关闭预览框结束预览时,可立即恢复到之前的视图。

而向前切换也是一样的,代码如下:

到这里,我想大家都已经清楚如何操作历史栈来改变URL,那么我们不仅要问,如果使用当前的URL重新访问站点,如何还原关闭之前的视图呢,其实这个就属于基本的操作了,只需要在onload函数时得到相应的参数,然后调用preview函数即可,如代码所示:

//从URL中获取到当前预览图片的ID
function getCurrPreviewId() {
var search = location.search;
if (!search) return -1; var params = {}; var keyValues = search.substring(1).split('&');
keyValues.forEach(function(item) {
var parts = item.split('=');
params[parts[0]] = parts[1];
});
if (!params['preview']) return -1; return params['preview'];
} //当页面加载时,如果URL中有预览信息,取出然后
window.onload = function() {
var id = getCurrPreviewId();
if (id < 1 || id > 3) return; preview(id);
};

到这里其实我们已经将整个过程讲解完成了,但还有一个重要的事件我们还没有涉及到,那就是window下面的onpopstate事件,当用户点击后退按钮或前进按钮时,或者当我们的程序执行history.back(),history.forward(),history.go()时,都会触发这个事件,它能够捕获这些操作完成之后,当前历史栈中的状态,我们可以利用这些信息,做进一步的操作。那么我们就来做个试验,看看onpopstate事件是如何被触发的:

在这个事件函数内,我们会打印事件对象中的state对象,其实就是我们执行pushState函数时放进去的对象,下面我们在控制台做下面几个操作看看效果:

如图所示,我们放入两条记录,当后退或前进的时候,都会执行到onpopstate事件函数,并且能够获取到当前的状态对象信息,如果我们的图片预览功能需要记录每一步的操作,那么我们就可以在onpopstate事件函数中获取到当前需要展现的图片ID,然后直接调用preview函数即可。

最后,需要注意的是,当前有些浏览器还不能很好的支持History API,所以我们最好判断一下浏览器的支持情况:

讲到这里,关于History的相关知识及应用基本上就告一段落了,希望大家能够细细体会它的应用场景,并加以练习,最后希望大家能够很好地掌握并运用到未来的项目中去。

HTML5新特性之History的更多相关文章

  1. html5新特性

    这一篇博文不会告诉你怎么去使用html5的新特性,只会给你总结一下新特性------对于好学的人可以把这篇文章当做一个目录 对于初接触的人来说是一个导向 对于已经接触过的人来说是一个检测你掌握程度的检 ...

  2. 转: HTML5新特性之Mutation Observer

    转: HTML5新特性之Mutation Observer Mutation Observer是什么 Mutation Observer(变动观察器)是监视DOM变动的接口.当DOM对象树发生任何变动 ...

  3. HTML5新特性之CSS+HTML5实例

    1.新的DOCTYPE和字符集 HTML5的一项准则就是化繁为简,Web页面的DOCTYPE被极大的简化. <!DOCTYPE html> 同时字符集声明也被简化了: <meta c ...

  4. HTML5新特性:FileReader 和 FormData

    连接在这里: HTML5新特性:FileReader 和 FormData

  5. web全栈架构师[笔记] — 03 html5新特性

    HTML5新特性 一.geolocation PC端 精度比较低 通过IP库定位 移动端 通过GPS window.navigator.geolocation 单次 getCurrentPositio ...

  6. HTML5新特性:范围样式

    原文出处:http://blog.csdn.net/hfahe/article/details/7381141        Chromium 最近实现了一个HTML5的新特性:范围样式,又叫做< ...

  7. html5新特性与用法大全了解一下

    有好多小伙伴私聊我问我html5新特性 和用法,下面我给大家具体介绍一下html5都新加了哪些新特性,下面我给大家总结一下. 1)新的语义标签 footer header 等等2)增强型表单 表单2. ...

  8. html5新特性contenteditable 属性更容易实现动态表单

    介绍html5新特性的一个属性:contenteditable 作用域全局.所有的块标签都可以,例如:span.p.div.td等标签.但是,不可以作用域<br/>类型的标签. conte ...

  9. HTML5新特性之文件和二进制数据的操作 Blob对象

    HTML5新特性之文件和二进制数据的操作 1.Blob对象 2.FileList对象 3.File对象 4.FileReader 对象 5.URL对象

随机推荐

  1. 3.11-3.15 HDFS HA

    一.背景 1. Hadoop2.0之前,在HDFS集群中NameNode存在单点故障(SPOF).对于只有一个NameNode的集群, 若NameNode机器出现故障,则整个集群将无法使用,直到Nam ...

  2. Codeforces Round #421 (Div. 2)D - Mister B and PR Shifts(模拟)

    传送门 题意 给出n个数,计算在进行n-1次右移中\(min\sum_{i=1}^nabs(p_i-i)\) 分析 我们设置cnt[p[i]-i]为一个数p[i]与它标准位置(如1的标准位置为1)的左 ...

  3. POJ2063【完全背包】

    题意: 给一个初始的钱,年数, 然后给出每个物品的购买价格 与 每年获得的利益, 求在给出的年份后手上有多少钱. 思路: 背包重量还是资金. dp[0]=初始资金: 重物的重量是他的价格,获利是价值. ...

  4. poj1159 【LCS】

    思路: 滚动数组; 贴一发挫code- #include <iostream> #include <cstdio> #include <string.h> #inc ...

  5. IDEA thymeleaf ${xxx.xxx}表达式报错,红色波浪线

    解决方法: 在<!DOCTYPE html>标签下面加上 <!--suppress ALL--> 代码如图: 不再报错,效果如下图:

  6. SVG新手入门

    特点 矢量图 属性:形状的参数(都没有单位) 添加事件跟html一样 修改样式跟html一样 属性操作: setAttribute/getAttribute 图形 <svg width=&quo ...

  7. LuoguP1268树的重量【构造/思维】By cellur925

    题目传送门 Description 给你一个矩阵$M$,$M(i,j)$表示$i$到$j$的最短距离.定义树的重量为树上各边权之和,对于任意给出的合法矩阵$M$,已知它所能表示树的重量是唯一确定的.给 ...

  8. websocket来回收发消息

    # server- # pip install geventwebsocket# pip install flask from flask import Flask, render_template, ...

  9. 基于Tcp协议的上传下载

    目录格式: 构建此目录就可随意使用! client端 import socket import sys import os import json import struct sk = socket. ...

  10. Common Divisors CodeForces - 182D || kmp最小循环节

    Common Divisors CodeForces - 182D 思路:用kmp求next数组的方法求出两个字符串的最小循环节长度(http://blog.csdn.net/acraz/articl ...