移动端Web常见问题

684 阅读3分钟

问题有哪些

  • 1px边框
  • click事件300ms延迟
  • Touch事件点击穿透
  • 移动端图片

1px边框

设计稿

1.高清屏下,1px 边框“变粗”的原因

  • 并不是真的变粗了,而是设计想要的 1像素,不是程序员眼中的 1CSS像素,而是 1物理像素
  • 如果 dpr=2,设计实际想要的就是 1px/2=0.5px
  • 如果 dpr=3,设计实际想要的就是 1px/3=0.3333px

2.解决方案

  • 和设计商量,如果不在意这个问题,不用去管
  • 直接设置“细”边框
    • 存在兼容性问题,不同的浏览器会有不同的表现
    • 对于 iOS8 以后的 iOS 系统推荐使用这种方法
  • 伪类 + transform(推荐)
  • 其他方案可参考:www.cnblogs.com/zzsdream/ar…

具体实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>1px 边框</title>
    <style>
      body {
        background-color: #f5f5f5;
      }

      .list {
        padding: 0;
        margin: 0;
        font-size: 30px;
      }
      .item {
        list-style: none;
        line-height: 60px;
        border-bottom: 1px solid #ccc;
      }

      /* 直接设置“细”边框 */
      .item1 {
        border-width: 0.5px;
      }
        //高清屏幕下 1px 对应更多的物理像素,所以 1 像素边框看起来比较粗,使用媒查询:
        @media screen and (-webkit-min-device-pixel-ratio:2){
            #demo{
                border: 0.5px solid black;
            }
        }  

      /* 伪类 + transform */
      @media (-webkit-min-device-pixel-ratio: 2) {
        .border-1px {
          position: relative;
          border: none;
        }
         /* 往后添加一个伪类去设置边框 */
        .border-1px::after {
          box-sizing: border-box;
          content: '';
          position: absolute;
          top: 0;
          left: 0;
          /* 将来会用scale缩小一半 */
          width: 200%;
          height: 200%;
          /* 设置相应边框 */
          border-bottom: 1px solid #ccc;
          /* border: 1px solid #ccc; */
          /* 设置圆角 */
          /* border-radius: 20px; */
          transform-origin: 0 0;
          transform: scale(0.5);

          /* background-color: rgba(255, 0, 0, 0.5); */
        }
      }
      @media (-webkit-min-device-pixel-ratio: 3) {
        .border-1px::after {
          width: 300%;
          height: 300%;
          transform: scale(0.3333);
        }
      }
    </style>
  </head>
  <body>
    <ul class="list">
      <li class="item">“变粗”的 1px 边框</li>
      <!-- <li class="item">变粗的原因</li>
      <li class="item">解决方案</li> -->
      <li class="item item1">直接设置“细”边框</li>
      <li class="item border-1px">伪类 + transform</li>
    </ul>
  </body>
</html>

click事件300ms延迟

移动端 click 屏幕产生 200-300 ms 的延迟响应,往往会造成按钮点击延迟甚至是点击失效。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>click 事件 300ms 延迟</title>
    <style>
      .btn {
        width: 100%;
        height: 300px;
        font-size: 100px;

        /* touch-action: manipulation; */
      }
    </style>
  </head>
  <body>
    <button id="btn" class="btn">提交</button>

    <script src="https://cdn.bootcdn.net/ajax/libs/fastclick/1.0.6/fastclick.min.js"></script>
    <script>
      const $btn = document.getElementById('btn');
      $btn.addEventListener(
        'touchstart',
        () => {
          console.time('click');
        },
        false
      );
      $btn.addEventListener(
        'click',
        () => {
          console.timeEnd('click');
          console.log('提交表单');
        },
        false
      );
    </script>
  </body>
</html>

移动端 click 事件 300ms 延迟的原因

  • 原因:double-tap to zoom 双击缩放 (等待检测你的操作是不是双击缩放)

解决方案

解决方法一

  • 不使用 click 事件,把 click 事件中要处理的放到 touchstart 或 touchend 中去处理
$btn.addEventListener(
    'touchend',
    () => {
        console.timeEnd('click');
        console.log('提交表单');
    },
    false
);

解决方案二

解决方案三

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, minimum-scale=1,maximum-scale=1, user-scalable=no"
    />
    <title>click 事件 300ms 延迟</title>
    <style>
      .btn {
        width: 100%;
        height: 300px;
        font-size: 100px;

        /* touch-action: manipulation; */
      }
    </style>
  </head>
  <body>
    <button id="btn" class="btn">提交</button>
	  <!-- 引入Fastclick 库 -->
    <script src="https://cdn.bootcdn.net/ajax/libs/fastclick/1.0.6/fastclick.min.js"></script>
    <script>
    	// 使用 Fastclick 库
      if ('addEventListener' in document) {
        document.addEventListener(
          'DOMContentLoaded',
          function () {
            FastClick.attach(document.body);
          },
          false
        );
      }

      const $btn = document.getElementById('btn');
      $btn.addEventListener(
        'touchstart',
        () => {
          console.time('click');
        },
        false
      );
      // $btn.addEventListener(
      //   'touchend',
      //   () => {
      //     console.timeEnd('click');
      //     console.log('提交表单');
      //   },
      //   false
      // );
      $btn.addEventListener(
        'click',
        () => {
          console.timeEnd('click');
          console.log('提交表单');
        },
        false
      );
    </script>
  </body>
</html>

Touch事件点击穿透

在发生触摸动作约300ms之后,移动端会模拟产生click动作,如果touch事件隐藏了原来元素 则click总作用到它底下的具有点击特性的元素,触发新元素的click事件和跳转,此现象被称为点击穿透

Touch 事件点击穿透的原因

事件触发的先后顺序是:touchstart -> touchend -> click。正是由于这种 click 事件的滞后性设计为事件穿透(点击穿透)埋下了伏笔。

  • touch事件结束后会默认触发该元素的click事件
  • 移动端 Touch 事件会立即触发,而 click 事件会延迟一段时间触发

常见的事件穿透场景:

  • 上层元素监听了触摸事件,触摸之后该层元素消失
  • 下层元素具有点击特性(监听了click事件或默认的特性(a标签、input、button标签))
  • 比如标元素触发触摸事件时隐藏或移除自身,对应位置元素触发 click 事件或 a 链接跳转。
  • 目标元素使用触摸事件跳转至新页面,新页面中对应位置元素触发 click 事件或 a 链接跳转。

注意:a 标签的链接跳转事件属于 click 事件

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta
              name="viewport"
              content="width=device-width, initial-scale=1.0, minimum-scale=1,maximum-scale=1, user-scalable=no"
              />
        <title>Touch 事件点击穿透</title>
      <style>
          .btn {
              width: 100%;
              height: 300px;
              font-size: 100px;
          }
          .mask {
              position: fixed;
              left: 0;
              top: 0;
              width: 100%;
              height: 100%;
              background: rgba(0, 0, 0, 0.5);
              opacity: 1;
              transition: opacity 0.5s;
          }
        </style>
    </head>
    <body>
        <button id="btn" class="btn">提交</button>
        <div id="mask" class="mask"></div>

        <script>
            const $btn = document.getElementById('btn');
            const $mask = document.getElementById('mask');
           // touchend和 touchstart都会造成点击穿透 点击遮罩层消失会触发下面的click事件
            
           // 由于 mask 元素触发 touchstart 触摸事件并立即隐藏掉自身,之后应该按先后顺               序触发 mask 元素的 touchend 和 click 事件。
           // 然而,当要触发 click 事件的时候由于 mask 元素已经隐藏掉了,于是触发了                 div 的 click 事件。
            $mask.addEventListener(
                'touchend',
                () => {
                    $mask.style.display = 'none';
                },
                false
            );

            $mask.addEventListener(
                'touchstart',
                () => {
                    console.time('click');
                },
                false
            );

            $btn.addEventListener(
                'click',
                () => {
                    console.timeEnd('click');
                    console.log('提交表单');
                },
                false
            );
        </script>
    </body>
</html>

解决方案

解决方法一

解决事件穿透的方法有很多,大致可以分为两类:第一种是禁止混用 click 和 touch 两种事件;另一种是延迟元素的隐藏或移除。

  • 让原本隐藏的元素延迟隐藏
$mask.addEventListener(
    'touchend',
    () => {
        // $mask.style.display = 'none';

        // 延时消失
         setTimeout(() => {
           $mask.style.display = 'none';
         }, 200);

        // 2.2.消失过程中添加动画效果  css添加transition和初始opacity = 1
        $mask.style.opacity = 0;
    },
    false
);

// 过渡结束将元素隐藏
$mask.addEventListener(
    'transitionend',
    () => {
        $mask.style.display = 'none';
    },
    false
);

解决方法二

阻止默认行为

//阻止该默认click行为的触发 
$mask.addEventListener('touchstart', function(e){
    e.preventDefault(); 
    console.log('hello')
    $mask.style.display = 'none';
})

解决方法三

使背后元素不具备click特性,用touchXxxx代替click

banner_img.addEventListener('touchstart',()=>{
    location.href = 'http://www.baidu.com'
})

解决方案四

让背后的元素暂时失去click事件,300毫秒左右再复原

#anode{
  pointer-events: none;
}
btn.addEventListener('touchstart',(event)=>{
    shade.style.display = 'none';
    setTimeout(()=>{
        anode.style.pointerEvents = 'auto'
    },500)
})

移动端图片

  • img图片
  • 背景图片

bg

bg_lg

img 图片

  • 一般使用百分比,并且只设置宽度或高度中的一个,不同时设置,让宽高能够等比例缩放,图片不失真

image-20210811235348857

  • 当图片实际宽度小于父容器宽度时,图片不会随着父容器宽度的变大而进一步拉伸

image-20210811235508976

  • 这个可以解决图片宽度拉伸问题 但是一旦超过图片原本宽度就会留白
/* 图片留白使其居中显示 */
.img-container {
    display: flex;
    justify-content: center;
}

背景图片

<style>
    .bg-container {
        width: 100%;
        /* 如果高度固定 */
        height: 200px;
        background: url(./bg.png) no-repeat;
        /* 缩放背景图片以完全覆盖背景区,可能背景图片部分看不见 */
        background-size: cover;

    }
</style>

<div class="bg-container"></div>
<style>
    .bg-container {
        width: 100%;
        /* 如果高度不固定 */
        /* 563px 224px   224 / 563 */
        /* padding相对于宽度的百分比 */
        padding-top: 39.7869%;

        background: url(./bg.png) no-repeat;
        /* 缩放背景图片以完全覆盖背景区,可能背景图片部分看不见 */
        background-size: cover;

    }
</style>

<div class="bg-container"></div>

还可以使用媒体查询

/* 小屏幕使用小图片 */
background: url(./bg.png) no-repeat;

/* 结合媒询一起使用 */
/* 大屏幕使用大图片 */
@media (min-width: 560px) {
    .bg-container {
        background-image: url(./bg_lg.png);
    }
}