原文:https://www.w3cplus.com/mobile/vw-layout-in-vue.htmljavascript
有關於移動端的適配佈局一直以來都是衆說紛紜,對應的解決方案也是有不少種。在《使用Flexible實現手淘H5頁面的終端適配》提出了Flexible的佈局方案,隨着viewport
單位愈來愈受到衆多瀏覽器的支持,所以在《再聊移動端頁面的適配》一文中提出了vw
來作移動端的適配問題。到目前爲止無論是哪種方案,都還存在必定的缺陷。言外之意,尚未哪個方案是完美的。css
事實上真的不完美?其實否則。最近爲了新項目中能更完美的使用vw
來作移動端的適配。探討出一種能解決不兼容viewport
單位的方案。今天整理一下,與你們一塊兒分享。若是方案中存在必定的缺陷,歡迎你們一塊兒拍正。html
對於Flexible或者說vw
的佈局,其原理不在這篇文章進行闡述。若是你想追蹤其中的原委,強烈建議你閱讀早前整理的文章《使用Flexible實現手淘H5頁面的終端適配》和《再聊移動端頁面的適配》。vue
說句題外話,因爲Flexible的出現,也形成不少同窗對
rem
的誤解。正如當年你們對div
的誤解同樣。也所以,你們都以爲rem
是萬能的,他能直接解決移動端的適配問題。事實並非如此,至於爲何,我想你們應該去閱讀flexible.js
源碼,我相信你會明白其中的原委。html5
回到咱們今天要聊的主題,怎麼實現vw
的兼容問題。爲了解決這個兼容問題,我將藉助Vue官網提供的構建工程以及一些PostCSS插件來完成。在繼續後面的內容以前,須要準備一些東西:java
對於這些起什麼做用,先不闡述,後續咱們會聊到上述的一些東西。node
對於NodeJs、NPM和Webpack相關介紹,你們能夠查閱其對應的官網。這裏默認你的系統環境已經安裝好Nodejs、NPM和Webpack。個人系統目前使用的Node版本是v9.4.0
;NPM的版本是v5.6.0
。事實上,這些都並不重要。webpack
爲了避免花太多的時間去深刻的瞭解Webpack(Webpack對我而言,太蛋疼了),因此我直接使用Vue-cli來構建本身的項目,由於我通常使用Vue來作項目。若是你想深刻的瞭解Webpack,建議你閱讀下面的文章:ios
接下來的內容,直接使用Vue官方提供的Vue-cli的構建工具來構建Vue項目。首先須要安裝Vue-cli:git
$ 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構建的項目,在項目的根目錄下有一個.postcssrc.js
,默認狀況下已經有了:
module.exports = { "plugins": { "postcss-import": {}, "postcss-url": {}, "autoprefixer": {} } }
對應咱們開頭列的的PostCSS插件清單,如今已經具有了:
簡單的說一下這幾個插件。
postcss-import
相關配置能夠點擊這裏。目前使用的是默認配置。只在.postcssrc.js
文件中引入了該插件。
postcss-import
主要功有是解決@import
引入路徑問題。使用這個插件,可讓你很輕易的使用本地文件、node_modules
或者web_modules
的文件。這個插件配合postcss-url
讓你引入文件變得更輕鬆。
postcss-url
相關配置能夠點擊這裏。該插件主要用來處理文件,好比圖片文件、字體文件等引用路徑的處理。
在Vue項目中,vue-loader
已具備相似的功能,只須要配置中將vue-loader
配置進去。
autoprefixer
插件是用來自動處理瀏覽器前綴的一個插件。若是你配置了postcss-cssnext
,其中就已具有了autoprefixer
的功能。在配置的時候,未顯示的配置相關參數的話,表示使用的是Browserslist指定的列表參數,你也能夠像這樣來指定last 2 versions
或者 > 5%
。
如此一來,你在編碼時再也不須要考慮任何瀏覽器前綴的問題,能夠專心擼碼。這也是PostCSS最經常使用的一個插件之一。
Vue-cli默認配置了上述三個PostCSS插件,但咱們要完成vw
的佈局兼容方案,或者說讓咱們能更專心的擼碼,還須要配置下面的幾個PostCSS插件:
要使用這幾個插件,先要進行安裝:
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
其實就是cssnext。該插件可讓咱們使用CSS將來的特性,其會對這些特性作相關的兼容性處理。其包含的特性主要有:
有關於cssnext
的每一個特性的操做文檔,能夠點擊這裏瀏覽。
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
插件主要用來把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
主要用來處理元素容器寬高比。在實際使用的時候,具備一個默認的結構
<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%; }
有關於寬高比相關的詳細介紹,若是你們感興趣的話,能夠閱讀下面相關的文章:
目前採用PostCSS插件只是一個過渡階段,在未來咱們能夠直接在CSS中使用
aspect-ratio
屬性來實現長寬比。
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
的這個方案。
Vue中的vue-loader
已經集成了CSS Modules的功能,我的建議在項目中開始使用CSS Modules。特別是在Vue和React的項目中,CSS Modules具備很強的優點和靈活性。建議看看CSS In JS相關的資料。在Vue中,使用CSS Modules的相關文檔能夠閱讀Vue官方提供的文檔《CSS Modules》。
postcss-viewport-units
插件主要是給CSS的屬性添加content
的屬性,配合viewport-units-buggyfill
庫給vw
、vh
、vmin
和vmax
作適配的操做。
這是實現vw
佈局必不可少的一個插件,由於少了這個插件,這將是一件痛苦的事情。後面你就清楚。
到此爲止,有關於所須要的PostCSS已配置完。而且簡單的介紹了各個插件的做用,至於詳細的文檔和使用,能夠參閱對應插件的官方文檔。
在《再聊移動端頁面的適配》一文中,詳細介紹了,怎麼使用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
主要分如下幾步走:
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
,你就能夠看到效果了。
若是你看到這裏了,但願這篇文章對你有所幫助。能幫助你解決項目中的實際問題,讓你再也不擔憂移動端的適配問題。固然更但願的是你在實際的項目中用起這個方案,把碰到的問題及時反饋給偶。若是你有更好的方案,歡迎在下面的評論中與咱們一塊兒分享。