前端导航项目

119 阅读11分钟

project from jirengu.com fangyinghang Frontend class

技术栈

  • jQuery
  • LocalStorage
  • SVG Symbols
  • 媒体查询
  • git&GitHub

思路

Figma作图,在线制作UI

实现手机端

  • 写HTML
  • 写CSS
  • 写JS(事件监听、DOM操作)

实现PC端

  • 通过媒体查询加CSS
  • 写JS(单独处理PC上的逻辑)

发布到GitHub

注意用户体验,效果展示:预览网页链接放在明细位置,方便快捷查看。

加README.md

开发

yarn global add parcel-bundler
parcel src/index.html

build命令

yarn build

实现全程:

  1. 页面控件创建

1.1 登陆Figma网站,创建新的设计文件,先设计再开发,新建2个Frame,分别为手机端和PC端,用画图形工具,画出搜索输入文本框,自定义宽高,背景色填充白色#fff,边框border改为#ddd浅灰色,然后改整个页面的背景色#eee浅灰色,不要自己擅自配色,用固定模型。

1.2 按住alt键,点击已创建的文本框,向右拖动为button按钮,复制得到一个容纳button的文本框,边框去掉,背景填充为蓝色,再加上文字:搜索,文字填充白色;

1.3 把button上的文本框、文字创建一个组,可以整体操作,

1.4 创建网站导航的框,大小一般设置为8x的像素,白色背景色,边框填充#ddd浅灰,新建文字。

1.5 复制新建的导航框组,插入图片,缩放比例时,按住Shift,保证图片不变形;

1.6创建PC页面的控件,可以把手机端刚才做好的样式,直接拖拽到pc页面中,通过选中再alt复制出想要的效果,pc端和手机端的视觉页面就都创建完成。

  1. html代码实现(与css的顺序是,代码简单可以全部写完html,代码复杂,可以先写一部分html再写css,如此循环)

2.1 新建一个项目文件夹,vscode打开,新建src文件夹,index.html文件和style.css文件,main.js文件,把head元素中的meta viewport替换为禁止触屏缩放的效果代码:

<meta name="viewport" content="width=device-width,initial-scale=1,
    minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">

2.2 写html的大致思路是,根据视觉稿来添加需要的元素,head元素,改title,加style链接:

<head>
    <title>前航 - 前端导航网站</title>
    <link rel="stylesheet" href="style.css">
</head>

2.3 body元素添加js链接:

<script src="main.js"></script>

2.4 body里面加两个子元素

/,header里面有input和button两个标签,main里面存放网站;main里面放ul(无序列表):

<main class="globalMain">
        <ul class="siteList">
            <li>
                <div class="site">
                    <div class="logo">A</div>
                    <div class="link">acfun.cn</div>
                </div>
            </li>
            <li>
                <div class="site">
                    <div class="logo"></div>
                    <div class="link">bilibili.com</div>
                </div>
            </li>
            <li>
                <div class="text">新增网站</div>
                </div>
            </li>
        </ul>
</main>

3 css代码实现,前提是html上的需要加样式的标签,需要加class,上面的代码已经直接加上;

3.1 预览网站,新建终端,parcel src/index.html,打开链接。

3.2 先重置css,并全局声明:

/*css reset*/
/*不能出现太多的height,用padding来撑*/
*{box-sizing: border-box;}  /*所有元素使用border-box*/
*::before,*::after{box-sizing: border-box;} /*所有的伪元素使用border-box*/
*{margin:0;padding: 0;}  /*干掉默认的margin和padding*/
ul,ol{list-style: none;}  /*干掉默认的ul和ol的Liststyle*/

3.3 把css选择器都添加上,层级结果可以不与html一致,只关心有没有层级关系,不关系具体什么关系,先确认body的范围,

body{border:1px solid red;}

如果给body加背景色,那么浏览器会只能的把背景色扩展到整个网页,就给body加一个灰色背景:

body{background:#eee;}

3.4 打开效果页面的源代码,并切换到手机端页面预览,开始给搜索输入文本框和按钮加样式,在globalHeader元素里:

/* style */ 
body{background: #eee;}
.globalHeader{
    margin: 20px;  /*偏移20像素*/
    display: flex;  /*flex布局*/
    justify-content: space-between;  /*input和按钮分两侧摆放*/  
}
.globalHeader > input{
    width:100%;
    margin-right: 10px;   /*input按钮有空隙*/
    height: 40px;
    padding: 0 10px;   /*上下为0,左右为10px*/
    border: 1px solid #ddd;
    border-radius: 4px;  /*input加圆角效果*/
}
.globalHeader > button{
    white-space:nowrap;  /*把搜索按钮强制变一行,也就是不换行*/
    padding: 0 28px;
    border: none;
    border-radius: 4px;
    background: #0282B9; /*在figma的button样式代码里找到*/
    color:white;    /*按钮上的文字搜索颜色*/
    line-height: 18px;      /*在figma的button样式代码里找到文字高度*/
}

3.5 接下来是网站导航框的平均布局,在main元素下,加一个html里面的.siteList,再新增网站html内的div.icon元素,使用iconfont图标:把图标加入购物车,在symbol内,自动生产代码,复制到html中:<script src="//at.alicdn.com/t/font_1473630_8pfq4zss4o8.js"></script>


```style.css
.icon{
    width: 1em;height: 1em;
    vertical-align: -0.15em;
    fill: currentColor;
    overflow: hidden;
}

.siteList .addButton{
    border :1px solid #ddd;
    background:#fff;
    width: 160px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    padding: 20px 0;
}
.siteList .addButton .icon{
    width: 56px;
    height: 56px;
}
.siteList .addButton .icon-wrapper{
    width: 64px;
    height: 64px;
    display: flex;
    justify-content: center;
    align-items: center;

}
<li>
      <div class="addButton">
           <div class="icon-wrapper">
              <svg class="icon">    /*注意classname不要与其他的重复*/
               <use xlink:href="#icon-add"></use>
               </svg>
            </div>
        <div class="text">新增网站</div>
                </div>
</li>
  1. js功能实现

4.1 搜索文本框功能实现,新建一个form表单,searchForm,

 <header class="globalHeader">
        <form class="searchForm">
            <input type="text">
            <button>搜索</button>
        </form>
    </header>

对应的css需要修改,因为header与input之间增加了一个标签form:

.globalHeader{
    margin: 20px;  /*偏移20像素*/
}
.searchForm{
    display: flex;  /*flex布局*/
    justify-content: space-between;  /*input和按钮分两侧摆放*/  
}
.searchForm > input{
    width:100%;
    margin-right: 10px;   /*input按钮有空隙*/
    height: 40px;
    padding: 0 10px;   /*上下为0,左右为10px*/
    border: 1px solid #ddd;
    border-radius: 4px;  /*input加圆角效果*/
}
.searchForm > button{
    white-space:nowrap;  /*把搜索按钮强制变一行,也就是不换行*/
    padding: 0 28px;
    border: none;
    border-radius: 4px;
    background: #0282B9; /*在figma的button样式代码里找到*/
    color:white;    /*按钮上的文字搜索颜色*/
    line-height: 18px;      /*在figma的button样式代码里找到文字高度*/
}

4.2 告诉浏览器,用户点击搜索按钮时,跳转到那个网页?用form表单构造请求;

<header class="globalHeader">
        <form class="searchForm" method="get"
            action="https://www.baidu.com/s"
        >
            <input name="wd" type="text">    /*word*/
            <button type="submit">搜索</button>  /*submit要加*/
        </form>
</header>
    
    

4.3 对于Acfun静态跳转到网页如何实现?用a标签包住div标签acfun。a标签是内联元素,div是块级元素,正常来说块级元素不能放在内联元素里,但是a标签特殊,默认样式需要改:

a{color: inherit;text-decoration: none;}
<li>
                <a href="https://www.acfun.cn">
                <div class="site">
                    <div class="logo">A</div>
                    <div class="link">acfun.cn</div>
                </div>
                </a>
            </li>
            <li>
                <a href="https://www.bilibili.com">
                <div class="site">
                    <div class="logo"></div>
                    <div class="link">bilibili.com</div>
                </div>
                </a>
</li>

4.4 插入图片,在logo标签下,放img标签,把图片拖拽到src目录里,或者直接粘贴到src/images/b.jpg

<li>
                <a href="https://www.bilibili.com">
                <div class="site">
                    <div class="logo">
                        <img src=".\images\b.jpg" alt="">
                    </div>
                    <div class="link">bilibili.com</div>
                </div>
                </a>
</li>

调整img图片的高宽:

img{max-width: 100%;max-height: 100%;}

4.5 实现新增网站功能:用jQuery监听addButton的点击事件,jQuery的引入可以用两种方法:一种是直接下载jQuery.js文件放在src里面,另一种是bootcdn,搜索jquery,找到最新版本,后缀带min.js的,目前是3.6.0,复制标签:<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

4.5.1 测试jQuery引入成功:打印出来一个函数,不报错就引入成功。

console.log(jQuery)
console.log($)

4.5.2 测试是否有监听onclick,点击新增框,控制台打印1,说明正常。

$('.addButton')
 .on('click',()=>{
    console.log(1)
 })

4.5.3 手机端登陆ip,查看效果。命令行ipconfig获取ip地址,如果ipconfig: command not found,则配置环境变量,我的电脑-属性-高级系统设置-环境变量,在系统变量中双击变量名为path的选项,将%SystemRoot%\system32;%SystemRoot%;加入到变量值的输入框中,win7的话,就在已添加的内容后面加分号;粘贴到里面,一路确定。

修改完后就可以使用了,重启cmder,在ipconfig里面找到无线局域网适配器 无线网络连接:IPv4 地址192.168.xx.xxx,用手机浏览器访问192.168.xx.xxx:1234 就可打开项目。

4.5.4 如果用户点击了新增网站,输入的网址不是正确的格式,怎么提示?

$('.addButton')
 .on('click',()=>{
   let  url = window.prompt('请问你要添加的网址是啥?')
   console.log(url)
   if(url.indexOf('http')!==0){
        url = 'https://'+ url
   }
   console.log(url)
 })

4.5.5 初步实现新增的网站直接添加到页面中,注意用反引号实现多行代码,

$('.addButton')
 .on('click',()=>{
   let  url = window.prompt('请问你要添加的网址是啥?')
   console.log(url)
   if(url.indexOf('http')!==0){
        url = 'https://'+ url
   }
    console.log(url)
    const $siteList =$('.siteList')
    const $lastLi = $siteList.find('li.last')
    const $li = $(`<li>
    <a href="${url}">
                <div class="site">
                    <div class="logo">${url[0]}</div>
                    <div class="link">${url}</div>
                </div>
                </a>          
    </li>`).insertBefore($lastLi)
    })

4.5.6 进入新网址后,新增的页面信息清楚,怎么存活,用数组来存,以下代码实现:

const $siteList =$('.siteList')
const $lastLi = $siteList.find('li.last')
const x =localStorage.getItem('x')  //尝试读取当前网站上的x
const xObject =JSON.parse(x)        //如果x能成功的变成一个对象
const hashMap = xObject ||[         //把这个对象放在hashMap里,如果不行,就初始化含2项的数组
    {logo: 'A',logoType:'text',url:'https://www.acfun.cn'},
    {logo: '.\images\b.jpg',logoType:'image',url:'https://www.bilibili.com'},
]
const simplifyUrl = (url)=>{
    return url.replace('https://', '')
              .replace('http://', '')
              .replace('www.', '')
              .replace(/\/.*/, '') 
}
const render = ()=>{
    $siteList. find('li:not(.last)').remove()
    hashMap.forEach(node=>{
        const $li =$(`<li>
        <a href="${node.url}">
        <div class="site">
            <div class="logo">${node.logo[0]}</div>
            <div class="link">${simplifyUrl(node.url)}</div>
        </div>
        </a>
    </li>`).insertBefore($lastLi)
    });
}
render()

$('.addButton')
 .on('click',()=>{
   let  url = window.prompt('请问你要添加的网址是啥?')
   console.log(url)
   if(url.indexOf('http')!==0){
        url = 'https://'+ url
   }
    console.log(url)
    hashMap.push({
        logo:url[0],
        logoType:'text',
        url:url
    }),
    render()
})

window.onbeforeunload = ()=>{           //关闭页面时会把当前的hashMap存到x里面,下次进入时会保存
    console.log('页面要关闭了')
    const string = JSON.stringify(hashMap) //把一个对象变成对象
    localStorage.setItem('x',string)
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1,
    minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
    <title>前航 - 前端导航网站</title>
    <style type="text/css">
        .icon {
          width: 1em;
          height: 1em;
          vertical-align: -0.15em;
          fill: currentColor;
          overflow: hidden;
        }
      </style>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <header class="globalHeader">
        <form class="searchForm" method="get"
            action="https://www.baidu.com/s"
        >
        <input name="wd" type="text">
        <button type="submit">搜索</button>
        </form>
    </header>
    <main class="globalMain">
        <ul class="siteList">
            
            <li class="last">
                <div class="addButton">
                    <div class="icon-wrapper">
                        <svg class="icon">
                            <use xlink:href="#icon-add"></use>
                          </svg>
                    </div>
                    <div class="text">
                    新增网站
                </div>
                </div>    
            </li>
        </ul>
    </main>
    <script src="//at.alicdn.com/t/font_1473630_8pfq4zss4o8.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="main.js"></script>
</body>
</html>
  1. 下面实现logo正确显示:先在html标签上插入1...2,确定要改的位置,<div class="logo">1${node.logo[0]}2</div>,直接再logo代码上,替换为调用简化url函数,再取大写就行;或者在css里面的 .logo样式,加上text-transform:uppercase,全大写。
$('.addButton')
 .on('click',()=>{
   let  url = window.prompt('请问你要添加的网址是啥?')
   console.log(url)
   if(url.indexOf('http')!==0){
        url = 'https://'+ url
   }
    console.log(url)
    hashMap.push({
        logo:simplifyUrl(url)[0].toUpperCase(),  //直接调用简化url函数,再取大写
        logoType:'text',
        url:url
    }),
    render()
})
  1. 继续优化用户输入网址的各种格式,优化到尽量少出bug,需要正则表达式(可去学一篇文章:三十分钟入门正则表达式)
const simplifyUrl = (url)=>{
    return url.replace('https://', '')
              .replace('http://', '')
              .replace('www.', '')
              .replace(/\/.*/, '')   //删除/开头的内容
  1. 实现删除某个导航网站,添加一个close标签,
const render = ()=>{
    $siteList. find('li:not(.last)').remove()
    hashMap.forEach(node=>{
        const $li =$(`<li>
        <a href="${node.url}">
        <div class="site">
            <div class="logo">${node.logo}</div>
            <div class="link">${simplifyUrl(node.url)}</div>
            <div class="close">xx</div>   //临时用xx,后期要改成正式的close按钮样式

7.1 把close按钮的css样式定位一下:

.siteList .site{
    
    width: 160px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    background: white;
    border:1px solid #ddd;
    border-radius: 4px;
    padding: 20px 0;
    position: relative; /*相对定位close按钮*/

.siteList .site > .close{
    position: absolute;  /*绝对定位close按钮*/
    right: 0;               /*定位close按钮*/
    top: 0;                 /*定位close按钮*/
}

7.2 用iconfont矢量图工具,搜索一个close样式按钮,添加到购物车,添加至项目,重命名新添加的图标,点击提示行更新代码,帮助文档,应用代码,复制那段到js里

<script src="//at.alicdn.com/t/font_1473630_8pfq4zss4o8.js"></script>
<div class="close">         
            <svg class="icon">
            <use xlink:href="#icon-close"></use>
            </svg>
</div>

7.3 优化一下图标的样式,

.siteList .site > .close{
    position: absolute;  /*绝对定位close按钮*/
    right: 10px;               /*定位close按钮*/
    top: 5px;                 /*定位close按钮*/
}

7.4 因为刚才添加的close按钮在a标签里面,如果点击close就会自动执行a标签的动作,所以要阻止这个冒泡,尝试了以后发现a标签还是与close冲突;删掉a标签,换成如下代码,其中点击close,关闭网页导航小窗口其实就是数组中的一项删掉!

hashMap.forEach((node,index)=>{     /*index加完以后有个括号记得写*/

$li.on('click',()=>{
        window.open(node.url)
    })
    
$li.on('click','.close',(e)=>{
        e.stopPropagation()  //阻止冒泡
        hashMap.splice(index,1)
        render()
    })
  1. PC端代码实现:

8.1 切换到PC端控制台,刷新页面,可以看到导航框是space-bettwen布局,需要改为平均布局,用负margin。首先把PC端的页面宽度设置一下,如果把searchForm设置为宽度400的话,可能会影响手机端,这样就换成媒体查询来实现,以避免对手机端效果的影响。

*{box-sizing: border-box;}
*:before,*:after{box-sizing: border-box;}
*{margin: 0; padding: 0;}
ul,ol{list-style: none;}
a{color: inherit;text-decoration: none;}
img{max-width: 100%;max-height: 100%;}
body{background:#eee;}
.globalHeader{
    margin: 20px; 
}
@media(min-width:500px){
    .globalHeader{
        margin: 60px 0 100px; 
    }
}
.searchForm{
    display: flex;
    justify-content: space-between;
}
@media (min-width:500px){       /*页面只支持500px宽度以上的效果实现*/
    .searchForm{
        max-width:400px;
        margin-left: auto;
        margin-right: auto;
    }
}
.searchForm > input{
    width: 100%;
    margin-right:10px ;
    height: 40px;
    padding: 0 10px;
    border: none;
    border: 1px solid #ddd;
    border-radius: 4px;
}
.searchForm > button{
    white-space: nowrap;
    padding: 0 28px;  /*有一个计算过程*/
    border: none;
    border-radius: 4px;
    background: #0282b9;
    color: white;
    font-size: 16px;
}

.globalMain{
    max-width: 900px;
    margin-left: auto;
    margin-right: auto;

}
.siteList{
    margin: 20px;
    display: flex;
    flex-wrap: wrap;

}

@media(min-width:500px){
    .siteList{
    margin-left: 0;
    margin-right: -25px;

}
}
.siteList > li{
    margin-bottom: 10px;
    margin-right: 25px;
}
.siteList .site{
    
    width: 160px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    background: white;
    border:1px solid #ddd;
    border-radius: 4px;
    padding: 20px 0;
    position: relative; /*相对定位close按钮*/
}
.siteList .site > .logo{
    width:64px;
    height: 64px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 64px;
    text-transform: uppercase;

}
.siteList .site > .link{
    font-size: 14px;
    margin-top: 4px;

}
.siteList .site > .close{
    position: absolute;         /*绝对定位close按钮*/
    right: 10px;               /*定位close按钮*/
    top: 5px;                 /*定位close按钮*/
}
.siteList .addButton{
    border: 1px solid #ddd;
    background: white;
    width: 160px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    padding: 20px 0;
}
.siteList .addButton .icon{
    width: 56px;
    height: 56px;

}
.siteList .addButton .text{
   font-size: 14px;
   margin-top: 4px;
}
.siteList .addButton .icon-wrapper{
    width: 64px;
    height: 64px;
    display: flex;
    justify-content: center;
    align-items: center;

}

8.2 PC端优化交互,隐藏close按钮,其他效果见详细代码,注意PC端的代码实现与手机端的联调,把手机端不想实现的效果,都隐藏在PC端的媒体查询中:

.siteList{
    margin: 20px;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}

@media(min-width:500px){
    .siteList{
    margin-left: 0;
    margin-right: -25px;
    justify-content:flex-start;
}
  1. 键盘导航:监听document
$(document).on('keypress',(e) => {
    const {key}= e          /*const key =e.key 的简化写法*/
    for (let i = 0; i< hashMap.length; i++) {
        if (hashMap[i].logo.toLowerCase() === key) {
            window.open(hashMap[i].url)
        }
    }
})
  1. 发布到Github上