likes
comments
collection
share

一个酷炫的移动端菜单栏效果

作者站长头像
站长
· 阅读数 23

移动端的菜单gif动图,效果如下:

一个酷炫的移动端菜单栏效果

实现过程也不难,最后效果如下:

一个酷炫的移动端菜单栏效果

接下来是代码:

首先是html结构

html
复制代码
<body>
    <div class="content">
        <div id="main"></div>
        <div id="menu">
            <div class="bar">
                <div class="item active-item" data-index=1 data-atr="menuLink">
                    <span class="icon">
                        <i class="iconfont icon-70BasicIcons-all-12"></i>
                    </span>
                    <span class="text">home</span>
                </div>
                <div class="item" data-index=2 data-atr="menuLink">
                    <span class="icon">
                        <i class="iconfont icon-70BasicIcons-all-13"></i>
                    </span>
                    <span class="text">edit</span>
                </div>
                <div class="item" data-index=3 data-atr="menuLink">
                    <span class="icon">
                        <i class="iconfont icon-70BasicIcons-all-05"></i>
                    </span>
                    <span class="text">profile</span>
                </div>
            </div>
            <div class="active-box-filter"></div>
        </div>
    </div>

</body>

结构说明:

<div id="menu"> 用fixed固定在页面底部, 底部的容器实际是<div class="bar">...</bar>使用了flex布局方式 icon和text分别使用<span>标签包裹起来,方便分别处理CSS效果 在三个菜单Item之后有一个classactive-box-filterdiv,其实就是gif中的半透明背景

观察gif效果可以看出:

  • 选中时:icon有像左移动的效果,整个item有一个半透明的背景,并且是从上一个item那里移动过来的,文字出 现
  • 离开时:icon向右归位,半透明背景移动到下一个item,文字渐渐消失

实现:

  • Item未被选中时,给包裹Icon的span一个translateX(130%),目的是水平往右移动,重叠在text的位置,而包裹text的span则将其opacity设为0,两者都添加一个0.2s的过渡效果transtion
css
复制代码
.icon{
    display: inline-block;
    transform: translateX(130%);
    transition: all .2s ease-in-out;
}
.text {
    opacity: 0;
    transition: all .2s ease-in-out;
}

Item被选中时,将Icon所属span的translateX(-15%),span会水平向左移动,text所属span的opacity设为1

css
复制代码
.active-item .icon{
    transform: translateX(-15%);
}
.active-item .text{
    opacity: 1;
}

上述完成后已经可以实现Icon的移动和Text的渐显

接下来是半透明背景

默认选中第一个菜单Item, 选中的item添加一个activeClassactive-item,背景的div的position设为absolute;并且其父元素添加相对位置属性,

html
复制代码
<div class="item active-item" ></div>
...
<div class="active-box-filter"></div>
css
复制代码
.active-item{
    color:white;
    transition: all .1s ease-in-out;
}
.active-box-filter{
    display: inline-block;
    position: absolute;
    width: 30%;
    height: 54%;
    background-color: rgba(255, 255, 255, 0.2);
    top: 23%;
    left: 2%;
    border-radius: 10px;
   transform: translateX(0px);
   transition: all .3s ease-in-out;
}

完整的CSS在文章最后

最后是JS,在html中我给三个菜单Item都分别添加自定义属性data-indexdata-attr,目的无外乎是为了在js操作更加便捷。

思路:

  • 选中时:判断event的target,如果target上data-attr=="menuLink",表示当前节点currentNode=e.target,否则currentNode=e.target.parentNode
  • 接下来获取菜单Item节点列表,利用data-index比较其和currentNode是否相等,
  • 若相等,则设置activeClass并且修改半透明背景块的水平移动距离translateX 完整js如下
js
复制代码
<script>
    let barNode = document.querySelector('.bar')
    barNode.addEventListener("click", nodeClick, false)

    function nodeClick(e) {
        let currentNode;
        let filterNode = document.querySelector(".active-box-filter")
        if (e.target.dataset.atr == "menuLink") {
            currentNode = e.target;
        } else if (e.target.parentNode.dataset.atr == "menuLink") {
            currentNode = e.target.parentNode
        }
        if (currentNode == undefined) return false;

        //childNode是一个NodeList,其自带一个forEach方法
        let childNode = document.querySelectorAll(".item");
        childNode.forEach((node, index) => {
            if (node.dataset.index !== currentNode.dataset.index) {
                node.className = "item"
            } else {
                node.className = "item active-item"
                filterNode.style["transform"] = `translateX(${110*(node.dataset.index-1)}%)`
            }
        })
    }
</script>

总结: 整个看下来,其实比较关键的是如何只使用一个半透明背景块做不定距离的移动,笔者尝试过使用一个after伪元素,但其在js计算translate的时候不太方便,弃之, 最后选择了一个单独的div做处理,并且利用translateX动画结合transtion的过渡,这样就能在视觉上看到背景块的所有运动。

如果你有更好的方案欢迎交流~~

完整CSS

css
复制代码
*,
html,
body {
    margin: 0;
    padding: 0;
    font-size:16px;
}

.content {
    position: relative;
}

#main {
    background-color: #e9ecff;
    min-height: 800px;
}

#menu {
    background-color: rgb(67, 47, 191);
    position: fixed;
    bottom: 0;
    left: 0;
    height: 4.375rem;
    width: 100%;
}


.bar {
    display: flex;
    width: inherit;
    height: inherit;
    justify-content: space-around;
    align-items: center;
}

.item {
    width: 5.625rem;
    position: relative;
    text-align: center;
    color:#9a9a9a;
    line-height: 2.81rem;
    transition: all .1s ease-in-out;
}
.active-box-filter{
    display: inline-block;
    position: absolute;
    width: 30%;
    height: 54%;
    background-color: rgba(255, 255, 255, 0.2);
    top: 23%;
    left: 2%;
    border-radius: 10px;
   transform: translateX(0px);
   transition: all .3s ease-in-out;
}

.active-item{
    color:white;
    transition: all .1s ease-in-out;
}


.icon{
    display: inline-block;
    transform: translateX(130%);
    transition: all .2s ease-in-out;
}
.text{
    opacity: 0;
    transition: all .2s ease-in-out;
}
.active-item .icon{
    transform: translateX(-15%);
}
.active-item .text{
    opacity: 1;
}