我们将了解HTML 5中History,禁止转载

作者: 前端  发布:2019-10-02

History API 与浏览器历史堆栈管理

2016/07/25 · HTML5 · History, HTML5, 浏览器

本文作者: 伯乐在线 - 欲休 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

移动端开发在某些场景中有着特殊需求,如为了提高用户体验和加快响应速度,常常在部分工程采用SPA架构。传统的单页应用基于url的hash值进行路由,这种实现不存在兼容性问题,但是缺点也有–针对不支持onhashchange属性的IE6-7需要设置定时器不断检查hash值改变,性能上并不是很友好。

而如今,在移动端开发中HTML5规范给我们提供了一个History接口,使用该接口可以自由操纵历史记录。本文并不详细介绍History接口,而是探究History接口如何影响浏览器历史堆栈,并且利用这个规律应用到具体的实际业务中,提出两种历史记录保存策略,使路由逻辑更清晰,让SPA更容易。

HTML 5 History API的”前生今世”

2014/10/23 · HTML5 · HTML5

原文出处: tuts+   译文出处:淡忘浅思的博客(@dwido)   

History是有趣的,不是吗?在之前的HTML版本中,我们对浏览历史记录的操作非常有限。我们可以来回使用可以使用的方法,但这就是一切我们能做的了。

但是,利用HTML 5的History API,我们可以更好的控制浏览器的历史记录了。例如:我们可以添加一条记录到历史记录的列表中,或者在没有刷新时,可以更新地址栏的URL。

HTML 5 History API的”前生今世”,api前生今世

原文:An Introduction To The HTML5 History API

译文:关于HTML 5 History API 的介绍

译者:dwqs

History是有趣的,不是吗?在之前的HTML版本中,我们对浏览历史记录的操作非常有限。我们可以来回使用可以使用的方法,但这就是一切我们能做的了。

但是,利用HTML 5的History API,我们可以更好的控制浏览器的历史记录了。例如:我们可以添加一条记录到历史记录的列表中,或者在没有刷新时,可以更新地址栏的URL。

 

为什么介绍History API ?

        在 这篇文章中,我们将了解HTML 5中History API的来源。在此之前,我们经常使用散列值来改变页面内容,特别是那些对页面特别重要的内容。因为没有刷新,所以对于单页面应用,改变其URL是不可能 的。此外,当你改变URL的散列值值,它对浏览器的历史记录没有任何影响。

        然后,现在对于HTML 5的History API来说,这些都是可以轻易实现的,但是由于单页面应用没必要使用散列值,它可能需要额外的开发脚本。它也允许我们用一种对SEO友好的方式建立新应用。此外,它能减少带宽,但是该怎么证明呢?

        在文章中,我将用History API开发一个单页应用来证明上述的问题。

        这也意味着我必须先在首页加载必要的资源。现在开始,页面仅仅加载需要的内容。换句话说,应用并不是一开始就加载了全部的内容,在请求第二个应用内容时,才会被加载。

注意,您需要执行一些服务器端编码只提供部分资源,而不是完整的页面内容。

 

浏览器支持

         在写这篇文章的时候,各主流浏览器对History API的支持是非常不错的,可以点击此处查看其支持情况,这个链接会告诉你支持的浏览器,并使用之前,总有良好的实践来检测支持的特定功能。

         为了用变成方式确定浏览器是否支持这个API,可以用下面的一行代码检验:

return !!(window.history && history.pushState);

 

         此外,我建议参考一下这篇文章:Detect Support for Various HTML5 Features.(ps:后续会翻译)

         如果你是用的现代浏览器,可以用下面的代码:

if (Modernizr.history) {
    // History API Supported
}

 

         如果你的浏览器不支持History API,可以使用history.js代替。

 

使用History

        HTML 5提供了两个新方法:

              1、history.pushState();                2、history.replaceState();

        两种方法都允许我们添加和更新历史记录,它们的工作原理相同并且可以添加数量相同的参数。除了方法之外,还有popstate事件。在后文中将介绍怎么使用和什么时候使用popstate事件。

        pushState()和replaceState()参数一样,参数说明如下:

              1、state:存储JSON字符串,可以用在popstate事件中。

              2、title:现在大多数浏览器不支持或者忽略这个参数,最好用null代替

              3、url:任意有效的URL,用于更新浏览器的地址栏,并不在乎URL是否已经存在地址列表中。更重要的是,它不会重新加载页面。

        两个方法的主要区别就是:pushState()是在history栈中添加一个新的条目,replaceState()是替换当前的记录值。如果你还对这个有迷惑,就用一些示例来证明这个区别。

        假设我们有两个栈块,一个标记为1,另一个标记为2,你有第三个栈块,标记为3。当执行pushState()时,栈块3将被添加到已经存在的栈中,因此,栈就有3个块栈了。

        同样的假设情景下,当执行replaceState()时,将在块2的堆栈和放置块3。所以history的记录条数不变,也就是说,pushState()会让history的数量加1.

比较结果如下图:

        到此,为了控制浏览器的历史记录,我们忽略了pushState()和replaceState()的事件。但是假设浏览器统计了许多的不良记录,用户可能会被重定向到这些页面,或许也不会。在这种情况下,当用户使用浏览器的前进和后退导航按钮时就会产生意外的问题。

        尽管当我们使用pushState()和replaceState()进行处理时,期待popstate事件被触发。但实际上,情况并不是这样。相反,当你浏览会话历史记录时,不管你是点击前进或者后退按钮,还是使用history.go和history.back方法,popstate都会被触发。

In WebKit browsers, a popstate event would be triggered after document’s onload event, but Firefox and IE do not have this behavior.(在WebKit浏览器中,popstate事件在document的onload事件后触发,Firefox和IE没有这种行为)。

      

Demo示例

       HTML:

<div class="container">
    <div class="row">
        <ul class="nav navbar-nav">
            <li><a href="home.html" class="historyAPI">Home</a></li>
            <li><a href="about.html" class="historyAPI">About</a></li>
            <li><a href="contact.html" class="historyAPI">Contact</a></li>
        </ul>
    </div>
    <div class="row">
        <div class="col-md-6">
            <div class="well">
                Click on Links above to see history API usage using <code>pushState</code> method.
            </div>
        </div>
        <div class="row">   
            <div class="jumbotron" id="contentHolder">
                <h1>Home!</h1>
                <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry.</p>
            </div>
        </div>
    </div>
</div>

 

       JavaScript:

<script type="text/javascript">
    jQuery('document').ready(function(){

        jQuery('.historyAPI').on('click', function(e){
            e.preventDefault();
            var href = $(this).attr('href');

            // Getting Content
            getContent(href, true);

            jQuery('.historyAPI').removeClass('active');
            $(this).addClass('active');
        });

    });

    // Adding popstate event listener to handle browser back button 
    window.addEventListener("popstate", function(e) {

        // Get State value using e.state
        getContent(location.pathname, false);
    });

    function getContent(url, addEntry) {
        $.get(url)
        .done(function( data ) {

            // Updating Content on Page
            $('#contentHolder').html(data);

            if(addEntry == true) {
                // Add History Entry using pushState
                history.pushState(null, null, url);
            }

        });
    }
</script>

 

          Demo 1:HTML 5 History API – pushState

历史条目在浏览器中被计算,并且可以很容易的使用浏览器的前进和后退按钮。View Demo  (ps:你在点击demo1的选项卡时,其记录会被添加到浏览器的历史记录,当点击后退/前进按钮时,可以回到/跳到你之前点击的选项卡对应的页面)

          Demo 2:HTML 5 History API – replaceState

历史条目在浏览器中被更新,并且不能使用浏览器的前进和后退按钮进行浏览。View Demo  (ps:你在点击demo1的选项卡时,其记录会被替换当前浏览器的历史记录,当点击后退/前进按钮时,不可以回到/跳到你之前点击的选项卡对应的页面,而是返回/跳到你进入demo2的上一个页面)

总结(ps:喜欢这两个字9159.com,~^_^~)

       HTML 5中的History API 对Web应用有着很大的影响。为了更容易的创建有效率的、对SEO友好的单页面应用,它移除了对散列值的依赖。

下一篇:你在博客收录集吗?

History是有趣的,不是吗?在之前的HTML版本中,我们对浏览历史记录的操作非常有限。我们可以来回使用可以使用的方法,但这就是一切我们能做的了。

History API回顾

HTML5 History API包括2个方法:history.pushState()和history.replaceState(),和1个事件:window.onpopstate。

为什么介绍History API ?

在这篇文章中,我们将了解HTML 5中History API的来源。在此之前,我们经常使用散列值来改变页面内容,特别是那些对页面特别重要的内容。因为没有刷新,所以对于单页面应用,改变其URL是不可能的。此外,当你改变URL的散列值值,它对浏览器的历史记录没有任何影响。

然后,现在对于HTML 5的History API来说,这些都是可以轻易实现的,但是由于单页面应用没必要使用散列值,它可能需要额外的开发脚本。它也允许我们用一种对SEO友好的方式建立新应用。此外,它能减少带宽,但是该怎么证明呢?

在文章中,我将用History API开发一个单页应用来证明上述的问题。

这也意味着我必须先在首页加载必要的资源。现在开始,页面仅仅加载需要的内容。换句话说,应用并不是一开始就加载了全部的内容,在请求第二个应用内容时,才会被加载。

注意,您需要执行一些服务器端编码只提供部分资源,而不是完整的页面内容。

javascript:historygo(-1) 网页已过期 怎解决

过期了。。。解决啥呀。。HTML5有个history的api,你可以去看看  

但是,利用HTML 5的History API,我们可以更好的控制浏览器的历史记录了。例如:我们可以添加一条记录到历史记录的列表中,或者在没有刷新时,可以更新地址栏的URL。

pushState

history.pushState(stateObject, title, url),包括三个参数。

第一个参数用于存储该url对应的状态对象,该对象可在onpopstate事件中获取,也可在history对象中获取。

第二个参数是标题,目前浏览器并未实现。

第三个参数则是设定的url。一般设置为相对路径,如果设置为绝对路径时需要保证同源。

pushState函数向浏览器的历史堆栈压入一个url为设定值的记录,并改变历史堆栈的当前指针至栈顶。

> 在这里笔者使用历史堆栈和当前指针,用以说明浏览器对历史记录的管理策略。文档中并没有使用这样的词汇,笔者为了更形象的介绍接口对浏览器历史记录的影响,使用这样的描述,如有不当之处请及时指出(不过目前以这套模型为基础的逻辑实现中并未出现悖论)。

浏览器支持

在写这篇文章的时候,各主流浏览器对History API的支持是非常不错的,可以点击此处查看其支持情况,这个链接会告诉你支持的浏览器,并使用之前,总有良好的实践来检测支持的特定功能。

为了用变成方式确定浏览器是否支持这个API,可以用下面的一行代码检验:

XHTML

return !!(window.history && history.pushState);

1
return !!(window.history && history.pushState);

此外,我建议参考一下这篇文章:Detect Support for Various HTML5 Features.(ps:后续会翻译)

如果你是用的现代浏览器,可以用下面的代码:

XHTML

if (Modernizr.history) { // History API Supported }

1
2
3
if (Modernizr.history) {
    // History API Supported
}

如果你的浏览器不支持History API,可以使用history.js代替。

html5 file api 上传文件

你可以到PHP100上面去咨询下 ~ 在html5中文件上传的话是将整个文件转换成base64编码然后在将文件存储的 而你的PHP程序可能不读base64把  

5 History API的”前生今世”,api前生今世 原文:An Introduction To The HTML5 History API 译文:关于HTML 5 History API 的介绍 译者:dwqs History是有趣...

 

replaceState

该接口与pushState参数相同,含义也相同。唯一的区别在于replaceState是替换浏览器历史堆栈的当前历史记录为设定的url。需要注意的是,replaceState不会改动浏览器历史堆栈的当前指针。

使用History

HTML 5提供了两个新方法:

1、history.pushState();                2、history.replaceState();

两种方法都允许我们添加和更新历史记录,它们的工作原理相同并且可以添加数量相同的参数。除了方法之外,还有popstate事件。在后文中将介绍怎么使用和什么时候使用popstate事件。

pushState()和replaceState()参数一样,参数说明如下:

1、state:存储JSON字符串,可以用在popstate事件中。

2、title:现在大多数浏览器不支持或者忽略这个参数,最好用null代替

3、url:任意有效的URL,用于更新浏览器的地址栏,并不在乎URL是否已经存在地址列表中。更重要的是,它不会重新加载页面。

两个方法的主要区别就是:pushState()是在history栈中添加一个新的条目,replaceState()是替换当前的记录值。如果你还对这个有迷惑,就用一些示例来证明这个区别。

假设我们有两个栈块,一个标记为1,另一个标记为2,你有第三个栈块,标记为3。当执行pushState()时,栈块3将被添加到已经存在的栈中,因此,栈就有3个块栈了。

同样的假设情景下,当执行replaceState()时,将在块2的堆栈和放置块3。所以history的记录条数不变,也就是说,pushState()会让history的数量加1.

比较结果如下图:

9159.com 1

 

到此,为了控制浏览器的历史记录,我们忽略了pushState()和replaceState()的事件。但是假设浏览器统计了许多的不良记录,用户可能会被重定向到这些页面,或许也不会。在这种情况下,当用户使用浏览器的前进和后退导航按钮时就会产生意外的问题。

尽管当我们使用pushState()和replaceState()进行处理时,期待popstate事件被触发。但实际上,情况并不是这样。相反,当你浏览会话历史记录时,不管你是点击前进或者后退按钮,还是使用history.go和history.back方法,popstate都会被触发。

In WebKit browsers, a popstate event would be triggered after document’s onload event, but Firefox and IE do not have this behavior.(在WebKit浏览器中,popstate事件在document的onload事件后触发,Firefox和IE没有这种行为)。

为什么介绍History API ?

onpopstate

该事件是window的属性。该事件会在调用浏览器的前进、后退以及执行history.forward、history.back、和history.go触发,因为这些操作有一个共性,即修改了历史堆栈的当前指针。在不改变document的前提下,一旦当前指针改变则会触发onpopstate事件。

Demo示例

HTML:

XHTML

<div class="container"> <div class="row"> <ul class="nav navbar-nav"> <li><a href="home.html" class="historyAPI">Home</a></li> <li><a href="about.html" class="historyAPI">About</a></li> <li><a href="contact.html" class="historyAPI">Contact</a></li> </ul> </div> <div class="row"> <div class="col-md-6"> <div class="well"> Click on Links above to see history API usage using <code>pushState</code> method. </div> </div> <div class="row"> <div class="jumbotron" id="contentHolder"> <h1>Home!</h1> <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry.</p> </div> </div> </div> </div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="container">
    <div class="row">
        <ul class="nav navbar-nav">
            <li><a href="home.html" class="historyAPI">Home</a></li>
            <li><a href="about.html" class="historyAPI">About</a></li>
            <li><a href="contact.html" class="historyAPI">Contact</a></li>
        </ul>
    </div>
    <div class="row">
        <div class="col-md-6">
            <div class="well">
                Click on Links above to see history API usage using <code>pushState</code> method.
            </div>
        </div>
        <div class="row">  
            <div class="jumbotron" id="contentHolder">
                <h1>Home!</h1>
                <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry.</p>
            </div>
        </div>
    </div>
</div>

JavaScript:

JavaScript

<script type="text/javascript"> jQuery('document').ready(function(){ jQuery('.historyAPI').on('click', function(e){ e.preventDefault(); var href = $(this).attr('href'); // Getting Content getContent(href, true); jQuery('.historyAPI').removeClass('active'); $(this).addClass('active'); }); }); // Adding popstate event listener to handle browser back button window.addEventListener("popstate", function(e) { // Get State value using e.state getContent(location.pathname, false); }); function getContent(url, addEntry) { $.get(url) .done(function( data ) { // Updating Content on Page $('#contentHolder').html(data); if(addEntry == true) { // Add History Entry using pushState history.pushState(null, null, url); } }); } </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<script type="text/javascript">
    jQuery('document').ready(function(){
 
        jQuery('.historyAPI').on('click', function(e){
            e.preventDefault();
            var href = $(this).attr('href');
 
            // Getting Content
            getContent(href, true);
 
            jQuery('.historyAPI').removeClass('active');
            $(this).addClass('active');
        });
 
    });
 
    // Adding popstate event listener to handle browser back button
    window.addEventListener("popstate", function(e) {
 
        // Get State value using e.state
        getContent(location.pathname, false);
    });
 
    function getContent(url, addEntry) {
        $.get(url)
        .done(function( data ) {
 
            // Updating Content on Page
            $('#contentHolder').html(data);
 
            if(addEntry == true) {
                // Add History Entry using pushState
                history.pushState(null, null, url);
            }
 
        });
    }
</script>

Demo 1:HTML 5 History API – pushState

历史条目在浏览器中被计算,并且可以很容易的使用浏览器的前进和后退按钮。View Demo  (ps:你在点击demo1的选项卡时,其记录会被添加到浏览器的历史记录,当点击后退/前进按钮时,可以回到/跳到你之前点击的选项卡对应的页面)

Demo 2:HTML 5 History API – replaceState

历史条目在浏览器中被更新,并且不能使用浏览器的前进和后退按钮进行浏览。View Demo  (ps:你在点击demo1的选项卡时,其记录会被替换当前浏览器的历史记录,当点击后退/前进按钮时,不可以回到/跳到你之前点击的选项卡对应的页面,而是返回/跳到你进入demo2的上一个页面)

        在这篇文章中,我们将了解HTML 5中History API的来源。在此之前,我们经常使用散列值来改变页面内容,特别是那些对页面特别重要的内容。因为没有刷新,所以对于单页面应用,改变其URL是不可能的。此外,当你改变URL的散列值值,它对浏览器的历史记录没有任何影响。

History API与业务实践

最常见的单页应用场景:列表页、商品详情页以及其内部的其他链接入口如图片页、评论页及其推荐其他商品详情页。以上提到的已经涉及到了4个单独业务逻辑页面(推荐的商品可复用商品详情页逻辑),分别是:列表、详情、图片详情和评论。将这4个页面合并到一个页面中,这就是最简单的SPA。为了用户的良好体验,必须设计合理的交互逻辑,最直观的就是浏览器(或手机app、微信公众号)的后退前进必须合乎业务逻辑特点。因此,这就涉及到了History API的使用,也牵扯到浏览器的历史记录管理。

9159.com 2

上图为具体的逻辑示意图。在列表页,点击其中一个商品,这里是商品1,进入详情页。详情页包括了该商品的轮播图、商品的图片详情入口、评论入口和推荐的其他商品入口。接下来进行如下操作:进入图片详情页,后退至详情页再进入评论页;后退至商品1详情页再由推荐商品入口进入商品9详情页,同样在商品9详情页进入图片详情页和评论页,再后退至商品9详情页;由推荐商品入口进入商品34详情页,再进行类似操作。最后保证在商品34图片详情页或评论页可以顺利后退至最初的商品列表页。

> 上文中加粗的“后退”,意味着使用浏览器后退按钮,或者使用手机自带的返回,再或者使用页面上提供的后退按钮。

这样一个很细小的需求,但是一旦真正放手去做却不是那么容易。仅仅根据History API的2个函数和1个事件去盲目的尝试实现,这属于盲人摸象,鲁棒性不高。不清楚浏览器的历史记录管理策略,不了解当前页面的历史记录数量,此种情况若要实现上述场景就有些麻烦。所以在具体动手写业务代码之前,需要搞懂History的pushState和replaceState具体如何影响历史记录栈。

总结(ps:喜欢这两个字~^_^~)

HTML 5中的History API 对Web应用有着很大的影响。为了更容易的创建有效率的、对SEO友好的单页面应用,它移除了对散列值的依赖。

赞 1 收藏 评论

9159.com 3

        然后,现在对于HTML 5的History API来说,这些都是可以轻易实现的,但是由于单页面应用没必要使用散列值,它可能需要额外的开发脚本。它也允许我们用一种对SEO友好的方式建立新应用。此外,它能减少带宽,但是该怎么证明呢?

探究浏览器历史记录策略与History API的关系

由于浏览器并未针对每个页面的历史记录提供具体访问的接口,因此所有的测试都是黑盒。但是在移动端的中,大都是webkit内核,其webcore的具体实现也都相近,因此该节得出的结论完全可以在移动端使用。

尽管无法访问当前页的历史记录栈,但是浏览器却提供了history.length属性,它标明了当前历史记录栈的个数。该值会帮助我们更好地分析History API对历史记录栈的影响。

9159.com 4

上图为测试实例。其中白色箭头意味着点击该链接并执行pushState操作(即操作1),黑色箭头则执行浏览器后退,红色的圆点为历史记录栈中的当前指针,而每个项则为历史记录栈,历史记录的个数则为其子项的数量。

初始在第一个搜索列表页,执行操作1后历史堆栈数量增加,当前指针上移一位至26788.html; 同理在执行3次操作1,历史堆栈递增3个,当前指针仍在栈顶,即78099.html; 此后进行浏览器后退,历史堆栈数量不变,当前指针下移一位至8819.html; 在此处再执行操作1,栈顶元素改变,当前指针移至栈顶,历史堆栈数量不变; 继续执行操作1,栈顶元素改变,指针移至栈顶,历史堆栈数量加一; 执行浏览器后退,栈顶元素不变,指针下移一位至8128.html,历史堆栈数量不变; 执行浏览器后退,栈顶元素不变,指针下移一位至8819.html,历史堆栈数量不变; 执行浏览器后退,栈顶元素不变,指针下移一位至8128.html,历史堆栈数量不变; 执行浏览器后退,栈顶元素不变,指针下移一位至26788.html,历史堆栈数量不变; 执行操作1,栈顶元素变为9721.html,指针上移至栈顶,历史堆栈数量变为3; 执行操作1,栈顶元素变为8387.html,指针上移至栈顶,历史堆栈数量变为4; 执行浏览器后退,栈顶元素不变,指针下移一位至9721.html,历史堆栈数量不变; 执行浏览器后退,栈顶元素不变,指针下移一位至26788.html,历史堆栈数量不变; 执行浏览器后退,栈顶元素不变,指针下移一位至search.html,历史堆栈数量不变; 执行操作1,栈顶元素变为xxx.html,指针上移至栈顶,历史堆栈数量变为2; …

至此,实验结束。虽然这里仅仅列出了这一个测试用例,但是其实笔者做了更多更复杂的测试,并且平台涉及了pc和移动端的浏览器、微信和原生webview,结果都一样。这一系列测试说明了很多问题,总结之一句话则是:

浏览器针对每个页面维护一个History栈。执行pushState函数可压入设定的url至栈顶,同时修改当前指针; 当执行back操作时,history栈大小并不会改变(history.length不变),仅仅移动当前指针的位置; 若当前指针在history栈的中间位置(非栈顶),此时执行pushState会改变history栈的大小。 总结pushState的规律,可发现当前指针在history栈顶部时执行pushState,会增加history栈大小;若current指针不在栈顶则会在当前指针所在位置添加项。执行back操作并不修改history栈大小,因此可以通过back和forward在当前大小的history栈中自由移动。

掌握这个规律,就知道如何维护历史记录,就知道在什么状态下需要pushState。回到最初的需求,产品经理规定从商品34的评论页,按后退按钮可以到达最初的列表页,但是他并没有详细规定如何后退。在这里就会有2中实现方式:

  • 每一次后退,会回到上次的访问地方。如,在商品34的评论页,会后退至商品34的详情页,再后退则会回到商品9的详情页,直至回到列表页。
  • 总共维护三层历史记录,第一层(栈底)为列表页,第二层为详情页,第三层(栈顶)为评论页或图片详情页。在该种实现下,由商品34的评论页第一次后退至商品34的详情页,第二次后退至列表页。

针对第一种,其实实现最为简单,因为这完全是由浏览器默认控制历史记录堆栈,而我们只需在合适的时机调用pushState将url插入到堆栈,然后在onpopstate处理函数中监听对应的时间即可:

window.addEventListener('popstate', function (e) { console.log('popstate') // 后退(前进)至商品详情页,异步加载数据并渲染 if(e.state && e.state.indexOf('/shop/sku/') !== -1){ ajaxDetail(e.state,true); }else // 后退(前进)至评论页,异步加载数据渲染 if(e.state && e.state.indexOf('/shop/comment/commentList.html') !== -1){ ajaxComment(e.state,true); }else // 后退(前进)至图片详情页,异步加载数据渲染 if(e.state && e.state.indexOf('/shop/item/pictext/') !== -1){ ajaxPic(e.state,true); }else // 后退(前进)至列表页,隐藏浮层 if(e.state && e.state.indexOf('/search/') !== -1){ // 隐藏spa的浮层 $('.spa-container').css('zIndex','-1'); } });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
window.addEventListener('popstate', function (e) {
    
    console.log('popstate')
    // 后退(前进)至商品详情页,异步加载数据并渲染
    if(e.state && e.state.indexOf('/shop/sku/') !== -1){
      ajaxDetail(e.state,true);  
    }else
    // 后退(前进)至评论页,异步加载数据渲染
    if(e.state && e.state.indexOf('/shop/comment/commentList.html') !== -1){
      ajaxComment(e.state,true);
    }else
    // 后退(前进)至图片详情页,异步加载数据渲染
    if(e.state && e.state.indexOf('/shop/item/pictext/') !== -1){
      ajaxPic(e.state,true);
    }else
    // 后退(前进)至列表页,隐藏浮层
    if(e.state && e.state.indexOf('/search/') !== -1){
      // 隐藏spa的浮层
      $('.spa-container').css('zIndex','-1');
    }
    
  });

针对第二种实现,则是本文的重点。毕竟,由浏览器默认维护的历史堆栈在某些业务场景中并不匹配,因此需要开发者自己维护一个历史记录栈。在本次实现中,由于总共涉及4张页面的显示,因此我们设定了3层历史堆栈,这很好理解。

为了构建这样的历史记录栈,在主页面(即列表页)中需要额外添加两条历史记录。这是由于默认打开列表页时,当前页面的url已加入历史记录栈中,

function push(state){ history.pushState(state, null, location.pathname + location.search); } // 'abc'用于标示初始列表页 history.replaceState('abc',null,location.pathname + location.search) // 压入两条历史记录 push(); push();

1
2
3
4
5
6
7
8
9
function push(state){
    history.pushState(state, null, location.pathname + location.search);
  }
  // 'abc'用于标示初始列表页
  history.replaceState('abc',null,location.pathname + location.search)
  
  // 压入两条历史记录
  push();
  push();

这样,打开列表页后就会创建3个历史记录,并且这3个历史记录的url都为列表页的url,这与后面的操作并无影响。

在列表页中打开详情页,需要做额外的处理。由于按照我们设计的历史记录栈,第二层应该为详情页,而此时在初始化后,历史记录栈的当前指针已指向栈顶元素,因此需要将当前指针下移一位。这里就需要history.back来完成。

$('.item-list').on('click','a',handler); // 异步加载详情数据 var handler = function(e,isScrollXClick){ var a = this; ajaxDetail($(a).attr('href'),isScrollXClick); return false; }; var isScrollXClick; /** * @params: url 请求路径 isScrollXClick: 是否点击推荐商品 * */ var ajaxDetail = function(url,isScrollXClick){ $.ajax({ url: '/api' + url, success: function(data){ ... ... if(!isScrollXClick){ console.log('I am back!') // 在代码中进行back or forward并不会立即出发popstate事件,以v8引擎为例,在执行back之后 // 的大概18us之后会触发事件,而此时如果立即通过replaceState修改url则会造成失败,修改的是 // history stack栈顶的url. // 这里通过异步执行replaceState兼容 history.back(); } // 异步触发 setTimeout(function(){ history.replaceState(url, null, url); }) // 针对推荐栏的商品,循环绑定事件,此处用事件代理优化 $('#J_PDSlider').on('click','a',function(e){ isScrollXClick = 1; handler.call(this,e,isScrollXClick); return false; }); }, error: function(xhr, type){ alert('Ajax error!') } }) };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
$('.item-list').on('click','a',handler);
 
// 异步加载详情数据
var handler = function(e,isScrollXClick){
    var a = this;
    ajaxDetail($(a).attr('href'),isScrollXClick);
    return false;
};
 
var isScrollXClick;
  /**
   * @params: url 请求路径 isScrollXClick: 是否点击推荐商品
   *
   */
  var ajaxDetail = function(url,isScrollXClick){
 
     $.ajax({
      url: '/api' + url,
      success: function(data){
        ...
        ...
        if(!isScrollXClick){
          console.log('I am back!')
 
          // 在代码中进行back or forward并不会立即出发popstate事件,以v8引擎为例,在执行back之后
          // 的大概18us之后会触发事件,而此时如果立即通过replaceState修改url则会造成失败,修改的是
          // history stack栈顶的url.
          
          // 这里通过异步执行replaceState兼容
          history.back();      
          
        }
          
        // 异步触发
        setTimeout(function(){
          history.replaceState(url, null, url);
        })
 
        // 针对推荐栏的商品,循环绑定事件,此处用事件代理优化
        $('#J_PDSlider').on('click','a',function(e){
          isScrollXClick = 1;
          handler.call(this,e,isScrollXClick);
          return false;
        });
      },
      error: function(xhr, type){
        alert('Ajax error!')
      }
     })
  };

在此处实现,通过isScrollXClick变量判断是否点击的是推荐商品,如果不是则需要执行back操作,下移指针。此时指针是指在第二层,但是浏览器和第二层历史记录的url仍为初始化设定的url,因此需要修改,在这里异步修改当前url。

之所以异步执行replaceState,是由于webkit触发popState事件决定的。在代码中执行history.back 或者history.forward,并不会立即返回,也不会立即触发popState事件。由于没有阅读webkit的源码,因此无从推测执行back或者forward后具体需要额外做什么操作,它们之间有着10us级别的间隔,因此此处必须使用setTimeout实现异步改变url。

在具体开发过程中,这个问题困扰着笔者好几天,终于在一次调试过程中发现浏览器url的变动,才联想到可能是由事件触发的时间差导致。

对于图片详情和评论的逻辑处理,则和上文类似,无需多言。

最后一次后退需要回到列表页,而在初始化阶段我们给列表页设置了state为“abc”,特殊的标示该路由,因此在popState事件处理中,我们就可以根据该项回到初始页:

window.addEventListener('popstate', function (e) { if(e.state && e.state.indexOf('/shop/sku/') !== -1){ ajaxDetail(e.state,true); }else if(e.state && e.state.indexOf('abc') !== -1){ // 隐藏spa的浮层 $('.spa-container').css('zIndex','-1'); push(); push(); } });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
window.addEventListener('popstate', function (e) {
 
    if(e.state && e.state.indexOf('/shop/sku/') !== -1){
      ajaxDetail(e.state,true);  
    }else if(e.state && e.state.indexOf('abc') !== -1){
      // 隐藏spa的浮层
      $('.spa-container').css('zIndex','-1');
      
      
      push();
      push();
    }
    
    
  });

如果回到初始页,隐藏浮层,同时在执行2次push操作。根据上节发现的规律,在初始页执行2次push操作,会在当前指针位置重新添加2个历史记录,当前指针指向栈顶元素,历史记录栈的数量不变,仍为3。这样就完成了简单的由开发者自定义维护历史堆栈的spa系统。

        在文章中,我将用History API开发一个单页应用来证明上述的问题。

回顾

之所以会写这篇文章完全是出于偶然,由于实际项目的各种需求我们不应该仅仅将眼光停留在使用API的层面上。另外,在开发过程中遇到难以解决的问题,需要提出各种合理的设想并用详实的实验证明,在得到相对应的结论后需要利用该结论去例证其他场景,这样才能确保解决方案的可靠性。目前网络上或者书籍中并未提供任何手动维护历史记录堆栈的方法,也未明确指出History API与浏览器历史记录之间如何影响,因此本文对于旨在利用History API实现spa的开发者而言还是有些指导意义的。

打赏支持我写出更多好文章,谢谢!

打赏作者

        这也意味着我必须先在首页加载必要的资源。现在开始,页面仅仅加载需要的内容。换句话说,应用并不是一开始就加载了全部的内容,在请求第二个应用内容时,才会被加载。

打赏支持我写出更多好文章,谢谢!

9159.com 5

1 赞 7 收藏 评论

注意,您需要执行一些服务器端编码只提供部分资源,而不是完整的页面内容。

关于作者:欲休

9159.com 6

前端自由人 个人主页 · 我的文章 · 1 ·  

9159.com 7

 

浏览器支持

         在写这篇文章的时候,各主流浏览器对History API的支持是非常不错的,可以点击此处查看其支持情况,这个链接会告诉你支持的浏览器,并使用之前,总有良好的实践来检测支持的特定功能。

         为了用变成方式确定浏览器是否支持这个API,可以用下面的一行代码检验:

return !!(window.history && history.pushState);

 

         此外,我建议参考一下这篇文章:Detect Support for Various HTML5 Features.(译文:)

         如果你是用的现代浏览器,可以用下面的代码:

if (Modernizr.history) {
    // History API Supported
}

 

         如果你的浏览器不支持History API,可以使用history.js代替。

 

使用History

        HTML 5提供了两个新方法:

              1、history.pushState();                2、history.replaceState();

        两种方法都允许我们添加和更新历史记录,它们的工作原理相同并且可以添加数量相同的参数。除了方法之外,还有popstate事件。在后文中将介绍怎么使用和什么时候使用popstate事件。

        pushState()和replaceState()参数一样,参数说明如下:

              1、state:存储JSON字符串,可以用在popstate事件中。

              2、title:现在大多数浏览器不支持或者忽略这个参数,最好用null代替

              3、url:任意有效的URL,用于更新浏览器的地址栏,并不在乎URL是否已经存在地址列表中。更重要的是,它不会重新加载页面。

        两个方法的主要区别就是:pushState()是在history栈中添加一个新的条目,replaceState()是替换当前的记录值。如果你还对这个有迷惑,就用一些示例来证明这个区别。

        假设我们有两个栈块,一个标记为1,另一个标记为2,你有第三个栈块,标记为3。当执行pushState()时,栈块3将被添加到已经存在的栈中,因此,栈就有3个块栈了。

        同样的假设情景下,当执行replaceState()时,将在块2的堆栈和放置块3。所以history的记录条数不变,也就是说,pushState()会让history的数量加1.

        比较结果如下图:

9159.com 8

        到此,为了控制浏览器的历史记录,我们忽略了pushState()和replaceState()的事件。但是假设浏览器统计了许多的不良记录,用户可能会被重定向到这些页面,或许也不会。在这种情况下,当用户使用浏览器的前进和后退导航按钮时就会产生意外的问题。

        尽管当我们使用pushState()和replaceState()进行处理时,期待popstate事件被触发。但实际上,情况并不是这样。相反,当你浏览会话历史记录时,不管你是点击前进或者后退按钮,还是使用history.go和history.back方法,popstate都会被触发。

In WebKit browsers, a popstate event would be triggered after document’s onload event, but Firefox and IE do not have this behavior.(在WebKit浏览器中,popstate事件在document的onload事件后触发,Firefox和IE没有这种行为)。

      

Demo示例

       HTML:

<div class="container">
    <div class="row">
        <ul class="nav navbar-nav">
            <li><a href="home.html" class="historyAPI">Home</a></li>
            <li><a href="about.html" class="historyAPI">About</a></li>
            <li><a href="contact.html" class="historyAPI">Contact</a></li>
        </ul>
    </div>
    <div class="row">
        <div class="col-md-6">
            <div class="well">
                Click on Links above to see history API usage using <code>pushState</code> method.
            </div>
        </div>
        <div class="row">   
            <div class="jumbotron" id="contentHolder">
                <h1>Home!</h1>
                <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry.</p>
            </div>
        </div>
    </div>
</div>

 

       JavaScript:

<script type="text/javascript">
    jQuery('document').ready(function(){

        jQuery('.historyAPI').on('click', function(e){
            e.preventDefault();
            var href = $(this).attr('href');

            // Getting Content
            getContent(href, true);

            jQuery('.historyAPI').removeClass('active');
            $(this).addClass('active');
        });

    });

    // Adding popstate event listener to handle browser back button 
    window.addEventListener("popstate", function(e) {

        // Get State value using e.state
        getContent(location.pathname, false);
    });

    function getContent(url, addEntry) {
        $.get(url)
        .done(function( data ) {

            // Updating Content on Page
            $('#contentHolder').html(data);

            if(addEntry == true) {
                // Add History Entry using pushState
                history.pushState(null, null, url);
            }

        });
    }
</script>

 

          Demo 1:HTML 5 History API – pushState

历史条目在浏览器中被计算,并且可以很容易的使用浏览器的前进和后退按钮。View Demo  (ps:你在点击demo1的选项卡时,其记录会被添加到浏览器的历史记录,当点击后退/前进按钮时,可以回到/跳到你之前点击的选项卡对应的页面)

Demo 2:HTML 5 History API – replaceState

历史条目在浏览器中被更新,并且不能使用浏览器的前进和后退按钮进行浏览。View Demo  (ps:你在点击demo1的选项卡时,其记录会被替换当前浏览器的历史记录,当点击后退/前进按钮时,不可以回到/跳到你之前点击的选项卡对应的页面,而是返回/跳到你进入demo2的上一个页面)

总结(ps:喜欢这两个字~^_^~)

       HTML 5中的History API 对Web应用有着很大的影响。为了更容易的创建有效率的、对SEO友好的单页面应用,它移除了对散列值的依赖。

本文由9159.com发布于前端,转载请注明出处:我们将了解HTML 5中History,禁止转载

关键词:

上一篇:没有了
下一篇:原文出处,作用域链