如何用JS做一個小清新導航?

xyyojl發表於2019-02-28

前言

本文主要介紹:

  1. 專案介紹(簡介,功能,使用到的技術棧)
  2. 專案效果展示
  3. 一步步實現專案效果
  4. 踩坑

一、專案介紹

名稱: 優美快捷的小清新導航

技術棧: HTML5,CSS3,JavaScript

功能描述:

  • 一個優美快捷的個人導航頁,附加主流搜尋引擎搜尋功能
  • 為鍵盤上的 26 個字母繫結了導航網址,敲擊鍵盤上的字母可跳轉相應網頁
  • 自定義與字母關聯的導航地址,資料存在本地LocalStorage中
  • 基於原生 JS,鍵盤按鍵動態生成,按鍵樣式完全使用CSS3完成

二、專案效果展示

專案地址 預覽地址

預覽圖

如何用JS做一個小清新導航?

看完上面的預覽圖,是不是有一點興奮,想自己敲出一個屬於自己的小清新導航。下面就開始動手敲程式碼吧!

三、一步步實現專案效果

(一)分析頁面

做一個網站之前,要搞清楚這個網站是什麼?寫一寫,畫一畫,可以使用用例圖,畫一個人,想想那個人可以有什麼操作?使用者進入我們網站有哪些功能?

使用者可以進行的操作:

  • 搜尋
    • 百度搜尋
    • google搜尋「前提是你懂得」
  • 敲擊鍵盤上的字母,開啟新頁面
  • 自定義與字母關聯的導航地址

(二)選擇資料結構

  • q對應著qq.com「雜湊」
  • 把鍵盤的結構的儲存下來「陣列」

(三)進行HTML佈局

先直接使用html打好框架:

<!DOCTYPE html>
<html lang="zh-Hans">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>小清新導航</title>
    <link rel="stylesheet" href="./css/normalize.css">
    <link rel="stylesheet" href="./css/style.css">
</head>
<body>
    <header>
        <ul>
            <li>
                <a href=""><img src="" alt="google"></a>
            </li>
            <li>
                <a href=""><img src="" alt="百度一下"></a>
            </li>
        </ul>
        <form action="">
            <ul>
                <li>
                    <img src="" alt="google">
                </li>
                <li>
                    <img src="" alt="百度一下">
                </li>
            </ul>
            <input type="text">
            <button></button>
        </form>
    </header>
    <main>
        <div id="main" class="wrapper">
            <div class="row">
                <div class="kbd_wrapper">
                    <kbd>
                        <span>q</span>
                        <img src="" alt="">
                        <button></button>
                    </kbd>
                </div>
            </div>
        </div>
    </main>
    <footer>
        <h3>小Tips</h3>
        <ul>
            <li>1. 按鍵盤上相應字母進入你想要到的網站</li>
            <li>2. 字母相關的導航地址可將滑鼠懸浮其上檢視</li>
            <li>3. 滑鼠移動到鍵盤的按鍵時,可自行編輯導航網址</li>
            <li>4. 編輯後,網址會保留下來,不用擔心重新整理之後不見了</li>
            <li>5. '.'代表未設定網站或無favicon</li>
        </ul>
    </footer>	
    <script src="./js/main.js"></script>
</body>
</html>
複製程式碼

給HTML標籤新增class,並新增圖片。注:僅僅是個人習慣!

<header>
    <ul id="searchEnginPic" class="searchEnginPic">
        <li class="active">
            <a href="https://www.google.com.hk/" target="_blank"><img src="./image/google.png" alt="google"></a>
        </li>
        <li>
            <a href="https://www.baidu.com/" target="_blank"><img src="./image/baidu.gif" alt="百度一下"></a>
        </li>
    </ul>
    <form action="" id="searchBar" class="searchBar">
        <ul id="searchEnginLogo" class="searchEnginLogo">
            <li class="active">
                <img src="./image/google.svg" alt="google">
            </li>
            <li>
                <img src="./image/baidu.svg" alt="baidu">
            </li>
        </ul>
        <input type="text" id="inputBar">
        <button id="searchBtn" class="searchBtn"></button>
    </form>
</header>
<main>
    <div id="main" class="wrapper">
        <div class="row">
            <div class="kbd_wrapper">
                <kbd class="key">
                    <span>q</span>
                    <img src="./image/dot.png" alt="">
                    <button>e</button>
                </kbd>
            </div>
        </div>
    </div>
</main>
<footer class="description">
    <h3>小Tips</h3>
    <ul class="tips-content">
        <li>1. 按鍵盤上相應字母進入你想要到的網站</li>
        <li>2. 字母相關的導航地址可將滑鼠懸浮其上檢視</li>
        <li>3. 滑鼠移動到鍵盤的按鍵時,可自行編輯導航網址</li>
        <li>4. 編輯後,網址會保留下來,不用擔心重新整理之後不見了</li>
        <li>5. '.'代表未設定網站或無favicon</li>
    </ul>
</footer>
複製程式碼

(四)用CSS美化介面

1.美化頭部搜尋

*{margin: 0;padding:0;}
ul{list-style: none;}
.clearfix::after{
    content: '';
    display: block;
    clear: both;
}

/* 注意:這裡圖片路徑是相對路徑 */
body{
    background: #ccc url(../image/bg.jpg) no-repeat center center;
    background-size: cover;
}

/* header */
header{
    width: 720px;
    height: 44px;
    margin: 100px auto 50px;
}
header .searchEnginPic{
    float: left;
    width: 120px;
    height: 44px;
}
header .searchEnginPic li{
    display: none;
}
header .searchEnginPic li.active{
    display: inline-block;
}
header .searchEnginPic li:nth-child(1) img{
    width: 120px; 
    padding: 2.5px 0;
}
header .searchEnginPic li:nth-child(2) img{
    width: 120px; 
    height: 44px;
}

header .searchBar{
    float: right;
    width: 580px;
    height: 44px;
    border-radius: 24px;
    position: relative;
    background: #fff;
}
header .searchBar:hover{
    box-shadow: 0 1px 6px 0 rgba(32,33,36,0.28);
    border-color: rgba(223,225,229,0);
}
header .searchBar .searchEnginLogo{
    position: absolute;
    left: 10px;
    width: 24px;
    height: 44px;
    cursor: pointer;
}
header .searchBar .searchEnginLogo li{
    display: none;
}
header .searchBar .searchEnginLogo li.active{
    display: inline-block;
}
.searchBar .searchEnginLogo li img{
    width: 24px;
    height: 24px;
    padding: 10px 0;
}
.searchBar input{
    position: absolute;
    left: 46px;
    top: 10%;
    width: 505px;
    height: 80%;
    border: none;
    outline: none;
}
.searchBar .searchBtn{
    position: absolute;
    right: 6px;
    top: 7px;
    width: 30px;
    height: 30px;
    background: url(../image/search-btn.png) center center no-repeat;
    cursor: pointer;
    border: none;
    outline: none;
}
複製程式碼

2.給鍵盤的按鍵新增樣式

/* main */
.wrapper div{
    width: 850px;
    margin: 0 auto;
    text-align: center;
}
.wrapper .kbd_wrapper{
    display: inline-block;
    vertical-align: top;
    width: 65px;
    height: 55px;
    border-radius: 7px;
    margin: 6px;
    box-shadow: 0 4px 3px 0 #3C3C3D, 0 0 1px 0 #3C3C3D;
    text-align: center;
}
.wrapper kbd{
    position: relative;
    display: inline-block;
    width: 65px;
    height: 50px;
    background: linear-gradient(to bottom, #fff 0%,#fff 70%,#f3f3f3 100%);
    border-radius: 7px;
    box-shadow: 0 5px 0 0 #767d81;
    color: #767D81;
    font-family: Helvetical;
    vertical-align: top;
    line-height: 50px;
    text-transform: uppercase;
    font-weight: bold;
    transition: all .2s linear;
}
.wrapper .kbd_wrapper:hover {
    cursor: pointer;
    animation: shake 0.82s cubic-bezier(.36, .07, .19, .97) both;
    transform: translate3d(0, 0, 0);
    backface-visibility: hidden;
    perspective: 1000px;
}
.wrapper kbd > button{
    display: none;
    position: absolute;
    right: 2px;
    bottom: 2px;
    font-size: 12px;
    width: 16px;
    height: 16px;
    border-radius: 5px;
    border: none;
    outline: none;
    color: #333;
    cursor: pointer;
}
.wrapper kbd:hover > button{
    display: inline-block;
}
.wrapper kbd > button:hover{
    background: #806605;
    color: #fff;
}
.key img{
    width: 16px;
    height: 16px;
    position: absolute;
    left: 4px;
    bottom: 2px;
}
@keyframes shake {
    10%, 90% {
        transform: translate3d(-1px, 0, 0);
    }
    20%, 80% {
        transform: translate3d(2px, 0, 0);
    }
    30%, 50%, 70% {
        transform: translate3d(-4px, 0, 0);
    }
    40%, 60% {
        transform: translate3d(4px, 0, 0);
    }
}
複製程式碼

3.美化底部tips

/* footer */
footer{
    width: 390px;
    margin: 50px auto 0;
}
footer h3{
    margin-bottom: 10px;
}
footer .tips-content li{
    padding: 3px 0;
}
複製程式碼

(五)使用JS建立HTML,新增事件實現效果

1. 初始化資料

初始化資料的時候,需要獲取localStorage裡面的data對應的hash

//1.初始化資料
let hashA = init();
let keys = hashA['keys'];
let hash = hashA['hash'];

// 下面是工具函式
function init(){
    let keys = {
        0 : ['q','w','e','r','t','y','u','i','o','p'],
        1 : ['a','s','d','f','g','h','j','k','l'],
        2 : ['z','x','c','v','b','n','m'],
        length : 3
    }
    let hash = {
        q : 'qq.com',
        w : 'wangdoc.com',
        e : undefined,
        r : 'react-juejin.foreversnsd.cn',
        t : 'tgideas.qq.com/doc/',
        y : 'youtube.com',
        i : 'iciba.com',
        o : undefined,
        p : undefined,
        a : undefined,
        s : 'segmentfault.com',
        d : 'dribbble.com',
        f : undefined,
        g : 'github.com',
        h : undefined,
        j : 'juejin.im',
        k : 'ke.qq.com',
        l : undefined,
        z : 'zhihu.com',
        x : 'xiedaimala.com',
        c : 'csdn.net',
        v : undefined,
        b : 'bilibili.com',
        n : undefined,
        m : 'mail.163.com'
    }
    let hasInLocalStorage = getFormLocalStorage('data');
    if(hasInLocalStorage){
        hash = hasInLocalStorage;
    }
    return {'keys':keys,'hash':hash}
}

function getFormLocalStorage(name){
    return JSON.parse(localStorage.getItem(name) || 'null')
}
複製程式碼

2. 生成鍵盤

記得要把class為wrapper的div的內容刪掉,用js生成html

//2.生成鍵盤
generateKeyBoard(keys,hash);

function generateKeyBoard(keys, hash) {
    for (let index = 0; index < keys['length']; index = index + 1) { //0 1 2
        let div = tag('div');
        main.appendChild(div);

        let row = keys[index];
        for (let index2 = 0; index2 < row['length']; index2 = index2 + 1) { //將固定的10換成可控制的
            let kbd_wrapper = tag('div');
            kbd_wrapper.className = 'kbd_wrapper';
            let kbd = tag('kbd');
            kbd.className = 'key';
            let span = createSpan(row[index2]);
            let img = createImage(hash[row[index2]]);
            let button = createButton(row[index2]);
        
            // 判斷按鍵是否已經有對應的網址
            if (hash[row[index2]] === undefined) {
                kbd.setAttribute('title', '未設定網站導航');
            } else {
                kbd.setAttribute('title', hash[row[index2]]);
            }

            kbd.onclick = function (e) {
                let website = e.currentTarget.getAttribute('title');
                if (website === '未設定網站導航') {
                    alert('請編輯此按鍵的網站再跳轉');
                } else {
                    window.open('http://' + website, "_blank");
                }
            }
            
            kbd.appendChild(span);
            kbd.appendChild(img);
            kbd.appendChild(button);
            kbd_wrapper.appendChild(kbd);
            div.appendChild(kbd_wrapper);
        }
    }
}

function tag(tagName) {
    let element = document.createElement(tagName);
    return element;
}

function createSpan(textContent) {
    let span = tag('span');
    span.textContent = textContent; //第一個陣列 第二個陣列 第三個陣列
    span.className = 'text';
    return span;
}

function createButton(id) {
    let button = tag('button');
    button.textContent = 'e';
    button.id = id;
    button.onclick = function (e) {
        //阻止事件冒泡
        e.stopPropagation();
        let button2 = e['target'];
        let img2 = button2.previousSibling;
        //獲取當前的id
        let key = button2['id'];
        //使用者輸入一個網址
        let web = prompt('請輸入一個網址:');
        //將原來的hash給替換掉
        hash[key] = web;
        img2.src = 'http://' + web + '/favicon.ico';
        console.log(e.target)
        img2.onerror = function (e) {
            e.target.src = './image/dot.png';
        }
        localStorage.setItem('data', JSON.stringify(hash));
    }
    return button;
}

function createImage(domain) { //hash[row[index2]]
    let img = tag('img');
    if (domain) {
        img.src = 'http://' + domain + '/favicon.ico';
    } else {
        img.src = './image/dot.png';
    }
    img.onerror = function (e) {
        e.target.src = './image/dot.png';
    }
    return img;
}
複製程式碼

3.監聽使用者動作

//3.監聽使用者動作
listenToUser(hash);
switchSearchEngin();

function listenToUser(hash) {
    // ifInputting作為一個開關
    let ifInputting = false;
    let inputBar = document.getElementById('inputBar');
    let searchBtn = document.querySelector('.searchBtn');
    inputBar.addEventListener('focus', function (e) {
        ifInputting = true;
        e.target.placeholder = '';
    })
    inputBar.addEventListener('focusout', function (e) {
        ifInputting = false;
        e.target.placeholder = '點選左邊圖示切換搜尋引擎';
    })
    searchBtn.onclick = function (e) {
        e.preventDefault();
        let searchContent = inputBar.value;
        // 判斷是什麼搜尋引擎
        let searchEnginLogo = document.getElementById('searchEnginLogo');
        let engin = searchEnginLogo.getAttribute('data-engin');
        switch (engin) {
            case 'baidu':
                window.open("https://www.baidu.com/s?wd=" + searchContent, '_blank');
                break;
            case 'google':
                window.open("https://www.google.com.hk/search?q=" + searchContent, '_blank');
                break;
        }
    }

    document.onkeypress = function (e) {
        let key = e['key'];
        let website = hash[key];
        if (!ifInputting) {
            if (website === undefined) {
                alert('請編輯此按鍵的網站再跳轉')
            } else {
                window.open('http://' + website, "_blank");
            }
        }
    }
}

// 切換搜尋引擎
function switchSearchEngin() {
    // 搜尋引擎預設是google
    let ifSwitch = false;
    let searchEnginLogo = document.getElementById('searchEnginLogo');
    let googleLogo = document.querySelector('#searchEnginLogo li:nth-child(1)');
    let baiduLogo = document.querySelector('#searchEnginLogo li:nth-child(2)');
    let googlePic = document.querySelector('#searchEnginPic li:nth-child(1)');
    let baiduPic = document.querySelector('#searchEnginPic li:nth-child(2)');
    searchEnginLogo.setAttribute('data-engin', 'google');
    searchEnginLogo.onclick = function () {
        if (!ifSwitch) {
            // google --> baidu
            googleLogo.classList.remove('active');
            baiduLogo.classList.add('active');
            googlePic.classList.remove('active');
            baiduPic.classList.add('active');
            searchEnginLogo.setAttribute('data-engin', 'baidu');
        } else {
            // baidu --> google
            baiduLogo.classList.remove('active');
            googleLogo.classList.add('active');
            baiduPic.classList.remove('active');
            googlePic.classList.add('active');
            searchEnginLogo.setAttribute('data-engin', 'google');
        }
        ifSwitch = !ifSwitch;
    }
}
複製程式碼

踩坑

(一)問題1 搜尋框輸入和按鍵按下跳轉出現衝突

解決辦法:

定義一個變數(boolean型別)作為一個開關,當input獲得焦點,將那個變數變成true,當input失去焦點,將那個變數變成false。同時事件觸發的時候判斷這個變數,來明確使用者是要輸入文字搜尋,還是想按下按鍵跳轉到想去的網頁。更加詳細的程式碼,請看上面或者是github上的程式碼。

let ifInputting = false;
let inputBar = document.getElementById('inputBar');
let searchBtn = document.querySelector('.searchBtn');
inputBar.addEventListener('focus', function (e) {
    ifInputting = true;
    e.target.placeholder = '';
})
inputBar.addEventListener('focusout', function (e) {
    ifInputting = false;
    e.target.placeholder = '點選左邊圖示切換搜尋引擎';
})

document.onkeypress = function (e) {
    let key = e['key'];
    let website = hash[key];
    if (!ifInputting) {
        if (website === undefined) {
            alert('請編輯此按鍵的網站再跳轉')
        } else {
            window.open('http://' + website, "_blank");
        }
    }
}
複製程式碼

(二)問題2:e.currentTarget和e.target的區別,捕獲和冒泡是什麼鬼?

我之前已經整理的一篇部落格深入理解DOM事件機制涉及到問題2的這些內容,這裡告訴你一個小技巧:ctrl+f,可以在網頁搜尋關鍵詞

(三)點選編輯按鈕時候,輸入網址後,點選確定,會新開一個網頁,為什麼呢?

解決辦法:其實是因為存在事件冒泡,所以導致kbd的click事件被觸發,所以只需要阻止事件冒泡即可

相關文章