Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

原生JS实现hash路由 #119

Open
louzhedong opened this issue Jan 14, 2019 · 0 comments
Open

原生JS实现hash路由 #119

louzhedong opened this issue Jan 14, 2019 · 0 comments

Comments

@louzhedong
Copy link
Owner

熟悉React,vue等各类框架的同学也肯定都使用过他们各自的路由框架,用起来确实十分方便。对于单页应用来说,路由确实是一个非常重要的组成部分。众所周知,目前的路由可以分为hash-router和history-router。本文将用原生JS来实现一个简单的hash-router,功能包包括路由的切换已经历史记录的前进后退

实现原理

​ 在Javascript中有一个API,可以监听当前浏览器地址中hash的变化(即地址上#后面的部分),利用这一特点,当我们通过某些按钮改变地址的hash时,可以针对性地做出某些改变,这就是hash理由的主要实现原理

在线demo

具体的实现代码

// 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();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant