移动端开发你会遇到的几个问题

621 阅读5分钟

最近接手维护一个奇特的h5小项目。在此总结一下开发遇到的一些有趣问题。

奇特之处

  1. 大部分元素都是图片元素,且使用绝对定位。(把我整蒙了 全是绝对定位。)
  2. 每个页面都有大量的动画效果。
  3. 需要机型适配,确保所有机型都能一屏展示每个页面。
  4. 加入了背景音乐。
  5. 滚动切换一屏页面。

技术栈

框架使用react、typescript

机型适配使用postcss-px2rem

问题

机型适配,一屏展示

要求每页ui都能一屏展示,偏偏给的设计稿还是iPhone11的。。。。

方案:使用rem单位 + postcss-px2rem + setSize函数

 // main.tsx
 const setSize = () => {
     const clientWidth = document.documentElement.clientWidth;
     const size = (clientWidth / 7.5);
     document.documentElement.style.fontSize = `${size}px`;
 };
 window.onresize = () => setSize();
 setSize();
 // postcss.config.js
 module.exports = () => {
     return {
         plugins: {
             'postcss-px2rem': {
                 remUnit: 100,
             },
         },
     };
 };

之前使用过flexible方案的同学应该比较熟悉上边的配置。但对于小于iPhone5高度的机型图片会溢出页面,且元素间距页发生了改变。于是我在setSize函数上增加了一个高度比例因子:

 const setSize = () => {
     const clientHeight = document.documentElement.clientHeight;
     const clientWidth = document.documentElement.clientWidth;
 ​
     let n = clientHeight >= 568 ? 1 : clientHeight / 667;
 ​
     const size = (clientWidth / 7.5) * n;
 ​
     document.documentElement.style.fontSize = `${size}px`;
 };
 ​

之所以选择以iPhone6 7 8 的高度作为除数是因为px转rem不一定能除尽,会存在小数点,这会导致实际的缩放比例偏小。

但是由于元素基本上都是绝对定位,对于使left或right来定位的元素会产生位移,所以最终全部的元素都先居中,再使用translateY:

 img {
     position: absolute;
     left:0;
     right:0;
     margin:0 auto;
     transform: translateY(60px);
 }

而元素间距变化的原因则是因为没有统一使用top或者bottom定位。所以最后对元素定位统一使用bottom。

不使用vw单位+postcss-px-to-viewport的原因是因为没有办法动态更改vw单位基准,对于小于iPhone5高度的机型都需要媒体查询然后设置重置所有元素的大小,这将是一场大的改动,所以上边的方案也需要更改定位元素的位置,但是整个项目也就改了10张图的样子。

图片缩放后模糊

这个问题也是我抛弃使用vw的原因。当时使用vw时想对于小于iPhone5的机型都进行整体页面的scale(),万万没想到,图片变得模糊了。 查阅多方资料得知:

我们平时使用的图片大多数都属于位图(png、jpg...),位图由一个个像素点构成的,每个像素都具有特定的位置和颜色值,当我们缩放图片后,一个像素点并不能被准确的分配上对应位图像素的颜色,所以产生了模糊。

解决方法如下:

 img {
     image-rendering:-moz-crisp-edges;
     image-rendering:-o-crisp-edges;
     image-rendering:-webkit-optimize-contrast;
     image-rendering: crisp-edges;
     -ms-interpolation-mode:nearest-neighbor;
 }

按照MDN的说法,image-rendering就是一个图像缩放算法。

不过我感觉和实际大小的图片还是有一定差距,果然这个方案被甲方爸爸ps了。。。

自闭合标签无法添加伪元素

有一个需求是给两个图片按钮添加擦亮效果:

 img { 
 width: 261px; 
 height: 48px; 
 bottom: 860px; 
 border-radius: 200px; 
 overflow: hidden; 
 &::before { 
 content: ''; 
     background-image: 
 linear-gradient(60deg, transparent, transparent, #fff, transparent,transparent); 
 position: absolute; 
 top: 0; 
 left: -50%; 
 width: 50%; 
 height: 100%; 
   } 
 } 

在这里插入图片描述

但是实际效果一点都没有,无论我怎么设置都不得行。于是查阅资料才知道:自闭合标签伪类设置无效。也就是说img 、input、iframe等元素都不支持。 而原因是因为:

  1. 自闭合元素的内容模型为空,没有独立的闭合标签,无法容纳别的标签作为自身的子元素。
  2. :before、:after伪类为元素内容的前后插入额外的内容,这就需要元素能够容纳子内容。

所以最终我还是妥协了,额外加了一个div父元素。

保留动画帧

由于有大量动画的存在,而且还存在多个动画先后执行的情况,所以倒是顺便把animation属性给整明白了:

animation 属性是 animation-nameanimation-duration, animation-timing-functionanimation-delayanimation-iteration-countanimation-directionanimation-fill-modeanimation-play-state 属性的一个简写属性形式。

animation-name属性指定应用的一系列动画,每个名称代表一个由@keyframes定义的动画序列。

animation-duration属性指定一个动画周期的时长。

animation-timing-function属性定义CSS动画在每一动画周期中执行的节奏。

animation-delay CSS属性定义动画于何时开始。

animation-iteration-count CSS 属性 定义动画在结束前运行的次数 可以是1次 无限循环.

animation-direction CSS 属性指示动画是否反向播放,它通常在简写属性animation中设定

animation-fill-mode 设置CSS动画在执行之前和之后如何将样式应用于其目标。

****animation-play-state ****CSS 属性定义一个动画是否运行或者暂停。可以通过查询它来确定动画是否正在运行。********

如果要保留最后的动画帧,则可以使用animation-fill-mode: forwards;

背景音乐在ios上无法自动播放

我推荐大家去看这篇文章:H5背景音乐解决方案

上边有关于为何无法自动播放的原因,以及解决方案。

无法获取Swiper的SwiperClass接口

为了实现了页面滚动切换,我们用了swiper,但是由于技术上有使用了typescript,而Swiper并没有暴露SwiperClass实例接口,而使用any又显得太不够严谨。

于是仔细看了Swiper的源码后发现,Swiper的onSwiper方法在swiper组件挂载后执行返回swiper实例,而它的Swiper的类型签名为:

 interface Swiper extends SwiperOptions {  
   onSwiper?: (swiper: SwiperClass) => void;  
  }  

所以果断决定从onSwiper上获取SwiperClass,代码如下:

 type SwiperInstance = Parameters<NonNullable<Swiper['onSwiper']>>[0];  

完美!