移動端適配及PC端適配心得總結體會(二) (可能比較全

前篇☞ 移動端適配及PC端適配心得總結體會(一) (可能比較全javascript

web適配產生的問題

1.瀏覽器mate指定內核

QQ瀏覽器 meta元素檢測 識別內核 規則介紹:css

識別爲chrome內核
  • doctype 爲標準
  • meta 標籤元素

例子:html

<!DOCTYPE html>
<html>
  <head>
    <!-- 下面3個meta中任選一個,便可正確識別 -->
    <meta name="renderer" content="webkit" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="X-UA-Compatible" content="chrome=1" />
    <title>chrome core</title>
  </head>
  <body>
  meta webkit
  </body>
</html>
複製代碼
識別爲IE內核
  • doctype 爲非標準
  • meta 元素檢測

例子:前端

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <!-- 下面3個meta中任選一個,便可正確識別 -->
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
    <meta name="renderer" content="ie-comp" />
    <meta name="renderer" content="ie-stand" />
    <title>ie core</title>
  </head>
  <body>
  meta ie
  </body>
</html>
複製代碼

參考:qq瀏覽器文檔vue

2. 1px邊框的問題

img

出現的緣由:java

按照iPhone6的尺寸,一張750px寬的設計圖,這個750px其實就是iPhone6的設備像素,在測量設計圖時量到的1px實際上是1設備像素,而當咱們設置佈局視口等於理想視口等於375px,而且因爲iPhone6的DPR爲2,寫css時的1px對應的是2設備像素,因此看起來會粗一點。ios

解決辦法git

1.border-image

基於media查詢判斷不一樣的設備像素比給定不一樣的border-imagegithub

.border_1px{
          border-bottom: 1px solid #000;
        }
        @media only screen and (-webkit-min-device-pixel-ratio:2){
            .border_1px{
                border-bottom: none;
                border-width: 0 0 1px 0;
                border-image: url(../img/1pxline.png) 0 0 2 0 stretch;
            }
        }
複製代碼
2. background-image

border-image相似,準備一張符合條件的邊框背景圖,模擬在背景上。web

.border_1px{
          border-bottom: 1px solid #000;
        }
@media only screen and (-webkit-min-device-pixel-ratio:2{
.border_1px{
        background: url(../img/1pxline.png) repeat-x left bottom;
        background-size: 100% 1px;
            }
        }
複製代碼

上面兩種都須要單獨準備圖片,並且圓角不是很好處理,可是能夠應對大部分場景。

3.僞類 + transform

基於media查詢判斷不一樣的設備像素比對線條進行縮放:

.border_1px:before{
          content: '';
          position: absolute;
          top: 0;
          height: 1px;
          width: 100%;
          background-color: #000;
          transform-origin: 50% 0%;
        }
        @media only screen and (-webkit-min-device-pixel-ratio:2){
            .border_1px:before{
                transform: scaleY(0.5);
            }
        }
        @media only screen and (-webkit-min-device-pixel-ratio:3){
            .border_1px:before{
                transform: scaleY(0.33);
            }
        }
複製代碼

這種方式能夠知足各類場景,若是須要知足圓角,只須要給僞類也加上border-radius便可。

4.使用svg(插件幫助postcss-write-svg)

上面咱們border-imagebackground-image均可以模擬1px邊框,可是使用的都是位圖,還須要外部引入。

藉助PostCSSpostcss-write-svg咱們能直接使用border-imagebackground-image建立svg1px邊框:

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

編譯後:

.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; }
複製代碼

3.適配iPhoneX

咱們須要將頂部和底部合理的擺放在安全區域內,iOS11新增了兩個CSS函數env、constant,用於設定安全區域與邊界的距離。

函數內部能夠是四個常量:

  • safe-area-inset-left:安全區域距離左邊邊界距離
  • safe-area-inset-right:安全區域距離右邊邊界距離
  • safe-area-inset-top:安全區域距離頂部邊界距離
  • safe-area-inset-bottom:安全區域距離底部邊界距離

注意:咱們必須指定viweport-fit後才能使用這兩個函數:

<meta name="viewport" content="width=device-width, viewport-fit=cover">
複製代碼

constantiOS < 11.2的版本中生效,enviOS >= 11.2的版本中生效,這意味着咱們每每要同時設置他們,將頁面限制在安全區域內:

body {
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}
複製代碼

當使用底部固定導航欄時,咱們要爲他們設置padding值:

{
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}
複製代碼

具體細節

aotu.io/notes/2017/…

4.關於橫屏

js識別

window.addEventListener("resize", ()=>{
    if (window.orientation === 180 || window.orientation === 0) { 
      // 正常方向或屏幕旋轉180度
        console.log('豎屏');
    };
    if (window.orientation === 90 || window.orientation === -90 ){ 
       // 屏幕順時鐘旋轉90度或屏幕逆時針旋轉90度
        console.log('橫屏');
    }  
});
複製代碼

css識別

@media screen and (orientation: portrait) {
  /*豎屏...*/
} 
@media screen and (orientation: landscape) {
  /*橫屏...*/
}
複製代碼

5.圖片模糊問題

1 產生緣由

咱們平時使用的圖片大多數都屬於位圖(png、jpg..),位圖由一個個像素點構成的,每一個像素都具備特定的位置和顏色值:

img

理論上,位圖的每一個像素對應在屏幕上使用一個物理像素來渲染,才能達到最佳的顯示效果。

而在dpr > 1的屏幕上,位圖的一個像素可能由多個物理像素來渲染,然而這些物理像素點並不能被準確的分配上對應位圖像素的顏色,只能取近似值,因此相同的圖片在dpr > 1的屏幕上就會模糊:

img

2 解決方案

爲了保證圖片質量,咱們應該儘量讓一個屏幕像素來渲染一個圖片像素,因此,針對不一樣DPR的屏幕,咱們須要展現不一樣分辨率的圖片。

如:在dpr=2的屏幕上展現兩倍圖(@2x),在dpr=3的屏幕上展現三倍圖(@3x)

img

3 media查詢

使用media查詢判斷不一樣的設備像素比來顯示不一樣精度的圖片:

.avatar{
            background-image: url(conardLi_1x.png);
        }
        @media only screen and (-webkit-min-device-pixel-ratio:2){
            .avatar{
                background-image: url(conardLi_2x.png);
            }
        }
        @media only screen and (-webkit-min-device-pixel-ratio:3){
            .avatar{
                background-image: url(conardLi_3x.png);
            }
        }
複製代碼

只適用於背景圖

4 image-set

使用image-set

.avatar {
    background-image: -webkit-image-set( "conardLi_1x.png" 1x, "conardLi_2x.png" 2x );
}
複製代碼

只適用於背景圖

5 srcset

使用img標籤的srcset屬性,瀏覽器會自動根據像素密度匹配最佳顯示圖片:

<img src="conardLi_1x.png" srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x">
複製代碼
6 JavaScript拼接圖片url

使用window.devicePixelRatio獲取設備像素比,遍歷全部圖片,替換圖片地址:

const dpr = window.devicePixelRatio;
const images =  document.querySelectorAll('img');
images.forEach((img)=>{
  img.src.replace(".", `@${dpr}x.`);
})
複製代碼
7 使用svg

SVG的全稱是可縮放矢量圖,不一樣於位圖的基於像素,SVG 則是屬於對圖像的形狀描述,因此它本質上是文本文件,體積較小,且無論放大多少倍都不會失真。

除了咱們手動在代碼中繪製svg,咱們還能夠像使用位圖同樣使用svg圖片:

<img src="conardLi.svg">
<img src="data:image/svg+xml;base64,[data]"> .avatar { background: url(conardLi.svg); } 複製代碼

參考:

移動端適配

頭條移動端適配

h5相關

什麼是h5

H5 技術是一系列移動端 web 前端技術的集合

img

H5 技術自己是用於移動端的 web 網頁。因爲 App 自己有個 webview 的容器,在容器裏能夠運行 Web 前端相關代碼,由此 H5 和原生 App 結合又衍生出來了 Hybrid App 技術

Hybrid App 技術大體原理

img

移動端響應式佈局

解決方案一:rem + pxToRem

原理
  1. 監聽屏幕視窗的寬度,經過必定比例換算賦值給htmlfont-size。此時,根字體大小就會隨屏幕寬度而變化。
  2. px 轉換成 rem, 常規方案有兩種,一種是利用sass/less中的自定義函數 pxToRem,寫px時,利用pxToRem函數轉換成 rem。另一種是直接寫px,編譯過程利用插件所有轉成rem。這樣 dom 中元素的大小,就會隨屏幕寬度變化而變化了。
實現
  1. 動態更新根字體大小
const MAX_FONT_SIZE = 420
// 定義最大的屏幕寬度
document.addEventListener('DOMContentLoaded', () => {
  const html = document.querySelector('html')
  let fontSize = window.innerWidth / 10
  fontSize = fontSize > MAX_FONT_SIZE ? MAX_FONT_SIZE : fontSize
  html.style.fontSize = fontSize + 'px'
})

複製代碼
  1. pxrem
pxToRem 方案一
$rootFontSize: 375 / 10;
// 定義 px 轉化爲 rem 的函數
@function px2rem ($px) {
    @return $px / $rootFontSize + rem;
}
.demo {
    width: px2rem(100);
    height: px2rem(100);
}
複製代碼
pxToRem方案二

vue-cli3 中裝 postcss-pxtorem 插件就能夠了,其餘平臺也是大體差很少的思路。

const autoprefixer = require('autoprefixer')
const pxtorem = require('postcss-pxtorem')
module.exports = { 
  // ...
  css: {
    sourceMap: true,
    loaderOptions: {
      postcss: {
        plugins: [
          autoprefixer(),
          pxtorem({
            rootValue: 37.5,
            propList: ['*']
          })
        ]
      }
    }
  }
}
複製代碼

繼續探索postcss-pxtorem插件源碼,查看它實現的原理。

function createPxReplace (rootValue, unitPrecision, minPixelValue) {
    return function (m, $1) {
        if (!$1) return m;
        var pixels = parseFloat($1);
        if (pixels < minPixelValue) return m;
        var fixedVal = toFixed((pixels / rootValue), unitPrecision);
        return (fixedVal === 0) ? '0' : fixedVal + 'rem';
    };
}
複製代碼

px變換成 rem 主要是這個函數,固然裏面有不少可配置的參數, 核心原理和咱們方案一差很少。方便在於,不須要每次寫px都要加上一個函數,代碼也清晰不少。

是否是全部元素 px 都要轉換成 rem呢?那可不必定哦,border 中的 px 不該該轉 rem,涉及到另一個 1px 的問題,上一篇文章詳細論述過,避免 pxrem,將 border 中的 px 大寫成 PX/Px/pX

解決方案二:vh + vw

原理

vw 相對於視窗寬度的單位,隨寬度變化而變化。

實現

與 rem 相似作法,直接使用 postcss-px-to-viewport 插件進行配置, 配置方式也是和 postcss-pxtorem 大同小異。插件的原理也相同

其餘解決方案

方案 缺陷
1 百分比 高度沒法百分比
2 媒體查詢 + metaviewport 不一樣設備寬度不一樣,縮放比沒法徹底肯定
3 flex 仍是沒法解決寬度超出問題

上面方案均存在致命缺陷,不推薦使用它完成移動端佈局計算。

flexrem 結合使用更佳

vue快速配置移動端模板

參考

開源庫解決方案

1.vant 組件庫

vant 組件庫中,默認採用 px 作計量單位,若是須要使用 rem,直接使用插件完美適配。

對於 vw 方案,vant 也是能夠經過插件將 px 轉成 vw,對於 vw 可能會存在一些坑點。

2.ant-design-mobile 組件庫

ant-design-mobile 組件庫仍然使用 px 單位

@hd: 1px; // 基本單位

// 字體尺寸
// ---
@font-size-icontext: 10 * @hd;
@font-size-caption-sm: 12 * @hd;
@font-size-base: 14 * @hd;
@font-size-subhead: 15 * @hd;
@font-size-caption: 16 * @hd;
@font-size-heading: 17 * @hd;

// 圓角
// ---
@radius-xs: 2 * @hd;
@radius-sm: 3 * @hd;
@radius-md: 5 * @hd;
@radius-lg: 7 * @hd;
@radius-circle: 50%;

// 邊框尺寸
// ---
@border-width-sm: 1PX;
@border-width-md: 1PX;
@border-width-lg: 2 * @hd;
複製代碼

h5遇到的問題彙總

img

1.ios滑動不流暢

表現

上下滑動頁面會產生卡頓,手指離開頁面,頁面當即中止運動。總體表現就是滑動不流暢,沒有滑動慣性。

產生緣由

爲何 iOS 的 webview 中 滑動不流暢,它是如何定義的?

原來在 iOS 5.0 以及以後的版本,滑動有定義有兩個值 autotouch,默認值爲 auto

-webkit-overflow-scrolling: touch; /* 當手指從觸摸屏上移開,會保持一段時間的滾動 */
-webkit-overflow-scrolling: auto; /* 當手指從觸摸屏上移開,滾動會當即中止 */
複製代碼

解決方案

1.在滾動容器上增長滾動 touch 方法

-webkit-overflow-scrolling 值設置爲 touch

.wrapper {
    -webkit-overflow-scrolling: touch;
}
複製代碼

設置滾動條隱藏: .container ::-webkit-scrollbar {display: none;}

可能會致使使用position:fixed; 固定定位的元素,隨着頁面一塊兒滾動

2.設置 overflow

設置外部 overflowhidden,設置內容元素 overflowauto。內部元素超出 body 即產生滾動,超出的部分 body 隱藏。

body {
    overflow-y: hidden;
}
.wrapper {
    overflow-y: auto;
}
複製代碼

二者結合使用更佳!


2.iOS 上拉邊界下拉出現白色空白

表現

手指按住屏幕下拉,屏幕頂部會多出一塊白色區域。手指按住屏幕上拉,底部多出一塊白色區域。

產生緣由

在 iOS 中,手指按住屏幕上下拖動,會觸發 touchmove 事件。這個事件觸發的對象是整個 webview 容器,容器天然會被拖動,剩下的部分會成空白。

解決方案
1. 監聽事件禁止滑動

移動端觸摸事件有三個,分別定義爲

1. touchstart :手指放在一個DOM元素上。
2. touchmove :手指拖曳一個DOM元素。
3. touchend :手指從一個DOM元素上移開。
複製代碼

顯然咱們須要控制的是 touchmove 事件

touchmove 事件的速度是能夠實現定義的,取決於硬件性能和其餘實現細節

preventDefault 方法,阻止同一觸點上全部默認行爲,好比滾動。

由此咱們找到解決方案,經過監聽 touchmove,讓須要滑動的地方滑動,不須要滑動的地方禁止滑動。

值得注意的是咱們要過濾掉具備滾動容器的元素。

實現以下:

document.body.addEventListener('touchmove', function(e) {
    if(e._isScroller) return;
    // 阻止默認事件
    e.preventDefault();
}, {
    passive: false
});
複製代碼
2. 滾動妥協填充空白,裝飾成其餘功能

在不少時候,咱們能夠不去解決這個問題,換一直思路。根據場景,咱們能夠將下拉做爲一個功能性的操做

好比: 下拉後刷新頁面


3.頁面放大或縮小不肯定性行爲

表現

雙擊或者雙指張開手指頁面元素,頁面會放大或縮小。

產生緣由

HTML 自己會產生放大或縮小的行爲,好比在 PC 瀏覽器上,能夠自由控制頁面的放大縮小。可是在移動端,咱們是不須要這個行爲的。因此,咱們須要禁止該不肯定性行爲,來提高用戶體驗。

原理與解決方案

HTML meta 元標籤標準中有個 中 viewport 屬性,用來控制頁面的縮放,通常用於移動端。以下圖 MDN 中介紹

<meta name="viewport" content="width=device-width, initial-scale=1.0">
複製代碼

所以咱們能夠設置 maximum-scaleminimum-scaleuser-scalable=no 用來避免這個問題

<meta name=viewport
  content="width=device-width, initial-scale=1.0, minimum-scale=1.0 maximum-scale=1.0, user-scalable=no">
複製代碼

阻止頁面放大(meta不起做用時)

window.addEventListener(
    "touchmove",
    function (event) {
      if (event.scale !== 1) {
        event.preventDefault();
      }
    },
    { passive: false }
  );
複製代碼

4.click 點擊事件延時與穿透

表現

監聽元素 click 事件,點擊元素觸發時間延遲約 300ms

點擊蒙層,蒙層消失後,下層元素點擊觸發。

產生緣由
爲何會產生 click 延時?

iOS 中的 safari,爲了實現雙擊縮放操做,在單擊 300ms 以後,若是未進行第二次點擊,則執行 click 單擊操做。也就是說來判斷用戶行爲是否爲雙擊產生的。可是,在 App 中,不管是否須要雙擊縮放這種行爲,click 單擊都會產生 300ms 延遲。

爲何會產生 click 點擊穿透?

雙層元素疊加時,在上層元素上綁定 touch 事件,下層元素綁定 click 事件。因爲 click 發生在 touch 以後,點擊上層元素,元素消失,下層元素會觸發 click 事件,由此產生了點擊穿透的效果。

原理與解決方案
解決方案一:使用 touchstart 替換 click

前面已經介紹了,移動設備不只支持點擊,還支持幾個觸摸事件。 那麼咱們如今基本思路就是用 touch 事件代替click 事件。

click 替換成 touchstart 不只解決了 click 事件都延時問題,還解決了穿透問題。由於穿透問題是在 touchclick 混用時產生。

在原生中使用

el.addEventListener("touchstart", () => { console.log("ok"); }, false);
複製代碼

在 vue 中使用

<button @touchstart="handleTouchstart()">點擊</button>
複製代碼

開源解決方案中,也是既提供了 click 事件,又提供了touchstart 事件。如 vant 中的 button 組件

img

那麼,是否能夠將 click 事件所有替換成 touchstart 呢?爲何開源框架還會給出 click 事件呢?

咱們想象一種情景,同時須要點擊和滑動的場景下。若是將 click 替換成 touchstart 會怎樣?

事件觸發順序: touchstart, touchmove, touchend, click

很容易想象,在我須要touchmove滑動時候,優先觸發了touchstart的點擊事件,是否是已經產生了衝突呢?

因此呢,在具備滾動的狀況下,仍是建議使用 click 處理。

在接下來的fastclick開源庫中也作了以下處理。

主要目的就是,在使用 touchstart 合成 click 事件時,保證其不在滾動的父元素之下。

解決方案二: 使用 fastclick 庫

使用 npm/yarn 安裝後使用

import FastClick from 'fastclick';
FastClick.attach(document.body, options);
複製代碼

一樣,使用fastclick庫後,click 延時和穿透問題都沒了

按照個人慣例,只要涉及開源庫,那麼咱們必定要去了解它實現的原理。主要是將現有的原生事件集合封裝合成一個兼容性較強的事件集合。

fastclick源碼 核心代碼不長, 1000 行不到。有興趣能夠了解一下!


5.軟鍵盤將頁面頂起來、收起未回落問題

表現

Android 手機中,點擊 input 框時,鍵盤彈出,將頁面頂起來,致使頁面樣式錯亂。

移開焦點時,鍵盤收起,鍵盤區域空白,未回落。

產生緣由

咱們在app 佈局中會有個固定的底部。安卓一些版本中,輸入彈窗出來,會將解壓 absolutefixed 定位的元素。致使可視區域變小,佈局錯亂。

原理與解決方案
方案1:

監聽input失焦事件,失焦即回落

/iphone|ipod|ipad/i.test(navigator.appVersion) &&
  document.addEventListener(
    "blur",
    (event) => {
      // 當頁面沒出現滾動條時才執行,由於有滾動條時,不會出現這問題
      // input textarea 標籤才執行,由於 a 等標籤也會觸發 blur 事件
      if (
        document.documentElement.offsetHeight <= document.documentElement.clientHeight &&
        ["input", "textarea"].includes(event.target.localName)
      ) {
        document.body.scrollIntoView(); // 回頂部
      }
    },
    true
  );
複製代碼
方案2:

軟鍵盤將頁面頂起來的解決方案,主要是經過監聽頁面高度變化,強制恢復成彈出前的高度。

//這裏應該改爲style,height屬性

// 記錄原有的視口高度
const originalHeight = document.body.clientHeight || document.documentElement.clientHeight;
window.onresize = function(){
  var resizeHeight = document.documentElement.clientHeight || document.body.clientHeight;
  if(resizeHeight < originalHeight ){
    // 恢復內容區域高度
    // const container = document.getElementById("container")
    // 例如 container.style.height = originalHeight;
  }
}
複製代碼

鍵盤不能回落問題出如今 iOS 12+ 和 wechat 6.7.4+ 中,而在微信 H5 開發中是比較常見的 Bug。

兼容原理,1.判斷版本類型 2.更改滾動的可視區域

const isWechat = window.navigator.userAgent.match(/MicroMessenger\/([\d\.]+)/i);
if (!isWechat) return;
const wechatVersion = wechatInfo[1];
const version = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
 
 // 若是設備類型爲iOS 12+ 和wechat 6.7.4+,恢復成原來的視口
if (+wechatVersion.replace(/\./g, '') >= 674 && +version[1] >= 12) {
  window.scrollTo(0, Math.max(document.body.clientHeight, document.documentElement.clientHeight));
}
複製代碼
複製代碼

window.scrollTo(x-coord, y-coord),其中window.scrollTo(0, clientHeight)恢復成原來的視口


6.iPhone X系列安全區域適配問題

表現

頭部劉海兩側區域或者底部區域,出現劉海遮擋文字,或者呈現黑底或白底空白區域。

產生緣由

iPhone X 以及它以上的系列,都採用劉海屏設計全面屏手勢。頭部、底部、側邊都須要作特殊處理。才能適配 iPhone X 的特殊狀況。

解決方案

設置安全區域,填充危險區域,危險區域不作操做和內容展現。

危險區域指頭部不規則區域,底部橫條區域,左右觸發區域。

img

具體操做爲:viewport-fit ,meta 標籤設置爲 cover,獲取全部區域填充。 判斷設備是否屬於 iPhone X,給頭部底部增長適配層

viewport-fit 有 3 個值分別爲:

  • auto:此值不影響初始佈局視圖端口,而且整個web頁面都是可查看的。
  • contain: 視圖端口按比例縮放,以適合顯示內嵌的最大矩形。
  • cover:視圖端口被縮放以填充設備顯示。強烈建議使用 safe area inset 變量,以確保重要內容不會出如今顯示以外。

設置 viewport-fit 爲 cover

<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes, viewport-fit=cover">
複製代碼

增長適配層

使用 safe area inset 變量

/* 適配 iPhone X 頂部填充*/
@supports (top: env(safe-area-inset-top)){
  body,
  .header{
      padding-top: constant(safe-area-inset-top, 40px);
      padding-top: env(safe-area-inset-top, 40px);
      padding-top: var(safe-area-inset-top, 40px);
  }
}
/* 判斷iPhoneX 將 footer 的 padding-bottom 填充到最底部 */
@supports (bottom: env(safe-area-inset-bottom)){
    body,
    .footer{
        padding-bottom: constant(safe-area-inset-bottom, 20px);
        padding-bottom: env(safe-area-inset-bottom, 20px);
        padding-top: var(safe-area-inset-bottom, 20px);
    }
}
複製代碼

safe-area-inset-top, safe-area-inset-right, safe-area-inset-bottom, safe-area-inset-left safe-area-inset-*由四個定義了視口邊緣內矩形的 top, right, bottomleft 的環境變量組成,這樣能夠安全地放入內容,而不會有被非矩形的顯示切斷的風險。

對於矩形視口,例如普通的筆記本電腦顯示器,其值等於零。

對於非矩形顯示器(如圓形錶盤,iPhoneX 屏幕,在用戶代理設置的四個值造成的矩形內,全部內容都可見。

其中 env() 用法爲 env( <custom-ident> , <declaration-value>? ),第一個參數爲自定義的區域,第二個爲備用值。

其中 var() 用法爲 var(<custom-property-name> , <declaration-value>?),做用是在 env() 不生效的狀況下,給出一個備用值。

constant()css 2017-2018 年爲草稿階段,是否已被標準化未知。而其餘iOS 瀏覽器版本中是否有此函數未知,做爲兼容處理而添加進去。

詳情請查看文章末尾的參考資料。


7.頁面生成爲圖片和二維碼問題

表現

在工做中有須要將頁面生成圖片或者二維碼的需求。可能咱們第一想到的,交給後端來生成更簡單。可是這樣咱們須要把頁面代碼所有傳給後端,網絡性能消耗太大。

解決方案
生成二維碼

使用 QRCode 生成二維碼

import QRCode from 'qrcode';
// 使用 async 生成圖片
const options = {};
const url = window.location.href;
async url => {
  try {
    console.log(await QRCode.toDataURL(url, options))
  } catch (err) {
    console.error(err);
  }
}
複製代碼

await QRCode.toDataURL(url, options) 賦值給 圖片 url 便可

生成圖片

主要是使用 htmlToCanvas 生成 canvas 畫布

import html2canvas from 'html2canvas';
html2canvas(document.body).then(function(canvas) {
    document.body.appendChild(canvas);
});
複製代碼

可是不僅僅在此處就完了,因爲是 canvas 的緣由。移動端生成出來的圖片比較模糊。

咱們使用一個新的 canvas 方法多倍生成,放入一倍容器裏面,達到更加清晰的效果,經過超連接下載圖片 下載文件簡單實現,更完整的實現方式以後更新

const scaleSize = 2;
const newCanvas = document.createElement("canvas");
const target = document.querySelector('div');
const width = parseInt(window.getComputedStyle(target).width);
const height = parseInt(window.getComputedStyle(target).height);
newCanvas.width = width * scaleSize;
newCanvas.height = widthh * scaleSize;
newCanvas.style.width = width + "px";
newCanvas.style.height =width + "px";
const context = newCanvas.getContext("2d");
context.scale(scaleSize, scaleSize);
html2canvas(document.querySelector('.demo'), { canvas: newCanvas }).then(function(canvas) {
  // 簡單的經過超連接設置下載功能
  document.querySelector(".btn").setAttribute('href', canvas.toDataURL());
}
複製代碼

根據須要設置 scaleSize 大小


8.微信公衆號分享問題

表現

在微信公衆號 H5 開發中,頁面內部點擊分享按鈕調用 SDK,方法不生效。

解決方案

添加一層蒙層,作分享引導。

由於頁面內部點擊分享按鈕沒法直接調用,而分享功能須要點擊右上角更多來操做。


9.H5 調用 SDK 相關解決方案

原生與 H5 的通訊

解決方案

使用 DSBridge 同時支持 iOS 與 Android

文檔見參考資料

SDK小組 提供方法
  1. 註冊方法 bridge.register
bridge.register('enterApp', function() {
  broadcast.emit('ENTER_APP')
})
複製代碼
  1. 回調方法 bridge.call
export const getSDKVersion = () => bridge.call('BLT.getSDKVersion')
複製代碼
事件監聽與觸發法
const broadcast = {
  on: function(name, fn, pluralable) {
    this._on(name, fn, pluralable, false)
  },
  once: function(name, fn, pluralable) {
    this._on(name, fn, pluralable, true)
  },
  _on: function(name, fn, pluralable, once) {
    let eventData = broadcast.data
    let fnObj = { fn: fn, once: once }
    if (pluralable && Object.prototype.hasOwnProperty.call(eventData, 'name')) {
      eventData[name].push(fnObj)
    } else {
      eventData[name] = [fnObj]
    }
    return this
  },
  emit: function(name, data, thisArg) {
    let fn, fnList, i, len
    thisArg = thisArg || null
    fnList = broadcast.data[name] || []
    for (i = 0, len = fnList.length; i < len; i++) {
      fn = fnList[i].fn
      fn.apply(thisArg, [data, name])
      if (fnList[i].once) {
        fnList.splice(i, 1)
        i--
        len--
      }
    }
    return this
  },
  data: {}
}
export default broadcast

複製代碼
踩坑注意

方法調用前,必定要判斷 SDK 是否提供該方法 若是 Android 提供該方法,iOS 上調用就會出現一個方法調用失敗等彈窗。 怎麼解決呢?

提供一個判斷是否 Android、iOS。根據設備進行判斷

export const hasNativeMethod = (name) =>
  return bridge.hasNativeMethod('BYJ.' + name)
}

export const getSDKVersion = function() {
  if (hasNativeMethod('getSDKVersion')) {
    bridge.call('BYJ.getSDKVersion')
  }
}
複製代碼

同一功能須要iOS,Android方法名相同,這樣更好處理哦


10.H5 調試相關方案策略

表現

調試代碼通常就是爲了查看數據定位 bug。分爲兩種場景,一種是開發和測試時調試,一種是生產環境上調試。

爲何有生產環境上調試呢?有些時候測試環境上無法復現這個 bug,測試環境和生產環境不一致,此時就須要緊急生產調試。

在 PC 端開發時,咱們能夠直接掉出控制檯,使用瀏覽器提供的工具操做devtools或者查看日誌。可是在 App 內部咱們怎麼作呢?

原理與解決方案
1. vconsole 控制檯插件

使用方法也很簡單

import Vconsole from 'vconsole'
new Vconsole()
複製代碼

img

有興趣看看它實現的基本原理,咱們關注的點應該在 vsconsole 如何打印出咱們全部 log 的 騰訊開源vconsole

上述方法僅用於開發和測試。生產環境中不容許出現,因此,使用時須要對環境進行判斷。

import Vconsole from 'vconsole'
if (process.env.NODE_ENV !== 'production') {
    new Vconsole()
}
複製代碼
2. 代理 + spy-debugger

操做稍微有點麻煩,不過我會詳細寫出,大體分爲 4 個步驟

  1. 安裝插件(全局安裝)
sudo npm install spy-debugger -g
複製代碼
  1. 手機與電腦置於同一 wifi 下,手機設置代理

設置手機的 HTTP 代理,代理 IP 地址設置爲 PC 的 IP 地址,端口爲spy-debugger的啓動端口

spy-debugger 默認端口:9888

Android :設置 - WLAN - 長按選中網絡 - 修改網絡 - 高級 - 代理設置 - 手動

IOS :設置 - Wi-Fi - 選中網絡, 點擊感嘆號, HTTP 代理手動

  1. 手機打開瀏覽器或者 app 中 H5 頁面
  2. 打開桌面日誌網站進行調試,點擊 npm 控制檯監聽地址。查看抓包和 H5 頁面結構

這種方式能夠調試生成環境的頁面,不須要修改代碼,能夠應付大多數調試需求

參考:小鎖君少

12 移動端100vh的問題彙總

問題描述:

在滾屏手機端要求全屏滾動(使用swiper),由於移動端高度不定,因此採用了100vh佈局,在模擬器裏很是和諧,結果在發如今移動端的 Chrome 和 微信/safari 瀏覽器中,由於瀏覽器欄和一些導航欄 標題欄 致使不同的呈現

呈現1: swiper插件的高設置爲innerHeight ,致使微信瀏覽器 在滾動的時候 會出現斷層

呈現2: 100vh 其實超出了屏幕高度,致使一些內容被擋住

出現的緣由:

最好避免100vh,而是依賴javascript來設置高度,以得到完整的視口體驗。

核心問題是移動瀏覽器(Chrome和Safari)有一個「幫助」功能,地址欄有時可見,有時隱藏,改變了視口的可見大小,

這些瀏覽器沒有將100vh的高度調整爲視口高度變化時屏幕的可見部分,而是將100vh設置爲隱藏地址欄的瀏覽器高度。結果是,當地址欄可見時,屏幕的底部部分將被切斷,從而破壞了100vh的初衷。

呈現3:

在咱們使用iOS Safari的瀏覽器時,上劃頁面的時候工具欄和地址欄會隱藏起來。

當頁面向上滑動的時候,工具欄會隱藏,此時的window.innerHeight會發生變化,可是css中的vh是不會變的,100vh = 隱藏工具欄時的window.innerHeight。(在IphoneXR中,顯示工具欄的時候window.innerHeight = 719px,工具欄隱藏時候window.innerHeight = 833px)

若是你的頁面的原本設計就是能夠滑動的長頁面是沒有什麼影響的,可是若是你的頁面準備作成只有一個視口大小的單頁面應用,那就會出現問題。 由於就算你設置width: 100%,頁面仍是會出現滾動,而且向上滾動達到必定閾值的時候,工具欄會隱藏,佈局徹底混亂。而且且在橫屏和豎屏下,工具欄的高度也是不一樣的。

此外若是咱們爲元素設置absolute或fixed屬性,想要將它們固定在底部位置的話,Safari也不能如咱們所願。如上文所講,實際上你的頁面有一部分被底部的工具欄擋住了,因此咱們設置在底部位置的元素也會被工具欄擋住。

解決的一些辦法:

1.對於由於nav 被擋住的東西使用calc動態算高度(沒嘗試)
min-height: calc(100vh - 0.9rem) //0.9rem是擋住的高度
複製代碼
2.使用js 動態設置高度

將高度設置爲window.innerHeight將正確地將高度設置爲窗口的可見部分。若是地址欄是可見的,那麼window.innerHeight是全屏的高度。若是地址欄是隱藏的,那麼window.innerHeight將是屏幕可見部分的高度,

3.在vue項目裏使用

${app}/src/app.vue

mounted() {
    // First we get the viewport height and we multiple it by 1% to get a value for a vh unit
    let vh = window.innerHeight * 0.01
    // Then we set the value in the --vh custom property to the root of the document
    document.documentElement.style.setProperty('--vh', `${vh}px`)
    // We listen to the resize event
    window.addEventListener('resize', () => {
      // We execute the same script as before
      let vh = window.innerHeight * 0.01
      document.documentElement.style.setProperty('--vh', `${vh}px`)
    })
  },
複製代碼

${app}/views/foo.vue

<style lang="scss" scoped>
.container {
  height: 100vh; /* 在以前設置爲100vh,能夠兼容某些不支持自定義屬性的瀏覽器。*/
  height: calc(var(--vh, 1vh) * 100 - 46px);
</style>
複製代碼
4.使用 height:100%

給body下的每一層級都設置高度100%

* {
  padding: 0;
  margin: 0;
  border: 0;
  outline: 0;
  box-sizing: border-box;
}
html {
  width: 100%;
  height: 100%;
  border: 5px solid red;
  overflow: hidden;
  //沒試驗 明天實驗一下
  height:-webkit-fill-available;
  
}
複製代碼
5.在蘋果瀏覽器裏,動態解決高度,使用css

工具欄高度75px,calc的計算屬性在低版本的safari中並不兼容 可使用方法4

給body下的每一層級都設置高度100%

calc(100vh - 75px)
複製代碼
6.關於父容器沒有高度, 子height: 100%,而致使的內容塌陷問題

場景描述:

<head>
        <style>
            .app { width: 100%;height: 100%;display: flex;flex: 1;flex-direction: column; }
            header { width: 100%;height: 75px; }
            main { flex: 1; }
            iframe { width: 100%;height: 100%;}
        </style>
    </head>
    <body>
        <div class="app">
            <header></header>
            <main>
                <iframe v-if="true"></iframe>           
              <div v-else></div>
            </main>
        </div>

複製代碼

具備百分比高度的元素的父級必須具備已定義的高度,而且必須具備height屬性.不然,具備百分比高度的元素必須默認爲height:auto(內容高度)

chrome會自動彌補這個問題,可是Safari會認爲這是一個缺陷,iframe的父級沒有給定的高度,直接致使iframe高度塌陷(隨內容高度)。因此在拉伸瀏覽器窗口的時候,iframe內容實際高度產生了變化,致使該問題的出現。

解決方案:

  1. 給定父級指定高度
main { flex: 1;height: calc(100% - 75px); }
height: calc(100vh - calc(100vh - 100%))
複製代碼
  1. 直接給iframe子級指定高度
iframe { width: 100%; height: calc(100vh - 75px); }
複製代碼

這裏不用100% - 75px,是由於100%依賴於父級指定高度,而vh是直接已瀏覽器但是窗口高度爲基準的。

7.使用-webkit-fill-available

(safari沒用)

的CSS

body {
  min-height: 100vh;
  /* mobile viewport bug fix */
  min-height: -webkit-fill-available;
}
html {
  height: -webkit-fill-available;
}
body {
  display: flex; 
  flex-direction: column;
  margin: 0;
  min-height: 100vh;
}

main {
  flex: 1;
}
複製代碼
8.完美解決辦法
<template>
  <div class="module"> <div class="module__item">20%</div> <div class="module__item">40%</div> <div class="module__item">60%</div> <div class="module__item">80%</div> <div class="module__item">100%</div> </div>
</template>

<script> export default { mounted() { // First we get the viewport height and we multiple it by 1% to get a value for a vh unit const vh = window.innerHeight * 0.01 // Then we set the value in the --vh custom property to the root of the document document.documentElement.style.setProperty('--vh', `${vh}px`) // We listen to the resize event window.addEventListener('resize', () => { // We execute the same script as before const vh = window.innerHeight * 0.01 document.documentElement.style.setProperty('--vh', `${vh}px`) }) } } </script>

<style> body { background-color: #333; } .module { height: 100vh; /* Use vh as a fallback for browsers that do not support Custom Properties */ height: calc(var(--vh, 1vh) * 100); margin: 0 auto; max-width: 30%; } .module__item { align-items: center; display: flex; height: 20%; justify-content: center; } .module__item:nth-child(odd) { background-color: #fff; color: #f73859; } .module__item:nth-child(even) { background-color: #f73859; color: #f1d08a; } </style>

複製代碼

13 針對safari瀏覽器的問題彙總

1.input標籤在safari蘋果瀏覽器中的高度默認

在作瀏覽器兼容的時候,發現input標籤在safari蘋果瀏覽器中的高度永遠都是默認的,這時候解決的辦法是加上line-height屬性就能夠設置;

若是Safari瀏覽器的input高度設置無論用,必定要設置line-height,而後去除iOS固有UI樣式:-webkit-appearance: none;

14 flex佈局的問題(擴展)

在容器中的每一個單元塊被稱之爲 flex item

主軸空間:main size

交叉軸空間:cross size

1.flex容器

div{ display: flex | inline-flex; }
複製代碼

當時設置 flex 佈局以後,子元素的 float、clear、vertical-align 的屬性將會失效,

flex:1 所在div 高度自適應屏幕的剩餘高度

2.給flex容器設置的6個屬性

1. flex-direction: 決定主軸的方向(即項目的排列方向)
.container {
    flex-direction: row(默認) | row-reverse | column | column-reverse;
}
複製代碼

2. flex-wrap: 決定容器內項目是否可換行

.container {
    flex-wrap: nowrap(默認) | wrap(項目主軸總尺寸超出容器時換行,第一行在上方) | wrap-reverse;(反向)
}
複製代碼
  1. flex-flow: flex-direction 和 flex-wrap 的簡寫形式 (沒有用)

4. justify-content:定義了項目在主軸的對齊方式。

.container {
    justify-content: 
      flex-start(默認值 左對齊)
    | flex-end(右對齊)
    | center 
    | space-between(兩端對齊,只有兩行的時候,分別在首尾)
    | space-around(每一個項目兩側的間隔相等,項目之間的間隔比項目與邊緣的間隔大一倍)
  	| space-evenly 子元素會均勻分佈在容器內,同時額外的空間將會被子元素的兩側所分享
}
複製代碼

記住 justify-content 只會在 盒子有剩餘空間能夠分配時 發揮做用。

5. align-items: 定義了項目在交叉軸上的對齊方式

.container {
    align-items:
      stretch(默認值):若是項目未設置高度或者設爲 auto,將佔滿整個容器的高度。
      flex-start (頂點對齊)
      flex-end	(尾點對齊)
      center
      baseline (項目的第一行文字的基線對齊)
}
複製代碼

6. align-content: 定義了多根軸線的對齊方式,若是項目只有一根軸線,那麼該屬性將不起做用

.container {
    align-content: flex-start | flex-end | center | space-between | space-around | stretch(默認值);
}
複製代碼

當你 flex-wrap 設置爲 nowrap 的時候,容器僅存在一根軸線,由於項目不會換行,就不會產生多條軸線。

當你 flex-wrap 設置爲 wrap 的時候,容器可能會出現多條軸線,這時候你就須要去設置多條軸線之間的對齊方式了。

對齊方式同justify-content 兩端/中間/等距對齊等

3.Flex 項目屬性

1. order: 定義項目在容器中的排列順序,數值越小,排列越靠前,默認值爲 0

設置了 order,使之可以排到最前面。

2. flex-basis: 定義了在分配多餘空間以前,項目佔據的主軸空間,瀏覽器根據這個屬性,計算主軸是否有多餘空間

默認值:auto,即項目原本的大小, 這時候 item 的寬高取決於 width 或 height 的值。

當主軸爲水平方向的時候,當設置了 flex-basis,項目的寬度設置值會失效,flex-basis 須要跟 flex-grow 和 flex-shrink 配合使用才能發揮效果。

  • 當 flex-basis 值爲 0 % 時,是把該項目視爲零尺寸的,故即便聲明該尺寸爲 140px,也並無什麼用。
  • 當 flex-basis 值爲 auto 時,則跟根據尺寸的設定值(假如爲 100px),則這 100px 不會歸入剩餘空間。

3. flex-grow: 定義項目的放大比例

默認值爲 0,即若是存在剩餘空間,也不放大

當全部的項目都以 flex-basis 的值進行排列後,仍有剩餘空間,那麼這時候 flex-grow 就會發揮做用了。

若是全部項目的 flex-grow 屬性都爲 1,則它們將等分剩餘空間。(若是有的話)

若是一個項目的 flex-grow 屬性爲 2,其餘項目都爲 1,則前者佔據的剩餘空間將比其餘項多一倍。

固然若是當全部項目以 flex-basis 的值排列完後發現空間不夠了,且 flex-wrap:nowrap 時,此時 flex-grow 則不起做用了,這時候就須要接下來的這個屬性。

4. flex-shrink: 定義了項目的縮小比例

默認值: 1,即若是空間不足,該項目將縮小,負值對該屬性無效。

若是一個項目的 flex-shrink 屬性爲 0,其餘項目都爲 1,則空間不足時,前者不縮小。

5. flex: flex-grow, flex-shrink 和 flex-basis的簡寫

.item{
    flex:  <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
    0 1 auto(默認)
} 
複製代碼

分別以下:

1.當 flex 取值爲一個非負數字,則該數字爲 flex-grow 值,flex-shrink 取 1,flex-basis 取 0%

.item {flex: 1;} (等分剩餘空間)
//等價於
.item {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: 0%;
}
複製代碼

2.當 flex 取值爲 0 時,對應的三個值分別爲 0 1 0%

.item {flex: 0;}(存在剩餘空間,也不放大)
//等價於
.item {
    flex-grow: 0;
    flex-shrink: 1;
    flex-basis: 0%;
}
複製代碼

最後求各位大佬多多關注~~~ 我會一直努力更新的!! 作一個合格的搬運工(笑
這篇文章參考了不少其餘大佬的優秀文章~ 有些參考連接可能沒有指出來 若有遺漏 必定補上❤

(*^▽^*)下個篇章打算分享一下最近面試出現的高頻考點和答案 ~!

相關文章
相關標籤/搜索