前言
作为移动开发者,相信大家对于使用html开发的App和使用原生Api开发的App,或多或少都有所体验。他们有各自的优势与劣势,今天我们就参考原生App的效果,通过使用vue.js框架来实现一款类似原生效果的应用。
项目简介
联华会员线上退货流程的实现。主要功能点:选店、定位、上传图片、售后查询。实现方案:vue.js
效果预览
23.gif
项目过程中遇到的难点与解决方案
1、原生的页面push跳转是一个从右边往左进入的动画,pop操作是一个从左往右淡出的动画。下面介绍如何使用vue的Router和transition 来达到这种效果。
- 所有的页面都通过vue-router进行管理,给router-view加一个动画。
- 如何获取当前页面是push操作还是pop操作?监听浏览器的popstate 事件。通过在入口文件中监听popstate事件来标记当前的页面是不是pop操作,如果是pop操作就给当前的路由添加一个属性isBack
window.addEventListener('popstate', function (e) {
router.isBack = true
}, false)
- 监听路由的isBack属性,给当前页面设置push动画还是pop动画。至此,这种动画效果就实现了。
watch: {
$route(to, from) {
// 切换动画
let isBack = this.$router.isBack
if (isBack) {
this.transitionName = 'slide-left'
} else {
this.transitionName = 'slide-right'
}
this.$router.isBack = false
}
}
2、页面的缓存与销毁问题
原生的App的push操作会缓存已经加载的页面,pop操作会销毁页面。h5的跳转使用vue-router进行管理,如果不对页面进行特殊的缓存处理,h5的页面会在push时销毁当前页面,重新创建新页面,这种体验明显的不如原生体验,下面我们介绍下如何使用vue的页面缓存机制来达到原生的这种效果。
- keep-alive是Vue提供的一个抽象组件,用来对组件进行缓存,从而节省性能。他有2个常用属性include、exclude属性。include属性表示只有name属性为xxx的组件会被缓存,(注意是组件的名字,不是路由的名字)。exclude属性表示除了name属性为xxx的组件不会被缓存,其它组件都会被缓存。这里我们使用include属性,将push操作的页面缓存起来。
1)第一步:必须设置页面的name属性。
export default new Router({
routes: [
{
path: '/',
name: 'Main',
component: Main,
meta: {
}
}
]
})
2)第二步:在main.js入口文件中监听router.isBack属性,判断是push还是pop,如果是push就将页面的name存到一个数组中,如果不是就从数组中移除该页面,同时这个数组我们通过vuex存储起来。这个数组中存储的页面就是我们需要缓存的页面。
router.beforeEach((to, from, next) => {
let isBack = router.isBack
let arr = store.state.keepAlivePages.slice()
if (isBack) {
// 从数组中移除
let index = arr.indexOf(from.name)
if (index !== -1) {
arr.splice(index, 1)
}
} else {
// 加入数组,push操作都要加入缓存数组
let index = arr.indexOf(from.name)
if (index === -1) {
arr.push(from.name)
}
let indexTo = arr.indexOf(to.name)
if (indexTo === -1) {
arr.push(to.name)
}
}
store.commit('SET_KEEPALIVEPAGES', arr)
next()
})
3) 最后在主页面中设置我们需要缓存的页面
3、如何实现原生界面的侧滑返回效果?
- 通过判断手势的滑动可以实现侧滑返回的效果,但是和原生的侧滑相比还是有一些效果差距的。需要注意的是在主页面的手势响应可能会影响到滑块Slider的滑动,我们可以在通过修饰符@touchmove.stop 阻止手势的响应,达到不影响Slider的滑动问题。
methods: {
bodyTouchStart(event) {
this.touchStartPoint = event.targetTouches[0].pageX
this.touchStartPointY = event.targetTouches[0].pageY
},
bodyTouchMove(event) {
// 实时计算distance
this.distance = event.targetTouches[0].pageX - this.touchStartPoint
this.distanceY = event.targetTouches[0].pageY - this.touchStartPointY
},
bodyTouchEnd() {
// 滚动视图可能会导致左滑,所以要判断y方向的距离
if (this.distance > 100 && Math.abs(this.distanceY) < 50) {
this.$refs.navigation.clickBack()
} else {
}
this.distance = 0
}
}
4、如何实现全局的导航栏?以及导航栏的标题、返回按钮的显示等功能?
- 在定义路由的时候,我们需要通过meta定义一些可配置的字段,比如是否显示导航栏、导航栏标题、是否显示返回按钮、是否显示Tabbar等等。
{
path: '/',
name: 'Home',
meta: {
title: '推荐', // 导航栏标题
showTabbar: true, // 是否显示Tabbar
showBack: false
},
component: resolve => require(['../views/home.vue'], resolve)
},
- 然后在主页面(一般是App.vue)中设置导航栏等配置
- 需要注意的是,在上面的demo中导航栏的标题取的是一个变量navTitle,为什么要通过变量取值?是因为有这样一种场景:比如进入商品详情页面,导航栏的标题是服务器返回的商品名称,这种情况页面已经渲染完成,再通过meta设置标题是没有效果的。所以目前的方案是用通知的方式,把标题传递过来,然后赋值。
mounted() {
// 动态改变详情页面的title
this.bus.$on('changeDetailTitle', (title) => {
this.$set(this.$route.meta, 'title', title)
this.navTitle = title
})
}
综述
原生App的体验效果确实优于H5,但是通过以上的各种效果的优化,目前我们的H5项目已经能够更加接近原生的体验效果,特别是页面缓存这一块意义重大。
最后附上效果图,大家注意pop后页面的内容哦,是不是和原生很像.......
24.gif