网站首页 » 前端开发 » Vue » Vue App 框架级页面过渡效果(IOS 版)
上一篇:
下一篇:

Vue App 框架级页面过渡效果(IOS 版)

前言

页面过渡效果现在的 APP 一抓一大把,比如:微信,QQ。个人认为页面的过渡效果对于提高用户体验来说,有很大的作用。谁不喜欢看到一个活生生的应用,谁又会喜欢古董级的应用?所以这篇文章就是让你体验下页面平滑过渡的那种细腻的,如丝般顺滑的感觉,感觉有了什么都好说。

这里说的 IOS 版本不是说只适用于 IOS ,而是页面效果切换是类 IOS 的切换动画效果。

Vue 页面过渡之旅

那我们就开始吧,在开始之前我们不妨看看这个演示图,为了让你可以看到更好的效果,我也不惜把 1M 多的 gif 图放上来了,所以我觉得我的诚意还是够的,但事与愿违,图片实在太大了,不过经过一番努力,减少操作及加快操作速度,最后图片降到了700多 k 中,上传成功,请看图:

Vue App 框架级页面过渡效果(IOS 版)

操作有点过快了,可能过渡动画都还没完全走完,这篇文章分享的这个过渡效果实现以下功能:

  • 淡入淡出同时过渡效果
  • 按操作记录来选择不同的淡入淡出方向(左右)

不仅仅是这样,你还可以通过本案例 get 到其它一些技能,比如:app 级的整体结构设计方案。因为本文重点不在这,所以只能简单的说下,这个结构分上(header.vue)、中、下(footer.vue)三大块,结构代码都放在 App.vue 文件中。并在中间区块(主要内容区)添加上路由显示区。所以路由跳转的页面都会在这个显示区里进行展示。所以头部跟底部相对来说就是通用的,这个通用不仅仅是结构相同,样式一样,而是所有的页面都共用这两个代码块,也就是说如果你某些页面的头部或者底部要加点东西或者删除点东西,全都必要在这两个文件中进行添加修改,然后通过v-if 或者 v-show 来区分不同页面显示不同的内容。这样做的好处就是维护起来方便,但可能在一些高度自定义的页面中的灵活性就没有那么大了。不过我个人觉得采用这种方式会比较好,不用每一个组件都单独引入。毕竟如果每个组件都要引入一个 header.vue 和 footer.vue (这个估计可以不用在每个组件中引,而是直接写在 App.vue 中),就算每一个页面都引一次 header.vue 也会让我觉得有点烦了。好了,关于 App 中的框架结构代码就说到这里,下面来看看代码本案例的实现代码,在这之前,先来看看文件的大体结构:

主要文件结构
├── index.html
├── assets
│  ├── fonts
│  │  ├── yunkus-icons.ttf   # 字体图标
│  │  └── yunksu-icons.woff  # 字体图标
│  └── style
│     ├── base.css
│     ├── normalize.css
│     └── icon.css
├── main.js
├── router
│  └── index.js              # 路由配置文件
├── store
│  ├── index.js
│  └── module
│     └── structure
│        └── index.js
├── App.vue                  # 大的框架结构组件
└── components
   ├── Header.vue          
   ├── Footer.vue
   ├── Home.vue
   ├── Comments.vue
   ├── Notice.vue
   ├── Collection.vue
   ├── Treasure.vue
   └── Search.vue
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import store from './store' // 这个一定要在 router 之前引入,不然在 /router/index.js 中引用 this.a.app.$store 时会返回 undefined
import router from './router'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  router,
  components: {
    App
  },
  template: '<App/>'
})

App.vue 文件代码很少,主要是引入 stroe 配置文件。

router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import Search from '@/components/Search'
import Treasure from '@/components/Treasure'
import Comments from '@/components/Comments'
import Notice from '@/components/Notice'
import Collection from '@/components/Collection'
import NoticeDetail from '@/components/NoticeDetail'

Vue.use(Router)

let router = new Router({
  routes: [{
    path: '/',
    name: 'Home',
    component: Home,
    meta: {
      name: "首页"
    }
  }, {
    path: '/search',
    name: 'Search',
    component: Search,
    meta: {
      name: "搜索"
    }
  }, {
    path: '/treasure',
    name: 'Treasure',
    component: Treasure,
    meta: {
      name: "宝藏"
    }
  }, {
    path: '/comments',
    name: 'Comments',
    component: Comments,
    meta: {
      name: "评论"
    }
  }, {
    path: '/notice',
    name: 'Notice',
    component: Notice,
    meta: {
      name: "通知"
    }
  }, {
    path: '/collection',
    name: 'Collection',
    component: Collection,
    meta: {
      name: "你收藏的所有文章都在这里啦"
    }
  }]
})
// 拿到 sessionStorage 对象
const history = window.sessionStorage
history.clear()
// 获取 count 如果没有则为0
let historyCount = history.getItem('count') * 1 || 0
// 设置根路由为 0
history.setItem('/', 0)
// 在进入路由之前处理以下逻辑
router.beforeEach((to, from, next) => {
  // 拿到 store 状态
  const store = this.a.app.$store;
  // 保存目标路由和出发路由的路径字符串,第一次点击时会返回 null
  const toIndex = history.getItem(to.path);
  const fromIndex = history.getItem(from.path);
  // 如果目标路由存在
  if (toIndex) {
    if (!fromIndex || parseInt(toIndex, 10) > parseInt(fromIndex, 10) || (toIndex === '0' && fromIndex === '0')) {
      // 提交 updateDirection 告诉 vuex 更改指定属性的状态值
      store.commit('updateDirection', 'forward')
    } else {
      store.commit('updateDirection', 'reverse')
    }
  } else { // toIndex 不存在,即用户点击了新的菜单路由
    ++historyCount
    history.setItem('count', historyCount)
    // 如果不是要根路由,则把目标路由的 path 值当作 key 值保存到 sessionStorage ,并且对应的值为用户操作步骤的记录数 historyCount 
    to.path !== '/' && history.setItem(to.path, historyCount)
    store.commit('updateDirection', 'forward')
  }
  next();
});

export default router
store/module/structure/index.js
let state = {
  direction: 'forward'
}

const mutations = {
  updateDirection(state, direction) {
    state.direction = direction
  }
}
export default {
  state,
  mutations
}
App.vue
<template>
  <div id="app">
     <yk-header v-show="hiddenHeader" :HMiddle="title" :transition="headerTransition">
      <template slot="HRight" v-if="title === '首页'">
        <div class="header-right-box"> 
          <router-link to="/search" class="header-button search-button"><i class="yk-icon yk-icon-search"></i></router-link>
          <router-link to="/" class="header-button more-button"><i class="yk-icon yk-icon-plus"></i></router-link>
        </div>
      </template> 

       <template slot="HRight" v-if="title === '评论'">
        <router-link :to="$router" class="header-button search-button"><i class="yk-icon yk-icon-more"></i></router-link>
      </template> 

       <template slot="HRight" v-if="title === '通知'">
        <router-link :to="$router" class="header-button search-button"><i class="yk-icon yk-icon-date"></i></router-link>
      </template> 

      <template slot="HRight" v-if="title === '你收藏的所有文章都在这里啦'">
        <router-link :to="$router" class="header-button search-button header-button-text">编辑</router-link>
      </template> 

       <template slot="HRight" v-if="title === '宝藏'">
         <router-link :to="$router" class="header-button search-button"><i class="yk-icon yk-icon-setting"></i></router-link>
      </template> 
    </yk-header>
      <transition :name="viewTransition" appear> 
         <router-view class="wrap"  v-bind:class="{'no-header no-footer':!hiddenHeader}"></router-view>
      </transition>
    <yk-footer v-show="hiddenHeader" ></yk-footer>
  </div>
</template>
<script>
import YkHeader from "@/components/Header";
import YkFooter from "@/components/Footer";
import { mapState } from "vuex";
import "@/assets/style/normalize.css";
import "@/assets/style/base.css";
import "@/assets/style/icon.css";
export default {
  name: "App",
  components: { YkHeader, YkFooter },
  methods: {},
  computed: {
    title() {
      // 从路由的 meta 中拿到当前路由所对应的标题名
      return this.$route.meta.name;
    },
    hiddenHeader() {
      if (this.$route.meta.name === "搜索") {
        return false;
      } else {
        return true;
      }
    },
    ...mapState({
      // 这里使用 mapState 就是为了简化写法,如果用旧套路的话,要得这么写 this.$store.state.structure
      // structure 为 store 的模块名
      direction: state => state.structure.direction
    }),
    headerTransition() {
      // 根据 direction 值的不同来变换切换效果类(用于头部文字切换)
      if (!this.direction) return "";
      return this.direction === "forward"
        ? "header-fade-in-right"
        : "header-fade-in-left";
    },
    viewTransition() {
      // 根据 direction 值的不同来变换切换效果类(用于页面切换)
      if (!this.direction) return "";
      return "pop-" + (this.direction === "forward" ? "in" : "out");
    }
  }
};
</script>

<style scope>
.pop-out-enter-active,
.pop-out-leave-active,
.pop-in-enter-active,
.pop-in-leave-active {
  will-change: transform;
  transition: all 500ms;
  height: 100%;
  top: 0;
  position: absolute;
  backface-visibility: hidden;
  perspective: 1000;
}
.no-header {
  padding-top: 0;
}
.no-footer {
  padding-bottom: 0;
}
.pop-out-enter {
  opacity: 0;
  transform: translate3d(-100%, 0, 0);
}
.pop-out-leave-active {
  opacity: 0;
  transform: translate3d(100%, 0, 0);
}
.pop-in-enter {
  opacity: 0;
  transform: translate3d(100%, 0, 0);
}
.pop-in-leave-active {
  opacity: 0;
  transform: translate3d(-100%, 0, 0);
}
</style>

上面的代码其实还可以简化下,比如:slot=”HRight” 的 template 标签中的内容可以合并到一个 template 中,然后把判断条件放对应的内容(<router-link >上)。但这写合并后,我个人觉得维护起来会更麻烦一些,还是用一个 template 对应一个页面会更好,结构看起来更加清晰明子。

components/Header.vue
<template>
  <div id="header">
		<div class="header-left">
      <slot v-if="$slots.HLeft" name="HLeft"></slot>
      <span v-if="showBack" @click="goBack" class="header-button header-left-back-box"><i class="yk-icon yk-icon-arrow-left"></i></span>
    </div>
		<h1 class="header-title" v-if="!shouldOverWriteTitle">
      <slot>
        <transition :name="transition">
            <span :key="HMiddle">{{HMiddle}}</span>
        </transition> 
       </slot>
    </h1>
    <div class="header-title" v-if="shouldOverWriteTitle">
      <transition :name="transition" appear>
        <slot v-if="$slots.HMiddle" name="HMiddle"></slot>
      </transition>
    </div>
		<div class="header-right">
         <slot v-if="$slots.HRight && !HRight" name="HRight"></slot>
         <span v-if="HRight" class="header-button header-button-text" :key="HRight">{{HRight}}</span>
    </div>
	</div>
</template>
<script>
export default {
  name: "YkHeader",
  props: {
    showBack: { type: Boolean },
    HLeft: {
      type: String,
      default: "Back"
    },
    HMiddle: {
      type: String
    },
    HRight: {
      type: String
    },
    transition: {
      type: String
    }
  },
  data() {
    return {
      shouldOverWriteTitle: false
    };
  },
  beforeMount() {
    if (this.$slots["HMiddle"]) {
      this.shouldOverWriteTitle = true;
    }
  },
  methods: {
    goBack() {
      this.$router.back();
    }
  }
};
</script>

<style scoped>
#header {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  width: 100vw;
  height: 13.337vw;
  background: #0085ff;
  font-size: 4.8vw;
  color: #fff;
}

#header .header-title {
  text-align: center;
  font-size: 4.8vw;
  font-weight: normal;
  height: 13.337vw;
  line-height: 13.337vw;
  margin: 0 24vw;
  overflow: hidden;
}

/* 这里的span 一定要设置成 block 或者是 inline-block ,不然就会有左右滑动效果 */
#header .header-title span {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#header .header-left,
#header .header-right {
  top: 0;
  position: absolute;
  max-width: 28vw;
  display: flex;
  height: 13.337vw;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  font-size: 4.8vw;
}
#header .header-left {
  left: 0;
}
#header .header-left-back-box {
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
#header .header-right {
  right: 0;
}
#header .header-right-box {
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
#header .header-right-box a {
  color: #fff !important;
}
#header .header-left .header-button + .header-button {
  margin-right: 1vw;
}
#header .header-button {
  height: 100%;
  width: 10.5vw;
  color: #fff;
  display: flex;
  justify-content: center;
  align-items: center;
}
#header .header-button i:focus {
  outline: none;
}
#header .header-button-text {
  padding: 0 2vw;
  font-size: 4.5vw;
}
.header-fade-in-right-enter-active {
  animation: fadeinR 0.5s;
}
.header-fade-in-left-enter-active {
  animation: fadeinL 0.5s;
}
@keyframes fadeinR {
  0% {
    opacity: 0;
    transform: translateX(150px);
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}
@keyframes fadeinL {
  0% {
    opacity: 0;
    transform: translateX(-150px);
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}
</style>
components/Footer.vue
<template>
	<div id="footer">
   <ul class="footer-main">
     <li>
       <router-link to="/" exact>
         <i class="yk-icon yk-icon-menu"></i>
         <span>首页</span>
       </router-link>
     </li>
     <li>
        <router-link to="/comments" exact>
          <i class="yk-icon yk-icon-news"></i>
         <span>评论</span>
       </router-link>
     </li>
     <li>
        <router-link to="/notice" exact>
          <i class="yk-icon yk-icon-bell"></i>
         <span>云库网</span>
       </router-link>
     </li>
     <li>
        <router-link to="/collection" exact>
          <i class="yk-icon yk-icon-star-off"></i>
         <span>收藏</span>
       </router-link>
     </li>
     <li>
       <router-link to="/treasure" exact>
          <i class="yk-icon yk-icon-goods"></i>
         <span>宝藏</span>
       </router-link>
     </li>
   </ul>
	</div>
</template>
<script>
export default {
  name: "YkFooter",
  data() {
    return {
      msg: "Welcome to Footer"
    };
  }
};
</script>

<style scoped>
#footer {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 13.337vw;
  background: #0085ff;
}
.footer-main {
  display: flex;
}
.footer-main li {
  width: 20vw;
  height: 13.337vw;
  box-sizing: border-box;
}
.footer-main li a {
  text-align: center;
  color: #fff;
  display: flex;
  width: 100%;
  height: 13.337vw;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  transition: background 0.3s ease-in-out 0s;
}
.footer-main li {
  display: flex;
}
.footer-main li a span {
  font-size: 3.5vw;
  margin-top: 1.5vw;
}
#footer .yk-icon {
  font-size: 5.5vw;
  transition: all 0.1s linear 0s;
}
.footer-main li .router-link-active {
  background: #016fd6;
}
.footer-main li .router-link-active .yk-icon {
  transform: scale(1.2);
}

上面那些就是主要的文件了,下面我们只需要做几个页面来进行跳转就可以。

Home.vue
<template>
  <div>
    <div class="home-page">
        <ul class="icon-list">
          <li><i class="yk-icon yk-icon-info"></i></li>
          <li><i class="yk-icon yk-icon-error"></i></li>
          <li><i class="yk-icon yk-icon-success"></i></li>
          <li><i class="yk-icon yk-icon-warning"></i></li>
          <li><i class="yk-icon yk-icon-question"></i></li>
          <li><i class="yk-icon yk-icon-back"></i></li>
          <li><i class="yk-icon yk-icon-arrow-left"></i></li>
          <li><i class="yk-icon yk-icon-arrow-down"></i></li>
          <li><i class="yk-icon yk-icon-arrow-right"></i></li>
          <li><i class="yk-icon yk-icon-arrow-up"></i></li>
          <li><i class="yk-icon yk-icon-caret-left"></i></li>
          <li><i class="yk-icon yk-icon-caret-right"></i></li>
          <li><i class="yk-icon yk-icon-caret-top"></i></li>
          <li><i class="yk-icon yk-icon-caret-bottom"></i></li>
          <li><i class="yk-icon yk-icon-d-arrow-left"></i></li>
          <li><i class="yk-icon yk-icon-d-arrow-right"></i></li>
          <li><i class="yk-icon yk-icon-minus"></i></li>
          <li><i class="yk-icon yk-icon-plus"></i></li>
          <li><i class="yk-icon yk-icon-remove"></i></li>
          <li><i class="yk-icon yk-icon-circle-plus"></i></li>
          <li><i class="yk-icon yk-icon-remove-outline"></i></li>
          <li><i class="yk-icon yk-icon-circle-plus-outline"></i></li>
          <li><i class="yk-icon yk-icon-close"></i></li>
          <li><i class="yk-icon yk-icon-check"></i></li>
          <li><i class="yk-icon yk-icon-circle-close"></i></li>
          <li><i class="yk-icon yk-icon-circle-check"></i></li>
          <li><i class="yk-icon yk-icon-circle-close-outline"></i></li>
          <li><i class="yk-icon yk-icon-circle-check-outline"></i></li>
          <li><i class="yk-icon yk-icon-zoom-out"></i></li>
          <li><i class="yk-icon yk-icon-zoom-in"></i></li>
          <li><i class="yk-icon yk-icon-d-caret"></i></li>
          <li><i class="yk-icon yk-icon-sort"></i></li>
          <li><i class="yk-icon yk-icon-sort-down"></i></li>
          <li><i class="yk-icon yk-icon-sort-up"></i></li>
          <li><i class="yk-icon yk-icon-tickets"></i></li>
          <li><i class="yk-icon yk-icon-document"></i></li>
          <li><i class="yk-icon yk-icon-goods"></i></li>
          <li><i class="yk-icon yk-icon-sold-out"></i></li>
        </ul>
    </div>
  </div>
</template>
<script>
export default {
  name: "YkHome"
};
</script>
<style scope>
.icon-list {
  overflow: hidden;
  position: relative;
}
.icon-list::before {
  position: absolute;
  content: "";
  bottom: 0;
  left: 0;
  top: 0;
  width: 1px;
  background-color: #c8c7cc;
  -webkit-transform: scaleX(0.5);
  transform: scaleX(0.5);
}
.icon-list li {
  position: relative;
  width: 25vw;
  height: 25vw;
  float: left;
  border: 1px solid #f1f1f1;
  box-sizing: border-box;
  text-align: center;
  line-height: 25vw;
  font-size: 11vw;
  color: #0085ff;
}
.icon-list li::before,
.icon-list li::after {
  position: absolute;
  content: "";
  background-color: #c8c7cc;
}
.icon-list li::before {
  height: 100%;
  right: 0;
  top: 0;
  width: 1px;
  -webkit-transform: scaleX(0.5);
  transform: scaleX(0.5);
}
.icon-list li::after {
  width: 100%;
  left: 0;
  bottom: 0;
  height: 1px;
  -webkit-transform: scaleY(0.5);
  transform: scaleY(0.5);
}
</style>
Comments.vue
<template>
<div>
	<div class="comments-page yk-box">
        <ul class="list-box">
            <li>这里的评论有没有水军</li>
            <li>这里的评论有没有水军</li>
            <li>这里的评论有没有水军</li>
            <li>这里的评论有没有水军</li>
            <li>这里的评论有没有水军</li>
            <li>这里的评论有没有水军</li>
             <li>这里的评论有没有水军</li>
            <li>这里的评论有没有水军</li>
            <li>这里的评论有没有水军</li>
            <li>这里的评论有没有水军</li>
        </ul>
	  </div>
  </div>
</template>
<script>
export default {
  name: "YkComments"
};
</script>
Notice.vue
<template>
<div>
	<div class="notice-page yk-box">
        <ul class="list-box">
            <li @click="goNotice">这里竟然有那么多通知,你是多久没来看我了</li>
            <li>这里竟然有那么多通知,你是多久没来看我了</li>
            <li>这里竟然有那么多通知,你是多久没来看我了</li>
            <li>这里竟然有那么多通知,你是多久没来看我了</li>
            <li>这里竟然有那么多通知,你是多久没来看我了</li>
            <li>这里竟然有那么多通知,你是多久没来看我了</li>
             <li>这里竟然有那么多通知,你是多久没来看我了</li>
            <li>这里竟然有那么多通知,你是多久没来看我了</li>
            <li>这里竟然有那么多通知,你是多久没来看我了</li>
            <li>这里竟然有那么多通知,你是多久没来看我了</li>
        </ul>
	  </div>
  </div>
</template>
<script>
export default {
  name: "Comments",
  methods: {
    goNotice() {
      this.$router.push("/noticeDetail");
    }
  }
};
</script>
Collection.vue
<template>
<div>
	<div class="comments-page yk-box">
        <ul class="list-box">
            <li>收藏那么多,你有看过吗?</li>
            <li>收藏那么多,你有看过吗?</li>
            <li>收藏那么多,你有看过吗?</li>
            <li>收藏那么多,你有看过吗?</li>
            <li>收藏那么多,你有看过吗?</li>
            <li>收藏那么多,你有看过吗?</li>
             <li>收藏那么多,你有看过吗?</li>
            <li>收藏那么多,你有看过吗?</li>
            <li>收藏那么多,你有看过吗?</li>
            <li>收藏那么多,你有看过吗?</li>
        </ul>
	  </div>
  </div>
</template>
<script>
export default {
  name: "Comments"
};
</script>
Treasure.vue
<template>
<div>
    <div class="treasure-page yk-box">
        <ul class="list-box">
            <li>数不完的宝藏等你拿</li>
            <li>数不完的宝藏等你拿</li>
            <li>数不完的宝藏等你拿</li>
            <li>数不完的宝藏等你拿</li>
            <li>数不完的宝藏等你拿</li>
            <li>数不完的宝藏等你拿</li>
            <li>数不完的宝藏等你拿</li>
        </ul>
    </div>
  </div>
</template>

<script>
export default {
  name: "YkSearch"
};
</script>
Search.vue
<template>
<div>
	<div id="search-page">
		    <div class="search-box">
            <div class="search-area">
                <span class="search-back" @click="goBack">Back</span>
                <div class="input-box">
                   <i class="yk-icon yk-icon-search"></i>
                   <input type="search" placeholder="输入内容">
                </div>
                <span class="search-btn">搜索</span>
            </div>
        </div>
        <div class="search-result-title">以下搜索内容由云库网提供</div>
        <div class="search-result">
            <ul>
                <li><a target="_blank" href="http://yunkus.com/vue-lifecycle-diagram/">Vue 生命周期钩子</a></li>
                <li><a target="_blank" href="http://yunkus.com/es6-primer-check-missing-patch/">《ES6 标准入门》查缺补漏</a></li>
                <li><a target="_blank" href="http://yunkus.com/vue-axios/">Vue axios 服务器通信</a></li>
                <li><a target="_blank" href="http://yunkus.com/vue-component-lazy-loading/">Vue 组件懒加载</a></li>
                <li><a target="_blank" href="http://yunkus.com/vue-plugins-development/">Vue 插件编写的完整套路</a></li>
            </ul>
        </div>
	</div>
  </div>
</template>
<script>
export default {
  name: "Search",
  data() {
    return {
      msg: "Welcome to Footer"
    };
  },
  methods: {
    goBack() {
      this.$router.back();
    }
  }
};
</script>
<style scoped>
#search-page {
  height: 100vh;
}
.search-result-title {
  font-size: 3vw;
  text-align: right;
  background: #f1f1f1;
  padding: 2vw 3.5vw;
  color: #ccc;
}
ul {
  list-style: none;
}
ul li {
  position: relative;
  padding: 2vw 0;
}
ul li a {
  color: #333;
}
ul li::after {
  position: absolute;
  right: 0;
  bottom: -1px;
  left: 0px;
  height: 1px;
  content: "";
  -webkit-transform: scaleY(0.5);
  transform: scaleY(0.5);
  background-color: #c8c7cc;
}
.search-box {
  height: 13.337vw;
}
.search-area {
  padding: 10px 0;
  display: flex;
  align-items: center;
}
.search-area .yk-icon {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  left: 6px;
  color: #ccc;
}
.input-box {
  -webkit-flex-grow: 1;
  flex-grow: 1;
  position: relative;
}
input {
  border-radius: 0.266667vw;
  -webkit-appearance: none;
  background-color: #f8f8f8;
  padding: 1.733333vw 4vw 1.733333vw 8.8vw;
  font-size: 0.346667rem;
  color: #666;
  outline: none;
  border: none;
  font-family: inherit;
  box-sizing: border-box;
  width: 100%;
}
input:placeholder {
  font-size: 5vw;
}
.search-back,
.search-btn {
  padding: 0 12px;
  white-space: nowrap;
  height: 100%;
}
.search-result {
  padding: 3vw 3vw;
}
</style>

好了,Vue App 框架级页面过渡效果IOS 版已经分享完成,代码有点多,不过也没关系,看重点就行,不必都看,毕竟有些代码都只是为了让这个案例更完整而已,比如后面的:Comments.vue、Collection.vue、Tearsure.vue、Notice.vue。

但是到这里还没有完,当你执行命令行 npm run build 进行打包后,访问资源时,会给你报一个错:

提示

TypeError: Cannot read property ‘app’ of undefined

app 未定义,点击错误跳转到 router/index.js 文件中的 const store = this.a.app.$store; 解决的方法是不用这种方式来到 $store ,而是把路由文件中的相关代码放到 main.js 中。所以我们需要修改下 main.js 和 router/index.js 这两个文件。

 main.js(新)
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import Router from 'vue-router'
import App from './App'
import store from './store' // 这个一定要在 router 之前引入,不然在 /router/index.js 中引用 this.a.app.$store 时会返回 undefined
import routerConfig from './router'

Vue.use(Router)
Vue.config.productionTip = false
let router = new Router({
  routes: routerConfig
})
/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  router,
  components: {
    App
  },
  template: '<App/>'
})

// 拿到 sessionStorage 对象
const history = window.sessionStorage
history.clear()
// 获取 count 如果没有则为0
let historyCount = history.getItem('count') * 1 || 0
// 设置根路由为 0
history.setItem('/', 0)
// 在进入路由之前处理以下逻辑
router.beforeEach((to, from, next) => {
  // 拿到 store 状态
  // console.log(to);
  // const store = this.a.app.$store;
  // 保存目标路由和出发路由的路径字符串,第一次点击时会返回 null
  const toIndex = history.getItem(to.path);
  const fromIndex = history.getItem(from.path);
  // 如果目标路由存在
  if (toIndex) {
    if (!fromIndex || parseInt(toIndex, 10) > parseInt(fromIndex, 10) || (toIndex === '0' && fromIndex === '0')) {
      // 提交 updateDirection 告诉 vuex 更改指定属性的状态值
      store.commit('updateDirection', 'forward')
    } else {
      store.commit('updateDirection', 'reverse')
    }
  } else { // toIndex 不存在,即用户点击了新的菜单路由
    ++historyCount
    history.setItem('count', historyCount)
    // 如果不是要根路由,则把目标路由的 path 值当作 key 值保存到 sessionStorage ,并且对应的值为用户操作步骤的记录数 historyCount 
    to.path !== '/' && history.setItem(to.path, historyCount)
    store.commit('updateDirection', 'forward')
  }
  next();
});

路由文件也只是写入路由信息

router/index.js
import Home from '@/components/Home'
import Search from '@/components/Search'
import Treasure from '@/components/Treasure'
import Comments from '@/components/Comments'
import Notice from '@/components/Notice'
import Collection from '@/components/Collection'
import NoticeDetail from '@/components/NoticeDetail'
let routers = [{
  path: '/',
  name: 'Home',
  component: Home,
  meta: {
    name: "首页"
  }
}, {
  path: '/search',
  name: 'Search',
  component: Search,
  meta: {
    name: "搜索"
  }
}, {
  path: '/treasure',
  name: 'Treasure',
  component: Treasure,
  meta: {
    name: "宝藏"
  }
}, {
  path: '/comments',
  name: 'Comments',
  component: Comments,
  meta: {
    name: "评论"
  }
}, {
  path: '/notice',
  name: 'Notice',
  component: Notice,
  meta: {
    name: "通知"
  }
}, {
  path: '/collection',
  name: 'Collection',
  component: Collection,
  meta: {
    name: "你收藏的所有文章都在这里啦"
  }
}, {
  path: '/noticeDetail',
  name: 'NoticeDetail',
  component: NoticeDetail,
  meta: {
    name: "这里只作测试通知的详情页标题过长时的点点点显示而已"
  }
}]
export default routers

到这里打完包后就可以访问资源了。注意,打包完后需要把资源放到服务器中才可以访问,线上本地都可以。除此之外打完包后还有两个小问题:

1、样式 显示异常,也就是打包时样式文件的引入顺序还不对,导致有些样式没有覆盖成功(比如:搜索页)。

2、还有一个就是过渡效果有删减,还没打包之前,离开和进入的页面动画是同时进行的。打包后,就只有页面的离开效果,进入效果不见了,让人看起来很是别扭。

在线演示链接:http://yunkus.com/demo/vue/vue-app-framework-transition-for-ios/

注意

请在浏览器的手机模式下浏览。

结过一翻研究后,终于找到了解决方法:把在 App.vue 中引入的那三个样式文件,放到 main.js 中引入并且要先于 App 根组件的引入。

关键代码
import "@/assets/style/normalize.css";
import "@/assets/style/base.css";
import "@/assets/style/icon.css";
import Vue from 'vue'
import Router from 'vue-router'
import App from './App'
import store from './store'
import routerConfig from './router'

记得把 App.vue 中的三个样式引入代码删掉。这样改完之后打包后运行,你会惊讶地发现竟然一箭双雕。上面两个问题都得到了完美解决。最终效果:http://yunkus.com/demo/vue/vue-app-framework-transition-for-ios/final/

1、如果你觉得看完这篇文章后你有所收获,如果按奈不住心中的热血沸腾,可以扫码赏我。

2、如果你觉得上面的代码看得不爽,你可以在打赏(¥6.6)时备注您的邮箱地址,我把源码发给你(整个 src 目录,可直接运行)。

3、如果你觉得浪费了你的时候,那么也没关系,我们当交个朋友,相互交流,共同进步。

  • 微信扫一扫,赏我

  • 支付宝扫一扫,赏我

声明

原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

本文永久链接:http://yunkus.com/vue-app-framework-transition-for-ios/

Leave a Reply

Your email address will not be published. Required fields are marked *

评论 END