阅读 7188

移动端适配及PC端适配心得总结体会(二) (可能比较全

前篇☞ 移动端适配及PC端适配心得总结体会(一) (可能比较全

web适配产生的问题

1.浏览器mate指定内核

QQ浏览器 meta元素检测 识别内核 规则介绍:

识别为chrome内核
  • doctype 为标准
  • meta 标签元素

例子:

<!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浏览器文档

2. 1px边框的问题

img

出现的原因:

按照iPhone6的尺寸,一张750px宽的设计图,这个750px其实就是iPhone6的设备像素,在测量设计图时量到的1px其实是1设备像素,而当我们设置布局视口等于理想视口等于375px,并且由于iPhone6的DPR为2,写css时的1px对应的是2设备像素,所以看起来会粗一点。

解决办法

1.border-image

基于media查询判断不同的设备像素比给定不同的border-image

.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类似,准备一张符合条件的边框背景图,模拟在背景上。

.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不同设备宽度不同,缩放比无法完全确定
3flex还是无法解决宽度超出问题

上面方案均存在致命缺陷,不推荐使用它完成移动端布局计算。

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%;
}
复制代码

最后求各位大佬多多关注~~~ 我会一直努力更新的!! 做一个合格的搬运工(笑
这篇文章参考了很多其他大佬的优秀文章~ 有些参考链接可能没有指出来 如有遗漏 一定补上❤

(*^▽^*)下个篇章打算分享一下最近面试出现的高频考点和答案 ~!