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