如何在Vue項目中使用vw實現移動端適配

如何在Vue項目中使用vw實現移動端適配

原文:https://www.w3cplus.com/mobile/vw-layout-in-vue.html      https://www.cnblogs.com/yikuu/p/9052148.html

 

 

  有關於移動端的適配佈局一直以來都是衆說紛紜,對應的解決方案也是有不少種。在《使用Flexible實現手淘H5頁面的終端適配》提出了Flexible的佈局方案,隨着viewport單位愈來愈受到衆多瀏覽器的支持,所以在《再聊移動端頁面的適配》一文中提出了vw來作移動端的適配問題。到目前爲止無論是哪種方案,都還存在必定的缺陷。言外之意,尚未哪個方案是完美的。

事實上真的不完美?其實否則。最近爲了新項目中能更完美的使用vw來作移動端的適配。探討出一種能解決不兼容viewport單位的方案。今天整理一下,與你們一塊兒分享。若是方案中存在必定的缺陷,歡迎你們一塊兒拍正。

準備工做
對於Flexible或者說vw的佈局,其原理不在這篇文章進行闡述。若是你想追蹤其中的原委,強烈建議你閱讀早前整理的文章《使用Flexible實現手淘H5頁面的終端適配》和《再聊移動端頁面的適配》。

說句題外話,因爲Flexible的出現,也形成不少同窗對rem的誤解。正如當年你們對div的誤解同樣。也所以,你們都以爲rem是萬能的,他能直接解決移動端的適配問題。事實並非如此,至於爲何,我想你們應該去閱讀flexible.js源碼,我相信你會明白其中的原委。

回到咱們今天要聊的主題,怎麼實現vw的兼容問題。爲了解決這個兼容問題,我將藉助Vue官網提供的構建工程以及一些PostCSS插件來完成。在繼續後面的內容以前,須要準備一些東西:

NodeJs
NPM
Webpack
Vue-cli
postcss-import
postcss-url
postcss-aspect-ratio-mini
postcss-cssnext
autoprefixer
postcss-px-to-viewport
postcss-write-svg
cssnano
postcss-viewport-units
Viewport Units Buggyfill
對於這些起什麼做用,先不闡述,後續咱們會聊到上述的一些東西。

使用Vue-cli來構建項目
對於NodeJs、NPM和Webpack相關介紹,你們能夠查閱其對應的官網。這裏默認你的系統環境已經安裝好Nodejs、NPM和Webpack。個人系統目前使用的Node版本是v9.4.0;NPM的版本是v5.6.0。事實上,這些都並不重要。

使用Vue-cli構建項目
爲了避免花太多的時間去深刻的瞭解Webpack(Webpack對我而言,太蛋疼了),因此我直接使用Vue-cli來構建本身的項目,由於我通常使用Vue來作項目。若是你想深刻的瞭解Webpack,建議你閱讀下面的文章:

Webpack文檔
Awesome Webpack
Webpack 教程資源收集
Vue+Webpack開發可複用的單頁面富應用教程
接下來的內容,直接使用Vue官方提供的Vue-cli的構建工具來構建Vue項目。首先須要安裝Vue-cli:

$ npm install -g vue-cli
 

全局先安裝Vue-cli,假設你安裝好了Vue-cli。這樣就可使用它來構建項目:

vue init webpack vw-layout
 

根據命令提示作相應的操做:



進入到剛建立的vw-layout:

cd vw-layout
 

而後執行:

npm run dev
 

在瀏覽器執行http://localhost:8080,就能夠看以默認的頁面效果:



之前的版本須要先執行npm i安裝項目須要的依賴關係。如今新版本的能夠免了。

這時,能夠看到的項目結構以下:

使用Vue-cli構建項目

安裝PostCSS插件
經過Vue-cli構建的項目,在項目的根目錄下有一個.postcssrc.js,默認狀況下已經有了:
module.exports = {
    "plugins": {
        "postcss-import": {},
        "postcss-url": {},
        "autoprefixer": {}
    }
}
  

對應咱們開頭列的的PostCSS插件清單,如今已經具有了:

postcss-import
postcss-url
autoprefixer
簡單的說一下這幾個插件。

postcss-import
postcss-import相關配置能夠點擊這裏。目前使用的是默認配置。只在.postcssrc.js文件中引入了該插件。

postcss-import主要功有是解決@import引入路徑問題。使用這個插件,可讓你很輕易的使用本地文件、node_modules或者web_modules的文件。這個插件配合postcss-url讓你引入文件變得更輕鬆。

postcss-url
postcss-url相關配置能夠點擊這裏。該插件主要用來處理文件,好比圖片文件、字體文件等引用路徑的處理。

在Vue項目中,vue-loader已具備相似的功能,只須要配置中將vue-loader配置進去。

autoprefixer
autoprefixer插件是用來自動處理瀏覽器前綴的一個插件。若是你配置了postcss-cssnext,其中就已具有了autoprefixer的功能。在配置的時候,未顯示的配置相關參數的話,表示使用的是Browserslist指定的列表參數,你也能夠像這樣來指定last 2 versions 或者 > 5%。

如此一來,你在編碼時再也不須要考慮任何瀏覽器前綴的問題,能夠專心擼碼。這也是PostCSS最經常使用的一個插件之一。

其餘插件
Vue-cli默認配置了上述三個PostCSS插件,但咱們要完成vw的佈局兼容方案,或者說讓咱們能更專心的擼碼,還須要配置下面的幾個PostCSS插件:

postcss-aspect-ratio-mini
postcss-px-to-viewport
postcss-write-svg
postcss-cssnext
cssnano
postcss-viewport-units
要使用這幾個插件,先要進行安裝:

npm i postcss-aspect-ratio-mini postcss-px-to-viewport postcss-write-svg postcss-cssnext postcss-viewport-units cssnano --S   
 

安裝成功以後,在項目根目錄下的package.json文件中,能夠看到新安裝的依賴包:

"dependencies": {
    "cssnano": "^3.10.0",
    "postcss-aspect-ratio-mini": "0.0.2",
    "postcss-cssnext": "^3.1.0",
    "postcss-px-to-viewport": "0.0.3",
    "postcss-viewport-units": "^0.1.3",
    "postcss-write-svg": "^3.0.1",
    "vue": "^2.5.2",
    "vue-router": "^3.0.1"
},
  

接下來在.postcssrc.js文件對新安裝的PostCSS插件進行配置:

複製代碼
module.exports = {
    "plugins": {
        "postcss-import": {},
        "postcss-url": {},
        "postcss-aspect-ratio-mini": {}, 
        "postcss-write-svg": {
            utf8: false
        },
        "postcss-cssnext": {},
        "postcss-px-to-viewport": {
            viewportWidth: 750,     // (Number) The width of the viewport.
            viewportHeight: 1334,    // (Number) The height of the viewport.
            unitPrecision: 3,       // (Number) The decimal numbers to allow the REM units to grow to.
            viewportUnit: 'vw',     // (String) Expected units.
            selectorBlackList: ['.ignore', '.hairlines'],  // (Array) The selectors to ignore and leave as px.
            minPixelValue: 1,       // (Number) Set the minimum pixel value to replace.
            mediaQuery: false       // (Boolean) Allow px to be converted in media queries.
        }, 
        "postcss-viewport-units":{},
        "cssnano": {
            preset: "advanced",
            autoprefixer: false,
            "postcss-zindex": false
        }
    }
}
複製代碼
 

特別聲明:因爲cssnext和cssnano都具備autoprefixer,事實上只須要一個,因此把默認的autoprefixer刪除掉,而後把cssnano中的autoprefixer設置爲false。對於其餘的插件使用,稍後會簡單的介紹。

因爲配置文件修改了,因此從新跑一下npm run dev。項目就能夠正常看到了。接下來簡單的介紹一下後面安裝的幾個插件的做用。

postcss-cssnext
postcss-cssnext其實就是cssnext。該插件可讓咱們使用CSS將來的特性,其會對這些特性作相關的兼容性處理。其包含的特性主要有:

postcss-cssnext

有關於cssnext的每一個特性的操做文檔,能夠點擊這裏瀏覽。

cssnano
cssnano主要用來壓縮和清理CSS代碼。在Webpack中,cssnano和css-loader捆綁在一塊兒,因此不須要本身加載它。不過你也可使用postcss-loader顯式的使用cssnano。有關於cssnano的詳細文檔,能夠點擊這裏獲取。

在cssnano的配置中,使用了preset: "advanced",因此咱們須要另外安裝:

npm i cssnano-preset-advanced --save-dev
 

cssnano集成了一些其餘的PostCSS插件,若是你想禁用cssnano中的某個插件的時候,能夠像下面這樣操做:

"cssnano": {
    autoprefixer: false,
    "postcss-zindex": false
}
 

上面的代碼把autoprefixer和postcss-zindex禁掉了。前者是有重複調用,後者是一個討厭的東東。只要啓用了這個插件,z-index的值就會重置爲1。這是一個天坑,千萬記得將postcss-zindex設置爲false。

postcss-px-to-viewport
postcss-px-to-viewport插件主要用來把px單位轉換爲vw、vh、vmin或者vmax這樣的視窗單位,也是vw適配方案的核心插件之一。

在配置中須要配置相關的幾個關鍵參數:

複製代碼
"postcss-px-to-viewport": {
    viewportWidth: 750,      // 視窗的寬度,對應的是咱們設計稿的寬度,通常是750
    viewportHeight: 1334,    // 視窗的高度,根據750設備的寬度來指定,通常指定1334,也能夠不配置
    unitPrecision: 3,        // 指定`px`轉換爲視窗單位值的小數位數(不少時候沒法整除)
    viewportUnit: 'vw',      // 指定須要轉換成的視窗單位,建議使用vw
    selectorBlackList: ['.ignore', '.hairlines'],  // 指定不轉換爲視窗單位的類,能夠自定義,能夠無限添加,建議定義一至兩個通用的類名
    minPixelValue: 1,       // 小於或等於`1px`不轉換爲視窗單位,你也能夠設置爲你想要的值
    mediaQuery: false       // 容許在媒體查詢中轉換`px`
}
複製代碼
 

目前出視覺設計稿,咱們都是使用750px寬度的,那麼100vw = 750px,即1vw = 7.5px。那麼咱們能夠根據設計圖上的px值直接轉換成對應的vw值。在實際擼碼過程,不須要進行任何的計算,直接在代碼中寫px,好比:

複製代碼
.test {
    border: .5px solid black;
    border-bottom-width: 4px;
    font-size: 14px;
    line-height: 20px;
    position: relative;
}
[w-188-246] {
    width: 188px;
}
複製代碼
 

編譯出來的CSS:

複製代碼
.test {
    border: .5px solid #000;
    border-bottom-width: .533vw;
    font-size: 1.867vw;
    line-height: 2.667vw;
    position: relative;
}
[w-188-246] {
    width: 25.067vw;
}
複製代碼
 

在不想要把px轉換爲vw的時候,首先在對應的元素(html)中添加配置中指定的類名.ignore或.hairlines(.hairlines通常用於設置border-width:0.5px的元素中):

<div class="box ignore"></div>
 

寫CSS的時候:

複製代碼
.ignore {
    margin: 10px;
    background-color: red;
}
.box {
    width: 180px;
    height: 300px;
}
.hairlines {
    border-bottom: 0.5px solid red;
}
複製代碼
 

編譯出來的CSS:

複製代碼
.box {
    width: 24vw;
    height: 40vw;
}
.ignore {
    margin: 10px; /*.box元素中帶有.ignore類名,在這個類名寫的`px`不會被轉換*/
    background-color: red;
}
.hairlines {
    border-bottom: 0.5px solid red;
}
複製代碼
 

上面解決了px到vw的轉換計算。那麼在哪些地方可使用vw來適配咱們的頁面。根據相關的測試:

容器適配,可使用vw
文本的適配,可使用vw
大於1px的邊框、圓角、陰影均可以使用vw
內距和外距,可使用vw
postcss-aspect-ratio-mini
postcss-aspect-ratio-mini主要用來處理元素容器寬高比。在實際使用的時候,具備一個默認的結構

<div aspectratio>
    <div aspectratio-content></div>
</div>
 

在實際使用的時候,你能夠把自定義屬性aspectratio和aspectratio-content換成相應的類名,好比:

<div class="aspectratio">
    <div class="aspectratio-content"></div>
</div>
 

我我的比較喜歡用自定義屬性,它和類名所起的做用是同等的。結構定義以後,須要在你的樣式文件中添加一個統一的寬度比默認屬性:

複製代碼
[aspectratio] {
    position: relative;
}
[aspectratio]::before {
    content: '';
    display: block;
    width: 1px;
    margin-left: -1px;
    height: 0;
}

[aspectratio-content] {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
}
複製代碼
 

若是咱們想要作一個188:246(188是容器寬度,246是容器高度)這樣的比例容器,只須要這樣使用:

  

[w-188-246] {
    aspect-ratio: '188:246';
}
 

有一點須要特別注意:aspect-ratio屬性不能和其餘屬性寫在一塊兒,不然編譯出來的屬性只會留下aspect-ratio的值,好比:

<div aspectratio w-188-246 class="color"></div>
 

編譯前的CSS以下:

[w-188-246] {
    width: 188px;
    background-color: red;
    aspect-ratio: '188:246';
}
 

編譯以後:

[w-188-246]:before {
    padding-top: 130.85106382978725%;
}
 

主要是由於在插件中作了相應的處理,不在每次調用aspect-ratio時,生成前面指定的默認樣式代碼,這樣代碼沒那麼冗餘。因此在使用的時候,須要把width和background-color分開來寫:

複製代碼
[w-188-246] {
    width: 188px;
    background-color: red;
}
[w-188-246] {
    aspect-ratio: '188:246';
}
複製代碼
 

這個時候,編譯出來的CSS就正常了:

複製代碼
[w-188-246] {
    width: 25.067vw;
    background-color: red;
}
[w-188-246]:before {
    padding-top: 130.85106382978725%;
}
複製代碼
 

有關於寬高比相關的詳細介紹,若是你們感興趣的話,能夠閱讀下面相關的文章:

CSS實現長寬比的幾種方案
容器長寬比
Web中如何實現縱橫比
實現精準的流體排版原理
目前採用PostCSS插件只是一個過渡階段,在未來咱們能夠直接在CSS中使用aspect-ratio屬性來實現長寬比。

postcss-write-svg
postcss-write-svg插件主要用來處理移動端1px的解決方案。該插件主要使用的是border-image和background來作1px的相關處理。好比:

複製代碼
@svg 1px-border {
    height: 2px;
    @rect {
        fill: var(--color, black);
        width: 100%;
        height: 50%;
    }
}
.example {
    border: 1px solid transparent;
    border-image: svg(1px-border param(--color #00b1ff)) 2 2 stretch;
}
複製代碼
 

編譯出來的CSS:

.example {
    border: 1px solid transparent;
    border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E") 2 2 stretch;
}
 

上面演示的是使用border-image方式,除此以外還可使用background-image來實現。好比:

複製代碼
@svg square {
    @rect {
        fill: var(--color, black);
        width: 100%;
        height: 100%;
    }
}

#example {
    background: white svg(square param(--color #00b1ff));
}
複製代碼
 

編譯出來就是:

#example {
    background: white url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%2300b1ff' width='100%25' height='100%25'/%3E%3C/svg%3E");
}
 

解決1px的方案除了這個插件以外,還有其餘的方法。能夠閱讀前期整理的《再談Retina下1px的解決方案》一文。

特別聲明:因爲有一些低端機對border-image支持度不夠友好,我的建議你使用background-image的這個方案。

CSS Modules
Vue中的vue-loader已經集成了CSS Modules的功能,我的建議在項目中開始使用CSS Modules。特別是在Vue和React的項目中,CSS Modules具備很強的優點和靈活性。建議看看CSS In JS相關的資料。在Vue中,使用CSS Modules的相關文檔能夠閱讀Vue官方提供的文檔《CSS Modules》。

postcss-viewport-units
postcss-viewport-units插件主要是給CSS的屬性添加content的屬性,配合viewport-units-buggyfill庫給vw、vh、vmin和vmax作適配的操做。

這是實現vw佈局必不可少的一個插件,由於少了這個插件,這將是一件痛苦的事情。後面你就清楚。

到此爲止,有關於所須要的PostCSS已配置完。而且簡單的介紹了各個插件的做用,至於詳細的文檔和使用,能夠參閱對應插件的官方文檔。

vw兼容方案
在《再聊移動端頁面的適配》一文中,詳細介紹了,怎麼使用vw來實現移動端的適配佈局。這裏不作詳細的介紹。建議你花點時間閱讀這篇文章。

先把未作兼容處理的示例二維碼貼一個:



你可使用手淘App、優酷APP、各終端自帶的瀏覽器、UC瀏覽器、QQ瀏覽器、Safari瀏覽器和Chrome瀏覽器掃描上面的二維碼,您看到相應的效果:



但還有不支持的,好比下表中的No,表示的就是不支持

品牌	型號	系統版本	分辨率	屏幕尺寸	手淘APP	優酷APP	原生瀏覽器	QQ瀏覽器	UC瀏覽器	Chrome瀏覽器
華爲	Mate9	Android7.0	1080 x 1920	5英寸	Yes	Yes	No	Yes	Yes	Yes
華爲	Mate7	Android4.2	1080 x 1920	5.2英寸	Yes	Yes	No	Yes	Yes	Yes
魅族	Mx4 (M460 移動4G)	Android4.4.2	1152 x 1920	5.36英寸	Yes	No	No	Yes	Yes	Yes
Oppo	R7007	Android4.3	1280 x 720	5英寸	Yes	No	No	Yes	Yes	No
三星	N9008 (Galaxy Note3)	Android4.4.2	1080 x 1920	5.7英寸	Yes	No	Yes	Yes	Yes	Yes
華碩	ZenFone5(x86)	Android4.3	720 x 280	5英寸	No	No	No	Yes	No	No
正因如此,不少同窗都不敢嘗這個螃蟹。懼怕去處理兼容性的處理。不過沒關係,今天我把最終的解決方案告訴你。

最終的解決方案,就是使用viewport的polyfill:Viewport Units Buggyfill。使用viewport-units-buggyfill主要分如下幾步走:

引入JavaScript文件
viewport-units-buggyfill主要有兩個JavaScript文件:viewport-units-buggyfill.js和viewport-units-buggyfill.hacks.js。你只須要在你的HTML文件中引入這兩個文件。好比在Vue項目中的index.html引入它們:

<script src="//g.alicdn.com/fdilab/lib3rd/viewport-units-buggyfill/0.6.2/??viewport-units-buggyfill.hacks.min.js,viewport-units-buggyfill.min.js"></script>
 

你也可使用其餘的在線CDN地址,也可將這兩個文件合併壓縮成一個.js文件。這主要看你本身的興趣了。

第二步,在HTML文件中調用viewport-units-buggyfill,好比:

複製代碼
<script>
    window.onload = function () {
        window.viewportUnitsBuggyfill.init({
            hacks: window.viewportUnitsBuggyfillHacks
        });
    }
</script>
複製代碼
 

爲了你Demo的時候能獲取對應機型相關的參數,我在示例中添加了一段額外的代碼,估計會讓你有點煩:

複製代碼
<script>
    window.onload = function () {
        window.viewportUnitsBuggyfill.init({
        hacks: window.viewportUnitsBuggyfillHacks
        });

        var winDPI = window.devicePixelRatio;
        var uAgent = window.navigator.userAgent;
        var screenHeight = window.screen.height;
        var screenWidth = window.screen.width;
        var winWidth = window.innerWidth;
        var winHeight = window.innerHeight;

        alert(
            "Windows DPI:" + winDPI +
            ";\ruAgent:" + uAgent +
            ";\rScreen Width:" + screenWidth +
            ";\rScreen Height:" + screenHeight +
            ";\rWindow Width:" + winWidth +
            ";\rWindow Height:" + winHeight
        )
    }
</script>
複製代碼
 

具體的使用。在你的CSS中,只要使用到了viewport的單位(vw、vh、vmin或vmax )地方,須要在樣式中添加content:

複製代碼
.my-viewport-units-using-thingie {
    width: 50vmin;
    height: 50vmax;
    top: calc(50vh - 100px);
    left: calc(50vw - 100px);

    /* hack to engage viewport-units-buggyfill */
    content: 'viewport-units-buggyfill; width: 50vmin; height: 50vmax; top: calc(50vh - 100px); left: calc(50vw - 100px);';
}
複製代碼
 

這可能會令你感到噁心,並且咱們不可能每次寫vw都去人肉的計算。特別是在咱們的這個場景中,我們使用了postcss-px-to-viewport這個插件來轉換vw,更沒法讓咱們人肉的去添加content內容。

這個時候就須要前面提到的postcss-viewport-units插件。這個插件將讓你無需關注content的內容,插件會自動幫你處理。好比插件處理後的代碼:



Viewport Units Buggyfill還提供了其餘的功能。詳細的這裏不闡述了。可是content也會引發必定的反作用。好比img和僞元素::before(:before)或::after(:after)。在img中content會引發部分瀏覽器下,圖片不會顯示。這個時候須要全局添加:

img {
    content: normal !important;
}
 

而對於::after之類的,就算是裏面使用了vw單位,Viewport Units Buggyfill對其並不會起做用。好比:

複製代碼
// 編譯前
.after {
    content: 'after content';
    display: block;
    width: 100px;
    height: 20px;
    background: green;
}

// 編譯後
.after[data-v-469af010] {
    content: "after content";
    display: block;
    width: 13.333vw;
    height: 2.667vw;
    background: green;
}
複製代碼
 

這個時候咱們須要經過添加額外的標籤來替代僞元素(這個情景我沒有測試到,後面本身親測一下)。

到了這個時候,你就不須要再擔憂兼容問題了。好比下面這個示例:



請用你的手機,無論什麼APP掃一掃,你就能夠看到效果。(當心彈框喲),若是你發現了仍是有問題,請把彈出來的信息截圖發給我。

如查你想看看別的機型效果,能夠點擊這裏、這裏、這裏、還有這裏。整個示例的源碼,能夠點擊這裏下載。

若是你下載了示你源碼,先要確認你的系統環境能跑Vue的項目,而後下載下來以後,解壓縮,接着運行npm i,再運行npm run dev,你就能夠看到效果了。

總結
若是你看到這裏了,但願這篇文章對你有所幫助。能幫助你解決項目中的實際問題,讓你再也不擔憂移動端的適配問題。固然更但願的是你在實際的項目中用起這個方案,把碰到的問題及時反饋給偶。若是你有更好的方案,歡迎在下面的評論中與咱們一塊兒分享。


 
 
相關文章
相關標籤/搜索