### 寫一個頁面路由轉場的管理工具
其實這個根本不復雜,主要是兩個技術,一個是管理location.hash,一個是頁面的切換/轉場動畫,然后就是維護頁面和hash的路由控制。[SUI Mobile](http://m.sui.taobao.org/)可以說就是這么一個東西。那么有沒有代碼量較少的可以參考學習一下呢,
這兒就有一個:[WeUI](https://weui.io/) ; 代碼在: https://weui.io/example.js
這個小實例就實現了一個簡單的單頁路由,盡管實現比較簡單,也沒有做兼容性,不過這個小代碼還是很有參考和學習價值的,它展示了單頁路由轉場的核心思想,而SM加強的部分其實就是,頁面是動態加載過來的,并且還有緩存機制等等。不管怎樣,這個小實例還是實現了單頁應用路由轉場的主要效果和功能,讓我們窺見了單頁路由轉場技術的真面目。
```javascript
/**
* Created by jf on 2015/9/11.
* Modified by bear on 2016/9/7.
*/
$(function () {
var pageManager = {
$container: $('#container'),
_pageStack: [],
_configs: [],
_pageAppend: function(){},
_defaultPage: null,
_pageIndex: 1,
setDefault: function (defaultPage) {
this._defaultPage = this._find('name', defaultPage);
return this;
},
setPageAppend: function (pageAppend) {
this._pageAppend = pageAppend;
return this;
},
init: function () {
var self = this;
$(window).on('hashchange', function () {
var state = history.state || {};
var url = location.hash.indexOf('#') === 0 ? location.hash : '#';
var page = self._find('url', url) || self._defaultPage;
if (state._pageIndex <= self._pageIndex || self._findInStack(url)) {
self._back(page);
} else {
self._go(page);
}
});
if (history.state && history.state._pageIndex) {
this._pageIndex = history.state._pageIndex;
}
this._pageIndex--;
var url = location.hash.indexOf('#') === 0 ? location.hash : '#';
var page = self._find('url', url) || self._defaultPage;
this._go(page);
return this;
},
push: function (config) {
this._configs.push(config);
return this;
},
go: function (to) {
var config = this._find('name', to);
if (!config) {
return;
}
location.hash = config.url;
},
_go: function (config) {
this._pageIndex ++;
history.replaceState && history.replaceState({_pageIndex: this._pageIndex}, '', location.href);
var html = $(config.template).html();
var $html = $(html).addClass('slideIn').addClass(config.name);
$html.on('animationend webkitAnimationEnd', function(){
$html.removeClass('slideIn').addClass('js_show');
});
this.$container.append($html);
this._pageAppend.call(this, $html);
this._pageStack.push({
config: config,
dom: $html
});
if (!config.isBind) {
this._bind(config);
}
return this;
},
back: function () {
history.back();
},
_back: function (config) {
this._pageIndex --;
var stack = this._pageStack.pop();
if (!stack) {
return;
}
var url = location.hash.indexOf('#') === 0 ? location.hash : '#';
var found = this._findInStack(url);
if (!found) {
var html = $(config.template).html();
var $html = $(html).addClass('js_show').addClass(config.name);
$html.insertBefore(stack.dom);
if (!config.isBind) {
this._bind(config);
}
this._pageStack.push({
config: config,
dom: $html
});
}
stack.dom.addClass('slideOut').on('animationend webkitAnimationEnd', function () {
stack.dom.remove();
});
return this;
},
_findInStack: function (url) {
var found = null;
for(var i = 0, len = this._pageStack.length; i < len; i++){
var stack = this._pageStack[i];
if (stack.config.url === url) {
found = stack;
break;
}
}
return found;
},
_find: function (key, value) {
var page = null;
for (var i = 0, len = this._configs.length; i < len; i++) {
if (this._configs[i][key] === value) {
page = this._configs[i];
break;
}
}
return page;
},
_bind: function (page) {
var events = page.events || {};
for (var t in events) {
for (var type in events[t]) {
this.$container.on(type, t, events[t][type]);
}
}
page.isBind = true;
}
};
function fastClick(){
var supportTouch = function(){
try {
document.createEvent("TouchEvent");
return true;
} catch (e) {
return false;
}
}();
var _old$On = $.fn.on;
$.fn.on = function(){
if(/click/.test(arguments[0]) && typeof arguments[1] == 'function' && supportTouch){ // 只擴展支持touch的當前元素的click事件
var touchStartY, callback = arguments[1];
_old$On.apply(this, ['touchstart', function(e){
touchStartY = e.changedTouches[0].clientY;
}]);
_old$On.apply(this, ['touchend', function(e){
if (Math.abs(e.changedTouches[0].clientY - touchStartY) > 10) return;
e.preventDefault();
callback.apply(this, [e]);
}]);
}else{
_old$On.apply(this, arguments);
}
return this;
};
}
function preload(){
$(window).on("load", function(){
var imgList = [
"./images/layers/content.png",
"./images/layers/navigation.png",
"./images/layers/popout.png",
"./images/layers/transparent.gif"
];
for (var i = 0, len = imgList.length; i < len; ++i) {
new Image().src = imgList[i];
}
});
}
function androidInputBugFix(){
// .container 設置了 overflow 屬性, 導致 Android 手機下輸入框獲取焦點時, 輸入法擋住輸入框的 bug
// 相關 issue: https://github.com/weui/weui/issues/15
// 解決方法:
// 0. .container 去掉 overflow 屬性, 但此 demo 下會引發別的問題
// 1. 參考 http://stackoverflow.com/questions/23757345/android-does-not-correctly-scroll-on-input-focus-if-not-body-element
// Android 手機下, input 或 textarea 元素聚焦時, 主動滾一把
if (/Android/gi.test(navigator.userAgent)) {
window.addEventListener('resize', function () {
if (document.activeElement.tagName == 'INPUT' || document.activeElement.tagName == 'TEXTAREA') {
window.setTimeout(function () {
document.activeElement.scrollIntoViewIfNeeded();
}, 0);
}
})
}
}
function setJSAPI(){
var option = {
title: 'WeUI, 為微信 Web 服務量身設計',
desc: 'WeUI, 為微信 Web 服務量身設計',
link: "https://weui.io",
imgUrl: 'https://mmbiz.qpic.cn/mmemoticon/ajNVdqHZLLA16apETUPXh9Q5GLpSic7lGuiaic0jqMt4UY8P4KHSBpEWgM7uMlbxxnVR7596b3NPjUfwg7cFbfCtA/0'
};
$.getJSON('https://weui.io/api/sign?url=' + encodeURIComponent(location.href.split('#')[0]), function (res) {
wx.config({
beta: true,
debug: false,
appId: res.appid,
timestamp: res.timestamp,
nonceStr: res.nonceStr,
signature: res.signature,
jsApiList: [
'onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareQQ',
'onMenuShareWeibo',
'onMenuShareQZone',
// 'setNavigationBarColor',
'setBounceBackground'
]
});
wx.ready(function () {
/*
wx.invoke('setNavigationBarColor', {
color: '#F8F8F8'
});
*/
wx.invoke('setBounceBackground', {
'backgroundColor': '#F8F8F8',
'footerBounceColor' : '#F8F8F8'
});
wx.onMenuShareTimeline(option);
wx.onMenuShareQQ(option);
wx.onMenuShareAppMessage({
title: 'WeUI',
desc: '為微信 Web 服務量身設計',
link: location.href,
imgUrl: 'https://mmbiz.qpic.cn/mmemoticon/ajNVdqHZLLA16apETUPXh9Q5GLpSic7lGuiaic0jqMt4UY8P4KHSBpEWgM7uMlbxxnVR7596b3NPjUfwg7cFbfCtA/0'
});
});
});
}
function setPageManager(){
var pages = {}, tpls = $('script[type="text/html"]');
var winH = $(window).height();
for (var i = 0, len = tpls.length; i < len; ++i) {
var tpl = tpls[i], name = tpl.id.replace(/tpl_/, '');
pages[name] = {
name: name,
url: '#' + name,
template: '#' + tpl.id
};
}
pages.home.url = '#';
for (var page in pages) {
pageManager.push(pages[page]);
}
pageManager
.setPageAppend(function($html){
var $foot = $html.find('.page__ft');
if($foot.length < 1) return;
if($foot.position().top + $foot.height() < winH){
$foot.addClass('j_bottom');
}else{
$foot.removeClass('j_bottom');
}
})
.setDefault('home')
.init();
}
function init(){
preload();
fastClick();
androidInputBugFix();
setJSAPI();
setPageManager();
window.pageManager = pageManager;
window.home = function(){
location.hash = '';
};
}
init();
});
```
### 參考
- [javascript history對象(歷史記錄)使用方法(實現瀏覽器前進后退)](http://www.jb51.net/article/45306.htm)
- [location.hash詳解](http://blog.csdn.net/baidu_31333625/article/details/54288223)
- [操縱瀏覽器的歷史記錄](https://developer.mozilla.org/zh-CN/docs/DOM/Manipulating_the_browser_history)
- [使用ajax和history.pushState無刷新改變頁面URL](http://blog.csdn.net/oyiboy/article/details/44258477)
- [如何讓搜索引擎抓取AJAX內容?](http://www.ruanyifeng.com/blog/2013/07/how_to_make_search_engines_find_ajax_content.html)
- [ajax與HTML5 history pushState/replaceState實例](http://www.zhangxinxu.com/wordpress/2013/06/html5-history-api-pushstate-replacestate-ajax/)
- [web app](http://www.hmoore.net/xiak/quanduan/255922)
- [Pjax是什么以及為什么推薦大家用](https://my.oschina.net/sub/blog/123447)
- [圖解用HTML5的popstate如何玩轉瀏覽器歷史記錄 - 水乙 - 博客園](https://www.cnblogs.com/shuiyi/p/5115188.html)
* * * * *
[javascript - 關于路由的問題 - SegmentFault](https://segmentfault.com/q/1010000010878501)
> 我還是把我在github找到的答案貼出來吧:前端路由,即由前端來維護一個路由規則。實現有兩種,一種是利用url 的hash,就是常說的錨點(#),JS 通過hashChange 事件來監聽url 的改變,IE7 及以下需要輪詢;另一種就是HTML5 的History 模式,它使url 看起來像普通網站那樣,以“/”分割,沒有#,但頁面并沒有跳轉,不過使用這種模式需要服務端支持,服務端在接受到所有的請求后,都指向同一個html 文件,不然會出現404。所以,SPA 只有一個html,整個完整所有的內容都在這一個html 里,通過js 來處理
[讓你徹底搞懂前端路由前世今生!](https://www.toutiao.com/a6517043046805144067/?tt_from=weixin&utm_campaign=client_share×tamp=1517478446&app=news_article&utm_source=weixin&iid=22069500288&utm_medium=toutiao_android&wxshare_count=1)
[vue 單頁應用(spa)前端路由實現原理](https://www.toutiao.com/a6539864898476704264/?tt_from=weixin&utm_campaign=client_share×tamp=1522838130&app=news_article_lite&utm_source=weixin&iid=25315997380&utm_medium=toutiao_android&wxshare_count=1)
[JavaScript之Vue、React路由原理及實現](https://www.toutiao.com/a6540404803082650115/?tt_from=weixin&utm_campaign=client_share×tamp=1522842141&app=news_article_lite&utm_source=weixin&iid=25315997380&utm_medium=toutiao_android&wxshare_count=1)
[面試官: 你了解前端路由嗎? - 掘金](https://juejin.im/post/5ac61da66fb9a028c71eae1b)
[Turbolinks5 概述及實現原理 | WinDy Blog](https://yafeilee.me/blogs/88)
[前端路由跳轉基本原理](https://mp.weixin.qq.com/s/xVDUtqA-V3jXO_-orFDfCg)
[令人驚嘆的前端路由原理解析和實現方式](https://mp.weixin.qq.com/s/FyXGkW5CMbtPX_t0cShgCA)
[## 前端路由跳轉基本原理](https://mp.weixin.qq.com/s/IyPpSix7haY_NfNJg10ilg)
[前端路由跳轉基本原理](https://mp.weixin.qq.com/s/xVDUtqA-V3jXO_-orFDfCg)
[React 路由狀態管理總結](https://mp.weixin.qq.com/s/yry-hmjb-iAvZ3N0hPsUMg)
* * * * *
last update:2018-2-1 17:50:31
- 開始
- 微信小程序
- 獲取用戶信息
- 記錄
- HTML
- HTML5
- 文檔根節點
- 你真的了解script標簽嗎?
- 文檔結構
- 已經落后的技術
- form表單
- html實體
- CSS
- css優先級 & 設計模式
- 如何編寫高效的 CSS 選擇符
- 筆記
- 小計
- flex布局
- 細節體驗
- Flex
- Grid
- tailwindcss
- JavaScript
- javascript物語
- js函數定義
- js中的數組對象
- js的json解析
- js中數組的操作
- js事件冒泡
- js中的判斷
- js語句聲明會提前
- cookie操作
- 關于javascript你要知道的
- 關于innerHTML的試驗
- js引擎與GUI引擎是互斥的
- 如何安全的修改對象
- 當渲染引擎遇上強迫癥
- 不要使用連相等
- 修改數組-對象
- 算法-函數
- 事件探析
- 事件循環
- js事件循環中的上下文和作用域的經典問題
- Promise
- 最佳實踐
- 頁面遮罩加載效果
- 網站靜態文件之思考
- 圖片加載問題
- 路由及轉場解決方案
- web app
- 寫一個頁面路由轉場的管理工具
- 談編程
- 技術/思想的斗爭
- 前端技術選型分析
- 我想放點html模板代碼
- 開發自適應網頁
- 后臺前端項目的開發
- 網站PC版和移動版的模板方案
- 前后端分離
- 淘寶前后端分離
- 前后端分離的思考與實踐(一)
- 前后端分離的思考與實踐(二)
- 前后端分離的思考與實踐(三)
- 前后端分離的思考與實踐(四)
- 前后端分離的思考與實踐(五)
- 前后端分離的思考與實踐(六)
- 動畫
- 開發小技巧
- Axios
- 屏幕適配
- 理論基礎
- 思考
- flexible.js原理
- 實驗
- rem的坑,為什么要設置成百分比,為什么又是62.5%
- 為什么以一個標準適配的,其它寬度也能同等適配
- 自適應、響應式、彈性布局、屏幕適配
- 適配:都用百分比?
- 番外篇
- 給你看看0.5px長什么樣?
- 用事實證明viewport scale縮放不會改變rem元素的大小
- 為什么PC端頁面縮放不會影響rem元素
- 究竟以哪個為設備獨立像素
- PC到移動端初試
- 深入理解px
- 響應式之柵格系統
- 深入理解px(二)
- 一篇搞定移動端適配
- flex版柵格布局
- 其他
- 瀏覽器加載初探
- 警惕你的開發工具
- JS模塊化
- webpack
- 打包原理
- 異步加載
- gulp
- 命名規范
- 接口開發
- sea.js學習
- require.js學習
- react學習
- react筆記
- vue學習
- vue3
- 工具、技巧
- 臨時筆記
- 怎么維護好開源項目
- 待辦
- 對前端MVV*C框架的思考
- jquery問題
- 臨時
- 好文
- 節流防抖