移動端H5多頁開發拍門磚經驗

hzr發表於2018-05-22

兩年前剛接觸移動端開發,剛開始比較疑惑,每次遇到問題都是到社群裡提問或者吸取前輩的經驗分享,感謝熱衷於分享的開發者為前端社群帶來欣欣向上的生命力。本文結合先前寫的文章和開發經驗分享給大家,希望也能幫助剛步入移動端開發的新人解惑。以下會以其中一個以公積金頁面開發專案作為例子,介紹移動端的一些常見問題和使用Vuejs作為lib進行多頁開發的經驗。

移動端自適應佈局

在專案中移動端最常用的自適應佈局方案就是flexbox結合rem。規範的分欄式使用flexbox,其他大部分不規則檢視使用rem,對於rem最常用的方案就是淘寶開源的可伸縮佈局方案

根據裝置裝置畫素比設定scale的值(scale = 1 / deviceRatio),這樣可以保持視口device-width始終等於裝置物理畫素,接著根據螢幕大小動態計算根字型大小,具體是將螢幕劃分為100等分,每份為a,1rem就等於10a。

標註

通常我們會拿到750寬的設計稿,這是基於iPhone6的物理解析度。有的設計師也許會偷懶,設計圖上面沒有任何的標註,如果我們邊開發邊量尺寸,無疑效率是比較低的。要麼讓設計師標註上,要麼自食其力。

如果設計師實在沒有時間,推薦使用markman進行標註,免費版閹割了一些功能(比如無法儲存本地)不過基本滿足了我們的需求了。

後來我發現比markman更好的標註工具PxCook,該工具可以顯示PSD設計圖中的圖層的樣式程式碼,對於前端來說簡直方便極了。

標註完成後開始寫我們的樣式,使用了淘寶的lib-flexible庫之後,我們的根字型基準值就為750/100*10 = 75px。此時我們從圖中若某個標註為100px,那麼css中就應該設定為100/75 = 1.333333rem。所以為了提高開發效率,可以使用px轉化為rem的外掛。下面是sublimeText和Vscode的轉換外掛:

px轉rem外掛

使用rem的幾點總結

  • 在所有的單位中,font-size推薦使用px,然後結合媒體查詢進行重要節點的控制,這樣可以滿足突出或者弱化某些字型的需求,而非整體調整。
  • 眾向的單位可以全部使用px,橫向的使用rem,因為移動裝置寬度有限,而高度可以無限向下滑動。但這也有特例,比如對於一些活動註冊頁面,需要在一螢幕內完全顯示,沒有下拉,這時候所有眾向或者橫向都應該使用rem作為單位。如圖:

shili

左圖的表單高度單位由於下邊空距較大,使用px在不同螢幕顯示更加;而右邊的活動註冊頁由於不能出現滾動條,所有的眾向高度、margin、padding都應該使用rem。

  • border、box-shadow、border-radius等一些效果應該使用px作為單位。

手機狀態列和瀏覽器導航欄的影響

之前釋出的文章中,有個SF的前端小夥伴提出的問題: 文中作者有重點強調佈局全部鋪滿,和下方與很多空隙的處理方案是不同的,在工作中我遇到這種情況,設計師的設計稿寬度為750×1334,但實際的展示高度並沒有那麼多,因為上方有導航欄還包括手機自己的狀態列展示,所以整體高度就達不到750,但是設計師設計稿是嚴格按照750進行設計的,這種情況下使用rem,嚴格按照設計師尺寸進行還原就會出現螢幕出現滾動條情況,請問針對這種情況您是怎麼處理的?是從設計稿上規範,還是從開發上有相應的措施

依舊以我的分享介面為例: 展示高度不同通常發生在微信及瀏覽器端,因為前者沒有位址列和工具欄,這樣顯示高度通常會和設計師設計的檢視吻合。那如果按照純padding,margin即使全部使用rem,在瀏覽器端依舊會超出一屏高度,對於分享頁面這種不是我們想要看到的。這時候就要做出取捨,我對主體區域採用絕對定位,這樣上面間隙雖然小,不過仍能保持在一個螢幕高度顯示。若採用margin padding在設定,必然已出現滾動條。當然這樣的前提是依賴設計圖的,通常設計師會為了空間感有保留一定的間隙,也不會將主要物件高度設的過高,否則太撐滿也不好看,開發上如果設計圖寬高沒有在一定界限之內,超出也無法避免,不過我們這種分享介面通常是通過微信分享好友,通過瀏覽器開啟的檢視效果出現滾動條其實也不怎麼影響不是麼? 下面附上微信端和瀏覽器端的效果圖:

微信端:

微信端

瀏覽器端:

瀏覽器端

Vuejs作為lib開發移動端頁面

為何不使用SPA模式

一般移動端使用vue是為了資料互動頻繁而且快速開發的頁面,為什麼不使用單頁SPA開發模式,原因大概幾點。

  • 為了快速開發,快速上線
  • 專案其他成員不熟悉SPA,不熟悉webpack
  • 參與專案時專案已使用多頁開發,短時間無法重構

拋開使用單頁的架構,開發多頁應用時,一個頁面互動邏輯與一個Vue例項對應。

基於介面返回資料的屬性注入

"基於介面返回資料的屬性注入"是個人建立的話術,拋開此概念,先說一下表單資料的繫結方式。

表單的資料繫結

一個重要的點是有幾份表單就分開幾個表單物件進行資料繫結

以上圖公積金查詢為例,由於不同城市會有不同的查詢要素,可能登陸方式只有一種,也可能有幾種。比如上圖有三種登陸方式,在使用vue佈局時,有兩種方案。

  • 1、 只建立一個表單用於資料繫結,點選按鈕觸發判斷
  • 2、有幾種登陸方式建立幾個表單,用一個欄位標識當前顯示的表單

由於使用第三方的介面,一開始也沒有先進行介面返回資料結構的檢視,採用了第一種錯誤的方式,錯誤一是每種登陸方式下面的登陸要素的數量也不同,錯誤二是資料繫結在同一個表單data下,當使用者在使用者名稱登陸方式輸入使用者名稱密碼後,切換到客戶號登陸方式,就會出現資料錯亂的情況。

解決完佈局問題後,我們需要根據設計圖定義一些狀態,比如當前登陸方式的切換、同意授權狀態的切換、按鈕是否可以點選的狀態、是否處於請求中的狀態。當然還有一些app穿過來的資料,這裡就忽略了。

 data: {
     tags: {
         arr: [''],
         activeIndex: 0
     },
     isAgreeProxy: true,
     isLoading: false
 }
複製程式碼

接著審查一下介面返回的資料,推薦使用chrome外掛postman,比如呼和浩特的登陸要素如下:

{
    "code": 2005,
    "data": [
        {
            "name": "login_type",
            "label": "身份證號",
            "fields": [
                {
                    "name": "user_name",
                    "label": "身份證號",
                    "type": "text"
                },
                {
                    "name": "user_pass",
                    "label": "密碼",
                    "type": "password"
                }
            ],
            "value": "1"
        },
        {
            "name": " login_type",
            "label": "公積金賬號",
            "fields": [
                {
                    "name": "user_name",
                    "label": "公積金賬號",
                    "type": "text"
                },
                {
                    "name": "user_pass",
                    "label": "密碼",
                    "type": "password"
                }
            ],
            "value": "0"
        }
    ],
    "message": "登入要素請求成功"
}
複製程式碼

可以看到呼和浩特有兩種授權登陸方式,我們在data中定義了一個loginWays,初始為空陣列,接著methods中定義一個請求介面的函式,裡面就是基於返回資料的基礎上為上面fields物件注入一個input欄位用於繫結,這就是所謂的基於介面返回資料的屬性注入。

methods: {
    queryloginWays: function(channel_type, channel_code) {
        var params = new URLSearchParams();
        params.append('channel_type', channel_type);
        params.append('channel_code', channel_code);
        axios.post(this.loginParamsProxy, params)
            .then(function(res) {
                console.log(res);
                var code = res.code || res.data.code;
                var msg = res.message || res.data.message;
                var loginWays = res.data.data ? res.data.data : res.data;
                // 查詢失敗
                if (code != 2005) {
                    alert(msg);
                    return;
                }
                // 新增input欄位用於v-model繫結
                loginWays.forEach(function(loginWay) {
                    loginWay.fields.forEach(function(field) {
                        field.input = '';
                    })
                })
                this.loginWays = loginWays;
                this.tags.arr = loginWays.map(function(loginWay) {
                    return loginWay.label;
                })
            }.bind(this))
    }
}
複製程式碼

即使返回的資料有我們不需要的資料也沒有關係,這樣保證我們不會遺失進行下一步登陸所需要的資料。

這樣多個表單繫結資料問題解決了,那麼怎麼進行頁面間資料傳遞?如果是app傳過來,那麼通常使用URL拼接的方式,使用window.location.search獲得queryString後再進行擷取;如果通過頁面套入javaWeb中,那麼直接使用"${欄位名}"就能獲取,注意要js中獲取java欄位需要加雙引號。

computed: {
        // 真實姓名
        realName: function() {
            return this.getQueryVariable('name') || ''
        },
        // 身份證
        identity: function() {
            return parseInt(this.getQueryVariable('identity')) || ''
        },
        /*If javaWeb
        realName: function() {
            return this.getQueryVariable('name') || ''
        },
        identity: function() {
            return parseInt(this.getQueryVariable('identity')) || ''
        }*/
    },
    methods: {
        getQueryVariable: function(variable) {
            var query = window.location.search.substring(1);
            var vars = query.split('&');
            for (var i = 0; i < vars.length; i++) {
                var pair = vars[i].split('=');
                if (decodeURIComponent(pair[0]) == variable) {
                    return decodeURIComponent(pair[1]);
                }
            }
            console.log('Query variable %s not found', variable);
        }
    }
複製程式碼

關於前端跨域除錯

在進行介面請求時,我們的頁面通常是在sublime的本地伺服器或者vscode本地伺服器預覽,所以請求介面會遇到跨域的問題,如果使用Gulp進行打包,可以使用外掛http-proxy-middleware,或者使用nginx。

使用Gulp

在專案構建的時候通常我們原始碼會放在src資料夾下,然後使用gulp進行程式碼的壓縮、合併、圖片的優化(根據需要)等等,我們會使用gulp。

解決跨域的問題可以用gulp-connect結合http-proxy-middleware,此時我們在gulp-connect中的本地伺服器進行預覽除錯。

gulpfile.js如下: 開發過程使用gulp server:dev命令,監聽檔案改動並使用livereload重新整理,並且代理src目錄;使用gulp命令進行打包;使用gulp server:dist代理dist生產目錄。

var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var autoprefixer = require('gulp-autoprefixer');
var useref = require('gulp-useref');
var connect = require('gulp-connect');
var proxyMiddleware = require('http-proxy-middleware');

// 開發跨域代理  將localhost:8088/api 對映到 https://api.xxxxx.com/
gulp.task('server:dev', ['listen'], function() {
    var middleware = proxyMiddleware(['/api'], {
        target: 'https://api.xxxxx.com/',
        changeOrigin: true,
        pathRewrite: {
            '^/api': '/'
        }
    });
    connect.server({
        root: env == 'dev' ? './src' : './dist',
        port: 8088,
        livereload: true,
        middleware: function(connect, opt) {
            return [middleware]
        }

    });
});

// 打包後跨域代理
gulp.task('server:dist', ['listen'], function() {
    var middleware = proxyMiddleware(['/api'], {
        target: 'https://api.xxxxx.com/',
        changeOrigin: true,
        pathRewrite: {
            '^/api': '/'
        }
    });
    connect.server({
        root: './dist',
        port: 8088,
        livereload: true,
        middleware: function(connect, opt) {
            return [middleware]
        }

    });
});

gulp.task('html', function() {
    gulp.src('src/*.html')
        .pipe(useref())
        .pipe(gulp.dest('dist'));
});
gulp.task('css', function() {
    gulp.src('src/css/main.css')
        .pipe(concat('main.css'))
        .pipe(autoprefixer({
            browsers: ['last 2 versions'],
            cascade: false
        }))
        .pipe(gulp.dest('dist/css/'));

    gulp.src('src/css/share.css')
        .pipe(concat('share.css'))
        .pipe(autoprefixer({
            browsers: ['last 2 versions'],
            cascade: false
        }))
        .pipe(gulp.dest('dist/css/'));

    gulp.src('src/vendors/css/*.css')
        .pipe(concat('vendors.min.css'))
        .pipe(autoprefixer({
            browsers: ['last 2 versions'],
            cascade: false
        }))
        .pipe(gulp.dest('dist/vendors/css'));
    return gulp
});
gulp.task('js', function() {
    return gulp.src('src/vendors/js/*.js')
        .pipe(concat('vendors.min.js'))
        .pipe(uglify())
        .pipe(gulp.dest('dist/vendors/js'));
});
gulp.task('img', function() {
    gulp.src('src/imgs/*')
        .pipe(gulp.dest('dist/imgs'));
});
gulp.task('listen', function() {
    gulp.watch('./src/css/*.css', function() {
        gulp.src(['./src/css/*.css'])
            .pipe(connect.reload());
    });
    gulp.watch('./src/js/*.js', function() {
        gulp.src(['./src/js/*.js'])
            .pipe(connect.reload());
    });
    gulp.watch('./src/*.html', function() {
        gulp.src(['./src/*.html'])
            .pipe(connect.reload());
    });
});
gulp.task('default', ['html', 'css', 'js', 'img']);
複製程式碼

使用nginx

在nginx配置使用proxy_pass,需要注意一點: 如果在proxy_pass後面的url加/,表示絕對根路徑;如果沒有/,表示相對路徑,把匹配的路徑部分也給代理走。

server {
	listen 80;
	server_name  localhost;
	root   [Your project root];
	index  index.html index.htm default.html default.htm;
	
    location ^~/api { 
      proxy_pass https://api.xxxxx.com/;
    } 
}
複製程式碼

公眾號網頁的除錯

如果你開發的H5基於微信jsSDK,你一定接觸過微信授權域名,微信會將授權資料傳給一個回撥頁面,而回撥頁面必須在你配置的域名下(含子域名)。比如我們獲取使用者的openid操作。而微信配置域名回去該域名根目錄下檢測一個xxx_verify_xxx.txt檔案,確保該域名是屬於你的。

所以要想在微信開發除錯工具中獲取openid,我們需要使用一種叫做內網穿透的工具。下面是自己比較常用的兩個工具:

ngrok

ngrok執行命令

ngrok -config ngrok.cfg start web
複製程式碼

在ngrok.exe目錄需要一個配置檔案ngrok.cfg 以下是配置示例:

server_addr: "tunnel.cn:4443"
trust_host_root_certs: false
tunnels:
  web:
   subdomain: "xxx" 
   proto:
    http: 8086
    https: 8086
複製程式碼

啟動後xxx.tunnel.cn:4443會指向你本地的8086埠,將xxx_verify_xxx.txt檔案放到8086埠根目錄即可配置授權域名成功。

花生殼

花生殼免費版對於個人開通僅需6元,然後每月會提供給你1G的流量,免費版不支援80埠,最多支援兩個域名,需要下載桌面客戶端。

新增域名對映很簡單,免費版無法配置自定義域名,由花生殼自動生成。

花生殼
配置成功後啟動客戶端可檢視當前的狀態

感謝閱讀,歡迎任何形式的技術提問和交流!歡迎關注公眾號前端新視界,把握技術前沿資訊。

相關文章