Effect:

Project address: github.com/biaochenxuy…

Effect Experience Address:

1. The sliding effect: https://biaochenxuying.github.io/route/index.html

2. The fading effect: https://biaochenxuying.github.io/route/index2.html

1. The demand

Because our company’s H 5 project is written with native JS, it needs routing, but now convenient routing is bound with some frameworks, such as vue-Router, framework7 routing; But instead of adding a framework for a routing function, write your own lightweight routing.

2. Implementation principle

At present, there are generally two types of front-end route implementation, one is Hash route and the other is History route.

2.1 the History of routing

The History interface allows you to manipulate the browser’s session History that has been accessed in tabs or frames.

attribute

  • History.length is a read-only property that returns the number of histories in the current session, including the current page. For example, the current property returns 1 for a new TAB loaded page.
  • History.state returns a value representing the state at the top of the History stack. This is a way to see the status without waiting for a PopState event.

methods

  • History.back()

Going to the previous page, the user can simulate this method by clicking the back button in the upper left corner of the browser. Equivalent to the history. The go (1).

Note: Calling this method when the browser session history is on the first page has no effect, and no errors are reported.

  • History.forward()

To go to the next page in browser history, the user can simulate this method by clicking the Forward button in the upper left corner of the browser. Equivalent to the history. The go (1).

Note: Calling this method when the browser history stack is at the top (the current page is on the last page) has no effect and no error.

  • History.go(n)

Loads the page from the browser history (session record) by relative location of the current page. For example, if the parameter is -1, the previous page is displayed. If the parameter is 1, the next page is displayed. When the integer argument is out of bounds, for example, if the current page is the first page, there are no previous pages, and I pass the parameter -1, then this method has no effect and does not return an error. Calls to the go() method with no arguments or arguments that are not integers also have no effect. (This is a bit different from IE, which supports strings as URL parameters).

  • History. PushState (), and the history. ReplaceState ()

Both apis accept three arguments, respectively

A. State Object – A JavaScript object associated with a new history entry created using the pushState() method. The popState event is emitted whenever the user navigates to a newly created state, and the state property of the event object contains a copy of the state object for the history entry.

B. Title – This parameter is currently ignored by FireFox, although it may be used in the future. Considering that this method may be modified in the future, it is safer to pass an empty string. Alternatively, you can pass in a short heading indicating the state to be entered.

C. Address (URL) – The address of the new history entry. The browser does not load the address after calling pushState(), but it may try to load it later, for example when the user restarts the browser. The new URL is not necessarily an absolute path; If it is a relative path, it will be based on the current URL; The incoming URL should be homologous with the current URL; otherwise, pushState() throws an exception. This parameter is optional; If not specified, the current URL of the document.

Similarity: Both apis manipulate browser history without causing a page refresh.

The difference is that pushState adds a new history, while replaceState replaces the current history.

Example:

Native route

 http://biaochenxuying.cn/
Copy the code

Perform:

window.history.pushState(null, null, "http://biaochenxuying.cn/home");
Copy the code

The route becomes:

 http://biaochenxuying.cn/home
Copy the code

For details, see MDN

2.2 the Hash routing

We often see # in urls, and there are two cases for #. One is what we call anchor points, such as typical back to top button principle, jump between headings on Github, etc., but the # in the route is not called anchor points, we call it hash.

At present, the routing implementation of mainstream front-end frameworks adopts Hash routing, which is also adopted in this project.

When the hash value changes, we can use the Hashchange event to listen and trigger some method in the callback function.

3. Code implementation

3.1 Simple – Single page routing

Let’s look at a simple version of native JS to simulate Vue route switching.

The principle of

  • Listen for a hashchange, which is a hashchange that matches the HTML content based on the current hash, and then use innerHTML to put the HTML content into the router-view.

This code is online:

<! 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> native analog Vue routing switch </title> <styletype="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 href= <a href="/news" class="router"> News </a> <a href="/team" class="router"> Team </a> <a href="/about" class="router"> About </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 == (The '#' + 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 == The '*';
                    });
                    if(defaultIndex >= 0) { window.location.hash = vue.routes[defaultIndex].redirect; }}}; vue.init(); } new Vue({ routes: [{ path:'/home',
                component: "< h1 > home page < / h1 > < a href =" https://github.com/biaochenxuying "> https://github.com/biaochenxuying < / a >"
            }, {
                path: '/news',
                component: "< h1 > news < / h1 > < a href =" http://biaochenxuying.cn/main.html "> http://biaochenxuying.cn/main.html < / a >"
            }, {
                path: '/team',
                component: '< H1 > Team 
      < H4 > Full stack training 
      '
            }, {
                path: '/about',
                component: 

< /h4>

'
}, { path: The '*', redirect: '/home'}}]); </script> </body> </html>Copy the code

3.2 Complex version – inline page version, with caching function

First of all, it is difficult for the front-end to implement the caching function of the route with JS, but it is ok to implement the caching function of the route like vue-Router, because it has vUE framework and virtual DOM technology, which can save the data of the current page.

To do the caching function, the first to know the browser forward, refresh, back these three operations.

However, there are several major limitations in browsers:

  • There is no provision to listen for forward or backward events
  • Developers are not allowed to read the browser history
  • Users can manually enter the address or change the URL using the browser-provided forward and backward Settings

Therefore, to customize the route, the solution is to maintain a route history record, stored in an array, to distinguish forward, refresh, rollback.

  • If the URL exists in the browser history, the browser history behind the current route is deleted.
  • The URL does not exist in the browsing history is forward, and when forward, push the current route into the array.
  • The URL is refreshed at the end of the browsing history without doing anything to the routing array.

In addition, the application routing path may allow the same route to appear more than once (for example, A -> B -> A), so add A key value to each route to distinguish between different instances of the same route.

The browsing record needs to be stored in sessionStorage so that the browsing record can be restored after the user refreshes.

3.2.1 route. Js
3.2.1.1 Jump method linkTo

Like vue-Router, which provides a router-link component for navigation, my framework also provides a linkTo method.

// Generate different keysfunction 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)
                returnV. tostring (16)}} // initialize the jump method window.linkto =function(path) {
                if (path.indexOf("?") !== -1) {
                    window.location.hash = path + '&key=' + genKey()
                } else {
                    window.location.hash = path + '? key=' + genKey()
                }
        }
Copy the code

Usage:

<a href= 1'#/list'> Listing 1</a> //2. The tag plus js call method <div onclick='linkTo(\"#/home\")'> </div> // 3. js calls trigger linkTo("#/list")
Copy the code
3.2.1.2 Constructor Router

Define the variables to use

function Router() { this.routes = {}; // Save all routes registered this.beforeFun = null; This. afterFun = null; // This. RouterViewId ="#routerView"; // Route mount point this.redirectRoute = null; // Route redirectedhash
        this.stackPages = true; RouterMap = []; // Route traversal this.historyFlag =' '// Route status, forward, back, refresh this.history = []; // Route history this.animationName ="slide"// Animation of page switch}Copy the code
3.2.1.3 Enabling the routing function

Including: initialization, route registration, history, page switching, page switching animation, hook before switching, hook after switching, scrolling position processing, caching.

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() // Initialize the jump method 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 ()}} // the page first loads the matching route window.adDeventListener ('load'.function(event) {
                // console.log('load', event);
                self.historyChange(event)
            }, false// Route switch window.addeventListener ('hashchange'.function(event) {
                // console.log('hashchange', event);
                self.historyChange(event)
            }, false}, // Route history changeshistoryChange: 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 ()}, // switch page 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); 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)
                }
            } elseHash = this.redirectRoute}}, // Route registration 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, ""); // filter space this.routes[path] = {callback: route.callback, // callback}}}, // switch before hook beforeEach:function(callback) {
            if (Object.prototype.toString.call(callback) === '[object Function]') {
                this.beforeFun = callback;
            } else {
                console.trace('Hook function not correct before route switch'}}, // Hook afterEach:function(callback) {
            if (Object.prototype.toString.call(callback) === '[object Function]') {
                this.afterFun = callback;
            } else {
                console.trace('Incorrect callback function after route switchover')}}}Copy the code
3.2.1.4 Registering the Router to the Window global
    window.Router = Router;
    window.router = new Router();
Copy the code

Full code: github.com/biaochenxuy…

3.2.2 Usage
3.2.2.1 JS Definition method
  • Callback is a callback executed after a page switch
<script type="text/javascript">
        var config = {
            routerViewId: 'routerView'// Mount point id of routing switch stackPages:true// Multilevel page cache animationName:"slide"// routes: [{path:"/home",
                name: "home",
                callback: function(route) {
                    console.log('home:', route)
                    var str = "< div > < a class =" back "onclick =" window. The history. The go (1) "> return < / a > < / div > < h2 > home page < / h2 > < input type =" text "> < div > < a href='javascript:void(0); "Onclick =" linkTo (\ \ "" # / list) '> list < / a > < / div > < div class =' height '> content placeholder < / 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. The history. The go (1) "> return < / a > < / div > < h2 > list < / h2 > < input type =" text "> < div > < a href='javascript:void(0); 'onclick='linkTo(\"#/detail\")'> "
                    document.querySelector("#list").innerHTML = str
                }
            }, {
                path: "/detail",
                name: "detail",
                callback: function(route) {
                    console.log('detail:', route)
                    var str = "< div > < a class =" back "onclick =" window. The history. The go (1) "> return < / a > < / div > < h2 > details < / h2 > < input type =" text "> < div > < a href='javascript:void(0); "Onclick =" linkTo (\ "# / detail2 \") 'details > 2 < / a > < / div > < div class =' height '> content placeholder < / 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. The history. The go (1) "> return < / a > < / div > < h2 > details 2 < / h2 > < input type =" text "> < div > < a href='javascript:void(0); 'onclick='linkTo(\"#/home\")'>
                    document.querySelector("#detail2").innerhtml = STR}}]} // Initialize the route router.init(config) router.beforeeach (function(transition) {
            console.log('Dosomething before switching', transition)
            setTimeout(function() {// Simulate a delay before switching, such as doing an asynchronous login authentication transition.next()}, 100)}) router.aftereach (function(transition) {
            console.log("After switching dosomething", transition)
        })
    </script>
Copy the code
3.2.2.2 HTML plus Script definition method
  • Id =”routerView” : indicates the view window during route switchover
  • Data-animationname =”slide” : Animation for switching, currently slide and fade.
  • Class =”page”: switching pages
  • Data-hash =”/home” : home is the callback method to perform when switching routes
  • Window. home: callback method with the same name as data-hash
<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. The history. The go (1) "> return < / a > < / div > < h2 > home page < / h2 > < input type =" text "> < div > < a href = '# / list' 
      
"
var str = "< div > < a class =" back "onclick =" window. The history. The go (1) "> return < / a > < / div > < h2 > home page < / h2 > < input type =" text "> < div > < div href='javascript:void(0); "Onclick =" linkTo (\ \ "" # / list) list '> < / div > < / div > < div class =' height '> content placeholder < / 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;"Content placeholder </div> <scripttype="text/javascript"> window.list = function(route) { console.log('list:', route) var str = "< div > < a class =" back "onclick =" window. The history. The go (1) "> return < / a > < / div > < h2 > list < / h2 > < input type =" text "> < div > < a href='javascript:void(0); 'onclick='linkTo(\"#/detail\")'> " 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. The history. The go (1) "> return < / a > < / div > < h2 > details < / h2 > < input type =" text "> < div > < a href='javascript:void(0); "Onclick =" linkTo (\ "# / detail2 \") 'details > 2 < / a > < / div > < div class =' height '> content placeholder < / 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;"Content placeholder </div> <scripttype="text/javascript"> window.detail2 = function(route) { console.log('detail2:', route) var str = "< div > < a class =" back "onclick =" window. The history. The go (1) "> return < / a > < / div > < h2 > details 2 < / h2 > < input type =" text "> < div > < a href='javascript:void(0); 'onclick='linkTo(\"#/home\")'> 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 before switching', transition) setTimeout(function() {// Simulate a delay before switching, such as doing an asynchronous login authentication transition.next()}, 100)}) router.aftereach (function(transition) { console.log("After switching dosomething", transition) }) </script> Copy the code

Reference items: github.com/kliuj/spa-r…

5. The last

I haven’t updated my article for more than a month, because the project is too tight and I have worked overtime. Taking advantage of my free time at home, I quickly wrote down this article, so as not to forget it. I hope it will be helpful to everyone.

If you think this article is good or helpful, please give it a thumbs up. Thank you.