移动端兼容问题收录

2,105 阅读9分钟

100vh问题

**原因:**safari浏览器的100vh包含地址栏和底部栏的高度,而其他浏览器只是视口高度

image.png

解决方案:

方案一:

npm i vh-check
import vhCheck from 'vh-check'
// 设置一个css变量
vhCheck('brwoser-address-bar')
height: calc(100vh - var(--browser-address-bar, 0px))

calc(100vh - var(--browser-address-bar, 0px)) 表示100vh的实际高度

vh-check文档

方案二:

使用js动态改变盒子高度,即使用window.innerHeight来获取实际的屏幕高度,然后赋值给需要改变的元素 即xxxx.style.height = window.innerHeight +'px’

参考文章

移动端100vh的问题与解决方案 - 夏如眷 - 博客园 (cnblogs.com)

(28条消息) 踩坑记录,移动端开发使用height=100vh_sha虫剂的博客-CSDN博客_移动端高度100%

1px问题

原因

做移动端页面时一般都会设置meta viewport的content=“width=device-width”,但css最低只支持1px,不足1px 会显示为1px。而如果设计稿是750px,750px的1px,在375px的屏幕上,则需要显示为0.5px,而css不支持0.5px,会将其显示为1px(但ios新版本支持0.5px,安卓暂不支持),所以兼容问题就产生了

js获取dpr

const dpr = devicePixelRatio >= 3? 3: devicePixelRatio >= 2? 2: 1;
document.documentElement.setAttribute('data-dpr', dpr);

解决方案

方案一

和设计师沟通,让设计稿中避免出现1px的值 优先沟通,从问题的根源解决

方案二

通过各种方式去兼容

方式一:伪元素 + transform: scale()缩放

原理: 在目标元素的后面追加一个 ::after 伪元素,让这个元素布局为 absolute 之后、整个伸展开铺在目标元素上,然后把它的宽和高都设置为目标元素的两倍,border值设为 1px。接着借助 CSS 动画特效中的放缩能力,把整个伪元素缩小为原来的 50%。此时,伪元素的宽高刚好可以和原有的目标元素对齐,而 border 也缩小为了 1px 的二分之一,间接地实现了 0.5px 的效果。

单边

/* 下边框 */
.div::after {
    content: '';
    box-sizing: border-box;
    position: absolute;
    z-index: 1;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 2px;
    border-bottom: 1px solid #bfbfbf;
    transform: scaleY(1/2);
    transform-origin: left bottom;
}

/* 右边框 */ 
.div::before {
    content: '';
    box-sizing: border-box;
    position: absolute;
    z-index: 1;
    right: 0;
    bottom: 0;
    width: 2px;
    height: 100%;
    border-right: 1px solid #bfbfbf;
    transform: scaleX(1/2);
    transform-origin: right bottom;
}

所有边

 .div::after {
    content: '';
    box-sizing: border-box;
    position: absolute;
    z-index: 1;
    left: 0;
    top: 0;
    width: 300%;
    height: 300%;
    border: 1.5px solid #bfbfbf;
    transform: scale(1/1.5/2);
    transform-origin: left top;
    border-radius: 6px;
}

参考文章

移动端1px问题解决方案 - 掘金 (juejin.cn)

1px像素问题(一):真正原因_醉逍遥neo的博客-CSDN博客

1px像素问题(二):解决方法_醉逍遥neo的博客-CSDN博客

前端1px问题及解决方案 - 掘金 (juejin.cn)

吃透移动端 1px|从基本原理到开源解决方案 - 知乎 (zhihu.com)

解决移动端1px边框问题的几种方法 - 掘金 (juejin.cn)

移动端1px解决方案 - 掘金 (juejin.cn)

点击事件300ms延迟问题

原因

移动端要判断是否是双击,所以单击之后不能够立刻触发click,要等300ms,直到确认不是双击了才触发click。所以就导致了click有延迟。

解决方案

<meta name="viewport" content="width=device-width, initial-scale=1.0">

注意

采用此解决方案,将不会再有浏览器默认的双击缩放功能

参考文章

关于h5端点击事件300ms延迟问题 - 掘金 (juejin.cn)

关闭IOS键盘首字母大写

解决方案

<input type='text' autocapitalize="false">

参考文章

autocapitalize - HTML(超文本标记语言) | MDN (mozilla.org)

让Chrome支持小于12px的文字

解决方案:

通过 transform: scale(x) 实现

div{
  font-size: 100px;
}
div span{
  display: inline-block;
  /* 
  这个span会继承div的字体大小,这里再设置为缩放为0.1倍,那就是10px。
  注意:这里缩放的是盒子的大小,来间接达到显示小字体大小的效果,
  实际并不是直接控制了字体大小。因此实际还是要避免设计稿中出现小于12px的字体大小
  */
  transform: scale(0.1);
}

去除IOS中被触摸元素的半透明遮罩

解决方案

button,
a,
textarea,
input {
    tap-highlight-color: rgba(0, 0, 0, 0);
    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

禁止ios长按触发系统菜单,禁止IOS & Android 长按时下载图片

解决方案

html,body{
    touch-callout: none;
    -webkit-touch-callout: none;
}

禁止 IOS & Android 用户选中文字

解决方案

html,body{
    user-select: none;
    -webkit-user-select: none;
}

边框支持渐变

CSS实现渐变色边框(Gradient borders)的5种方法 - 掘金 (juejin.cn)

ios浏览器不支持yyyy-MM-dd HH:mm:ss或yyyy-MM-dd格式对Date进行初始化

解决方案

使用第三方工具(如:dayjs)进行日期对象和字符串的相互转换

不同设备使用不同的多倍图

问题描述

  • 适用普通屏的图片在 retina 屏中,图片展示就会显得模糊;
  • 适用 retina 屏的图片在普通屏中,图片展示就会缺少色差、没有锐利度,并且浪费带宽; 所以如果对性能、美观要求很高的场景,需要根据 dpr 区分使用对应的图片

原因

我们最常用的iphone6,7,8来说,他们屏幕1 css 像素就 = 2个物理像素,不仅如此,随着屏幕分辨率的提高,物理像素比也跟着提高。因此,如果我们需要在移动端上显示一张50 css像素的图片,则需要准备一张100物理像素或者更高的图片,再手动的设置为50px,才能在移动端上清楚的显示出来,否则就会出现下列效果。

image.png

scss方式

@mixin bg-image($url) {
  background-image: url($url + ".png");
  @media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2){
    background-image: url($url + "@2x.png")
  }
  @media (-webkit-min-device-pixel-ratio:3),(min-device-pixel-ratio:3){
    background-image: url($url + "@3x.png")
  }
}

使用示例

div{ 
    width:30px; 
    height:20px; 
    background-size:30px 20px; 
    background-repeat:no-repeat; 
    @include bg-image('special_1'); 
}

css方式

/*默认大小*/
.photo {background-image: url(image100.png);}
/* 如果设备像素大于等于2,则用2倍图 */
@media screen and (-webkit-min-device-pixel-ratio: 2),
screen and (min--moz-device-pixel-ratio: 2) {
  .photo {
    background-image: url(image200.png);
    background-size: 100px 100px;
  }
}
/* 如果设备像素大于等于3,则用3倍图 */
@media screen and (-webkit-min-device-pixel-ratio: 3),
screen and (min--moz-device-pixel-ratio: 3) {
  .photo {
    background-image: url(image300.png);
    background-size: 100px 100px;
  }
}
.photo {width:100px;height:100px;}

安全区适配

安全区域指的是一个可视窗口范围,处于安全区域的内容不受圆角(corners)、齐刘海(sensor housing)、小黑条(Home Indicator)影响,如下图蓝色区域:

image.png

也就是说,我们要做好适配,必须保证页面可视、可操作区域是在安全区域内。(上图的蓝色区域)

适配步骤

设置网页在可视窗口的布局方式

<meta name="viewport" content="width=device-width, viewport-fit=cover">

只有设置了 viewport-fit=cover,才能使用 env()

可以通过加内边距 padding 扩展高度

{
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

或者通过计算函数 calc 覆盖原来高度

{
  height: calc(60px(假设值) + constant(safe-area-inset-bottom));
  height: calc(60px(假设值) + env(safe-area-inset-bottom));
}

注意,这个方案需要吸底条必须是有背景色的,因为扩展的部分背景是跟随外容器的,否则出现镂空情况。

通过新增一个新的元素

通过新增一个新的元素(空的颜色块,主要用于小黑条高度的占位),然后吸底元素可以不改变高度只需要调整位置,像这样:

{
  margin-bottom: constant(safe-area-inset-bottom);
  margin-bottom: env(safe-area-inset-bottom);
}

空的颜色块:

{
  position: fixed;
  bottom: 0;
  width: 100%;
  height: constant(safe-area-inset-bottom);
  height: env(safe-area-inset-bottom);
  background-color: #fff;
}

部分奇特的 Android 手机

很多 Android 手机也会按照 iOS 的标准来实现安全区域,因此上面的属性在大部分 Android 手机上也能正常使用。

但是,我们在测试的过程中发现,有几个奇特的手机,会出现下图的状况:

image.png

通过 Chrome 查看样式,发现他会识别 safe-area-inset-top 等预定义变量,但又将其解析为 0。

这就导致即使我们设置了兜底的数据,也无法使用。例如我们在不支持安全区域属性时,使用兜底的 padding-top: 25PX 样式(大写的 PX 是为了不被插件转义成 vw 或 rem),但上述的 Android 设备中,兜底样式也不会生效:

body {
  /* prettier-ignore */
  padding-top: 25PX;
  padding-top: constant(safe-area-inset-top);
  padding-top: env(safe-area-inset-top);
}

解决方案

首先我们向页面中插入一个看不见的 div,将 div 的高度设置为安全距离的高度,然后再通过 js 获取其高度,若高度为 0,则说明没有生效。

let status = 0; // 0:还没数据,-1:不支持,1:支持

/**
 * 判断当前设置是否支持constant(safe-area-inset-top)或env(safe-area-inset-top);
 * 部分Android设备,可以认识safa-area-inset-top,但会将其识别为0
 * @returns {boolean} 当前设备是否支持安全距离
 */
const supportSafeArea = (): boolean => {
  if (status !== 0) {
    // 缓存数据,只向 body 插入一次 dom 即可
    return status === 1;
  }
  const div = document.createElement('div');
  const id = 'test-check-safe-area';
  const styles = [
    'position: fixed',
    'z-index: -1',
    'height: constant(safe-area-inset-top)',
    'height: env(safe-area-inset-top)',
  ];
  div.style.cssText = styles.join(';');
  div.id = id;
  document.body.appendChild(div);
  const areaDiv = document.getElementById(id);
  if (areaDiv) {
    status = areaDiv.offsetHeight > 0 ? 1 : -1; // 该 div 的高度是否为 0
    areaDiv.parentNode?.removeChild(areaDiv);
  }
  return status === 1;
};

那么在已经设置了安全区域属性的地方,都需要额外执行下 supportSafeArea()方法:

const SignTaskDetail = () => {
  const [safaArea, setSafeArea] = useState(true); // 当前页面是否支持 safe-area-inset-top

  useEffect(() => {
    setSafeArea(supportSafeArea());
  }, []);

  return (
    <div
      className={classNames('task-page', {
        'task-page-not-safearea': !safaArea, // 不支持时需要额外设置属性
      })}
    ></div>
  );
};

参考文章

2022 年移动端适配方案指南 — 全网最新最全 - 掘金 (juejin.cn)

(37条消息) iPhoneX安全区域与H5引发的问题(Safe Area)_余光、的博客-CSDN博客

javascript - iPhoneX安全区域(Safe Area)底部小黑条在微信小程序和H5的屏幕适配_个人文章 - SegmentFault 思否

如何解决移动端的安全区域为0的问题 - 知乎 (zhihu.com)

移动端分辨率适配

一次说清楚,以后不要再问移动端怎么适配了! - 知乎 (zhihu.com)

2022 年移动端适配方案指南 — 全网最新最全 - 掘金 (juejin.cn)