最近接手维护一个奇特的h5小项目。在此总结一下开发遇到的一些有趣问题。
奇特之处
- 大部分元素都是图片元素,且使用绝对定位。(把我整蒙了 全是绝对定位。)
- 每个页面都有大量的动画效果。
- 需要机型适配,确保所有机型都能一屏展示每个页面。
- 加入了背景音乐。
- 滚动切换一屏页面。
技术栈
框架使用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等元素都不支持。 而原因是因为:
- 自闭合元素的内容模型为空,没有独立的闭合标签,无法容纳别的标签作为自身的子元素。
- :before、:after伪类为元素内容的前后插入额外的内容,这就需要元素能够容纳子内容。
所以最终我还是妥协了,额外加了一个div父元素。
保留动画帧
由于有大量动画的存在,而且还存在多个动画先后执行的情况,所以倒是顺便把animation属性给整明白了:
animation
属性是 animation-name
,animation-duration
, animation-timing-function
,animation-delay
,animation-iteration-count
,animation-direction
,animation-fill-mode
和 animation-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];
完美!