摘要:實(shí)現(xiàn)原理現(xiàn)在前端的路由實(shí)現(xiàn)一般有兩種,一種是路由,另外一種是路由。現(xiàn)在的前端主流框架的路由實(shí)現(xiàn)方式都會(huì)采用路由,本項(xiàng)目采用的也是。當(dāng)值發(fā)生改變的時(shí)候,我們可以通過事件監(jiān)聽到,從而在回調(diào)函數(shù)里面觸發(fā)某些方法。
效果圖:
項(xiàng)目地址:https://github.com/biaochenxuying/route
效果體驗(yàn)地址:
1. 滑動(dòng)效果: https://biaochenxuying.github.io/route/index.html
2. 淡入淡出效果: https://biaochenxuying.github.io/route/index2.html
1. 需求因?yàn)槲宜镜?H 5 的項(xiàng)目是用原生 js 寫的,要用到路由,但是現(xiàn)在好用的路由都是和某些框架綁定在一起的,比如 vue-router ,framework7 的路由;但是又沒必要為了一個(gè)路由功能而加入一套框架,現(xiàn)在自己寫一個(gè)輕量級(jí)的路由。
2. 實(shí)現(xiàn)原理現(xiàn)在前端的路由實(shí)現(xiàn)一般有兩種,一種是 Hash 路由,另外一種是 History 路由。
2.1 History 路由History 接口允許操作瀏覽器的曾經(jīng)在標(biāo)簽頁或者框架里訪問的會(huì)話歷史記錄。
屬性History.length 是一個(gè)只讀屬性,返回當(dāng)前 session 中的 history 個(gè)數(shù),包含當(dāng)前頁面在內(nèi)。舉個(gè)例子,對(duì)于新開一個(gè) tab 加載的頁面當(dāng)前屬性返回值 1 。
History.state 返回一個(gè)表示歷史堆棧頂部的狀態(tài)的值。這是一種可以不必等待 popstate?事件而查看狀態(tài)而的方式。
方法History.back()
前往上一頁, 用戶可點(diǎn)擊瀏覽器左上角的返回按鈕模擬此方法. 等價(jià)于 history.go(-1).
Note: 當(dāng)瀏覽器會(huì)話歷史記錄處于第一頁時(shí)調(diào)用此方法沒有效果,而且也不會(huì)報(bào)錯(cuò)。
History.forward()
在瀏覽器歷史記錄里前往下一頁,用戶可點(diǎn)擊瀏覽器左上角的前進(jìn)按鈕模擬此方法. 等價(jià)于 history.go(1).
Note: 當(dāng)瀏覽器歷史棧處于最頂端時(shí)( 當(dāng)前頁面處于最后一頁時(shí) )調(diào)用此方法沒有效果也不報(bào)錯(cuò)。
History.go(n)
通過當(dāng)前頁面的相對(duì)位置從瀏覽器歷史記錄( 會(huì)話記錄 )加載頁面。比如:參數(shù)為 -1的時(shí)候?yàn)樯弦豁摚瑓?shù)為 1 的時(shí)候?yàn)橄乱豁? 當(dāng)整數(shù)參數(shù)超出界限時(shí) ( 譯者注:原文為 When integerDelta is out of bounds ),例如: 如果當(dāng)前頁為第一頁,前面已經(jīng)沒有頁面了,我傳參的值為 -1,那么這個(gè)方法沒有任何效果也不會(huì)報(bào)錯(cuò)。調(diào)用沒有參數(shù)的 go() 方法或者不是整數(shù)的參數(shù)時(shí)也沒有效果。( 這點(diǎn)與支持字符串作為 url 參數(shù)的 IE 有點(diǎn)不同)。
history.pushState() 和 history.replaceState()
這兩個(gè) API 都接收三個(gè)參數(shù),分別是
a. 狀態(tài)對(duì)象(state object) — 一個(gè)JavaScript對(duì)象,與用 pushState() 方法創(chuàng)建的新歷史記錄條目關(guān)聯(lián)。無論何時(shí)用戶導(dǎo)航到新創(chuàng)建的狀態(tài),popstate 事件都會(huì)被觸發(fā),并且事件對(duì)象的state 屬性都包含歷史記錄條目的狀態(tài)對(duì)象的拷貝。
b. 標(biāo)題(title) — FireFox 瀏覽器目前會(huì)忽略該參數(shù),雖然以后可能會(huì)用上。考慮到未來可能會(huì)對(duì)該方法進(jìn)行修改,傳一個(gè)空字符串會(huì)比較安全。或者,你也可以傳入一個(gè)簡(jiǎn)短的標(biāo)題,標(biāo)明將要進(jìn)入的狀態(tài)。
c. 地址(URL) — 新的歷史記錄條目的地址。瀏覽器不會(huì)在調(diào)用 pushState() 方法后加載該地址,但之后,可能會(huì)試圖加載,例如用戶重啟瀏覽器。新的 URL 不一定是絕對(duì)路徑;如果是相對(duì)路徑,它將以當(dāng)前 URL 為基準(zhǔn);傳入的 URL 與當(dāng)前 URL 應(yīng)該是同源的,否則,pushState() 會(huì)拋出異常。該參數(shù)是可選的;不指定的話則為文檔當(dāng)前 URL。
相同之處: 是兩個(gè) API 都會(huì)操作瀏覽器的歷史記錄,而不會(huì)引起頁面的刷新。不同之處在于: pushState 會(huì)增加一條新的歷史記錄,而 replaceState 則會(huì)替換當(dāng)前的歷史記錄。
例子:
本來的路由
http://biaochenxuying.cn/
執(zhí)行:
window.history.pushState(null, null, "http://biaochenxuying.cn/home");
路由變成了:
http://biaochenxuying.cn/home
詳情介紹請(qǐng)看:MDN
2.2 Hash 路由我們經(jīng)常在 url 中看到 #,這個(gè) # 有兩種情況,一個(gè)是我們所謂的錨點(diǎn),比如典型的回到頂部按鈕原理、Github 上各個(gè)標(biāo)題之間的跳轉(zhuǎn)等,但是路由里的 # 不叫錨點(diǎn),我們稱之為 hash。
現(xiàn)在的前端主流框架的路由實(shí)現(xiàn)方式都會(huì)采用 Hash 路由,本項(xiàng)目采用的也是。
當(dāng) hash 值發(fā)生改變的時(shí)候,我們可以通過 hashchange 事件監(jiān)聽到,從而在回調(diào)函數(shù)里面觸發(fā)某些方法。
3. 代碼實(shí)現(xiàn) 3.1 簡(jiǎn)單版 - 單頁面路由先看個(gè)簡(jiǎn)單版的 原生 js 模擬 Vue 路由切換。
原理監(jiān)聽 hashchange ,hash 改變的時(shí)候,根據(jù)當(dāng)前的 hash 匹配相應(yīng)的 html 內(nèi)容,然后用 innerHTML 把 html 內(nèi)容放進(jìn) router-view 里面。
這個(gè)代碼是網(wǎng)上的:
3.2 復(fù)雜版 - 內(nèi)聯(lián)頁面版,帶緩存功能原生模擬 Vue 路由切換
首先前端用 js 實(shí)現(xiàn)路由的緩存功能是很難的,但像 vue-router 那種還好,因?yàn)橛?vue 框架和虛擬 dom 的技術(shù),可以保存當(dāng)前頁面的數(shù)據(jù)。
要做緩存功能,首先要知道瀏覽器的 前進(jìn)、刷新、回退 這三個(gè)操作。
但是瀏覽器中主要有這幾個(gè)限制:
沒有提供監(jiān)聽前進(jìn)后退的事件
不允許開發(fā)者讀取瀏覽記錄
用戶可以手動(dòng)輸入地址,或使用瀏覽器提供的前進(jìn)后退來改變 url
所以要自定義路由,解決方案是自己維護(hù)一份路由歷史的記錄,存在一個(gè)數(shù)組里面,從而區(qū)分 前進(jìn)、刷新、回退。
url 存在于瀏覽記錄中即為后退,后退時(shí),把當(dāng)前路由后面的瀏覽記錄刪除。
url 不存在于瀏覽記錄中即為前進(jìn),前進(jìn)時(shí),往數(shù)組里面 push 當(dāng)前的路由。
url 在瀏覽記錄的末端即為刷新,刷新時(shí),不對(duì)路由數(shù)組做任何操作。
另外,應(yīng)用的路由路徑中可能允許相同的路由出現(xiàn)多次(例如 A -> B -> A),所以給每個(gè)路由添加一個(gè) key 值來區(qū)分相同路由的不同實(shí)例。
這個(gè)瀏覽記錄需要存儲(chǔ)在 sessionStorage 中,這樣用戶刷新后瀏覽記錄也可以恢復(fù)。
像 vue-router 那樣,提供了一個(gè) router-link 組件來導(dǎo)航,而我這個(gè)框架也提供了一個(gè) linkTo 的方法。
// 生成不同的 key function genKey() { var t = "xxxxxxxx" return t.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0 var v = c === "x" ? r : (r & 0x3 | 0x8) return v.toString(16) }) } // 初始化跳轉(zhuǎn)方法 window.linkTo = function(path) { if (path.indexOf("?") !== -1) { window.location.hash = path + "&key=" + genKey() } else { window.location.hash = path + "?key=" + genKey() } }
用法:
//1. 直接用 a 標(biāo)簽 列表1 //2. 標(biāo)簽加 js 調(diào)用方法首頁// 3. js 調(diào)用觸發(fā) linkTo("#/list")
定義好要用到的變量
function Router() { this.routes = {}; //保存注冊(cè)的所有路由 this.beforeFun = null; //切換前 this.afterFun = null; // 切換后 this.routerViewId = "#routerView"; // 路由掛載點(diǎn) this.redirectRoute = null; // 路由重定向的 hash this.stackPages = true; // 多級(jí)頁面緩存 this.routerMap = []; // 路由遍歷 this.historyFlag = "" // 路由狀態(tài),前進(jìn),回退,刷新 this.history = []; // 路由歷史 this.animationName = "slide" // 頁面切換時(shí)的動(dòng)畫 }
包括:初始化、注冊(cè)路由、歷史記錄、切換頁面、切換頁面的動(dòng)畫、切換之前的鉤子、切換之后的鉤子、滾動(dòng)位置的處理,緩存。
Router.prototype = { init: function(config) { var self = this; this.routerMap = config ? config.routes : this.routerMap this.routerViewId = config ? config.routerViewId : this.routerViewId this.stackPages = config ? config.stackPages : this.stackPages var name = document.querySelector("#routerView").getAttribute("data-animationName") if (name) { this.animationName = name } this.animationName = config ? config.animationName : this.animationName if (!this.routerMap.length) { var selector = this.routerViewId + " .page" var pages = document.querySelectorAll(selector) for (var i = 0; i < pages.length; i++) { var page = pages[i]; var hash = page.getAttribute("data-hash") var name = hash.substr(1) var item = { path: hash, name: name, callback: util.closure(name) } this.routerMap.push(item) } } this.map() // 初始化跳轉(zhuǎn)方法 window.linkTo = function(path) { console.log("path :", path) if (path.indexOf("?") !== -1) { window.location.hash = path + "&key=" + util.genKey() } else { window.location.hash = path + "?key=" + util.genKey() } } //頁面首次加載 匹配路由 window.addEventListener("load", function(event) { // console.log("load", event); self.historyChange(event) }, false) //路由切換 window.addEventListener("hashchange", function(event) { // console.log("hashchange", event); self.historyChange(event) }, false) }, // 路由歷史紀(jì)錄變化 historyChange: function(event) { var currentHash = util.getParamsUrl(); var nameStr = "router-" + (this.routerViewId) + "-history" this.history = window.sessionStorage[nameStr] ? JSON.parse(window.sessionStorage[nameStr]) : [] var back = false, refresh = false, forward = false, index = 0, len = this.history.length; for (var i = 0; i < len; i++) { var h = this.history[i]; if (h.hash === currentHash.path && h.key === currentHash.query.key) { index = i if (i === len - 1) { refresh = true } else { back = true } break; } else { forward = true } } if (back) { this.historyFlag = "back" this.history.length = index + 1 } else if (refresh) { this.historyFlag = "refresh" } else { this.historyFlag = "forward" var item = { key: currentHash.query.key, hash: currentHash.path, query: currentHash.query } this.history.push(item) } console.log("historyFlag :", this.historyFlag) // console.log("history :", this.history) if (!this.stackPages) { this.historyFlag = "forward" } window.sessionStorage[nameStr] = JSON.stringify(this.history) this.urlChange() }, // 切換頁面 changeView: function(currentHash) { var pages = document.getElementsByClassName("page") var previousPage = document.getElementsByClassName("current")[0] var currentPage = null var currHash = null for (var i = 0; i < pages.length; i++) { var page = pages[i]; var hash = page.getAttribute("data-hash") page.setAttribute("class", "page") if (hash === currentHash.path) { currHash = hash currentPage = page } } var enterName = "enter-" + this.animationName var leaveName = "leave-" + this.animationName if (this.historyFlag === "back") { util.addClass(currentPage, "current") if (previousPage) { util.addClass(previousPage, leaveName) } setTimeout(function() { if (previousPage) { util.removeClass(previousPage, leaveName) } }, 250); } else if (this.historyFlag === "forward" || this.historyFlag === "refresh") { if (previousPage) { util.addClass(previousPage, "current") } util.addClass(currentPage, enterName) setTimeout(function() { if (previousPage) { util.removeClass(previousPage, "current") } util.removeClass(currentPage, enterName) util.addClass(currentPage, "current") }, 350); // 前進(jìn)和刷新都執(zhí)行回調(diào) 與 初始滾動(dòng)位置為 0 currentPage.scrollTop = 0 this.routes[currHash].callback ? this.routes[currHash].callback(currentHash) : null } this.afterFun ? this.afterFun(currentHash) : null }, //路由處理 urlChange: function() { var currentHash = util.getParamsUrl(); if (this.routes[currentHash.path]) { var self = this; if (this.beforeFun) { this.beforeFun({ to: { path: currentHash.path, query: currentHash.query }, next: function() { self.changeView(currentHash) } }) } else { this.changeView(currentHash) } } else { //不存在的地址,重定向到默認(rèn)頁面 location.hash = this.redirectRoute } }, //路由注冊(cè) map: function() { for (var i = 0; i < this.routerMap.length; i++) { var route = this.routerMap[i] if (route.name === "redirect") { this.redirectRoute = route.path } else { this.redirectRoute = this.routerMap[0].path } var newPath = route.path var path = newPath.replace(/s*/g, ""); //過濾空格 this.routes[path] = { callback: route.callback, //回調(diào) } } }, //切換之前的鉤子 beforeEach: function(callback) { if (Object.prototype.toString.call(callback) === "[object Function]") { this.beforeFun = callback; } else { console.trace("路由切換前鉤子函數(shù)不正確") } }, //切換成功之后的鉤子 afterEach: function(callback) { if (Object.prototype.toString.call(callback) === "[object Function]") { this.afterFun = callback; } else { console.trace("路由切換后回調(diào)函數(shù)不正確") } } }
window.Router = Router; window.router = new Router();
完整代碼:https://github.com/biaochenxu...
callback 是切換頁面后,執(zhí)行的回調(diào)
參考項(xiàng)目:https://github.com/kliuj/spa-...
5. 最后項(xiàng)目地址:https://github.com/biaochenxuying/route
博客常更地址1 :https://github.com/biaochenxuying/blog
博客常更地址2 :http://biaochenxuying.cn/main.html
足足一個(gè)多月沒有更新文章了,因?yàn)轫?xiàng)目太緊,加班加班啊,趁著在家有空,趕緊寫下這篇干貨,免得忘記了,希望對(duì)大家有所幫助。
如果您覺得這篇文章不錯(cuò)或者對(duì)你有所幫助,請(qǐng)點(diǎn)個(gè)贊,謝謝。
對(duì) 全棧修煉 有興趣的朋友可以掃下方二維碼關(guān)注我的公眾號(hào)
我會(huì)不定期更新有價(jià)值的內(nèi)容,長(zhǎng)期運(yùn)營(yíng)。
關(guān)注公眾號(hào)并回復(fù) 福利 可領(lǐng)取免費(fèi)學(xué)習(xí)資料,福利詳情請(qǐng)猛戳: Python、Java、Linux、Go、node、vue、react、javaScript
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/108955.html
摘要:什么是前后端同構(gòu)明確三個(gè)概念后端渲染指?jìng)鹘y(tǒng)的或的渲染機(jī)制前端渲染指使用來渲染頁面大部分內(nèi)容,代表是現(xiàn)在流行的單頁面應(yīng)用同構(gòu)渲染指前后端共用,首次渲染時(shí)使用來直出。 什么是前后端同構(gòu) 明確三個(gè)概念:「后端渲染」指?jìng)鹘y(tǒng)的 ASP、Java 或 PHP 的渲染機(jī)制;「前端渲染」指使用 JS 來渲染頁面大部分內(nèi)容,代表是現(xiàn)在流行的 SPA 單頁面應(yīng)用;「同構(gòu)渲染」指前后端共用 JS,首次渲染時(shí)...
摘要:如何實(shí)現(xiàn)前端路由要實(shí)現(xiàn)前端路由,需要解決兩個(gè)核心如何改變卻不引起頁面刷新如何檢測(cè)變化了下面分別使用和兩種實(shí)現(xiàn)方式回答上面的兩個(gè)核心問題。 原文鏈接:github.com/whinc/blog/… 在單頁應(yīng)用如此流行的今天,曾經(jīng)令人驚嘆的前端路由已經(jīng)成為各大框架的基礎(chǔ)標(biāo)配,每個(gè)框架都提供了強(qiáng)大的路由功能,導(dǎo)致路由實(shí)現(xiàn)變的復(fù)雜。想要搞懂路由內(nèi)部實(shí)現(xiàn)還是有些困難的,但是如果只想了解路由實(shí)現(xiàn)基本...
摘要:使用值來作路由。原生應(yīng)用本身就是多頁的場(chǎng)景,頁面間狀態(tài)的隔離比共享更重要一些。使用開發(fā)的是原生應(yīng)用,頁面棧的管理使用的也是原生的特性,沒有但是有模塊可以實(shí)現(xiàn)頁面的前進(jìn)和后退等操作。 系列文章的目錄在 ? 這里 (由于 我比較懶 最近一段時(shí)間在忙其他事,系列文章拖了好久終于又更新了。。。) 什么是 vue-router ? vue-router 官方文檔 vue-router 是針對(duì) V...
摘要:這里借鑒了一下的處理方式,我們把單獨(dú)模塊的包裝成一個(gè)函數(shù),提供一個(gè)全局的回調(diào)方法,加載完成時(shí)候再調(diào)用回調(diào)函數(shù)。 前端路由實(shí)現(xiàn)之 #hash 先上github項(xiàng)目地址: spa-routers運(yùn)行效果圖showImg(https://segmentfault.com/img/bVFi7l?w=581&h=312); 背景介紹 用了許多前端框架來做spa應(yīng)用,比如說backbone,ang...
閱讀 2238·2021-09-23 11:52
閱讀 1906·2021-09-02 15:41
閱讀 3026·2019-08-30 10:47
閱讀 1989·2019-08-29 17:14
閱讀 2346·2019-08-29 16:16
閱讀 3195·2019-08-28 18:29
閱讀 3429·2019-08-26 13:30
閱讀 2615·2019-08-26 10:49