講道理,如今開發移動端基本是不會有這麼一個問題的。但做爲移動端之前的經典坑,我這裏也拿出來講上一說吧。
移動設備上的web網頁是有300ms延遲的,玩玩會形成按鈕點擊延遲甚至是點擊失效。這是因爲區分單擊事件和雙擊屏幕縮放的歷史緣由形成的。但在2014年的Chrome 32版本已經把這個延遲去掉了,so you know。但若是你仍是出現了300ms的延遲問題,也是有路子搞定的。javascript
解決方案以下:css
1.fastclick能夠解決在手機上點擊事件的300ms延遲html
2.zepto的touch模塊,tap事件也是爲了解決在click的延遲問題前端
3.觸摸事件的響應順序爲 touchstart --> touchmove --> touchend --> click,也能夠經過綁定ontouchstart事件,加快對事件的響應,解決300ms延遲問題vue
4.若移動設備兼容性正常的話(IE/Firefox/Safari(IOS 9.3)及以上),只需加上一個meta標籤java
<meta name="viewport" content="width=device-width">
即把viewport設置成設備的實際像素,那麼就不會有這300ms的延遲。jquery
當今的手機端,各式各樣的手機,屏幕分辨率也是各有不一樣,爲了讓頁面能夠能夠兼容各大手機,解決方案以下android
1.設置meta標籤viewport屬性,使其無視設備的真實分辨率,直接經過dpi,在物理尺寸和瀏覽器之間重設分辨率,從而達到能有統一的分辨率的效果。而且禁止掉用戶縮放webpack
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
2.使用rem進行屏幕適配,設置好root元素的font-size大小,而後在開發的時候,全部與像素有關的佈局統一換成rem單位。針對不一樣的手機,使用媒體查詢對root元素font-size進行調整ios
移動端開發時,屏幕有豎屏和橫屏模式,當屏幕進行旋轉時,字體大小則有可能會發生變化,從而影響頁面佈局的總體樣式,爲避免此類狀況發生,只需設置以下樣式便可
* { -webkit-text-size-adjust: none; }
* { -webkit-tap-highlight-color: rgba(0,0,0,0); }
不過這個方法在如今的安卓瀏覽器下,只能去掉那個橙色的背景色,點擊產生的高亮邊框仍是沒有去掉,有待解決!
一個CSS3的屬性,加上後,所關聯的元素的事件監聽都會失效,等於讓元素變得「看得見,點不着」。IE到11纔開始支持,其餘瀏覽器的當前版本基本都支持。詳細介紹見這
pointer-events: none;
<input type="text" autocapitalize="none">
<meta name="format-detection" content="telephone=no" />
-webkit-touch-callout: none;
-webkit-user-select: none;
CSS3中的calc變量在iOS6瀏覽器中必須加-webkit-前綴,目前的FF瀏覽器已經無需-moz-前綴。 Android瀏覽器目前仍然不支持calc,因此要在以前增長一個保守尺寸:
div { width: 95%; width: -webkit-calc(100% - 50px); width: calc(100% - 50px); }
iOS下fixed元素容易定位出錯,軟鍵盤彈出時,影響fixed元素定位,android下fixed表現要比iOS更好,軟鍵盤彈出時,不會影響fixed元素定位 。iOS4下不支持position:fixed 解決方案: 可用iScroll插件解決這個問題
針對此種狀況只須要對不觸發click事件的那些元素添加一行css代碼便可
cursor: pointer;
/*設置內嵌的元素在 3D 空間如何呈現:保留 3D*/ -webkit-transform-style: preserve-3d; /*(設置進行轉換的元素的背面在面對用戶時是否可見:隱藏)*/ -webkit-backface-visibility: hidden;
解決方法:
1.儘量地使用合成屬性transform和opacity來設計CSS3動畫,不使用position的left和top來定位
2.開啓硬件加速
-webkit-transform: translate3d(0, 0, 0); -moz-transform: translate3d(0, 0, 0); -ms-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0);
解決方法:經過正則去除
this.value = this.value.replace(/\u2006/g, ‘‘);
input 的placeholder會出現文本位置偏上的狀況:PC端設置line-height等於height可以對齊,而移動端仍然是偏上,解決方案時是設置css
line-height:normal;
解決方法以下:
這裏兩種方法都是經過設置css屬性將浮動元素的父元素變成間接變成BFC元素,而後使得子元素高度能夠撐開父元素。這裏須要注意的時,最好使用方法1, 由於inline-block元素自己會自帶一些寬高度撐開其自己。
點擊瀏覽器的回退,有時候不會自動執行js,特別是在mobilesafari中。這與往返緩存(bfcache)有關係。 解決方法 :
window.onunload = function () {};
解決方法:
-webkit-overflow-scrolling: touch;
iOS原始輸入法,中文輸入時,沒法觸發keyup事件,且keyup.enter事件不管中英文,都沒法觸發
解決方法:
業務場景重現: 項目中須要寫一個搜索組件,相關代碼以下
<template> <div class="y-search" :style="styles" :clear="clear"> <form action="#" onsubmit="return false;"> <input type="search" class="y-search-input" ref="search" v-model=‘model‘ :placeholder="placeholder" @input="searchInputFn" @keyup.enter="searchEnterFn" @foucs="searchFocusFn" @blur="searchBlurFn" /> <y-icons class="search-icon" name="search" size="14"></y-icons> </form> <div v-if="showClose" @click="closeFn"> <y-icons class="close-icon" name=‘close‘ size=‘12‘></y-icons> </div> </div> </template>
其中我須要在enter的時候進行對應的搜索操做並實現失焦,解決方法其實很簡單,在enter時進行DOM操做便可
searchEnterFn (e) { document.getElementsByClassName(‘y-search-input‘)[0].blur() // dosomething yourself }
對了,這裏還有一個坑,就是在移動端使用input類型爲search的時候,必須使用form標籤包裹起來,這樣在移動端呼出鍵盤的enter纔會是搜索按鈕,不然只是默認的enter按鈕。
這個點不能算坑,只能算是小技巧分享吧。
業務場景重現:不少時候,在開發項目的時候是須要抽離公共組件和業務組件的。而有些公共組件在全局註冊的同時可能還須要拓展成vue的實例方法,經過把它們添加到 Vue.prototype 上實現,方便直接使用js全局調用。拿一個Message組件作例子吧,代碼比較簡單,就直接上代碼了。
1.首先開發好Message.vue文件。
<template> <div class=‘y-mask-white-dialog‘ v-show=‘show‘> <div class=‘y-message animated zoomIn‘ > <span v-html="msg"></span> </div> </div> </template> <script> export default { name: ‘yMessage‘, props: { msg: String, timeout: Number, callback: Function, icon: String, }, data() { return { show: true, }; } }; </script> <style lang="stylus" scoped> .y-mask-white-dialog { background-color: rgba(0, 0, 0, .4); position: fixed; z-index: 999; bottom: 0; right: 0; left: 0; top: 0; display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; -webkit-box-pack: center; -webkit-justify-content: center; -ms-flex-pack: center; justify-content: center; -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; } .y-message { min-width: 2.9rem; max-width: 5.5rem; width:100%; padding: 0.32rem; font-size: 14px; text-align: center; border-radius: 4px; background :rgba(0,0,0,0.8); color: #fff; animation: zoomIn .15s ease forwards; } </style>
2.構建Message的Constructor
import Vue from ‘vue‘; const MsgConstructor = Vue.extend(require(‘./Message.vue‘)); const instance = new MsgConstructor({ // el: document.createElement(‘div‘), }).$mount(document.createElement(‘div‘)); MsgConstructor.prototype.closeMsg = function () { const el = instance.$el; el.parentNode && el.parentNode.removeChild(el); typeof this.callback === ‘function‘ && this.callback(); }; const Message = (options = {}) => { instance.msg = options.msg; instance.timeout = options.timeout || 2000; instance.icon = options.icon; instance.callback = options.callback; document.body.appendChild(instance.$el); const timer = setTimeout(() => { clearTimeout(timer); instance.closeMsg(); }, instance.timeout + 100); }; export default Message;
3.在main.js裏面進行組件註冊
import Message from ‘./components/Message‘; Vue.component(Message.name, Message) Vue.prototype.$message = Message
而後你就能夠盡情使用Message組件了.
this.$message({ msg: ‘test message‘ // ... })
這也是一個小技巧!項目中須要開發swiper輪播圖,那麼你懂的,圖片確定是須要保證等比縮放展現。
<div class="parent"> <img :src="imgSrc" > </div> <style lang="stylus" scoped> .parent { width: 100px; height: 100px; display: flex; align-items: center; img { width :100%; height: auto; } } </style>
是否是賊簡單,是的,賊簡單。這個樣式同時適應手機全屏預覽豎屏的狀況,當手機橫屏的時候,加一個媒體查詢便可搞定
@media (orientation: landscape) { img { width auto height 100% margin auto } }
業務重現:考慮到項目後期會作國際化,前端須要對項目中幾乎全部的枚舉值進行過濾處理,從而進行展現
接下來就直接講講這塊吧。既然要過濾,那麼首選確定是vue提供的filter指令。這裏我舉一個支付方式的枚舉值處理的例子。首先配置代碼以下
// 配置文件 export default { env: (process.env.NODE_ENV === ‘development‘ ? require(‘./env/dev‘) : require(‘./env/pro‘)), headShow: false, lng: ‘zh‘, };
枚舉代碼以下
// 獲取語言環境 import config from ‘../config/index‘; const {lng} = config; // 帳戶類型 const type = { zh: { 1: ‘銀行‘, 2: ‘支付寶‘, 3: ‘微信支付‘, }, en: { 1: ‘bank_type‘, 2: ‘alipay_type‘, 3: ‘wxpay_type‘, } } export default type[lng];
枚舉註冊代碼分別以下
import accountType from ‘./accountType‘; // 帳戶類型 const factory = { accountType(value) { if (value === -1) { return ‘請選擇帳戶類型‘; } return accountType[value] ? accountType[value] : ‘請選擇帳戶類型‘; } } const filter = [ { name: ‘formatEnum‘, // 過濾類型 filter: function(value, type, status) { return factory[type](value, status); } } ]; export default { filter }; // filter import baseFilter from ‘./filter/index‘; const filters = [ ...baseFilter.filter ]; filters.map(f => { Vue.filter(f.name, f.filter); return ‘‘; });
接下來就能夠輕鬆使用啦
<li> <label支付類型</label> <span> {{info.account_type | formatEnum(‘accountType‘)}} </span> </li>
currentValue = _this.currentValue = _this.value ? _this.value.replace(/-/g, ‘/‘) : ‘‘;
時間過濾代碼以下
const filter = [ { name: ‘formatEnum‘, // 過濾類型 filter: function(value, type, status) { return factory[type](value, status); } }, { name: ‘formatDate‘, // 日期 filter: function(value, format = ‘yyyy-MM-dd‘) { if (!value || value === ‘‘) return ‘‘; let v = value; if (!isNaN(value)) { v = +value * 1000; } const newDate = new Date(v); return newDate.Format(format); }, } ];
業務重現:因爲不一樣的用戶,可能擁有不一樣權限,而目前咱們的項目是基於微信公衆號進行開發的,頁面權限這邊也是交給了咱們前端處理。既然要前端配置權限,那麼咱們能想到的比較好的方式就是經過配置路由文件,完成權限斷定。下面我會列舉一小部分代碼(以咱們的工單列表)進行演示,路由配置代碼以下
const getWorkOrder = pageName => resolve => require([‘../pages/WorkOrder‘], pages => resolve(pages[pageName])) let routers = []; routers = [ { path: ‘/workorder‘, name: ‘workorder‘, component: room, children: [ { path: ‘list‘, // 管家端工單列表 name: ‘list‘, rule: 3, component: getWorkOrder(‘WorkOrderList‘) //WorkOrder, }, { path: ‘managerList‘, // 店長端工單列表 name: ‘managerList‘, rule: 6, component: getWorkOrder(‘ManagerWorkOrder‘) // WorkOrder, }, ] } ]
而後進行路由統一過濾處理
import Vue from ‘vue‘; import Store from ‘store‘; import Router from ‘vue-router‘; import routers from ‘./router.config‘; Vue.use(Router); // 遍歷路由名稱以及權限 let arr = {}; const routeName = function (router) { if (!router) return false; router.forEach((v) => { arr[v.name] = v.rule; routeName(v.children); }) } routeName(routers); const RouterModel = new Router({ mode: ‘history‘, routes: routers, }); // 路由鉤子,進入路由以前判斷 RouterModel.beforeEach((to, from, next) => { // 處理query 參數,傳入到 jumpUrl,便於登陸後跳轉到對應頁面 let qu = Object.entries(to.query); if (qu.length > 0) { qu = qu.map((item, index) => { return item.join(‘=‘); }) } if (arr[to.name]) { // 若是有權限須要 const userInfo = Store.get(‘yu_info‘); const cookie = Vue.prototype.$util.getCookie(‘X-Auth-Token‘); const userId = Vue.prototype.$util.getCookie(‘userid‘); if (userInfo && cookie && +userId > 0) { next(); } else { // 未登陸,跳轉登陸 let param = `jumpUrl=${to.path}`; if (qu.length > 0) { param += `&${qu.join("&")}`; } if (arr[to.name] && !to.query.rule) { param += `&rule=${arr[to.name]}`; } window.location.href = `/login?${param}`; } } else { // 若是不須要權限放行 next(); } }) export default RouterModel;
而後在登錄界面定位到微信受權
getCode() { // 定位到微信受權,如果不須要受權能夠在此到處理 let query = this.$route.query; let param = `jumpUrl=${query.jumpUrl || ‘/home‘}`; let path = window.location.origin; // 登陸角色處理 let cfg = api[query.rule ? query.rule : 1]; if (query.rule) { param += `&rule=${query.rule}`; } let redirect_url = encodeURIComponent(`${path}/login?${param}`); let url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${cfg.tempApp}&redirect_uri=${redirect_url}&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect`; if (query.rule === 3) { url += ‘&agentid=1000004‘; } location.replace = url; }
若是不是微信端,訪問到到rule規則的界面時,則會以下
而當微信受權經過的時候,rule權限不足的狀況則會以下
開發項目,在先後端聯調的時候確定是會趕上跨域的問題的。很簡單,作個反向代理唄,對於想了解正向代理和反向代理的,請點擊這裏
vue-cli腳手架搭建的工程中,在config/index.js文件中能夠利用預留的proxyTable一項,設置地址映射表,以下
proxyTable: { ‘/api‘: { target: ‘http://www.example.com‘, // your target host changeOrigin: true, // needed for virtual hosted sites pathRewrite: { ‘^/api‘: ‘‘ // rewrite path } } }
而後使用http-proxy-middleware插件對api請求地址進行代理
// proxy api requests Object.keys(proxyTable).forEach(function (context) { var options = proxyTable[context] if (typeof options === ‘string‘) { options = { target: options } } app.use(proxyMiddleware(options.filter || context, options)) })
決定用戶體驗最重要的一個點之一,這個點的重要性,相信不用我說了。下面直接談實戰。
// 全局註冊 import VueLazyload from ‘vue-lazyload‘; Vue.use(VueLazyload, { error: require(‘./assets/close.svg‘), loading: require(‘./assets/loading.svg‘), attempt: 1, }); // 使用 <img v-lazy="room.img" :alt="room.community_name" width="100%">
除此以外,還有不少點可作優化,進而提高首屏加載速度。
這個老生常談了,爲了減小圖片請求次數,加快頁面加載,固然會考慮使用雪碧圖。儘可能使用::before或::after僞類,Sprites中的圖片排版能夠更緊 ,圖片體積更小, HTML更簡潔。
<style lang="stylus" scoped> .chose-house { height: 86px; width: 64px; margin 0 auto position relative &::before { content: ‘\20‘; height: 100%; width: 100%; position: absolute; left: 0; top: 0; background: url(‘../../assets/sprite-min.png‘) 0px 0px no-repeat; } } </style>
關於路由懶加載這一部分,尤大在vue-router文檔中也有所說起,連接點擊這裏。
其實在vue項目中使用路由懶加載很是簡單,咱們要作的就是把路由對應的組件定義成異步組件,代碼以下
//在router/index.js中引入組件時可以使用以下異步方式引入 const Foo = resolve => { // require.ensure 是 Webpack 的特殊語法,用來設置 code-split point // (代碼分塊) require.ensure([‘./Foo.vue‘], () => { resolve(require(‘./Foo.vue‘)) }) } // or const Foo = resolve => require([‘./Foo.vue‘], resolve)
再將組件按組分塊,如
const Foo = r => require.ensure([], () => r(require(‘./Foo.vue‘)), ‘group-foo‘)
實際項目中的代碼則如同我在章節《路由權限斷定》說起到的同樣
const getWorkOrder = pageName => resolve => require([‘../pages/WorkOrder‘], pages => resolve(pages[pageName])) let routers = []; routers = [ { path: ‘/workorder‘, name: ‘workorder‘, component: room, children: [ { path: ‘list‘, // 管家端工單列表 name: ‘list‘, rule: 3, component: getWorkOrder(‘WorkOrderList‘) //WorkOrder, }, { path: ‘managerList‘, // 店長端工單列表 name: ‘managerList‘, rule: 6, component: getWorkOrder(‘ManagerWorkOrder‘) // WorkOrder, }, ] } ]
如上將組件經過傳遞pageName參數分別打包到了各個chunk中,這樣每一個組件加載時都只會加載本身對應的代碼,從而加快渲染速度!
當時咱們爲了優化首屏渲染速度,也是考慮到這一點,項目的src/main.js文件主要負責註冊全局組件,插件,路由,以及實例化Vue等。在webpack的配置裏面也是當成entry入口進行了配置,若是我在main.js裏面講每一個組件都進行import的話,那麼它將會所有一塊兒註冊打包,頁面加載也會將每一個組件文件都加載下來,這樣對渲染速度仍是有必定影響的。
解決方法就是:按需註冊,這樣在打包的時候,會按需加載首頁(其餘界面也一樣適用)使用到的全局組件。基本步驟以下:
將須要註冊的組件寫進components/base.js文件中,而後exports出來
exports.Foo = require(‘./Foo.vue‘); exports.Bar = require(‘./Bar.vue‘); exports.Baz = require(‘./Baz.vue‘);
在main.js中進行註冊
const components = [ require(‘./components/base‘).Foo, require(‘./components/base‘).Bar, require(‘./components/base‘).Baz, ]; components.map(component => { Vue.component(component.name, component); });
1:ios狀態欄與頁面內容重疊(webview中),若是是在微信端的網頁,能夠忽略這個坑。
解決方法:判斷設備類型,若是是ios則加入獨有的樣式:通常狀態欄的高度是20px
var u = navigator.userAgent, app = navigator.appVersion; var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios終端 if(isiOS){ $("body").addClass("uh_ios7"); }
樣式文件下加這段:
.uh_ios7 .uh,.uh_ios7{ padding: 20px 0 0; }
3:當頁面輸入框獲取焦點的時候,ios頁面滾動致使頭部與狀態欄重疊:
首先咱們下載插件: 傳送門
cordova plugin add ionic-plugin-keyboard.
而後在app.js中加入代碼:
$ionicPlatform.ready(function () { if (window.cordova && window.cordova.plugins.Keyboard) { window.cordova.plugins.Keyboard.hideKeyboardAccessoryBar(false); if (window.ionic.Platform.isIOS()) { window.cordova.plugins.Keyboard.disableScroll(true); } } });
注意:在ios上$ionicPlatform.ready可能進不去————解決辦法就是:
將cordova.js放在頁面最底部引入!!!!fuck!!
4:ios上如何讓絕對定位在頁面底部的輸入框,隨鍵盤的出現而上移(僅限網頁)?
利用dom方法:scrollintoView()。
若是滾動頁面也是DOM沒有解決的一個問題。爲了解決這個問題,瀏覽器實現了一下方法,以方便開發人員如何更好的控制頁面的滾動。在各類專有方法中,HTML5選擇了scrollIntoView()做爲標準方法。scrollIntoView()能夠在全部的HTML元素上調用,經過滾動瀏覽器窗口或某個容器元素,調用元素就能夠出如今視窗中。若是給該方法傳入true做爲參數,或者不傳入任何參數,那麼窗口滾動以後會讓調動元素頂部和視窗頂部儘量齊平。若是傳入false做爲參數,調用元素會盡量所有出如今視口中(可能的話,調用元素的底部會與視口的頂部齊平。)不過頂部不必定齊平.
例子:document.querySelector(".pinlun_input").scrollIntoView(false) //這是js方法,不要用jquery選擇器執行此方法
五、安卓瀏覽器看背景圖片,有些設備會模糊。
用同等比例的圖片在PC機上很清楚,可是手機上很模糊,緣由是什麼呢?
通過研究,是devicePixelRatio做怪,由於手機分辨率過小,若是按照分辨率來顯示網頁,這樣字會很是小,因此蘋果當初就把iPhone 4的960*640分辨率,在網頁裏只顯示了480*320,這樣devicePixelRatio=2。如今android比較亂,有1.5的,有2的也有3的。
想讓圖片在手機裏顯示更爲清晰,必須使用2x的背景圖來代替img標籤(通常狀況都是用2倍)。例如一個div的寬高是100*100,背景圖必須得200*200,而後background-size:contain;,這樣顯示出來的圖片就比較清晰了。
代碼能夠以下:
background:url(../images/icon/all.png) no-repeat center center;
-webkit-background-size:50px 50px;
background-size: 50px 50px;display:inline-block; width:100%; height:50px;
或者指定 background-size:contain;均可以,你們試試!
六、圖片加載
若您遇到圖片加載很慢的問題,對這種狀況,手機開發通常用canvas方法加載:
下面舉例說明一個canvas的例子:
<li><canvas></canvas></li>
js動態加載圖片和li 總共舉例17張圖片!
vartotal=17;
varzWin=$(window);
varrender=function(){
varpadding=2;
varwinWidth=zWin.width();
varpicWidth=Math.floor((winWidth-padding*3)/4);
vartmpl ='';
for(vari=1;i<=totla;i++){
varp=padding;
varimgSrc='img/'+i+'.jpg';
if(i%4==1){
p=0;
}
tmpl +='<li style="width:'+picWidth+'px;height:'+picWidth+'px;padding-left:'+p+'px;padding-top:'+padding+'px;"><canvas id="cvs_'+i+'"></canvas></li>';
varimageObj = newImage();
imageObj.index = i;
imageObj.onload = function(){
varcvs =$('#cvs_'+this.index)[0].getContext('2d');
cvs.width = this.width;
cvs.height=this.height;
cvs.drawImage(this,0,0);
}
imageObj.src=imgSrc;
}
}
render();
三、假如手機網站不用兼容IE瀏覽器,通常咱們會使用zeptojs。