历史:
最早没有 xhr 的时候,要想得到无刷新的用户体验,唯一的方法就是隐藏帧技术(frame) ,发展到现在的 iframe,这个技术仍然有它的使用场景,即在文件上传以及 简单情况下浏览器的前进后退导航功能 上。
使用:
当一个页面嵌入一个iframe时,当javascript变动iframe的 src(或者操作document open 与 close),会在浏览器上生成一条历史记录,点击后退的话,主页面没有变化,但是iframe会加载历史记录中的上一条src(或者文档),因此可以解 决传统用户体验的浏览器导航部分。
适用范围:
一般用于界面切换,当界面有大的变化,且没有修改或删除操作时 ,可以由用户点击浏览器的返回操作来达到上一个操作界面。
技术描述:(firefox 适用,ie不适用)
主页面 javascript 设置里面的 iframe 的 src (向服务器传递信息),当iframe 加载完毕后,在iframe的页面调用主页面的回调函数,达到更新主页面的作用,并向浏览器添加一条历史记录。当用户点击后退后,iframe会加载历史记录的上一条页面,重新调用上一个页面所应该的主页面callback,修改主页面到上一个功能界面,达到是主页面回退的效果. iframe 一般是设置样式 display:none,排除在文档流中,不参与用户界面。
示例 (firefox 适用):
主页面 :index.html
<iframe id='i' style='display:none'></iframe>
<div id='d'></div>
<div id='d2'></div>
<input type='button' onclick='bclick();' value='change'/>
<script>
var ii=0;
function callback(str){
//alert(ii);
document.getElementById('d').innerHTML='iframe call back :'+str;
}
function bclick() {
document.getElementById('d2').innerHTML='mimic ajax '+ii;
document.getElementById('i').src='iframe.html?i='+(ii++);
}
window.onload=function(){
document.getElementById('i').src='iframe.html';
}
</script>
隐藏帧页面 :iframe.html
<script>
window.onload=function(){
parent.callback(window.location);
}
</script>
实战(多浏览器解决方案)
根据 advanced dom scripting 以及 YUI YAHOO.util.History 提出的方案,可以解决浏览器导航问题以及书签功能。
对于 firefox,ie8 可以直接给url添加 hash ,则firefox,ie8会把变动的 url加到浏览器history中,而ie6,7 则不会。
firefox
每次操作添加 hash 到 url,并后台 setInterval 监控 url hash 的变化,根据当前的hash 进行 ajax 操作,( 用户点击后退只会改变hash值。 )
updated 2010-12-29 :
firefox chrome 已经支持 hashchange 事件,不再需要定时器监控了,监听事件即可。
ie 6,7
设 置隐藏的iframe,每次操作用脚本设置iframe的内容 (注意:如果仅仅设置 src 参数 ?x=xx 不同,ie6,7 并不会产生历史纪录,必须src不同,直接脚本产生 iframe 新document(open,write,close)也可以产生历史纪录),将当前hash 插入到生成的iframe内容中,后台监控 setInterval iframe 内容中 hash 的变化,如果变化( 用户点击后退会使当前页面iframe的内容变化 ),则提取hash值,添加到主页面url,执行相关ajax 操作。
YUI BHM 相关操作:
/**
* Update the IFrame with our new state.
*
* @method _updateIFrame
* @private
* @return {boolean} true if successful. false otherwise.
*/
function _updateIFrame (fqstate) {
var html, doc;
html = '<html><body><div id="state">' + fqstate + '</div></body></html>';
try {
doc = _histFrame.contentWindow.document;
doc.open();
doc.write(html);
doc.close();
return true;
} catch (e) {
return false;
}
}
关键是 open() ,close() 会增加历史 ,之后可以将数据附在 doc 上,后退的话数据就会回来,但是注意 close() 后会触发 iframe load 事件,yui3 采用 hash 来存储数据:
HistoryHash._updateIframe = function (hash, replace) {
var iframeDoc = iframe.contentWindow.document,
iframeLocation = iframeDoc.location;
iframeDoc.open().close();
if (replace) {
iframeLocation.replace(hash.charAt(0) === '#' ? hash : '#' + hash);
} else {
iframeLocation.hash = hash;
}
};
ie8
很简单,由于有相关事件直接监控 Hash 变动即可 ,不用像firefox一样轮循查看 :
if (YAHOO.env.ua.ie) {
if (typeof document.documentMode === "undefined" || document.documentMode < 8) {
// IE < 8 or IE8 in quirks mode or IE7 standards mode
//利用 iframe 来生成历史纪录
_checkIframeLoaded();
} else {
// IE8 in IE8 standards mode
//直接利用特有事件
YAHOO.util.Event.on(top, "hashchange",
function () {
var hash = _getHash();
_handleFQStateChange(hash);
_storeStates();
});
_initialized = true;
YAHOO.util.History.onLoadEvent.fire();
}
} else {
//其他浏览器,轮训hash值变化
// Start the thread that will have the responsibility to
// periodically check whether a navigate operation has been
// requested on the main window. This will happen when
// YAHOO.util.History.navigate has been called or after
// the user has hit the back/forward button.
// On Safari 1.x and 2.0, the only way to catch a back/forward
// operation is to watch history.length... We basically exploit
// what I consider to be a bug (history.length is not supposed
// to change when going back/forward in the history...) This is
// why, in the following thread, we first compare the hash,
// because the hash thing will be fixed in the next major
// version of Safari. So even if they fix the history.length
// bug, all this will still work!
counter = history.length;
// On Gecko and Opera, we just need to watch the hash...
hash = _getHash();
setInterval(function () {
var state, newHash, newCounter;
newHash = _getHash();
newCounter = history.length;
if (newHash !== hash) {
hash = newHash;
counter = newCounter;
_handleFQStateChange(hash);
_storeStates();
} else if (newCounter !== counter && YAHOO.env.ua.webkit) {
hash = newHash;
counter = newCounter;
state = _fqstates[counter - 1];
_handleFQStateChange(state);
_storeStates();
}
}, 50);
书签问题:
多个浏览器都可以通过,添加书签附带了当前页面的hash值。载入书签时,首先读取Hash值进入相应的ajax操作。
demo:
updated : 2010-12-29
由于 ie>7 以及 firefox ,chrome 都已经支持 hashchange 事件以及历史存储,那么实际上难点就是 ie6,7 下使用 iframe 来记录导航历史 :
simple hashchange for ie6,7 @ google code
kissy 采用先进的事件补丁技术优雅地解决了这个问题 ,在任何浏览器下都可以直接 :
function t() {
alert("current hash :" + window.location.hash);
}
KISSY.Event.on(window, "hashchange", t);
即可 !
PS:html5 提出了新的标准 ,目前 chrome 下已经实现:
session history and navigation
github 也做了尝试:
how to rewrite url without refresh like github
PS2: 如果不想使得修改 hash 而引起浏览器增加历史记录,可使用 location.replace(‘#xx’) ,对于 ie6,7 模拟的iframe,情况则只需要只修改 body 的 innerHtml :
iframe.contentWindow.document.body.innerHTML=’#xx’