跳至主要內容

实战回

xmut-lby大约 14 分钟

实战回

本集我们来进行两个实战练习,希望能使大家对如何进行前端开发有一定了解。 本集我们实现两个功能,第一个实现一个弹出式的组件(下拉式菜单)。第二个实现一个较为复杂的页面导航条。

弹出式组件

设计目标

如图,这是一个非常常见的页面元素,也就是所谓的弹出式菜单。 正常情况下只显示上面一条主菜单,当鼠标移动到某个菜单项时,就会弹出子菜单。这是页面上非常常见的一种呈现方式。

如何实现?

我们先给出初始菜单条的代码:

初始代码
<div class="container">
    <nav class="nav nav1">
        <ul>
            <li>
                <a href="#">庄体育</a>
            </li>
            <li>
                <a href="#">消息</a>
            </li>
            <li>
                <a href="#">动态</a>
            </li>
            <li>
                <a href="#">收藏</a>
            </li>
            <li>
                <a href="#">历史</a>
            </li>
        </ul>
    </nav>
</div>
.container{
    width:600px;
    margin: auto;
}
.nav ul{
    list-style: none;
    background: #333;
    padding-left:0px;
    color: #fff;
}

.nav>ul>li{
    display:inline-block;
}

.subnav{
    min-width:110px;
}

li:hover .subnav{
    visibility:visible;
    opacity:100;
}

.subnav a{
    border-left: 0px;
    border-top: 1px solid #595959;
}

.nav a{
    display:block;
    padding: 10px 20px;
    line-height: 1.2em;
    color: #fff;
    border-left: 1px solid #595959
}

a:link{
    text-decoration:none;
}

如果你对一个功能的实现不知道如何下手的话,除了百度之外,还可以看别人是怎么实现的。 例如我们可以观察B站的弹出菜单时如何实现的:

开启F12之后,选中一个弹出式菜单,比如直播、番剧之类的,注意在右侧的元素里要点开对应的属性。

然后触发元素弹出和回收,你应该能观察到元素界面发生了变化:

未弹出时
未弹出时
弹出时
弹出时

你应该能观察到元素界面发生的变化,并通过这个变化窥探到弹出菜单的秘密:

  1. 收回时,其css变成display:none,关闭了显示。
  2. 弹出式,其css的display:none消失,因此块就显示出来了。

弹出式组件的实现方式基本都是类似的:

  1. 将需要弹出的组件放置在页面的合适位置。
  2. 默认关闭其显示。
  3. 当触发其显示时,通过修改css,使其显示在页面上。
  4. 需要收回时,通过修改css,使其从页面上消失。
  5. 切换消失和显示的方式有很多种,根据你的需要可以选择,最简单的是display:none和display:block的切换。

前端工程师一定要懂得分析别人的实现方式,前端实现几乎是所有代码里最容易逆向的一种。

开始动手

我们写一个ul块来放置子菜单,并将这个块放在第一个菜单项的下方:

    <li>
        <a href="#">庄体育</a>
    </li>
    <ul>
        <li><a href="#">我的好友</a></li>
        <li><a href="#">我的积分</a></li>
        <li><a href="#">退出登录</a></li>
    </ul>

这里立刻就会出现问题:

你会发现子菜单块将剩余的菜单都挤到下一行了。 道理很简单,因为子菜单是一个块,也必须是一个块,否则无法依次向下排列。而菜单快被我们设置成display:inline-block以使他们可以显示在同一行上。那么庄体育这个内联块后接了一个块元素,自然会产生换行,这样剩余的块就会被挤到下面的行里去。 这里子菜单块不能再留在原来的文档流之中,否则会影响原有的布局,一般的处理上,我们会将其申明成position:absolute来将其从文档流中抽出来:

    <ul class="subnav">
        ...
    </ul>
.subnav{
    position:absolute;
    left:0px;
}

于是你获得:

块正确了,也不影响原有的布局,但位置不对。因为position:absolute是以body作为定位位置的。事实上这种说法并不正确,可以看这里open in new window

绝对定位元素相对的元素是它最近的一个祖先,该祖先满足:position的值必须是:relative、absolute、fixed,若没有这样的祖先则相对于body进行定位。偏移值由其top、bottom、left、right值确定。

我们有两种处理方式:

  1. 就让菜单块相对body定位。
  2. 让菜单块随其父元素进行定位。

显然第二种方式是我们提倡的,牢记一点:程序设计中,相对定位优于绝对定位。相对定位可以让我们更方便地调整页面的结构。 所以我们在最上方的ul处加入position:relative

step 1:放置菜单
<div class="container">
    <nav class="nav nav1">
        <ul style="position:relative">
            <li>
                <a href="#">庄体育</a>
            </li>
            <ul class="subnav">
                <li><a href="#">我的好友</a></li>
                <li><a href="#">我的积分</a></li>
                <li><a href="#">退出登录</a></li>
            </ul>
            <li>
                <a href="#">消息</a>
            </li>
            <li>
                <a href="#">动态</a>
            </li>
            <li>
                <a href="#">收藏</a>
            </li>
            <li>
                <a href="#">历史</a>
            </li>
        </ul>
    </nav>
</div>


 























.container{
    width:600px;
    margin: auto;
}
.nav ul{
    list-style: none;
    background: #333;
    padding-left:0px;
    color: #fff;
}

.nav>ul>li{
    display:inline-block;
}

.subnav{
    min-width:110px;
    position:absolute;
    left:0px;
}

li:hover .subnav{
    visibility:visible;
    opacity:100;
}

.subnav a{
    border-left: 0px;
    border-top: 1px solid #595959;
}

.nav a{
    display:block;
    padding: 10px 20px;
    line-height: 1.2em;
    color: #fff;
    border-left: 1px solid #595959
}

a:link{
    text-decoration:none;
}

















 
 























弹出和回收子菜单

现在我们看如何控制子菜单的弹出和回收。我们首先为子菜单设置display:none来隐藏这个菜单:

.subnav{
    min-width:110px;
    position:absolute;
    left:0px;
    display: none;
}




 

现在来处理鼠标悬停显示的问题。我们之前已经知道这个可以用伪类:hover来选中。然而这里的一个问题是,我们只能悬停在菜单项(也就是“庄体育”这个li)上。 然后你会发现这行不通:

<div class="container">
    <nav class="nav nav1">
        <ul style="position:relative">
            <li class="parent">
                <a href="#">庄体育</a>
            </li>
            <!-- ... -->
        </ul>
    </nav>
</div>
.parent:hover{ /* 鼠标悬停在 .parent 上时生效 */
    display : block;
}

这个css选择器的意思是:当鼠标悬停在.parent类的元素上时触发。但我们简单分析一下就知道这是不对的:当鼠标悬停时,我们是想要.subnav显示,不是.parent显示

而如果你这样写:

.navsub:hover{
    display: block;
}

也是不行的,因为这个选择器必须当鼠标悬停在.navsub上时才会选中,而此时对应的.navsub已经处于display:none状态,也就是隐藏状态了,你无法悬停在这个块上

似乎我们陷入了死循环,不过这里有个比较不那么大众化的解决方案:相邻兄弟选择器open in new window

所以这样写是可以的:

li:hover+.navsub{
    display: block;
}

试试,你会发现非常完美:鼠标移上去之后弹出,鼠标移开之后收回。

不过很可惜,这也不是标准答案:当你试图将鼠标移动到子菜单时,你做不到

仔细分析下,这非常符合逻辑。

因为上面css的规则的意思是:当鼠标悬停到li时,li相邻的.navsub应该使用以下的规则。但当你试图移动到子菜单,也就是.navsub时,这条规则就不成立了!。

那么怎么做呢,一个比较不那么好的解决方案是,给.navsub:hover也加上:

.navsub:hover{
    display: block;
}

也就是相当于:在你移出li时,趁浏览器还没反应过来把.navsub隐藏起来之前,就移入.navsub并触发下面的那条css

也就是说:只要你鼠标足够快,浏览器就追不上你。

不过我们有一种更优雅的实现方式:.navsub块移入父元素也就是li块:

<div class="container">
    <nav class="nav nav1">
        <ul style="position:relative">
            <li>
                <a href="#">庄体育</a>
                <ul class="subnav">
                    <li><a href="#">我的好友</a></li>
                    <li><a href="#">我的积分</a></li>
                    <li><a href="#">退出登录</a></li>
                </ul>
            </li>
            <li>
                <a href="#">消息</a>
            </li>
            <li>
                <a href="#">动态</a>
            </li>
            <li>
                <a href="#">收藏</a>
            </li>
            <li>
                <a href="#">历史</a>
            </li>
        </ul>
    </nav>
</div>



 
 
 
 
 
 
 
 















当然此时css的选择器也必须改为子类选择器open in new window

li:hover>.subnav{
    display: block;
}

简单分析一下就非常清楚这种方式是合理的:子菜单(.subnav)也是li的一部分,因此当你移入子元素时,上面的选择器依然会生效

css进阶

我们上面的例子里,鼠标一移动上去,菜单就弹出了,一移出,菜单就关闭。我们说他的响应是瞬时的。如果你观察很多其他的网站的话,会发现很多网站的弹出窗口都有动画效果。那么我们要如何实现这个动画呢? 以前比较常用的方法是用jQuery,jQuery提供了fadeInfadeOut函数来实现淡入淡出等效果。更高阶的还有动画效果。他们的本质都是利用定时器来实现一段动画。 随着css3的流行,大家都逐步转移到使用css3来制作动画了。

可以看CSS 过渡open in new window 和这边的一个例子open in new window

当鼠标移动到块上是,块会缓慢地变长,看代码:

div
{
    width:100px;
    height:100px;
    background:blue;
    transition:width 2s;
}

div:hover
{
    width:300px;
}

这里的div里设置了transition: width 2s;,他的意思是,当width变化时,这个变化需要用2s的时间来过渡完成。下面3个-开头的规则都是为了兼容旧版本。 当鼠标移动上去时,触发div:hover规则生效,width从100px变成300px,但这个过程需要2s的时间来完成,所以你会看到一个渐变的动画。

渐隐和渐入

我们可以利用这一特性来实现动画。注意因为我们要渐入和渐隐,此时不能再用display属性来控制显示和不显示,我们要利用visibility属性来控制可见和不可见,再用opacity来控制其显示的透明度,实现渐渐出现和渐渐消失的视觉效果:

.subnav{
    position:absolute;
    left:0px;
    min-width:110px;
    visibility:hidden; /*初始不可见*/
    opacity: 0; /*透明度0*/
    transition: all 0.25s;/*所有效果都在0.25s的时间内渐变*/
}

li:hover>.subnav{
    visibility: visible;
    opacity: 100;
}

思考一下

如何实现菜单弹出的效果? 提示一下: 看看heightmax-heightoverflow这几个css规则。

网页导航条

设计目标

我们给出初始的代码,本节只讨论如何布局,不讨论如何美化,css风格的调整请自行尝试——一个很简单的技巧,你可以对照B站的F12对照着做。

原始代码

我已经把初始css配置放到style.css里了,我们对布局的调整将在layout.css里进行。

最终我们希望能达到这样的效果:

最终的效果
最终的效果

这种模式是比较常见的一种导航条。

如果你对代码不是很熟悉的话,看代码很容易晕掉,这里有两个技巧:

看代码的技巧

  1. 善用你的编辑器,尤其对这种html语言的文件,要懂得对代码进行折叠,这样可以更容易搞清楚代码的逻辑框架。
  2. 跑起来,对于我们的页面,浏览器可以帮我们更好地将元素和css对照起来。

实际上这两点对于所有类型的代码都是适用的,尤其对于一些逻辑比较复杂的代码,打断点并跑起来是理解代码的捷径。 所以请记住:程序设计(软件开发)是实践性学科,一切以实际运行为准。

原始代码解析

在浏览器和你的vscode里打开页面。我们先来分析html的架构。

在我们这个代码里,所有内容都包括在body下的div.header-bar块里。div.header-bar也是我们的主块。

主块里包含三个块,分别是:ul.left-entrydiv.center-search-containerul.right-entry。 这样左侧的菜单组件就是li.left-entry-item,右侧的菜单组件我们用li.right-entry-item来控制。

标记简写法

到现在你应该适应标记的简写法了。例如ul.left-entry,表示<ul class="left-entry">。 又或者form#nav-searchform,就是<form id="nav-searchform">

我们先不看中间的搜索栏,单看左右菜单条的话,就会发现实际上就是简单的ul>li(那么你知道,为什么li的前面没有像普通li前面一样的圆点吗?)。

这些菜单项的内容除了文字内容之外,还有一些是svg,这是一种可缩放矢量图形open in new window,对应你页面上看到的图标。

step 1:总体布局和左侧菜单

显然我们希望这三个块横向并列排布。那么我们就需要将这三个块的父块(div.header-bar)设置成display:flex。 同时我们希望这三个块尽量分布在整个页面宽度上,所以我们用space-between来设置分布:

.header-bar{
    display: flex;
    justify-content: space-between;
}
设置容器
设置容器

菜单项也应该要水平分布,因此左右菜单项也要配置flex

.left-entry{
    display: flex;
}

.right-entry{
    display: flex;
}

我们得到类似的结果:

菜单flex布局
菜单flex布局

挤在一起了,而且几个项的竖直方向上并没有对齐,我们设置一下边距,并设置flex容器交叉轴上居中对齐:

.left-entry{
    display: flex;
    align-items: center;
}

.left-entry-item{
    margin-left: 20px;
}


 


 
 
 

你应该会看到一个相当像样的结果了:

左侧菜单布局结果
左侧菜单布局结果

现在开始第一步的技术总结

  1. display:flex:启动弹性布局。
  2. justify-content: space-between:将元素在主轴(横轴)上尽量分散分布占满宽度。
  3. align-items: center:在交叉轴(纵轴)上居中对齐元素。
  4. 适当使用margin来调整元素之间的间隔。

你可能会说:你没说过space-between属性啊,你没说过align-items啊。

且住,大学的课程本来就不应该做成说明书的罗列。我们在布局那章已经讲过flex布局的很多知识,并给出了对应的属性。 如果你按大学的学习方式学习过:也就是说,自己动手试过,那么你至少应该有一些印象。

另一种能让你记住的方式:照着教程做几遍——也就是本节正在做的事。

你看,无论何种方式,都要求你动手做起来。

step 2:右侧菜单

和左侧一样,我们也为右侧菜单设置主轴对齐。不过这里有一个问题,就是右侧菜单的图标是要位于文字之上的。 不过根据默认的状态,他们是在同一行的:

右侧菜单的起始状态
右侧菜单的起始状态

我们观察下右侧菜单项的布局,都是a下面套一个svg和一个文字span

<li class="right-entry-item">
    <a href="#" target="_blank">
        <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path>....</path>
            <path>....</path>
        </svg>
        <span class="right-entry-text">消息</span>
    </a>
</li>


 
 
 
 
 


我们要将svg放置到span上,当然我们可以将这两个块都申明为display:block,之后再做处理。

另一种比较好的方式还是继续使用flex布局,在a上设置,svgspan就是这个容器的两个元素。

注意flex布局可以设置纵轴为主轴,再配置align-items:center就可以居中对齐了:

.right-entry-item > a{
    display: flex;
    flex-direction: column; /* 主轴设置为纵轴 */
    align-items: center; /* 交叉轴居中 */
    margin-left: 20px;
}

我们同时配置margin来略作排版。

最后我们对投稿按钮进行风格化:

.header-upload-entry{
    display: flex;
    flex-direction: row;
    align-items: center;
    padding: 10px 20px;
}

.header-upload{
    margin-left: 20px;
}

并对整个右侧的ul.right-entry配置一个右边距来调整他的位置:

.right-entry{
    display: flex;
    align-items: center;
    margin-right: 50px
}



 

step 3:中间搜索栏

这里不再仔细分析搜索栏的具体处理过程,直接给出最后的结果:

#nav-searchform{
    display: flex;
    align-items: center;
}

.center-search-content{
    min-width: 200px;
    max-width: 400px;
    flex-grow: 1;
}

.center-search-container{
    margin: 0px 50px;
    flex-grow: 1;
    display: flex;
    justify-content: center;
}

.nav-search-content{
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nav-search-input{
    flex: 1;
}





 
 
 
 
 
 
 
 
 
 
 
 










注意中间.center-search-content.center-search-container的两段规则,是为了处理搜索栏的逻辑。自己实际动手时可以先不管这段。

最后要注意,搜索条和左右的菜单栏可能没有对齐,这我们已经处理很多次了。其次,我们的结果还少了菜单下面的一条阴影。这些部分就留给大家自己做了。

这里可以得到最终的代码。