Open
Description
熟悉React,vue等各类框架的同学也肯定都使用过他们各自的路由框架,用起来确实十分方便。对于单页应用来说,路由确实是一个非常重要的组成部分。众所周知,目前的路由可以分为hash-router和history-router。本文将用原生JS来实现一个简单的hash-router,功能包包括路由的切换已经历史记录的前进后退
实现原理
在Javascript中有一个API,可以监听当前浏览器地址中hash的变化(即地址上#后面的部分),利用这一特点,当我们通过某些按钮改变地址的hash时,可以针对性地做出某些改变,这就是hash理由的主要实现原理
具体的实现代码
// html
<main>
<div class="nav">
<ul class="nav-list">
<li class="nav-item index active"><a href="#/index">推荐</a></li>
<li class="nav-item rank"><a href="#/rank">排行榜</a></li>
<li class="nav-item songList"><a href="#/songList">歌单</a></li>
<li class="nav-item broadcast"><a href="#/broadcast">主播电台</a></li>
<li class="nav-item newest"><a href="#/newest">最新音乐</a></li>
</ul>
</div>
<div class="main-content">
<div class="main-box index">推荐</div>
<div class="main-box rank">排行榜</div>
<div class="main-box songList">歌单</div>
<div class="main-box broadcast">主播电台</div>
<div class="main-box newest">最新音乐</div>
</div>
</main>
<div class="nav-area">
<div class="nav-area-back" onclick="handleRouterBack()">后退</div>
<div class="nav-area-front" onclick="handleRouterFront()">前进</div>
</div>
// css
.nav-list {
list-style: none;
line-height: 45px;
text-align: center;
}
.nav-list .nav-item {
display: inline-block;
margin: 0 30px;
}
.nav-list .nav-item:first-child {
margin-left: 0;
}
.nav-list .nav-item:last-child {
margin-right: 0;
}
.nav-list .nav-item a {
text-decoration: none;
color: #000;
font-size: 14px;
cursor: pointer;
}
.nav-list .nav-item.active a {
color: #B92500;
}
.nav-list .nav-item a:hover {
color: #B92500;
}
.main-content .main-box {
display: none;
}
.main-content .main-box-show {
display: block;
}
.nav-area {
position: absolute;
top: 20px;
left:50px;
}
.nav-area-back, .nav-area-front {
display: inline-block;
}
.nav-area .active {
color: #C52C04;
cursor: pointer;
}
// javascript
function Router(callback) {
this.routes = {}; // 消费者模式,将对应路径的执行函数存入一个map里
this.routeHistory = []; // 路由历史
this.currentUrl = ''; // 当前的路由地址
this.currentIndex = -1; // 当前的路由序列号
this.frontOrBack = false; // 是否的点击前进后退造成的路由变化,此时不需要监听到路由变化函数
this.callback = callback; // 每次路由改变后调用此回调函数,将currentIndex, routeHistory 传出去,将后续依赖路由的操作与路由本身解耦
}
// 注册路由对象,将对应路径的执行函数存入一个map里
Router.prototype.route = function (path, callback) {
this.routes[path] = callback || function () { };
}
// 监听路由变化
Router.prototype.refersh = function () {
if (this.frontOrBack) { // 前进后退造成的路由变化,此时不需要改变routeHistory的数据
this.frontOrBack = false;
} else {
this.currentUrl = location.hash.slice(1) || '/index';
this.routeHistory = this.routeHistory.slice(0, this.currentIndex + 1);
this.routeHistory.push(this.currentUrl);
this.currentIndex++;
}
// 执行对应路由绑定的函数
this.routes[this.currentUrl]();
this.callback(this.currentIndex, this.routeHistory);
}
// 路由后退
Router.prototype.back = function () {
if (this.currentIndex > 0) {
this.frontOrBack = true;
this.currentIndex--;
this.currentUrl = this.routeHistory[this.currentIndex];
window.location.hash = this.currentUrl;
}
}
// 路由前进
Router.prototype.front = function () {
const historyLength = this.routeHistory.length;
if (this.currentIndex < historyLength - 1) {
this.frontOrBack = true;
this.currentIndex++;
this.currentUrl = this.routeHistory[this.currentIndex];
window.location.hash = this.currentUrl;
}
}
Router.prototype.init = function () {
window.addEventListener('load', this.refersh.bind(this), false);
// 监听hash的变化
window.addEventListener('hashchange', this.refersh.bind(this), false);
}
const router = new Router((currentIndex, routeHistory) => {
// 如果当前路由前面还有路由
let backDom = document.querySelector('.nav-area-back');
if (backDom) {
if (currentIndex > 0) {
backDom.classList.add('active');
} else {
backDom.classList.remove('active');
}
}
// 如果当前路由后还有路由
let frontDom = document.querySelector(".nav-area-front");
if (frontDom) {
if (currentIndex < routeHistory.length - 1) {
frontDom.classList.add('active');
} else {
frontDom.classList.remove('active');
}
}
});
router.init();
const tabs = [
{
name: '推荐',
path: '/index'
},
{
name: '排行榜',
path: '/rank'
},
{
name: '歌单',
path: '/songList'
},
{
name: '主播电台',
path: '/broadcast'
},
{
name: '最新音乐',
path: '/newest'
}
]
tabs.forEach(item => {
router.route(item.path, () => {
const tabName = item.path.split('/')[1];
const tabDom = document.querySelectorAll('.main-box');
const navDom = document.querySelectorAll('.nav-item');
const tabDomLength = tabDom.length;
const navDomLength = navDom.length;
for (let i = 0; i < tabDomLength; i++) {
if (tabDom[i].classList.contains(tabName)) {
tabDom[i].classList.add('main-box-show');
} else {
tabDom[i].classList.remove('main-box-show');
}
}
for (let i = 0; i < navDomLength; i++) {
if (navDom[i].classList.contains(tabName)) {
navDom[i].classList.add('active');
} else {
navDom[i].classList.remove('active');
}
}
})
})
function handleRouterBack() {
router.back();
}
function handleRouterFront() {
router.front();
}
Metadata
Assignees
Labels
No labels