1. 程式人生 > >轉:如何在Vue項目中使用vw實現移動端適配

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

作用 -html res 尺寸 3.3 常用 css user 復用

https://www.w3cplus.com/mobile/vw-layout-in-vue.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安裝項目需要的依賴關系。現在新版本的可以免了。

這時,可以看到的項目結構如下:

技術分享圖片

安裝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
        }
    }
}

特別聲明:由於cssnextcssnano都具有autoprefixer,事實上只需要一個,所以把默認的autoprefixer刪除掉,然後把cssnano中的autoprefixer設置為false。對於其他的插件使用,稍後會簡單的介紹。

由於配置文件修改了,所以重新跑一下npm run dev。項目就可以正常看到了。接下來簡單的介紹一下後面安裝的幾個插件的作用。

postcss-cssnext

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

技術分享圖片

有關於cssnext的每個特性的操作文檔,可以點擊這裏瀏覽。

cssnano

cssnano主要用來壓縮和清理CSS代碼。在Webpack中,cssnanocss-loader捆綁在一起,所以不需要自己加載它。不過你也可以使用postcss-loader顯式的使用cssnano。有關於cssnano的詳細文檔,可以點擊這裏獲取。

cssnano的配置中,使用了preset: "advanced",所以我們需要另外安裝:

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

cssnano集成了一些其他的PostCSS插件,如果你想禁用cssnano中的某個插件的時候,可以像下面這樣操作:

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

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

postcss-px-to-viewport

postcss-px-to-viewport插件主要用來把px單位轉換為vwvhvmin或者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;
}

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

  • 容器適配,可以使用vw
  • 文本的適配,可以使用vw
  • 大於1px的邊框、圓角、陰影都可以使用vw
  • 內距和外距,可以使用vw

postcss-aspect-ratio-mini

postcss-aspect-ratio-mini主要用來處理元素容器寬高比。在實際使用的時候,具有一個默認的結構

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

在實際使用的時候,你可以把自定義屬性aspectratioaspectratio-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:246188是容器寬度,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時,生成前面指定的默認樣式代碼,這樣代碼沒那麽冗余。所以在使用的時候,需要把widthbackground-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-imagebackground來做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庫給vwvhvminvmax做適配的操作。

這是實現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.jsviewport-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的單位(vwvhvminvmax )地方,需要在樣式中添加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)。在imgcontent會引起部分瀏覽器下,圖片不會顯示。這個時候需要全局添加:

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,你就可以看到效果了。

總結

如果你看到這裏了,希望這篇文章對你有所幫助。能幫助你解決項目中的實際問題,讓你不再擔心移動端的適配問題。當然更希望的是你在實際的項目中用起這個方案,把碰到的問題及時反饋給偶。如果你有更好的方案,歡迎在下面的評論中與我們一起分享。

著作權歸作者所有。
商業轉載請聯系作者獲得授權,非商業轉載請註明出處。
原文: https://www.w3cplus.com/mobile/vw-layout-in-vue.html ? w3cplus.com

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