Ajax的流行和前端MVVM框架的快速發展給Web開發帶來了極大的便利,也讓Web應用體驗越來越好,近些年單頁應用也隨之流行起來。Ajax的應用可以讓網頁實現無重新整理更新資料,但其也會造成瀏覽器無法前進和後退(瀏覽器和手機的物理返回鍵)的問題。早期這個問題通常是藉助iframe
來解決。得益於HTML5,目前有了一些諸如用pjax
(ajax+pushState)的解決方案。本文要講解的是如何用錨點
和history API
來實現單頁面應用頁面之間的切換。
講解之前先說說需求,效果如下圖:
需求點:
點選手機資訊所在項,從右邊劃出手機編輯頁面
輸入手機號碼和驗證碼儲存後返回基本資訊頁面
瀏覽器點選前進後退可以在兩個頁面之前切換
支援手機的物理返回鍵(等價於
history.back()
)頁面需要區域性重新整理,保持url一致
整個問題的難點應該在於前進和後退的處理,其它的需求以下僅作簡單說明。
頁面的html主要結構如下:
<div class="main" id="main">
<div v-show="!edit" v-cloak>
<ul class="list">
<li @click="toggle(1)">
<label for="">手機</label>
<div class="list-info">{{showPhone}}</div>
</li>
<!-- 其他的基本資訊 -->
</ul>
<div class="btn">退出登入</div>
</div>
<div class="translate" :class="{open: edit}">
<div class="input-wrap">
<input type="text" class="input" v-model="phone" placeholder="請輸入手機">
</div>
<div class="input-wrap">
<input type="text" class="input" placeholder="請輸入收到的驗證碼" v-model="code">
<a class="input-btn">獲取驗證碼</a>
</div>
<div class="text-right">收不到?試試語音驗證碼</div>
<div class="btn btn-submit" @click="toggle(0)" :class="{disabled: !phone || !code}">提交</div>
</div>
</div>複製程式碼
以上將基本資訊頁面和編輯頁面分別包括在兩個同級的div
標籤內。
部分主要的CSS樣式如下(未作字首處理):
.translate {
position: fixed;
top: 0;
bottom: 0;
width: 100%;
background: #efeff4;
-webkit-transform: translateX(100%);
-webkit-transition: transform .4s;
}
.translate.open {
-webkit-transform: translateX(0);
}複製程式碼
預設情況下,編輯頁面平移至右側不可見的範圍內,當開啟編輯頁面時,新增open
類名,使其平移至可見範圍內。
接下來,用vue來實現頁面的基本的邏輯。基本的程式碼如下:
var vm = new Vue({
el: '#main',
data: {
edit: false,
phone: '',
code: ''
},
methods: {
toggle: function(value){
//這裡處理邏輯
}
},
computed: {
showPhone: function(){
return (this.phone || '13688888888').replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3')
}
}
});複製程式碼
以下通過兩種方法來完成以上的需求
方法一: 使用history API 來實現
HTML5位history提供了以下API:
window.history.pushState(stateObj, title, url)
: 向當前瀏覽記錄棧中新增一條新的歷史記錄,新增後頁面不會重新載入,引數分別代表:stateObj
:描述新記錄的物件或字串,方便以後使用,使用history.state
可以獲取,如{id: 0, name: 'home'}
title
: 一個字串,代表頁面的標題,目前多數瀏覽器基本會忽略該引數url
: 一個字串,新頁面的url地址window.history.replaceState(stateObj, title, url)
: 與pushState
一致,不同的是不會往歷史棧新增新記錄,而是替換當前的瀏覽記錄,常用於落地頁。popstate(e)事件
當使用者點選瀏覽器的前進
和後退
按鈕時,就會觸發該事件,事件接收一個引數,指向當前歷史記錄。前面設定的stateObj則是包含於此物件。e
列印出來格式如下:
有了以上的API,針對需求,實現的思路大致如下:
進入頁面後使用
replaceState
替換當前瀏覽記錄,stateObj
值為{page: 'home'}
點選手機編輯後,使用
pushState
往瀏覽記錄記錄新增新記錄,stateObj
值為{page: 'edit'}
,同時為編輯頁面新增open
類,使其進入可視範圍當進行前進和後退時,監聽
popstate
事件,並獲取當前的history.state
,通過判斷state.page
的值來實現頁面的切換
最終處理的邏輯如下:
var vm = new Vue({
el: '#main',
data: {...},
methods: {
toggle: function(value){
this.edit = value;//切換頁面
if(value){//如果切換到編輯頁面,則新增新的瀏覽記錄
history.pushState({page: 'edit'},'');
} else {//從編輯頁面回到基本資訊頁面
history.back();
}
}
},
computed: {...}
});
history.replaceState({page:'home'},'' );//進入頁面後替換當前瀏覽記錄
window.addEventListener('popstate', function(e){//監聽前進和後退
if (history.state) {
vm.edit = history.state.page == 'edit';//切換頁面
}
})複製程式碼
執行結果如下:
這樣子我們就藉助history API
實現了一個簡單的單頁面頁面切換,有了這個基礎就可以實現更復雜的應用,如果你還想在頁面切換的過程中改變url(這有利於SEO),則可以通過指定pushState
和replaceState
的url
引數來實現。
方法二: 使用location.hash(即錨點)
錨點一般用於頁面內的快速定位,通過指定錨點,可以使頁面跳轉至指定元素所在的位置。改變hash值具有不重新整理頁面的特點。使用錨點來操作瀏覽器的前進和後退主要用到以下兩個:
location.hash
: 獲取當前的錨點值,返回空字串或者如#detail
格式的值window.onhashchange
: HTML5新增的事件,用於監聽地址的hash值的改變,接收一個回撥函式作為引數,回到函式接收接收一個物件,物件的格式如下:
藉助hash
實現我們的需求的基本思路如下:
點選手機編輯後,改變
hash
值為'#edit'監聽
hash
值的變化,通過判斷值來控制頁面的切換(在這裡改變vm.edit
的值)
因此,完整的程式碼將變為如下:
var vm = new Vue({
el: '#main',
data: {...},
methods: {
toggle: function(value){
location.hash = value ? '#edit' : '';
}
},
computed: {...}
}
});
window.addEventListener('hashchange', function(e){
vm.edit = location.hash == '#edit';
})複製程式碼
在這裡例子中,這種方式看起的程式碼要比使用history API
的程式碼簡潔
執行結果如下(注意瀏覽器的地址變化):
總結
以上通過兩種方案實現了單頁面中頁面之前的切換並能夠操作瀏覽器的前進和後退。事實上,history API
和location.hash
的應用不僅僅侷限與此。除此之外,它們還可以應用於輪播效果、分頁、路由等場景。掌握了其使用,結合一些封裝,就能用於比較完整的系統中。