最近在优化老的项目组件时,想利用vue自身的条件渲染和动画过渡来达到logo随侧边栏进行伸缩。目的效果如下:

logo缩小状态   logo放大状态

在进入正题前,先说说我的实现思路(部分含关键代码)。大牛请跳过,若有不足也请指正。

优化方向和解决方案:

  • 伸缩状态能够被保存(F5保存,页面关闭清除):使用vuex和cookie中(可用sessionStorage代替)。
// store.js
export default new Vuex.Store({
  state: {
    sidebar: {
      opened: !+Cookies.get('sidebarStatus')
    }
  },
  mutations: {
    TOGGLE_SIDEBAR: state => {
      if (state.sidebar.opened) {
        Cookies.set('sidebarStatus', 1)
      } else {
        Cookies.set('sidebarStatus', 0)
      }
      state.sidebar.opened = !state.sidebar.opened
    }
  },
  actions: {
    toggleSideBar ({ commit }) {
      commit('TOGGLE_SIDEBAR')
    }
  }
})
  • logo优化:svg上传阿里图标库,制作成字体来使用,一是节省图片大小,二是利于控制图片(颜色大小etc)。
  • 尽量减少代码以及页面渲染消耗
<template>
  <div class="sidebar-container">
    <div class="logo">
      <div style="height:50px;width:100%;">
        <transition name="fade">
          <i v-show="!isCollapse" key="logo-open" class="iconfont logo-open"></i>
          <i v-show="isCollapse" key="logo-close" class="iconfont logo-close"></i>
        </transition>
      </div>
      <!-- <transition name="fade">
        <div v-show="!isCollapse" key="logo-open" style="width:200px;height:50px;"><i class="iconfont iconweizhuLOGO logo-open"></i></div>
        <div v-show="isCollapse" key="logo-close" style="width:50px;height:50px;"><i class="iconfont iconweizhuLOGO1 logo-close"></i></div>
      </transition> -->
    </div>
    <div class="nav-container">
      <el-scrollbar class="page-scroll">
        <el-menu class="navBar"
          mode="vertical"
          :default-active="$route.path"
          :collapse="isCollapse"
          background-color="#304156"
          text-color="#ccc"
          active-text-color="#fff"
          unique-opened
          router>
          <template v-for="(item, index) in navList">

            <el-submenu v-if="item.children" :index="`${index}`" :key="index">
              <template slot="title">
                <i class="iconfont" :class="item.icon"></i>
                <span slot="title">{{item.title}}</span>
              </template>

              <template v-for="(item2, index2) in item.children">
                <el-submenu v-if="item2.children" :index="`${index}-${index2}`" :key="`${index}-${index2}`">

                  <template slot="title">{{item2.title}}</template>

                  <el-menu-item  v-for="(item3, index3) in item2.children" :index="item3.url" :key="`${index}-${index2}-${index3}`">{{item3.title}}</el-menu-item>

                </el-submenu>
                <el-menu-item v-else :index="item2.url" :key="`${index}-${index2}`">{{item2.title}}</el-menu-item>
              </template>

            </el-submenu>

            <el-menu-item v-else :index="item.url" :key="index">
              <i class="iconfont" :class="item.icon"></i>
              <span slot="title">{{item.title}}</span>
            </el-menu-item>

          </template>
        </el-menu>
      </el-scrollbar>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  data () {
    return {
      navList: [
        { title: '我的应用', icon: 'iconapp', url: '/myapp/list' }
      ]
    }
  },
  computed: {
    ...mapState([
      'sidebar'
    ]),
    isCollapse () {
      return !this.sidebar.opened
    }
  }
}
</script>

<style lang="scss" scoped>
.logo{
  position: absolute;
  width: 100%;
  z-index: 999;
  top: 0px;
  left: 0px;
  height: 50px;
  >div{
    text-align: center;
  }
  .iconfont{
    font-size: 28px;
    color: #fff;
    line-height: 50px;
  }
}
.navBar /deep/{
  padding-top: 90px;
  border: none;
  .el-menu-item, .el-submenu__title{
    height: 50px;
    line-height: 50px;
    > i.iconfont{
      position: relative;
      display: inline-block;
      width: 16px;
      height: 16px;
      font-size: 16px;
      margin-right: 4px;
    }
    > i.iconfont::before{
      position: absolute;
      width: 16px;
      height: 16px;
      top: -16px;
      left: 0;
    }
  }
  >.el-submenu>.el-menu .el-menu-item{
    position: relative;
    padding-left: 53px !important;
  }
  >.el-submenu>.el-menu .el-menu-item::before{
    content: "";
    position: absolute;
    top: 22px;
    left: 40px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #fff;
  }
}
.el-menu--collapse /deep/{
  width: 50px;
  .el-tooltip, .el-submenu__title{
    i.iconfont{
      font-size: 14px;
    }
  }
}
.navBar:not(.el-menu--collapse){
  width: 200px;
}
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}
</style>

好了,进入正题。

在优化开始之前其实是没有使用vue中的transition组件的,这导致了一个logo在随侧边栏打开或者关闭的时候,过渡期间跟侧边栏无法保持同步(请原谅我的手速无法解决到哪个尴尬的场景)。很明显地可以看到,侧边是宽度有过渡效果的放大缩小,而logo是直接在放大和缩小之间进行转换,没有过渡效果的logo宽度会在侧边栏过渡期间撑爆侧边栏,看着特别不舒服。

第一个想到的是用js监听侧边栏宽度,然后动态改变logo宽度,在恰当时间点对logo进行神不知鬼不觉地替换,达到完美同步。

在理了理思路和考虑渲染成本和代码长度之后我放弃了。

因为第二个办法就是直接使用transition和v-show组合,并让logo宽度进行自适应。也就是以上代码。结果却是这样

transition里面只能使用单个元素,如果多个元素,请使用transition-group。

这里要说一下条件渲染的两个元素,v-if和v-show。

官方有详细说明,我这里只是简单总结一下。

v-ifv-show
重新渲染dom节点,条件为假时dom不存在控制css的display属性来决定是否隐藏,条件真假dom都存在
支持template,有附属元素v-else不支持template和v-else

也就是说,上面的bug有两个解决方案,一个是v-show + transition-group或者v-if + v-else + transition。后者v-if + v-else会被视为一个元素(亲测)而v-show在这种情况下只能算两个元素。官方似乎没有说明这个隐藏属性,当然逻辑上其实也不需要太多说明。最后我这里选择了v-show + transition的方案,因为不需要重复渲染dom。

知识点GET到了之后,又进入题外,不需要的可以直接alt+f4了。

到这里为止,我以为优化工作基本完成,事实上,这种方式还有bug。在logo切换时,会有同时存在大小logo的情况,还是会错位。

官方文档有解决方案:mode=“out-in”

自定义过渡动画

<transition-group name="fade">
  <i v-show="!isCollapse" key="logo-open" class="iconfont logo-open"></i>
  <i v-show="isCollapse" key="logo-close" class="iconfont logo-close"></i>
</transition-group>

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-leave-to{
  display: none;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}

当然,如果不建议这点dom节点渲染的消耗,也可以使用

<transition name="fade" mode="out-in">
  <i v-if="!isCollapse" key="logo-open" class="iconfont logo-open"></i>
  <i v-else key="logo-close" class="iconfont logo-close"></i>
</transition>

 

Logo

前往低代码交流专区

更多推荐