ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【HTML5 API】历史记录管理

2021-12-09 02:32:23  阅读:157  来源: 互联网

标签:历史记录 浏览器 状态 对象 state API ui HTML5


历史记录管理

Web浏览器会记录在一个窗口中载入的所有文档,同时提供了“后退”和“前进”按钮,允许用户在这些文档之间切换浏览。这种浏览器历史记录模型最早在“文档都是被动的,所有的计算都在服务器上完成”那个时期就已经存在了。如今,Web应用通常都是动态地生成或载入页面内容,并在无须刷新页面的情况下就显示新的应用状态。如果想要提供用户能够通过浏览器的“后退”和“前进”按钮,直观地切换应用状态,像这类应用就必须自己处理应用的历史记录管理。HTML5定义了两种用于历史记录管理的机制。

其中比较简单的历史记录管理技术就是利用location.hash和hashchange事件。浏览器甚至在HTML5标准化之前就已经开始实现该技术了。在绝大多数浏览器中(IE早期版本除外),设置location.hash属性会更新显示在地址栏中的URL,同时会在浏览器的历史记录中添加一条记录。hash属性设置URL的片段标识符,通常是用于指定要滚动到的文档中某一部分的ID。但是location.hash不一定非要设置为一个元素的ID:它可以设置成任何的字符串。如果能 够将应用状态编码成一个字符串,就可以使用该字符串作为片段标识符。

设置了location.hash属性后,接下来要实现允许用户通过“后退”和“前进”按钮来切换不同的文档状态。这个时候,应用必须要想办法检测状态变化,以便它能够读取出存储在片段标识符中的状态并相应地更新自己的状态。支持HTML5的浏览器一旦发现片段标识符发生了改变,就会在Window对象上触发一个hashchange事件。这样,在支持hashchange事件的浏览器中,就可以通过设置window.onhashchange为一个处理程序函数,使得每次由于切换历史记录导致片段标识符变化的时候,都会调用该处理程序函数。当调用该处理程序函数的时候,就可以对location.hash的值进行解析,然后使用该值包含的状态信息来重新显示应用。

HTML5还定义了一个相对更加复杂和强健的历史记录管理方法,该方法包含history.pushState()方法和popstate事件。当一个Web应用进入一个新的状态的时候,它会调用history.pushState()方法将该状态添加到浏览器的浏览历史记录中。该方法的第一个参数是一个对象,该对象包含用于恢复当前文档状态所需的所有信息。该对象可以是任何能够通过JSON.stringify()方法转换成相应字符串形式的对象,也可以是其他类似Date和RegExp这样特定的本地类型。该方法的第二个可选参数是一个可选的标题(普通的文本字符串),浏览器可以使用它(比如,在一个<Back>菜单中)来标识浏览历史记录中保存的状态。该方法的第三个参数是一个可选的URL,表示当前状态的位置。相对的URL都是以文档的当前位置为参照,通常该URL只是简单地指定URL(诸如#state)这样的hash(或者“片段标识符”)部分。将一个URL和状态关联,可以允许用户将应用的内部状态作为书签添加到浏览器中,并当在URL中包含足够信息的时候,应用可以在从书签中载入的时候就恢复它的状态。


结构性复制

正如上面所提到的,pushstate()方法接受一个状态对象并为该对象创建一份私有副本。这是对一个对象进行深拷贝或者深复制:它会递归地复制所有嵌套对象或者数组的内容。HTML5标准将这类复制称为“结构性复制”(structured clone)。创建一个结构性复制的过程就好比是将一个对象传递给JSON.stringify()方法,然后再将结果字符串传递给JSON.parse()方法。但是JSON只支持JavaScript的基础类型和对象以及数组。在HTML5标准中提到,结构性复制算法必须还能够复制Date对象、RegExp对象、ImageData对象(来自<canvas>元素)、FileList对象、File对象以及Blob对象。但是在结构性复制算法中会显式排除JavaScript中的函数和错误以及绝大部分诸如窗口、文档、元素等这类宿主对象。或许还不会存储文件或者图片数据作为历史状态的一部分,但是结构性复制还被其他一些HTML5相关的标准用到。


除了pushstate()方法之外,History对象还定义了replaceState()方法,该方法和pushState()方法接受同样的参数,但是不同的是,它不是将新的状态添加到浏览历史记录中,而是用新的状态代替当前的历史状态。
当用户通过“后退”和“前进”按钮浏览保存的历史状态时,浏览器会在Window对象上触发一个popstate事件。与该事件相关联的事件对象有一个state属性,该属性包含传递给pushState()方法的状态对象的副本(另一个结构性复制)。

下例是一个简单的Web应用——一个猜数字的游戏——它使用这些HTML5技术来保存应用记录,允许用户通过“后退”来回顾或者撤销对数字的猜测。

例:使用pushState()方法进行历史记录管理
<!DOCTYPE html>
<html><head><title>I'm thinking of a number...<title>
<script>
  window.onload = newgame;        // 页面载入的时候就开始一个新的游戏
  window.onpopstate = popState;   // 处理历史记录相关事件
  var state,ui;                   // 全局变量,在newgame()方法中会对其初始化

  function newgame(playagain) {   // 开始一个新的猜数字游戏
    // 初始化一个包含需要的文档元素的对象
    ui = { 
      heading: null,  // 文档最上面的<h1>元素
      prompt: null,   // 要求用户输入一个猜测数字
      input: null,    // 用户输入猜测数字的地方
      low: null,      // 可视化的三个表格单元格
      mid: null,      // 猜测的数字范围
      high: null;     
    };
    // 查询这些元素中每个元素的
    for(var id in ui) ui[id] document.getElementById(id);

    // 给input字段定义一个事件处理程序函数
    ui.input.onchange = handleGuess; 

    // 生成一个随机的数字并初始化游戏状态
    state = {
      n:Math.f1oor(99 * Math.random())+1, // 整数:0 < n < 100
      low: 0,           // 可猜测数字范围的下限
      high: 100,        // 可猜测数字范围的上限
      guessnum: 0,      // 猜测的次数
      guess: undefined  // 最后一次猜测
    };

    // 修改文档内容来显示该初始状态 
    display(state);

    // 此函数会作为onload事件处理程序调用,
    // 同时当单击显示在游戏最后的”再玩一次"按钮时候,也会调用它
    // 在第二种调用情况下,playagain数值为true
    // 如果playagain为true,则保存新的游戏状态
    // 但是如果是作为onload事件处理程序调用的情况下,则不保存状态
    // 这是因为,当通过浏览器历史记录从其他文档状态回退到当前的游戏状态时,
    // 也会触发load事件。如果这种情况下,也保存状态的话,
    // 会将真正的游戏历史状态记录覆盖掉
    // 在支持pushState()方法的浏览器中,load事件之后总是有一个popstate事件
    // 因此,这里的处理方式是,等待popstate事件而不是直接进行状态保存
    // 如果该事件提供一个状态对象,则直接使用该对象即可
    // 如果该事件没有状态对象,就表示这实际上是一个新的游戏,
    // 则使用replacestate来保存最新的游戏状态
    if (playagain === true) save(state);
  }  
    // 如果支持的话,就使用pushState()方法将游戏状态保存到浏览器历史记录中 
    function save(state) {
      if (!history.pushstate) return; // 如果pushState()方法没有定义的话,则什么也不做

      // 这里会将一个保存的状态和URL关联起来
      // 该URL显示猜测的数字,但是不对游戏状态进行编码,
      // 因此,这对于书签是没有用的
      // 不能简单地将游戏状态写到URL中,因为这会将游戏一些机密数字暴露在地址栏中
      var url = "#guess" + state.guessnum;
      // 保存状态对象和URL
      history.pushState(state, // 要保存的状态对象
                        "",    // 状态标题:当前浏览器会忽略它
                        url);  // 状态URL:对书签是没有用的
    }
    // 这是onpopstate的事件处理程序,用于恢复历史状态 
    function popState(event) {
        if (event.state) { // 如果事件有一个状态对象,则恢复该状态 
            // 要注意的是,event.state是对已保存状态对象南一个深拷贝 
            // 因此无须改变保存的值就可以修改该对象 
            state = event.state; // 恢复历史状态 
            display(state);      // 显示恢复而状态
        }
        else {
            // 当第一次载入页面时,会触发一个没有状态的popstate事件 
            // 用真实的状态将null状态替换掉:參见newgame()方法中的注释 
            // 这里不需要调用display()方法
            history.replaceState(state, "", "#guess" + state.guessnum); 
      }
  };


  // 每次用户猜测一个数字的时候,都会调用此事件处理程序
  // 此处理程序用于更新游戏的状态、保存游戏状态并显示游戏状态
  function handleGuess() {
      // 从input字段中获取用户猜测的数字 
      var g = parseInt(this.value);
      // 如果该值是限定范围中的一个数字
      if ((g > state.low) && (g < state.high)) {
          // 对应地更新状态对象
          if (g < state.n) state.low = g;
          else if (g > state.n) state.high = g; 
          state.guess = g;
          state.guessnum++;
          // 在浏览器历史记录中保存新的状态
          save(state);
          // 根据用户猜测情况来修改文档 
          display(state);
      }
      else { // 无效的猜测:不保存状态
          alert("Please enter a number greater than " + state.low + "and less than " + state.high);
      }
  }
  // 修改文档来显示游戏当前状态
  function display(state) {
      // 显示文档的导航和标题
      ui.heading.innerHTML = document.title =
          "I'm thinking of a number between " + 
          state.low + " and " + state.high + ".";
      
      // 使用一个表格来显示数字的取值范围
      ui.low.style.width = state.low + "%"
      ui.mid.style.width = (state.high-state.low) + "%"
      ui.high.style.width = (100-state.high) + "%"
      
      // 确保input字段是可见的、空的并且是聚焦的
      ui.input.style.visibility = "visible";
      ui.input.value = "";
      ui.input.focus();

      // 根据用户最近的猜测,设置提示
      if (state.guess === undefined)
          ui.prompt.innerHTML = "Type your guess and hit Enter: “; 
      else if (state.guess < state.n)
          ui.prompt.innerHTML = state.guess + " is too low.Guess again: ";
      else if (state.guess > state.n)
          ui.prompt.innerHTML = state.guess + " is too high.Guess again: ”; 
      else {
          // 当猜对了的时候,就隐藏inputs段并显示”再玩一次”按钮
          ui.input.style.visibility = "hidden"; // 不需要再猜了
          ui.heading.innerHTML = document.title = state.guess + " is correct! ";
          ui.prompt.innerHTML =
              "You Win! <button onclick='newgame(true)'>Play Again</button>";
      }
  }
  </script>
  <style> /* 通过CSS样式美化游戏界面 */
      #prompt { font-size: 16pt; }
      table { width: 90%; margin:10px; margin-left:5%; }  >       #low, #high { background-color: lightgray; height: 1em; }
      #mid { background-color: green; }
    </style>
  </head>
<body><!-- 下面的HTML元素是游戏的UI -->
<!-- 游戏标题和数字猜测范围的文本表示 -->
<h1 id="heading">I'm thinking of a number...</h1>
<!-- 用于确保猜测的数字在有效范围内 -->
<table><tr><td id="low"></td><td id="mid"></td><td id="high"></td></tr></table> 
<!-- 用户输入猜测数字的地方 -->
    <label id="prompt"></label><input id="input" type="text">
  </body>
</html>

标签:历史记录,浏览器,状态,对象,state,API,ui,HTML5
来源: https://www.cnblogs.com/Huang-zihan/p/15661413.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有