整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          前端如何適配不同尺寸的屏幕

          前端如何適配不同尺寸的屏幕

          前端開發中,比較重要的一個環節就是要適配各個屏幕的尺寸。

          PC端比較簡單的是響應式和自適應。響應式比較簡單,通過Media查詢頁面寬度,再加載相應的樣式即可。自適應就是用百分比,rem,vw這樣的單位去做。

          但是今天主要講的是移動端的適配。從iphone4到iphone7P,3.5寸小屏到如今的5.5寸大屏,如何提供一套簡單的適配方案呢?這里推薦一下手淘團隊的方案--Flexable。

          不同分辨率,不同尺寸的屏幕。首先普及一些基本概念:

          物理像素:物理像素又被稱為設備像素,他是顯示設備中一個最微小的物理部件。每個像素可以根據操作系統設置自己的顏色和亮度。正是這些設備像素的微小距離欺騙了我們肉眼看到的圖像效果。

          設備獨立像素:設備獨立像素也稱為密度無關像素,可以認為是計算機坐標系統中的一個點,這個點代表一個可以由程序使用的虛擬像素(比如說CSS像素),然后由相關系統轉換為物理像素。

          css像素:CSS像素是一個抽像的單位,主要使用在瀏覽器上,用來精確度量Web頁面上的內容。一般情況之下,CSS像素稱為與設備無關的像素(device-independent pixel),簡稱DIPs。

          屏幕密度:屏幕密度是指一個設備表面上存在的像素數量,它通常以每英寸有多少像素來計算(PPI)。

          設備像素比:設備像素比簡稱為dpr,其定義了物理像素和設備獨立像素的對應關系。它的值可以按下面的公式計算得到:

          設備像素比=物理像素/設備獨立像素。

          眾所周知,iPhone6的設備寬度和高度為375pt * 667pt,根據上面公式,我們可以很輕松得知其物理像素為750pt * 1334pt。在不同的屏幕上,CSS像素所呈現的物理尺寸是一致的,而不同的是CSS像素所對應的物理像素具數是不一致的。在普通屏幕下1個CSS像素對應1個物理像素,而在Retina屏幕下,1個CSS像素對應的卻是4個物理像素。

          了解了前面一些相關概念之后,接下來我們來看實際解決方案。在整個手淘團隊,我們有一個名叫lib-flexable的庫。可以百度搜索flexable下載這個庫。之后就可以把庫引用到你的項目中去了。

          讀到這里,大家應該都知道,我們接下來要做的事情,就是如何把視覺稿中的px轉換成rem。

          目前Flexible會將視覺稿分成100份,(主要為了以后能更好的兼容vh和vw),而每一份被稱為一個單位。同時1rem單位被認定為10a。針對我們這份視覺稿可以計算出:1a=7.5px;1rem=75px。

          這樣一來,對于視覺稿上的元素尺寸換算,只需要原始的px值除以rem基準值即可。例如此例視覺稿中的圖片,其尺寸是176px * 176px,轉換成為2.346667rem * 2.346667rem。在實際生產當中,如果每一次計算px轉換rem,或許會覺得非常麻煩,或許直接影響大家平時的開發效率。為了能讓大家更快進行轉換,我們團隊內的同學各施所長,為px轉換rem寫了各式各樣的小工具。CSSREM是一個CSS的px轉rem值的Sublime Text3自動完成插件。

          文為Varlet組件庫源碼主題閱讀系列第八篇,讀完本篇,可以了解到移動端頁面如何適配各種尺寸的屏幕,包括pc端,另外如何將觸摸事件轉換成鼠標事件。

          移動端適配

          開發移動端頁面,我們通常都會按照一個固定寬度的設計稿來做,但是實際上的手機屏幕尺寸五花八門,如果不進行適配的話會比較影響使用體驗。

          Varlet組件庫的設計就是基于375px寬度的設計稿,然后使用postcss-px-to-viewport進行移動端適配,這個PostCSS插件會將px單位轉換成vw單位,1vw等于1/100的視口寬度,所以使用vw作為單位就會隨著視口的寬度進行變化達到適配不同機型的效果。

          px轉vw也很簡單,假設某個元素的寬高為100px,設計稿寬度為375px,那么視口也就相當于是375px,那么1vw=375 / 100=3.75px,那么100px / 3.75px=26.66vw,公式如下:

          vw=px / (viewportSize / 100)

          接下來我們從零創建一個Vite項目來看一下postcss-px-to-viewport插件的使用。

          創建項目:

          npm init vite@latest

          根據選項創建一個Vue的項目,然后寫一個非常簡單的按鈕:

          接下來安裝依賴和啟動服務,效果如下:

          假設我們的設計稿就是375px,那么我們切換到尺寸更大一點的機型看看:

          直接上iPad,可以看到按鈕尺寸沒有變,但是因為屏幕變大了而顯得按鈕太小了,這顯然是不夠友好的,接下來我們就配置一下postcss-px-to-viewport插件。

          這個插件本身是一個PostCSS的插件,所以首先要支持PostCss,在Vite項目中使用PostCSS很簡單,只要項目中包含有效的PostCSS 配置,Vite就會自動使其應用于所有導入的CSS,所以我們要做的就是增加一個PostCSS 配置,參考postcss-px-to-viewport插件文檔,先安裝:

          npm install postcss-px-to-viewport

          然后創建postcss.config.js文件,寫入如下內容:

          module.exports={
            plugins: {
              "postcss-px-to-viewport": {
                // 需要轉換的單位
                unitToConvert: "px",
                // 設計稿的視口寬度
                viewportWidth: 375,
                // 單位轉換后保留的精度
                unitPrecision: 4,
              },
            },
          };

          再次啟動服務看看效果:

          報錯了,雖然不知道為什么會把這個配置文件也當成ES Module解析,但是解決方法很簡單,把后綴名改成.cjs即可,再次重啟:

          可以看到按鈕變大了,單位也由我們書寫的px變成了vw。

          桌面端適配

          這個適配指的不是尺寸,因為前面已經使用vw解決了尺寸的適配問題,這里主要是指事件,具體來說是我們在移動端使用的交互事件一般是touch事件,但是桌面端肯定不支持,所以為了讓我們的移動端組件庫不至于在桌面端完全無法使用,需要將touch事件轉成mouse事件。

          Varlet使用的是@varlet/touch-emulator這個包來實現的,使用也很簡單,安裝:

          npm i @varlet/touch-emulator

          導入:

          import '@varlet/touch-emulator'

          接下來修改一下我們上面的示例,給按鈕增加一個touchstart事件:

          然后分別在模擬器和非模擬器環境下單擊一下按鈕:

          顯然,非模擬器環境下單擊是沒有效果的,接下來配置一下@varlet/touch-emulator,再次查看非模擬器環境下的點擊效果:

          可以看到成功觸發了。

          接下來就來窺探一下@varlet/touch-emulator都做了些什么。

          // 判斷是否是瀏覽器環境
          const inBrowser=typeof window !=='undefined'
          // 判斷該環境是否支持touch事件
          const supportTouch=inBrowser && 'ontouchstart' in window
          // ...

          首先進行了一下環境判斷,如果不滿足這兩個條件就不需要做任何處理。

          // ...
          if (inBrowser && !supportTouch) {
            createTouchEmulator()
          }
          // ...

          滿足條件則調用createTouchEmulator方法:

          // ...
          function createTouchEmulator() {
            window.addEventListener('mousedown', (event)=> onMouse(event, 'touchstart'), true)
            window.addEventListener('mousemove', (event)=> onMouse(event, 'touchmove'), true)
            window.addEventListener('mouseup', (event)=> onMouse(event, 'touchend'), true)
          }
          // ...

          監聽了三個鼠標事件,分別對應三個touch事件,注意addEventListener方法第三個參數都傳了true,這個參數默認是false,表示在事件冒泡的階段調用事件處理函數,傳true就表示在事件捕獲的階段調用事件處理函數,舉個栗子,比如我們給頁面上的一個div也綁定了mousedown事件,然后當我們鼠標在這個div上按下,如果是冒泡階段,那么div的事件函數會先被調用,如果是捕獲階段,那么window的事件函數會先被調用,所以這里傳true筆者猜測是因為如果是冒泡階段觸發的話,某個元素的可能會阻止冒泡,那么就不會觸發window上綁定的這幾個事件了。

          這幾個處理方法內都調用了onMouse方法:

          // ...
          let initiated=false
          let eventTarget
          function onMouse(mouseEvent, touchType) {
            // 事件類型、事件目標
            const { type, target }=mouseEvent
            // mousedown=true(mousedown事件)
            //             false(mouseup事件)
            //             保持(mousemove事件)
            initiated=isMousedown(type) ? true : isMouseup(type) ? false : initiated
            // 如果是鼠標移動事件且鼠標沒有按下則返回
            if (isMousemove(type) && !initiated) return
            // 判斷是否要更新事件目標
            if (isUpdateTarget(type)) eventTarget=target
            // 手動構造對應的touch事件并觸發
            triggerTouch(touchType, mouseEvent)
            // 如果鼠標松開了則清除保存的事件目標
            if (isMouseup(type)) eventTarget=null
          }
          
          const isMousedown=(eventType)=> eventType==='mousedown'
          const isMousemove=(eventType)=> eventType==='mousemove'
          const isMouseup=(eventType)=> eventType==='mouseup'
          // ...

          這個方法首先根據鼠標事件的類型設置了initiated變量,記錄鼠標的按下狀態,如果是鼠標移動事件且鼠標沒有按下,那么個方法會直接返回,因為touch事件都需要先按下才會觸發,然后調用了isUpdateTarget方法判斷是否要更新事件目標:

          const isUpdateTarget=(eventType)=>
            isMousedown(eventType) || !eventTarget || (eventTarget && !eventTarget.dispatchEvent)

          鼠標按下顯然對應的是touchstart,觸發的第一個touch事件,事件目標肯定也是新的,所以需要更新,理論上不同手指的事件目標是可能不一樣的,但是由于桌面端鼠標事件只能有一個,所以直接用一個變量保存即可。

          eventTarget不存在當然也需要更新,但是筆者覺得這種情況應該不會出現,因為touchstart或者說是mousedown事件肯定是最先被觸發的,eventTarget應該已經有值了。

          第三個條件筆者也沒有理解,按理說只要是DOM元素應該都會有dispatchEvent方法。

          接下來調用了triggerTouch方法:

          // ...
          function triggerTouch(touchType, mouseEvent) {
            const { altKey, ctrlKey, metaKey, shiftKey }=mouseEvent;
            // bubbles:該事件是否冒泡
            // cancelable:該事件能否被取消
            const touchEvent=new Event(touchType, { bubbles: true, cancelable: true });
            // 設置幾個鍵的按下標志
            touchEvent.altKey=altKey;
            touchEvent.ctrlKey=ctrlKey;
            touchEvent.metaKey=metaKey;
            touchEvent.shiftKey=shiftKey;
            // 設置三種類型的觸摸點對象數據
            touchEvent.touches=getActiveTouches(mouseEvent);
            touchEvent.targetTouches=getActiveTouches(mouseEvent);
            touchEvent.changedTouches=createTouchList(mouseEvent);
            // 派發事件
            eventTarget.dispatchEvent(touchEvent);
          }
          // ...

          先手動創建一個對應類型的touchEvent對象,設置該事件支持冒泡,然后設置了相關按鍵的按下狀態,筆者也是才知道TouchEvent事件是需要這幾個屬性的:

          然后設置觸摸點數據,一共有三種類型:

          • touches:當前屏幕上所有觸摸點的列表
          • targetTouches:當前對象上所有觸摸點的列表
          • changedTouches:涉及當前(引發)事件的觸摸點的列表

          移動端觸摸點是可能存在多個的,比如我同時好幾個手指一起觸摸,可以通過這三個列表進行區分,同樣舉個栗子,比如我給一個div綁定了三個touch事件,第一次我一個手指觸摸到div上,此時這三個列表的值是一樣的,就是第一個手指的觸摸點,然后我第二個手指也開始觸摸,但是不是觸摸到div上,而是其他元素上,那么此時touches列表會包含兩個手指的觸摸點,targetTouches列表只會包含第一個手指的觸摸點,changedTouches列表則為第二個手指的觸摸點。手指全部松開后,這三個列表都將為空。

          但是在桌面端,鼠標觸摸點顯然只有一個,所以這三個列表其實都是相同的。

          touches和targetTouches都調用了getActiveTouches方法獲取:

          // ...
          function getActiveTouches(mouseEvent) {
            const { type }=mouseEvent;
            if (isMouseup(type)) return createTouchList();
            return updateTouchList(mouseEvent);
          }
          // ...

          松開事件touchList是空的,所以返回一個空列表即可,調用的是createTouchList方法:

          // ...
          function createTouchList() {
            const touchList=[];
          
            touchList.item=function (index) {
              return this[index] || null;
            };
          
            return touchList;
          }
          // ...

          原生的TouchList對象存在一個item方法,返回列表中以指定值作為索引的 Touch 對象,所以使用數組來代表TouchList需要自行提供一個同名方法。

          其他事件類型則會調用updateTouchList方法:

          // ...
          function updateTouchList(mouseEvent) {
            const touchList=createTouchList();
          
            touchList.push(new Touch(eventTarget, 1, mouseEvent));
            return touchList;
          }
          // ...

          同樣先創建了一個touchList,然后創建了一個Touch實例添加進去,這個Touch類定義如下,模擬的是原生的Touch對象:

          // ...
          function Touch(target, identifier, mouseEvent) {
            const { clientX, clientY, screenX, screenY, pageX, pageY }=mouseEvent;
          
            this.identifier=identifier;
            this.target=target;
            this.clientX=clientX;
            this.clientY=clientY;
            this.screenX=screenX;
            this.screenY=screenY;
            this.pageX=pageX;
            this.pageY=pageY;
          }
          // ...

          changedTouches直接調用的是createTouchList方法,顯然無論何時返回的都是空的列表,這個似乎是有點問題的,因為前面說了,只有一個觸摸點的話這三個列表的值應該都是一樣的。

          最后在事件目標上進行了事件的派發。

          總結一下,整體所做的事情就是監聽鼠標的三個事件,然后手動創建對應的touch事件對象,最后在事件目標元素上進行派發即可。

          前端開發中,移動端不同設備的屏幕適配一直是個繞不開的技術話題。目前比較流行的方案是類似淘寶的flexible。其原理是使用js動態計算html的font-size,利用rem來實現不同寬度的適配。使用js方案雖然比較成熟,但也有它的一些缺點,比如性能損耗,由于js的阻塞加載和動態計算,頁面不免會出現卡頓和閃屏的現象,影響用戶體驗。今天我們不使用js,完全使用css來實現適配,來看看是怎么實現的吧!

          移動端屏幕適配

          方案一:使用meta標簽

          在html的head中插入下面的meta標簽:

          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta name="viewport" content="width=375, user-scalable=no">

          沒錯,是兩個viewport標簽。width=device-width寫在上面,width=375寫在下面,375就是以哪個設備寬度為基準,現在大部分設計稿都是以iphone6的375寬度為基準做2倍圖。加了上面兩個mata標簽,后面的css就可以完全使用px為單位直接使用,整個頁面會自動按設備寬度進行等比例縮放。看下面的演示效果:

          <script src="https://lf6-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>


          方案二:使用css的calc計算屬性

          在css中定義html的font-size為:calc(100vw/3.75),calc、vw能兼容ios8+和android4.4+,可放心使用,如下:

          html {
            font-size: calc(100vw/3.75);
            -webkit-text-size-adjust: 100%;
          }

          然后在css中,就可以將所有的px單位除以100,得到rem單位了。比如:

          .row>div {
            float: left;
            width: .82rem;
            height: .82rem;
            text-align: center;
            line-height: .82rem;
            margin-left: .05rem;
            background-color: #f0f0f0;
          }

          上面的rem單位轉換,建議大家可以使用px2rem這個插件完成,webpack、vscode都能支持。設置時將rootFontSize 設為100即可。

          設置px2rem參數

          在vscode中,可以使用ctrl+shift+p,選擇px2rem就可以將當前頁面的px全部轉換為rem。

          px2rem在vscode中的使用方法


          當然,rem和px可以相互共存,比如我標題欄就想要44px高,這樣就不會縮放了。看下面的演示效果:

          純css實現移動端適配

          總結:兩種方案如何選擇

          方案一,直接使用html的mata實現整個頁面的縮放,比較適合那些宣傳單頁或全屏游戲交互類,無法實現px與rem共存的情況。

          方案二,利用了rem來縮放,可實現與px共存,在借助px2rem的情況下,能高效方便的實現適配。

          綜合考慮,小編建議使用方案二。你,學會了嗎?


          主站蜘蛛池模板: 国产精品成人一区二区三区| 一区二区三区高清在线| 夜夜添无码一区二区三区| 国内精品视频一区二区三区八戒| 怡红院AV一区二区三区| 视频一区二区三区在线观看| 中文字幕精品亚洲无线码一区应用| 久久久99精品一区二区| 偷拍激情视频一区二区三区| 中文字幕AV一区二区三区 | 成人免费一区二区三区在线观看| 日本在线视频一区二区三区| 日本不卡免费新一区二区三区| 2018高清国产一区二区三区| 一区二区三区影院| 丝袜美腿一区二区三区| 综合人妻久久一区二区精品| 无码午夜人妻一区二区不卡视频| 无码少妇一区二区| 亚洲午夜一区二区三区| 国产亚洲自拍一区| 波多野结衣电影区一区二区三区| 久久人妻av一区二区软件| 国产传媒一区二区三区呀| 亚洲国产av一区二区三区丶| 中文字幕一区二区三区乱码| 精品中文字幕一区在线| 中文字幕日韩精品一区二区三区| 国产成人精品视频一区| 文中字幕一区二区三区视频播放 | 国产精品夜色一区二区三区| 国产午夜精品一区二区三区不卡| 国产精品久久久久一区二区三区 | 国产在线一区观看| 精品无码国产AV一区二区三区 | 国产凸凹视频一区二区| 成人精品一区久久久久| 精品视频一区二区观看| 麻豆一区二区三区蜜桃免费| jizz免费一区二区三区| 久久久人妻精品无码一区|