移動端適配
相對於PC端來講,移動端設備分辨率百花齊放,千奇百怪,對於每個開發者來講,移動端適配是咱們進行移動端開發第一個須要面對的問題。css
在移動端咱們常常能夠在head標籤中看到這段代碼:html
<meta name='viewport' content='width=device-width,initial-scale=1,user-scale=no' />
經過meta標籤對viewport的設置,定義了頁面的縮放比例;要了解這些參數的意義,咱們須要先知道幾個視口寬度的意義。前端
-
layoutviewport佈局寬度,就是網頁的寬度 -
visualviewport但是寬度,就是瀏覽器窗口的寬度,這個值決定了咱們手機一屏能看到的內容;visualviewport和layoutviewport的 -
大小關係,決定了是否會出現滾動條,當visualviewport更大或者恰好等於layoutviewport時是不會出現滾動條的。 -
idealviewport爲瀏覽器定義的可完美適配移動端的viewport,固定不變,能夠認爲是設備視口寬度device-width。
meta的設置其實就是對layoutviewport和visualviewport進行設置。vue
-
width=device-width表示頁面寬度layoutviewport與設備視口寬度idealviewport一致 -
initial-scale=1表示頁面寬度和網頁寬度與設備視口寬度的初始縮放比例,visualviewport由這個比例決定,可是對於layoutviewport來講,它同時受到兩個屬性的影響,而後取其中較大的那個值。
user-scale=no禁止縮放
因此如今咱們知道,這段在移動端常見的代碼的意思,即將visualviewport和layoutviewport設置爲idealviewport的值;這樣咱們在移動端就不會出現滾動條,網頁內容能夠比較好的展現出來,在這個前提下咱們再考慮頁面的適配問題。webpack
UI出圖的時候通常是有一個固定的寬度的,而咱們實際的移動端設備的寬度卻都不太同樣,可是若是頁面元素的縮放比例和頁面寬度的縮放比例一致,在不一樣尺寸的設備下咱們網頁的效果也將會是一致的。web
使用相對單位
rem
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 中的較大值vue-router
和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的表現與咱們想象的截然不同。
-
iOS彈出鍵盤;軟鍵盤喚起後,頁面的 fixed元素將失效(iOS認爲用戶更但願的是元素隨着滾動而移動,也就是變成了 absolute定位),既然變成了absolute,因此當頁面超過一屏且滾動時,失效的 fixed 元素就會跟隨滾動了。 -
當元素祖先的 transform 屬性非 none時,定位容器由視口改成該祖先。說的簡單點,就是position:fixed的元素會相對於最近的而且應用了transform的祖先元素定位,而不是窗口。致使這個現象的緣由是使用了transform的元素將建立一個新的堆疊上下文。堆疊上下文(Stacking Context):堆疊上下文是 HTML 元素的三維概念,這些 HTML 元素在一條假想的相對於面向(電腦屏幕的)視窗或者網頁的用戶的z 軸上延伸,HTML元素依據其自身屬性按照優先級順序佔用層疊上下文的空間。順序以下圖所示,總之堆疊上下文會對定位關係產生影響。想要進一步能夠查看不受控制的position:fixed。
鍵盤彈出與使用transform屬性的狀況在移動端是很常見的,因此須要謹慎使用position:fixed。
推薦使用flex
flex,即彈性佈局,移動端兼容性較好,可以知足大部分佈局需求。如今咱們使用flex來實現h5中常見的頂部標題欄+中部滾動內容+底部導航欄的佈局
頁面跳轉
轉場動畫
在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.meta
let fromDepth = from.meta
if (fromDepth > toDepth) {
this.transitionName = 'fade-left'
} else if (fromDepth < toDepth) {
this.transitionName = 'fade-right'
} else {
this.transitionName = 'fade'
}
}
}
}
</script>
登陸跳轉
雖然這樣可以實現跳轉效果,可是須要在編寫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;
底部導航欄
以前咱們已經實現了底部導航欄的基本樣式,這裏咱們再作一些說明。當頁面路由路徑與router-link的路由匹配時,router-link將會被設置爲激活狀態,咱們能夠經過設置active-class來設置路徑激活時應用的類名,默認爲router-link-active,而激活的類名還有一個router-link-exact-active,這個類名是由exact-active-class來設置的,一樣是設置路徑激活時應用的類名;active-class與exact-active-class實際上是由路由的匹配方式決定的。
通常路由的匹配方式是包含匹配。舉個例子,若是當前的路徑是 /a 開頭的,那麼 也會被設置 CSS 類名。按照這個規則,每一個路由都會激活 ,而使用exact屬性可使用「精確匹配模式」。精確匹配只有當路由徹底相同的時候纔會被激活。
路由守衛
移動端的路由守衛通常不會太複雜,主要是登陸權限的判斷,咱們設置一個路由白名單,將全部不須要登陸權限的路由放入其中;對於須要登陸的路由作判斷,沒有登陸就跳轉登陸頁面,要求用戶進行登陸後在訪問,若是登陸後須要返回原有路由就把目標頁面的路由做爲參數傳遞給登陸頁面,再在登陸後進行判斷,若是存在目標頁面參數就跳轉目標頁面,沒有就跳轉首頁。
若是你的應用涉及到權限,那須要標註每一個路由須要的權限,在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')
}
}
})
組件
自動加載
在咱們的項目中,每每會使用的許多組件,通常使用頻率比較高的組件爲了不重複導入的繁瑣通常是做爲全局組件在項目中使用的。而註冊全局組件咱們首先須要引入組件,而後使用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)
經過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>
經過插件的方式來使用組件
在不少第三方組件庫中,咱們常常看到直接使用插件的方式調用組件的方式,好比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() // 關閉
第三方組件
移動端各類組件、插件已經相對完善,在項目開發中重複造輪子是一件很不明智的事情;開發項目時咱們能夠藉助第三方組件、插件提升咱們的開發效率。
經常使用組件庫
VantUI是有贊開源的一套輕量、可靠的移動端Vue組件庫;支持按需引入、主題定製、SSR,除了經常使用組件外,針對電商場景還有專門的業務組件,若是是開發電商項目的話,推薦使用。官方文檔關於主題定製是在webpack.config.js中進行設置的:
// webpack.config.js
module.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";`
}
}
}
}
}
另外vux、mint-ui也是很好的選擇。
經常使用插件
better-scroll是一個爲移動端各類滾動場景提供絲滑的滾動效果的插件,若是在vue中使用能夠參考做者的文章當 better-scroll 碰見 Vue。
最後

本文分享自微信公衆號 - 前端瓶子君(pinzi_com)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。