一站式解决:H5开发中的各类坑与技巧【2】

1944次阅读 155人点赞 作者: WuBin 发布时间: 2023-07-27 10:12:48
扫码到手机查看

常见问题

1px 问题

现象

在 H5 页面中,可能需要设置边框宽度为 1px,但在 Retina 屏幕上,1px 可能会看起来比实际要粗。

原因

这是因为移动设备的物理像素密度与 CSS 像素的比例(设备像素比)导致的。

解决方案

利用伪元素和 scale 来实现 0.5px 的效果。

.border-1px {
    position: relative;
}

.border-1px:after {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    width: 200%;
    height: 200%;
    border: 1px solid #000;
    transform: scale(0.5);
    transform-origin: left top;
    box-sizing: border-box;
}

sticky 的兼容性问题

在某些 Android 设备的原生浏览器中,使用 position: sticky 实现的元素不能正常吸顶。

这是因为这些浏览器不支持 position: sticky。

解决方案

使用 react-sticky 组件:通过计算 <Sticky> 组件相对于<StickyContai ner>组件的位置进行工作。

npm install react-sticky
<StickyContainer>
  <Sticky>{({ style }) => <h1 style={style}>Sticky element</h1>}</Sticky>
</StickyContainer>

使用 JS:通过自定义滚动事件的监听,根据 top 的改变来实现吸顶层 fixed 和 absolute 的转换。

<div id="stickyElement">吸顶bar</div>
<div id="content">这是主要内容</div>
<script>
    window.addEventListener('scroll', function() {
    var stickyElement = document.getElementById('stickyElement');
    var stickyElementRect = stickyElement.getBoundingClientRect();
    if (stickyElementRect.top <= 0) {
        // 当元素到达顶部,将其定位方式改为固定
        stickyElement.style.position = 'fixed';
        stickyElement.style.top = '0';
      } else {
        // 当元素离开顶部,将其定位方式改回绝对
        stickyElement.style.position = 'absolute';
        stickyElement.style.top = 'initial';
      }
    });

</script>
``

`

3. 在 Vue 项目中,可以直接使用 vue-sticky 组件。

```js
npm install vue-sticky --save
directives: {
  'sticky': VueSticky,
}
<ELEMENT v-sticky="{ zIndex: NUMBER, stickyTop: NUMBER, disabled: [true|false]}">
  <div> <!-- sticky wrapper, IMPORTANT -->
    CONTENT
  </div>
</ELEMENT>

软键盘将页面顶起来、收起未回落问题

在 Android 设备上,点击 input 框弹出键盘时,可能会将页面顶起来,导致页面样式错乱。失去焦点时,键盘收起,键盘区域空白,未回落。

原因

键盘不能回落问题出现在 iOS 12+ 和 wechat 6.7.4+ 中,而在微信 H5 开发中是比较常见的 Bug。

兼容原理,1.判断版本类型 2.更改滚动的可视区域

解决方案

通过监听页面高度变化,强制恢复成弹出前的高度。

const originalHeight = document.documentElement.clientHeight || document.body.clientHeight;

window.onresize = function() {
    const resizeHeight = document.documentElement.clientHeight || document.body.clientHeight;

    if (resizeHeight < originalHeight) {
        document.documentElement.style.height = originalHeight + 'px';
        document.body.style.height = originalHeight + 'px';
    }
}

使用 line-height 实现文字垂直居中,发现文字偏上

实际这个Bug一直存在,没有好的解决方案,详情见Android浏览器下line-height垂直居中为什么会偏离?

采用 flex 布局,align-items: center 来替代,兼容性更高。

.elem {
  display: flex;
  justify-content: center;
  align-items: center;
}

border-radius 画出的圆在移动端有毛边

给元素添加 overflow: hidden 属性。

.elem {
  overflow: hidden;
}

安卓上去掉语音输入按钮

input::-webkit-input-speech-button {
  display: none;
}

Vue 单页应用在 iOS 上微信分享失效,图片,标题和描述均未正常显示,安卓上分享正常

我们一般在 APP.vue 的 mounted 生命周期中初始化微信 SDK,此时页面的地址 hash 是#/,而首页的 hash 是#/home,导致初始化微信 SDK 时传入的分享 url 和用户实际触发分享操作时页面的 url 不一致,致使在 iOS 上分享失败。

解决:初始化微信分享 SDK 时传入的地址,和实际触发分享时页面的地址保持一致。

iOS safari 被点击元素会出现半透明灰色遮罩

给 html 或者 body 加入以下 css 代码。

body {
  -webkit-tap-highlight-color: rgba(0,0,0,0);
  -webkit-user-modify: read-write-plaintext-only;
}

iOS 禁止保存或拷贝图像

长按图片保存场景下,禁止 IOS 默认识别图像行为。

img {
  -webkit-touch-callout: none;
}

iOS 端微信 H5 页面上下滑动时卡顿

给滚动元素加上-webkit-overflow-scrolling属性。

body {
  -webkit-overflow-scrolling:touch;
}

iOS 默认输入框内阴影重置

阻止 iOS 默认的美化页面的策略-webkit-appearance:none;

input {
  border: 0;
  -webkit-appearance:none;
}

对非可点击元素(div,span 等)监听 click 事件,部分 ios 版本不会触发事件

  1. 添加 css 属性 cursor: pointer;
  2. 换成 button 元素。
cursor: pointer;

<button></button>

手机底部刘海存在背景,和页面背景色不一致

通过指定 body 的背景色来解决。

body {
  background-color: #fff;
  // or 暗色模式
  // background-color: #000;
}

对于带有 hash 的 H5 链接,部分手机厂商的 webview 打开 H5 页面会加载两次

这是部分 webview 对于特殊 url 有独特的解析和加载逻辑,去掉 hash 即可

https://www.example.com/a/b#/

body存在默认背景色

body 标签在大部分浏览器中的默认背景色是白色,但在极少数浏览器中的背景颜色是淡绿色或者其他颜色。通过指定 body 背景色为#fff,来兼容更多设备。

body {
  background-color: #fff;
}

旋转屏幕的时候,字体大小调整的问题

css
body {
  -webkit-text-size-adjust: 100%;
}

IOS解析日期问题

在某些情况下,苹果系统上解析YYYY-MM-DD HH:mm:ss格式的日期会报错Invalid Date,而安卓系统则没有这个问题。解决这个问题的一种方法是将日期字符串中的-替换为/

const dateString = "2023-07-16 00:00:00";
const fixedDateString = dateString.replace(/-/g, "/");
const date = new Date(fixedDateString);

滚动穿透

滚动穿透(scrolling through)是指在一个固定区域内滚动时,滚动事件透过该区域继续传递到其下方的元素,导致同时滚动两个区域的现象。滚动穿透可能会对用户体验产生负面影响,因为用户可能意外地滚动到不相关的内容。

这个问题一直很无解,只能hack去兼容

overflow: hidden

先锁住body

.modal-open {
  &,
  body {
    overflow: hidden;
    height: 100%;
  }
}

还原body滚动区域

// 获取滚动区域的容器元素
const container = document.querySelector('.container');

// 获取滚动区域的内容元素
const content = document.querySelector('.content');

// 记录滚动位置
let scrollTop = 0;

// 禁止滚动穿透
function disableScroll() {
  // 记录当前滚动位置
  scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  
  // 设置滚动区域容器的样式,将其高度设置为固定值,并设置滚动条样式
  container.style.height = '100%';
  container.style.overflow = 'hidden';
  
  // 阻止窗口滚动
  document.body.classList.add('no-scroll');
  document.body.style.top = `-${scrollTop}px`;
}

// 启用滚动穿透
function enableScroll() {
  // 恢复滚动区域容器的样式
  container.style.height = '';
  container.style.overflow = '';

  // 允许窗口滚动
  document.body.classList.remove('no-scroll');
  document.body.style.top = '';

  // 恢复滚动位置
  window.scrollTo(0, scrollTop);
}

// 示例使用,当某个事件触发时禁止滚动穿透
function disableScrollEvent() {
  disableScroll();
}

// 示例使用,当某个事件触发时启用滚动穿透
function enableScrollEvent() {
  enableScroll();
}

ant-mobile组件库解决方式

  • 针对触摸滑动事件touchmove,通过监听滑动方向和滚动元素的状态,决定是否阻止默认的滑动行为,从而防止滚动穿透。
  • 在需要锁定滚动的情况下,给document添加touchstarttouchmove事件的监听器,通过捕获触摸滑动事件,并根据情况阻止默认行为,从而避免滚动穿透。
  • 在解锁滚动时,从document移除对触摸事件的监听器,恢复默认的滑动行为。
  • 
    // 移植自vant:https://github.com/youzan/vant/blob/HEAD/src/composables/use-lock-scroll.ts
    export function useLockScroll(
      rootRef: RefObject<HTMLElement>,
      shouldLock: boolean | 'strict'
    ) {
      const touch = useTouch()
    
      const onTouchMove = (event: TouchEvent) => {
        touch.move(event)
    
        const direction = touch.deltaY.current > 0 ? '10' : '01'
        const el = getScrollParent(
          event.target as Element,
          rootRef.current
        ) as HTMLElement
        if (!el) return
    
        // This has perf cost but we have to compatible with iOS 12
        if (shouldLock === 'strict') {
          const scrollableParent = getScrollableElement(event.target as HTMLElement)
          if (
            scrollableParent === document.body ||
            scrollableParent === document.documentElement
          ) {
            event.preventDefault()
            return
          }
        }
    
        const { scrollHeight, offsetHeight, scrollTop } = el
        let status = '11'
    
        if (scrollTop === 0) {
          status = offsetHeight >= scrollHeight ? '00' : '01'
        } else if (scrollTop + offsetHeight >= scrollHeight) {
          status = '10'
        }
    
        if (
          status !== '11' &&
          touch.isVertical() &&
          !(parseInt(status, 2) & parseInt(direction, 2))
        ) {
          if (event.cancelable) {
            event.preventDefault()
          }
        }
      }
    
      const lock = () => {
        document.addEventListener('touchstart', touch.start)
        document.addEventListener(
          'touchmove',
          onTouchMove,
          supportsPassive ? { passive: false } : false
        )
    
        if (!totalLockCount) {
          document.body.classList.add(BODY_LOCK_CLASS)
        }
    
        totalLockCount++
      }
    
      const unlock = () => {
        if (totalLockCount) {
          document.removeEventListener('touchstart', touch.start)
          document.removeEventListener('touchmove', onTouchMove)
    
          totalLockCount--
    
          if (!totalLockCount) {
            document.body.classList.remove(BODY_LOCK_CLASS)
          }
        }
      }
    
      useEffect(() => {
        if (shouldLock) {
          lock()
          return () => {
            unlock()
          }
        }
      }, [shouldLock])
    }
    

    相关资料

    点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
    关键词:h5
    推荐阅读
    • uniapp实现被浏览器唤起的功能

      当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

      8769次阅读 563人点赞 发布时间: 2022-12-14 16:34:53 立即查看
    • Vue

      盘点Vue2和Vue3的10种组件通信方式

      Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

      3674次阅读 274人点赞 发布时间: 2022-08-19 09:40:16 立即查看
    • JS

      几个高级前端常用的API

      推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

      13775次阅读 888人点赞 发布时间: 2021-11-11 09:39:54 立即查看
    • PHP

      【正则】一些常用的正则表达式总结

      在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

      12532次阅读 422人点赞 发布时间: 2021-10-09 15:58:58 立即查看
    • 【中文】免费可商用字体下载与考证

      65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

      11038次阅读 881人点赞 发布时间: 2021-07-05 15:28:45 立即查看
    • Vue

      Vue3开发一个v-loading的自定义指令

      在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

      15139次阅读 1207人点赞 发布时间: 2021-07-02 15:58:35 立即查看
    • JS

      关于手机上滚动穿透问题的解决

      当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

      14612次阅读 1191人点赞 发布时间: 2021-05-31 09:25:50 立即查看
    • Vue

      Vue+html2canvas截图空白的问题

      在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

      28356次阅读 2230人点赞 发布时间: 2021-03-02 09:04:51 立即查看
    • Vue

      vue-router4过度动画无效解决方案

      在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

      24476次阅读 1885人点赞 发布时间: 2021-02-23 13:37:20 立即查看
    交流 收藏 目录