Vue.js开发移动端经验总结

一、移动端适配


在移动端我们经常可以在head标签中看到这段代码:

<meta name='viewport' content='width=device-width,initial-scale=1,user-scale=no' />

通过meta标签对viewport的设置,定义了页面的缩放比例;要了解这些参数的意义,我们需要先知道几个视口宽度的意义。

a)layoutviewport布局宽度,就是网页的宽度

b)visualviewport可是宽度,就是浏览器窗口的宽度,这个值决定了我们手机一屏能看到的内容;visualviewport和layoutviewport的

c)大小关系,决定了是否会出现滚动条,当visualviewport更大或者刚好等于layoutviewport时是不会出现滚动条的。

d)idealviewport为浏览器定义的可完美适配移动端的viewport,固定不变,可以认为是设备视口宽度device-width。

meta的设置其实就是对layoutviewport和visualviewport进行设置。

a)width=device-width表示页面宽度layoutviewport与设备视口宽度idealviewport一致

b)initial-scale=1表示页面宽度和网页宽度与设备视口宽度的初始缩放比例,visualviewport由这个比例决定,但是对于layoutviewport来说,它同时受到两个属性的影响,然后取其中较大的那个值。user-scale=no禁止缩放。

二、使用相对单位


rem 是相对于根元素 html 的 font-size 来做计算。通常在页面初始化时加载时通过对document.documentElement.style.fontSize 设置来实现。一般我们将根元素html的font-size设置为宽度的1/10,不同设备的宽度不同,但是同样数值的rem比例与设备的宽度比例是一致的。

document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px';

在实际项目中我们无须在开发中自己进行转换,可以使用pxtorem在输出的时候将px转换为rem。

三、视口单位


将视口宽度window.innerWidth和视口高度window.innerHeight(即layoutviewport)等分为 100 份。

vw : 1vw 为视口宽度的 1% vh : 1vh 为视口高度的 1% vmin : vw 和 vh 中的较小值 vmax : 选取 vw 和 vh 中的较大值

和rem相比较,视口单位不需要使用js对根元素进行设置,兼容性稍差,但是大部分设备都已经支持了,同样的无须再开发时进行单位换算,直接使用相关的插件postcss-px-to-viewport在输出的时候进行转换。

四、修改viewport


之前我们提到了layoutviewport布局宽度实际上不是一个固定值,而是通过meta设置属性,通过idealviewport计算出来的值,我们可以通过控制meta的属性来将layoutviewport固定为某一个值。一般设计图的宽度为750px,现在我们的目标就是将layoutviewport设置为750px;layoutviewport受到两个属性的影响,width属性我们之间设置为750,initial-scale缩放比例应该为idealviewport的宽度/750;当我们未改变meta标签属性的时候,layoutviewport的值其实就是idealviewport的值,所以可以通过document.body.clientWidth或者window.innerWidth来获取。

;(function () {    const width = document.body.clientWidth || window.innerWidth    const scale = width / 750    const content = 'width=750, initial-scale=' + scale + ', minimum-scale=' + scale + ', maximum-scale=' + scale + ', viewport-fit=cover'    document.querySelector('meta[name="viewport"]').content = content})()

设置完成之后,layoutviewport在不同的设备中会始终保持为750px,我们开发时可以直接使用设计稿尺寸。

五、需要谨慎对待的fixed


position:fixed在日常的页面布局中非常常用,在许多布局中起到了关键的作用。它的作用是:position:fixed的元素将相对于屏幕视口(viewport)的位置来指定其位置。并且元素的位置在屏幕滚动时不会改变。但是,在许多特定的场合,position:fixed的表现与我们想象的大相径庭。

a)iOS弹出键盘;软键盘唤起后,页面的 fixed元素将失效(iOS认为用户更希望的是元素随着滚动而移动,也就是变成了 absolute定位),既然变成了absolute,所以当页面超过一屏且滚动时,失效的 fixed 元素就会跟随滚动了。

b)当元素祖先的 transform 属性非 none时,定位容器由视口改为该祖先。说的简单点,就是position:fixed的元素会相对于最近的并且应用了transform的祖先元素定位,而不是窗口。导致这个现象的原因是使用了transform的元素将创建一个新的堆叠上下文。堆叠上下文(Stacking Context):堆叠上下文是 HTML 元素的三维概念,这些 HTML 元素在一条假想的相对于面向(电脑屏幕的)视窗或者网页的用户的z 轴上延伸,HTML元素依据其自身属性按照优先级顺序占用层叠上下文的空间。顺序如下图所示,总之堆叠上下文会对定位关系产生影响。想要进一步可以查看不受控制的position:fixed。

键盘弹出与使用transform属性的情况在移动端是很常见的,所以需要谨慎使用position:fixed。

六、推荐使用flex


flex,即弹性布局,移动端兼容性较好,能够满足大部分布局需求。现在我们使用flex来实现h5中常见的顶部标题栏+中部滚动内容+底部导航栏的布局。

七、页面跳转


1、转场动画

在vue中我们通过vue-router来管理路由,每个路由跳转类似与在不同的页面之间进行切换,从用户友好的角度来说,每次切换页面的时候最好添加一个转场效果。如果转场动画不区分路由是打开新页面、还是返回之前页面我们只需要在外使用添加一个动画效果即可;但是一般打开和返回是应用不同的动画效果的,所以我们需要在切换路由的时候区分路由是前进还是后退。为了区分路由的动作,我们在路由文件中设置meta为数字,meta表示其路由的深度,然后监听$route,根据to、from meta值的大小设置不同的跳转动画。如果应用到多种跳转动画,可以根据详情,具体情况具体应用。

<template><transition :name="transitionName"><router-view></router-view></transition> </template><script>export default {name: 'app',data () {return { transitionName: 'fade' }},watch: {'$route' (to, from) {let toDepth = to.metalet fromDepth = from.metaif (fromDepth > toDepth) {this.transitionName = 'fade-left'} else if (fromDepth < toDepth) {this.transitionName = 'fade-right'} else {this.transitionName = 'fade'}}}}</script>

2、登录跳转

虽然这样能够实现跳转效果,但是需要在编写router时添加设置,比较麻烦;我们可以使用开源项目vue-navigation来实现,更加方便,无须对router进行多余的设置。npm i -S vue-navigation安装,在main.js中导入:

import Navigation from 'vue-navigation'

Vue.use(Navigation, {router}) // router为路由文件

在App.vue中设置:

this.$navigation.on('forward', (to, from) => {    this.transitionName = 'fade-right' }) 

this.$navigation.on('back', (to, from) => {    this.transitionName = 'fade-left' }) 

this.$navigation.on('replace', (to, from) => {    this.transitionName = 'fade' })

vue-navigation插件还有一个重要的功能就是保存页面状态,与keep-alive相似,但是keep-alive保存状态无法识别路由的前进后退,而实际应用中,我们的需求是返回页面时,希望页面状态保存,当进入页面时希望获取新的数据,使用vue-navigation可以很好的实现这个效果。具体使用可以查看vue-navigation有详细使用说明与案例。另外也可以尝试vue-page-stack,两个项目都能实现我们需要的效果,vue-page-stack借鉴了vue-navigation,也实现了更多的功能,并且最近也一直在更新。

PS: 这里的动画效果引用自animate.scss;

3、底部导航栏

之前我们已经实现了底部导航栏的基本样式,这里我们再做一些说明。当页面路由路径与router-link的路由匹配时,router-link将会被设置为激活状态,我们可以通过设置active-class来设置路径激活时应用的类名,默认为router-link-active,而激活的类名还有一个router-link-exact-active,这个类名是由exact-active-class来设置的,同样是设置路径激活时应用的类名;active-class与exact-active-class其实是由路由的匹配方式决定的。

一般路由的匹配方式是包含匹配。举个例子,如果当前的路径是 /a 开头的,那么 也会被设置 CSS 类名。按照这个规则,每个路由都会激活 ,而使用exact属性可以使用“精确匹配模式”。精确匹配只有当路由完全相同的时候才会被激活。

4、路由守卫

移动端的路由守卫一般不会太复杂,主要是登录权限的判断,我们设置一个路由白名单,将所有不需要登录权限的路由放入其中;对于需要登录的路由做判断,没有登录就跳转登录页面,要求用户进行登录后在访问,如果登录后需要返回原有路由就把目标页面的路由作为参数传递给登录页面,再在登录后进行判断,如果存在目标页面参数就跳转目标页面,没有就跳转首页。

如果你的应用涉及到权限,那需要标注每个路由需要的权限,在meta中设置roles,roles是数组来保存需要的权限;从后台的接口中获取用户拥有的权限和roles进行对比就可以判断是否具有相关权限了。

const whiteList = ['/login']

router.beforeEach((to, from, next) => {  

const hasToken = store.getters.auth 

 if (hasToken) {   

 if (to.path === '/login') {     

 next({ path: '/' })   

 } else {      

const needRoles = to.meta && to.meta.roles && to.meta.roles.length > 0     

 if (needRoles) {        

const hasRoles = store.state.user.roles.some(role => to.meta.roles.includes(role))

if (hasRoles) {   

 next()        

} else {   

 next('/403') 

  }  

} else { 

next()      

}    

 } else {   

 if (whiteList.includes(to.path)) {  

next()    

} else {      

next('/login')    

 }})

八、组件


1、自动加载

在我们的项目中,往往会使用的许多组件,一般使用频率比较高的组件为了避免重复导入的繁琐一般是作为全局组件在项目中使用的。而注册全局组件我们首先需要引入组件,然后使用Vue.component进行注册;这是一个重复的工作,我们每次创建组件都会进行,如果我们的项目是使用webpack构建(vue-cli也是使用webpack),我们就可以通过require.context自动将组件注册到全局。创建components/index.js文件:

export default function registerComponent (Vue) { 

 /**   * 参数说明:   

* 1. 其组件目录的相对路径  

 * 2. 是否查询其子目录   

* 3. 匹配基础组件文件名的正则表达式   **/  

const modules = require.context('./', false, /\w+.vue$/)  

modules.keys().forEach(fileName => {    

// 获取组件配置   

 const component = modules(fileName)   

 // 获取组件名称,去除文件名开头的 `./` 和结尾的扩展名   

 const name = fileName.replace(/^\.\/(.*)\.\w+$/, '$1')   

 // 注册全局组件    

// 如果这个组件选项是通过 `export default` 导出的,    

// 那么就会优先使用 `.default`,    

// 否则回退到使用模块的根。    

Vue.component(name, component.default || component)  })}

之后在main.js中导入注册模块进行注册,使用require.context我们也可以实现vue插件和全局filter的导入。

import registerComponent from './components'

registerComponent(Vue)

2、通过v-model绑定数据

v-model是语法糖,它的本质是对组件事件进行监听和数据进行更新,是props和 o n 监 听 事 件 的 缩 写 , v − m o d e l 默 认 传 递 v a l u e , 监 听 i n p u t 事 件 。现 在 我 们 使 用 v − m o d e l 来 实 现 下 数 字 输 入 框 , 这 个 输 入 框 只 能 输 入 数 字 , 在 组 件 中 我 们 只 需 要 定 义 v a l u e 来 接 受 传 值 , 然 后 在 输 入 值 满 足 我 们 输 入 条 件 ( 输 入 为 数 字 ) 的 时 候 使 用 on监听事件的缩写,v-model默认传递value,监听input事件。现在我们使用v-model来实现下数字输入框,这个输入框只能输入数字,在组件中我们只需要定义value来接受传值,然后在输入值满足我们输入条件(输入为数字)的时候使用 on监听事件的缩写,v−model默认传递value,监听input事件。现在我们使用v−model来实现下数字输入框,这个输入框只能输入数字,在组件中我们只需要定义value来接受传值,然后在输入值满足我们输入条件(输入为数字)的时候使用emit触发input事件。

<template>  

<div>    

<input type="text" :value="value" @input="onInput">  

</div>

</template>

<script>

export default {  

name: 'NumberInput',  

props: {    value: String  },  

methods: {    

onInput (event) {      

if (/^\d+$/.test(event.target.value)) {    

this.$emit('input', event.target.value)    

 } else {    

 event.target.value = this.value     

 }    }  }}

</script>

使用的时候,我们只需要使用v-model绑定值就可以了。v-model默认会利用名为value的prop和名为input的事件,但是很多时候我们想使用不同的prop和监听不同的事件,我们可以使用model选项进行修改。

Vue.component('my-checkbox', {  

model: {    prop: 'checked',    event: 'change'  },  

props: {    

// this allows using the `value` prop for a different purpose    

value: String,   

 // use `checked` as the prop which take the place of `value`    

checked: {      type: Number,      default: 0    }  },  

// ...})

<my-checkbox v-model="foo" value="some value"></my-checkbox>

上述代码相当于:

<my-checkbox  :checked="foo"  @change="val => { foo = val }"  value="some value"></my-checkbox>

3、通过插件的方式来使用组件

在很多第三方组件库中,我们经常看到直接使用插件的方式调用组件的方式,比如VantUI的Dialog弹出框组件,我们不但可以使用组件的方式进行使用,也可以通过插件的形式进行调用。

this.$dialog.alert({  message: '弹窗内容'});

将组件作为插件使用的原理其实并不复杂,就是使用手动挂载Vue组件实例。

import Vue from 'vue';

export default function create(Component, props) {    

// 先创建实例    

const vm = new Vue({        

render(h) {           

 // h就是createElement,它返回VNode            

return h(Component, {props})       

 }    }).$mount();   

// 手动挂载    

document.body.appendChild(vm.$el);    

// 销毁方法    

const comp = vm.$children[0];    

comp.remove = function() {       

 document.body.removeChild(vm.$el);       

 vm.$destroy();    }    

return comp;}

调用create传入组件和props参数就可以获取组件的实例,通过组件实例我们就可以调用组件的各种功能了。

<template>  

<div class="loading-wrapper" v-show="visible">    加载中  </div>

</template><script>

export default {  name: 'Loading',  

data () {    return {      visible: false    }  },  

methods: {    

show () {      this.visible = true    },   

 hide () {      this.visible = false    }  }}

</script>

<style lang="css" scoped>.loading-wrapper {  position: absolute;  top: 0;  bottom: 0;  width: 100%;  background-color: rgba(0, 0, 0, .4);  z-index: 999;}

</style>

<!--使用-->const loading = create(Loading, {})loading.show() // 显示loading.hide() // 关闭

4、第三方组件

移动端各种组件、插件已经相对完善,在项目开发中重复造轮子是一件很不明智的事情;开发项目时我们可以借助第三方组件、插件提高我们的开发效率。

常用组件库:

VantUI是有赞开源的一套轻量、可靠的移动端Vue组件库;支持按需引入、主题定制、SSR,除了常用组件外,针对电商场景还有专门的业务组件,如果是开发电商项目的话,推荐使用。官方文档关于主题定制是在webpack.config.js中进行设置的:

// webpack.config.jsmodule.exports = {  rules: [    {      test: /\.less$/,      use: [        // ...其他 loader 配置        {          loader: 'less-loader',          options: {            modifyVars: {              // 直接覆盖变量              'text-color': '#111',              'border-color': '#eee'              // 或者可以通过 less 文件覆盖(文件路径为绝对路径)              'hack': `true; @import "your-less-file-path.less";`            }          }        }      ]    }  ]};

但我们的项目可能是使用vue-cli构建,这时我们需要在vue.config.js中进行设置:

module.exports = {  css: {    loaderOptions: {      less: {        modifyVars: {          'hack': `true; @import "~@/assets/less/vars.less";`        }      }    }  }}

九、常用插件


better-scroll是一个为移动端各种滚动场景提供丝滑的滚动效果的插件

swiper是一个轮播图插件,如果是在vue中使用可以直接使用vue-awesome-swiper,vue-awesome-swiper基于Swiper4,并且支持SSR。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容

  • 移动端适配 相对于PC端来说,移动端设备分辨率百花齐放,千奇百怪,对于每一个开发者来说,移动端适配是我们进行移动端...
    markdown阅读 2,467评论 0 42
  • 一、什么是Vue.js 1. vue是一种数据驱动的前端框架 this.msg="我爱你",通过改变数据,然后自动...
    在路上919阅读 1,562评论 0 2
  • ☞☞ 个人主页欢迎访问 ☜☜ 最近在开发一个移动端项目微信公众号,前端框架我用当然是Vue,期间遇到了很多问题,在...
    苏日俪格阅读 7,812评论 7 51
  • 推荐指数: 6.0 书籍主旨关键词:特权、焦点、注意力、语言联想、情景联想 观点: 1.统计学现在叫数据分析,社会...
    Jenaral阅读 5,685评论 0 5
  • 城空了,有树长出来 我的城死了 铸起它的人,杀死它的人 不愿因为这件事而骄傲 一座城的终结 永远因为终结这件事而显...
    于十六阅读 2,841评论 6 17