Изображение эффекта:
адрес проекта:GitHub.com/Tablesink нужно…
Адрес эффекта:
1. Эффект скольжения: https://biaochenxuying.github.io/route/index.html
2. Эффект постепенного появления и исчезновения: https://biaochenxuying.github.io/route/index2.html
1. Спрос
Так как проект H 5 нашей компании написан на нативном js, используется роутинг, но теперь удобный роутинг привязан к некоторым фреймворкам, таким как vue-router, framework7 роутинг; необходимо добавить набор фреймворков для функцию маршрутизации, а теперь напишите облегченную маршрутизацию самостоятельно.
2. Принцип реализации
Обычно существует два типа реализаций внешней маршрутизации: одна — хэш-маршрутизация, а другая — маршрутизация по истории.
2.1 Маршрутизация истории
Интерфейс «История» позволяет управлять историей сеансов браузера, к которым обращались на вкладке или фрейме.
Атрибуты
- History.length — это свойство только для чтения, которое возвращает количество истории в текущем сеансе, включая текущую страницу. Например, верните значение 1 для текущего свойства страницы, загруженной новой вкладкой.
- History.state возвращает значение, представляющее состояние в верхней части стека истории. Это способ просмотреть состояние, не дожидаясь события popstate.
метод
- History.back()
Чтобы перейти на предыдущую страницу, пользователь может щелкнуть кнопку «Назад» в верхнем левом углу браузера, чтобы имитировать этот метод, эквивалентный history.go(-1).
Примечание. Вызов этого метода, когда история сеансов браузера находится на первой странице, не имеет никакого эффекта, и об ошибке не сообщается.
- History.forward()
Чтобы перейти на следующую страницу в истории браузера, пользователь может щелкнуть кнопку «Вперед» в верхнем левом углу браузера, чтобы имитировать этот метод, эквивалентный history.go(1).
Примечание. Вызов этого метода, когда стек истории браузера находится вверху (когда текущая страница находится на последней странице), не имеет никакого эффекта, и об ошибке не сообщается.
- History.go(n)
Загрузить страницу из истории браузера (записи сеанса) с ее относительным положением относительно текущей страницы. Например: когда параметр равен -1, это предыдущая страница, а когда параметр равен 1, это следующая страница.Когда целочисленный параметр выходит за пределы (Примечание переводчика: исходный текст Когда целоеДельта выходит за пределы границ), например: если текущая страница является первой страницей, перед ней нет страницы, значение параметра, которое я передаю, равно -1, то этот метод не имеет никакого эффекта и не сообщит об ошибке. Вызов метода go() без аргументов или аргументов, не являющихся целыми числами, также не имеет никакого эффекта. (Это немного отличается от IE, который поддерживает строки в качестве параметров URL).
- history.pushState() и history.replaceState()
Оба API получают три параметра:
а. государственный объект— Объект JavaScript, связанный с новой записью в истории, созданной с помощью метода pushState(). Всякий раз, когда пользователь переходит к вновь созданному состоянию, запускается событие popstate, а свойство состояния объекта события содержит копию объекта состояния записи истории.
б. Название— Браузер FireFox в настоящее время игнорирует этот параметр, хотя он может быть использован в будущем. Учитывая, что этот метод может быть изменен в будущем, безопаснее передавать пустую строку. В качестве альтернативы вы можете передать короткий заголовок, указывающий состояние, в которое нужно войти.
в. Адрес (URL)— Адрес новой записи истории. Браузер не загрузит адрес после вызова метода pushState(), но может попытаться загрузить его позже, например, если пользователь перезапустит браузер. Новый URL-адрес не обязательно должен быть абсолютным путем; если это относительный путь, он будет основан на текущем URL-адресе; входящий URL-адрес и текущий URL-адрес должны иметь одно и то же происхождение, в противном случае pushState() выдаст ошибку исключение. Этот параметр является необязательным, если он не указан, это текущий URL-адрес документа.
То же самое: оба API работают с историей браузера, не вызывая обновления страницы.
Разница в том, что pushState добавляет новую историю, а replaceState заменяет текущую историю.
пример:
исходный маршрут
http://biaochenxuying.cn/
воплощать в жизнь:
window.history.pushState(null, null, "http://biaochenxuying.cn/home");
Маршрут становится:
http://biaochenxuying.cn/home
Подробнее см.:MDN
2.2 Маршрутизация хэшей
Мы часто видим # в URL-адресах. Есть два случая для этого #. Один из них — это то, что мы называем якорями, например типичный принцип кнопки «наверх», переход между заголовками на Github и т. д., но # в маршруте Это не называется якорем, а мы называем это хэшем.
Реализация маршрутизации текущих основных интерфейсных фреймворков будет использовать хеш-маршрутизацию, которая также используется в этом проекте.
Когда значение хеша изменяется, мы можем прослушивать его через событие hashchange и запускать определенные методы в функции обратного вызова.
3. Реализация кода
3.1 Простая версия — одностраничная маршрутизация
Давайте сначала рассмотрим простую версию нативного js для имитации переключения маршрутизации Vue.
принцип
- Прислушайтесь к хеш-изменению, когда хэш изменится, сопоставьте соответствующий html-контент в соответствии с текущим хешем, а затем используйте innerHTML, чтобы поместить html-контент в представление маршрутизатора.
Этот код онлайн:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="author" content="">
<title>原生模拟 Vue 路由切换</title>
<style type="text/css">
.router_box,
#router-view {
max-width: 1000px;
margin: 50px auto;
padding: 0 20px;
}
.router_box>a {
padding: 0 10px;
color: #42b983;
}
</style>
</head>
<body>
<div class="router_box">
<a href="/home" class="router">主页</a>
<a href="/news" class="router">新闻</a>
<a href="/team" class="router">团队</a>
<a href="/about" class="router">关于</a>
</div>
<div id="router-view"></div>
<script type="text/javascript">
function Vue(parameters) {
let vue = {};
vue.routes = parameters.routes || [];
vue.init = function() {
document.querySelectorAll(".router").forEach((item, index) => {
item.addEventListener("click", function(e) {
let event = e || window.event;
event.preventDefault();
window.location.hash = this.getAttribute("href");
}, false);
});
window.addEventListener("hashchange", () => {
vue.routerChange();
});
vue.routerChange();
};
vue.routerChange = () => {
let nowHash = window.location.hash;
let index = vue.routes.findIndex((item, index) => {
return nowHash == ('#' + item.path);
});
if (index >= 0) {
document.querySelector("#router-view").innerHTML = vue.routes[index].component;
} else {
let defaultIndex = vue.routes.findIndex((item, index) => {
return item.path == '*';
});
if (defaultIndex >= 0) {
window.location.hash = vue.routes[defaultIndex].redirect;
}
}
};
vue.init();
}
new Vue({
routes: [{
path: '/home',
component: "<h1>主页</h1><a href='https://github.com/biaochenxuying'>https://github.com/biaochenxuying</a>"
}, {
path: '/news',
component: "<h1>新闻</h1><a href='http://biaochenxuying.cn/main.html'>http://biaochenxuying.cn/main.html</a>"
}, {
path: '/team',
component: '<h1>团队</h1><h4>全栈修炼</h4>'
}, {
path: '/about',
component: '<h1>关于</h1><h4>关注公众号:BiaoChenXuYing</h4><p>分享 WEB 全栈开发等相关的技术文章,热点资源,全栈程序员的成长之路。</p>'
}, {
path: '*',
redirect: '/home'
}]
});
</script>
</body>
</html>
3.2 Сложная версия — встроенная версия страницы с кэшированием
Во-первых, реализовать кеширующую функцию роутинга с помощью js во фронтенде сложно, но это неплохо, как vue-router, ведь технология vue framework и виртуальный дом могут сохранять данные текущей страницы.
Чтобы использовать функцию кэширования, вы должны сначала знать три операции браузера: вперед, обновление и откат.
Однако в браузере в основном есть следующие ограничения:
- Нет события для прослушивания вперед и назад
- Не позволяйте разработчикам читать историю просмотров
- Пользователь может вручную ввести адрес или использовать перенаправление вперед и назад, предоставляемое браузером, для изменения URL-адреса.
Таким образом, для настройки маршрутизации решение состоит в том, чтобы поддерживать запись истории маршрутизации, которая хранится в массиве, чтобы различать перенаправление, обновление и откат.
- Если URL-адрес существует в записи просмотра, это означает, что нужно вернуться назад. При возврате назад запись просмотра за текущим маршрутом будет удалена.
- Если URL-адрес не существует в записи просмотра, это означает движение вперед.При движении вперед вставьте текущий маршрут в массив.
- URL-адрес обновляется в конце записи просмотра. При обновлении он ничего не делает с массивом маршрутизации.
Кроме того, путь маршрутизации приложения может позволять одному и тому же маршруту появляться несколько раз (например, A -> B -> A), поэтому добавьте значение ключа к каждому маршруту, чтобы различать разные экземпляры одного и того же маршрута.
Эта запись просмотра должна храниться в sessionStorage, чтобы запись просмотра можно было восстановить после обновления пользователя.
3.2.1 route.js
3.2.1.1 Метод перехода linkTo
Как и vue-router, он предоставляет компонент router-link для навигации, а мой фреймворк также предоставляет метод 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)
})
}
// 初始化跳转方法
window.linkTo = function(path) {
if (path.indexOf("?") !== -1) {
window.location.hash = path + '&key=' + genKey()
} else {
window.location.hash = path + '?key=' + genKey()
}
}
Применение:
//1. 直接用 a 标签
<a href='#/list' >列表1</a>
//2. 标签加 js 调用方法
<div onclick='linkTo(\"#/home\")'>首页</div>
// 3. js 调用触发
linkTo("#/list")
3.2.1.2 Маршрутизатор-конструктор
Определите переменные, которые будут использоваться
function Router() {
this.routes = {}; //保存注册的所有路由
this.beforeFun = null; //切换前
this.afterFun = null; // 切换后
this.routerViewId = "#routerView"; // 路由挂载点
this.redirectRoute = null; // 路由重定向的 hash
this.stackPages = true; // 多级页面缓存
this.routerMap = []; // 路由遍历
this.historyFlag = '' // 路由状态,前进,回退,刷新
this.history = []; // 路由历史
this.animationName = "slide" // 页面切换时的动画
}
3.2.1.3 Реализация функции маршрутизации
В том числе: инициализация, прописанные маршруты, история, переключение страниц, анимация переключения страниц, хуки до переключения, хуки после переключения, обработка позиций прокрутки и кеширование.
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()
// 初始化跳转方法
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)
},
// 路由历史纪录变化
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);
// 前进和刷新都执行回调 与 初始滚动位置为 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 {
//不存在的地址,重定向到默认页面
location.hash = this.redirectRoute
}
},
//路由注册
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, //回调
}
}
},
//切换之前的钩子
beforeEach: function(callback) {
if (Object.prototype.toString.call(callback) === '[object Function]') {
this.beforeFun = callback;
} else {
console.trace('路由切换前钩子函数不正确')
}
},
//切换成功之后的钩子
afterEach: function(callback) {
if (Object.prototype.toString.call(callback) === '[object Function]') {
this.afterFun = callback;
} else {
console.trace('路由切换后回调函数不正确')
}
}
}
3.2.1.4 Зарегистрируйтесь на маршрутизаторе в глобальном окне
window.Router = Router;
window.router = new Router();
Полный код:GitHub.com/Tablesink нужно…
3.2.2 Как использовать
3.2.2.1 метод определения js
- callback — это обратный вызов, выполняемый после переключения страниц
<script type="text/javascript">
var config = {
routerViewId: 'routerView', // 路由切换的挂载点 id
stackPages: true, // 多级页面缓存
animationName: "slide", // 切换页面时的动画
routes: [{
path: "/home",
name: "home",
callback: function(route) {
console.log('home:', route)
var str = "<div><a class='back' onclick='window.history.go(-1)'>返回</a></div> <h2>首页</h2> <input type='text'> <div><a href='javascript:void(0);' onclick='linkTo(\"#/list\")'>列表</a></div><div class='height'>内容占位</div>"
document.querySelector("#home").innerHTML = str
}
}, {
path: "/list",
name: "list",
callback: function(route) {
console.log('list:', route)
var str = "<div><a class='back' onclick='window.history.go(-1)'>返回</a></div> <h2>列表</h2> <input type='text'> <div><a href='javascript:void(0);' onclick='linkTo(\"#/detail\")'>详情</a></div>"
document.querySelector("#list").innerHTML = str
}
}, {
path: "/detail",
name: "detail",
callback: function(route) {
console.log('detail:', route)
var str = "<div><a class='back' onclick='window.history.go(-1)'>返回</a></div> <h2>详情</h2> <input type='text'> <div><a href='javascript:void(0);' onclick='linkTo(\"#/detail2\")'>详情 2</a></div><div class='height'>内容占位</div>"
document.querySelector("#detail").innerHTML = str
}
}, {
path: "/detail2",
name: "detail2",
callback: function(route) {
console.log('detail2:', route)
var str = "<div><a class='back' onclick='window.history.go(-1)'>返回</a></div> <h2>详情 2</h2> <input type='text'> <div><a href='javascript:void(0);' onclick='linkTo(\"#/home\")'>首页</a></div>"
document.querySelector("#detail2").innerHTML = str
}
}]
}
//初始化路由
router.init(config)
router.beforeEach(function(transition) {
console.log('切换之 前 dosomething', transition)
setTimeout(function() {
//模拟切换之前延迟,比如说做个异步登录信息验证
transition.next()
}, 100)
})
router.afterEach(function(transition) {
console.log("切换之 后 dosomething", transition)
})
</script>
3.2.2.2 метод определения html plus script
- id="routerView" : окно просмотра страницы при переключении маршрута
- data-animationName="slide": Анимация при переключении, в настоящее время есть слайд и затухание.
- class="page": страница для переключения
- data-hash="/home": home — метод обратного вызова, выполняемый при переключении маршрутов
- window.home : метод обратного вызова, имя должно совпадать с именем хэша данных
<div id="routerView" data-animationName="slide">
<div class="page" data-hash="/home">
<div class="page-content">
<div id="home"></div>
<script type="text/javascript">
window.home = function(route) {
console.log('home:', route)
// var str = "<div><a class='back' onclick='window.history.go(-1)'>返回</a></div> <h2>首页</h2> <input type='text'> <div><a href='#/list' >列表1</div></div><div class='height'>内容占位</div>"
var str = "<div><a class='back' onclick='window.history.go(-1)'>返回</a></div> <h2>首页</h2> <input type='text'> <div><div href='javascript:void(0);' onclick='linkTo(\"#/list\")'>列表</div></div><div class='height'>内容占位</div>"
document.querySelector("#home").innerHTML = str
}
</script>
</div>
</div>
<div class="page" data-hash="/list">
<div class="page-content">
<div id="list"></div>
<div style="height: 700px;border: solid 1px red;background-color: #eee;margin-top: 20px;">内容占位</div>
<script type="text/javascript">
window.list = function(route) {
console.log('list:', route)
var str = "<div><a class='back' onclick='window.history.go(-1)'>返回</a></div> <h2>列表</h2> <input type='text'> <div><a href='javascript:void(0);' onclick='linkTo(\"#/detail\")'>详情</a></div>"
document.querySelector("#list").innerHTML = str
}
</script>
</div>
</div>
<div class="page" data-hash="/detail">
<div class="page-content">
<div id="detail"></div>
<script type="text/javascript">
window.detail = function(route) {
console.log('detail:', route)
var str = "<div><a class='back' onclick='window.history.go(-1)'>返回</a></div> <h2>详情</h2> <input type='text'> <div><a href='javascript:void(0);' onclick='linkTo(\"#/detail2\")'>详情 2</a></div><div class='height'>内容占位</div>"
document.querySelector("#detail").innerHTML = str
}
</script>
</div>
</div>
<div class="page" data-hash="/detail2">
<div class="page-content">
<div id="detail2"></div>
<div style="height: 700px;border: solid 1px red;background-color: pink;margin-top: 20px;">内容占位</div>
<script type="text/javascript">
window.detail2 = function(route) {
console.log('detail2:', route)
var str = "<div><a class='back' onclick='window.history.go(-1)'>返回</a></div> <h2>详情 2</h2> <input type='text'> <div><a href='javascript:void(0);' onclick='linkTo(\"#/home\")'>首页</a></div>"
document.querySelector("#detail2").innerHTML = str
}
</script>
</div>
</div>
</div>
<script type="text/javascript" src="./js/route.js"></script>
<script type="text/javascript">
router.init()
router.beforeEach(function(transition) {
console.log('切换之 前 dosomething', transition)
setTimeout(function() {
//模拟切换之前延迟,比如说做个异步登录信息验证
transition.next()
}, 100)
})
router.afterEach(function(transition) {
console.log("切换之 后 dosomething", transition)
})
</script>
Справочный проект:GitHub.com/Test Level 6/Fear-Japanese…
5. Наконец
Статью не обновлял уже больше месяца,так как проект слишком плотный,и работаю сверхурочно.Пока свободен дома,быстро пишу эту сухую статью,чтоб не забыть,надеюсь на это будет полезен всем.
Если вы считаете, что эта статья хороша или полезна, пожалуйста, поставьте лайк, спасибо.