佳宸学习和分享笔记的地方

0%

h5开发经验总结

h5开发总结

做了两个月的创意h5,其中遇到了一些坑与兼容适配方面的问题,做个记录总结,基于vue开发

1. 布局

1.1 元素的长宽

通常用rem来设置宽高,rem: 相对于根元素html的字体大小的单位

把标准@x2设计稿750宽的根元素的字体大小设为50px,就可以直接按设计稿的长宽/100来直接定义元素的宽高了

1
html { font-size: calc(100vw / 15);}

1.2 底部菜单栏的高度兼容

这个问题包括了浏览器端底部菜单,还有ios端的微信第一次进入,会有底部状态栏

qq浏览器把底部菜单栏,当作了body的一部分,相当于是一个固定在底部的菜单栏,但是层级是最高的,所以当使用绝对定位,bottom:0放元素在底部的时候,元素是会被覆盖掉的。或者高度设为100%或者100vh底下的元素也会被覆盖掉

纠正最大长度

通过global变量监听获取当前视口的高度window.innerHeight。并随时更新。然后在在CSS中自定义变量--vh。动态改变它的高度,第一次知道setProperty 可以让js设置css的变量,js与css通信

1
2
3
4
5
6
7
8
9
10
11
// 监听它的高度变化,写在项目初始化
window.addEventListener('resize', () => {
vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
});
// css
.topic1 {
width: 100vw;
height: calc(var(--vh, 1vh) * 100);
...
}

img

jobstopic1.png

1.3 需要适配到的宽高比

短屏适配需要做到9/13的宽高比例才能完全在浏览器有底部栏出现的情况下显示正常,最好要向设计师提前说明不要将元素撑的太满,做一部分的长度适应

不然设计师以iphonex为设计原型,又不留白,页面实现就会超出可视区域

1
2
3
4
// 屏幕宽高比9比13
@media (min-aspect-ratio: 9/13) {
...
}

1.4 滚动条问题

一般设置绝对定位元素会超出屏幕,这时会出现滚动条

1
2
3
4
5
6
// 在每个页面夫元素设置可视宽高,然后超出部分隐藏
.wrap {
width: 100vw;
height: 100vh;
overflow: hidden;
}

如果页面只在一屏展示,禁止可上下滑动,页面体验效果更好

1
2
3
4
document.body.addEventListener('touchmove', function (e) {
e.preventDefault();
}, { passive: false });
}

手指按住屏幕下拉,屏幕顶部会多出一块白色区域。用了以上的禁止touchmove事件就不会出现下滑效果,体验更像原生程序

image-20200725151901785.png

2. 动画

2.1 序列帧动画的实现

为什么不用GIF图

  1. gif 支持颜色少(最大256色)、Alpha 透明度支持差,图像锯齿毛边比较严重;
  2. 不能直接控制开始、停止、动画时间,灵活性差;
  3. gif 会引起页面周期性的绘画,性能较差。

实现

原理:把雪碧图的第一帧设为初始背景,短边设为显示宽高,通过CSS3 animation动画位移雪碧图的 background-position ,模拟动画效果

1
2
3
4
5
6
7
8
9
10
11
.sprite {
...
background-image: url(bubbleImg.png);
animation: clickBubble 333ms steps(7) both infinite;
}
....
@keyframes clickBubble{
to {
background-position: 0 -20.72rem;
}
}

animation动画:

  • animation-name:动画keyframes 名称,可以用百分比表示时间节点,也可以用from-to,表示0%-100%

  • animation-fill-mode: both;应用目标时立即应用第一个关键帧中定义的值,并且保留由执行期间遇到的最后一个关键帧计算值

  • animation-iteration-count动画运行的次数 infinite 无限循环

  • animation-timing-function: steps();

    steps 函数指定了一个阶跃函数,它接受两个参数。

    第一个参数接受一个整数值,表示两个关键帧之间分几步完成。

    第二个参数有两个值< start > or < end >。默认值为< end > 。

    step-start 等同于 step(1, start)。step-end 等同于 step(1, end)。

    steps(7)就是关键帧分7步跳跃。把单个png图的宽或高*7设为最终背景移动位置

2.2 解决动画帧抖动问题

帧动画在部分机型出现了抖动或着位移的问题,发现是rem转为px存在小数点,除不尽误差的问题,误差就会导致有些机型有抖动或者位移现象。

既然我们通常是根据屏幕的尺寸,计算并设置根元素的font-size,从而影响rem的基准值。那不同的尺寸之间肯定是没有一个公约数的,也就是说我们没办法设定一个基准值来保证不同的屏幕尺寸下,rem值在换算成px值的时候是整数。

当320px的屏幕基准像素为12px时,iphone8(375px)下html的font-size 就是14.0625px,iphone8p下font-size就是15.525px。

浏览器在计算像素精度时,并不是直接全部取整或者取余的,这点其实你稍微想想一下就能得到结论。那我们上文这样在109rem的宽度下取16帧的时候,自然也就会出现多1px或者少1px的误差。这也就导致了我们逐帧动画出现了抖动!

解决:

  1. 可以使用transform: scale()将其放大,然后rem除余成px的时候误差会减小
  2. 使用svg设置外层尺寸(rem),再使用实际的大小设置内容的尺寸(px),我们保留rem自适应屏幕宽度特性的同时,也确保内部内容的大小计算不会出现精度问题(因为设定的都是整数的px)
1
2
3
4
5
<svg viewBox="0, 0, 536, 652" class="tiger-tail">
<foreignObject class="inner-html" width="536" height="652">
<div class="rainbow-wave"></div>
</foreignObject>
</svg>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 外层svg包rem
.tiger-tail {
position: absolute;
width: 5.36rem;
height: 6.52rem;
left: 1.2rem;
// 里层用用雪碧图原图加animation
.inner-html {
width: 536px;
height: 652px;
.rainbow-wave {
width: 26800px;
height: 652px;
background-image: url("../img/topic5/sprites.png");
...
animation-timing-function: steps(50);
}
}
}
@keyframes keyframes-rainbow-wave {
to {
background-position: -26800px 0;
}
}

Tips:

  • 建议不要使用太长或者太宽的雪碧图,使用矩形的雪碧图,cdn存图的时候会把过长的图片给裁剪掉

    image-20200719203741766.png

  • ios下帧率最好不要太高,不然会导致不断刷新的问题,每秒50帧的时候会无限刷新,设置25帧就不会出现这个问题了

  • 推荐一个帧动画生成工具https://gka.js.org/#/

2.3 CSS 星轨运动实现

image-20200619151431465.png

因为星星轨迹是椭圆且有角度的,而且是椭圆轨迹

之前看到篇文章 分层动画在CSS中沿弯曲路径移动,所以感觉可以通过X轴加上Y轴的分层动画融合,加上速度不同步,实现对角线曲线运动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.dot {
animation: xAxis 2.5s infinite ease-in;
}
.dot::after {
animation: yAxis 2.5s infinite ease-out;
}
@keyframes xAxis {
50% {
animation-timing-function: ease-in;
transform: translateX(100px);
}
}
@keyframes yAxis {
50% {
animation-timing-function: ease-out;
transform: translateY(-100px);
}
}

要实现它的曲率主要靠,速度不同步,比如y轴速度大于x轴,运动曲线就会是一个上拱的抛物线
调节特定的曲率 ease-in,out,linear就远远不够了,需要用到赛贝尔曲线来调节
animation-timing-function: cubic-bezier(0.3, 0.27, 0.07, 1.64);
可是即使有可视化的曲线调节,但是结合xy轴相乘就非常难控制了,与线条轨迹不能重合。所以只能另谋出路了

正圆的旋转实现

使用传统的圆周转,实现绕圈旋转的animation,一个周期旋转一周。

1
2
3
4
5
6
7
@keyframes spin {
to {transform: rotate(1turn); /* 旋转一周 */}
}
.avatar {
animation: spin 3s infinite linear;
transform-origin: 50% 150px; /* 150px = 旋转半径 */
}

可是物体在绕圆形路径旋转时,发现自身也颠倒了

css-secrets-8-35

解决方法:时利用两个元素在向不同方向旋转时旋转角度互相抵消的原理,实现图像沿环形路径旋转同时保持自身角度的不变。外层旋转 ,里面物体反旋转抵消被旋转效果,就是有环形轨迹运动物体不颠倒的效果了,然而这样子就需要一个相反的动画,使用animation-direction: reverse;可以得到一个反向原动画,不需要创建第二个动画

再优化:每个transform-origin可以模拟两次translate()

1
2
3
4
5
6
7
// 以200px,300px 为圆心旋转30度
transform: rotate(30deg);
transform-origin: 200px 300px;
// 通过顺序位移达到同样的效果
transform: translate(200px, 300px)
rotate(30deg)
translate(-200px, -300px);

通过translate的抵消,出来了一串简介的代码:

1
2
3
4
5
@keyframes spin{
from{ transform: rotate(0turn) translateY(-150px) translateY(50%) rotate(1turn); }
to { transform: rotate(1turn) translateY(-150px) translateY(50%) rotate(0turn); }
}
.avatar { animation: spin 3s infinite linear; }

学习的文章:https://www.w3cplus.com/css3/css-secrets/animation-along-a-circular-path.html

正圆实现了,如何实现椭圆呢,之前找的分层动画也没有白费,在元素里加一个y轴的上下移动,融合入正圆,就可以画出椭圆效果来

再在大div里加入一个角度,在animation里再旋转棱形块,旋转了运动轨迹,行星又是水平的效果,达到设计图的要求

2.4 如何实现3d开门效果

实现方法:两个div叠加,外层div进行旋转

1
2
3
4
5
6
7
8
@keyframes flip-to-left {
from { transform: rotateY(0); }
to { transform: rotateY(-180deg); }
}
.paper[data-right] {
transform-origin: left center;
animation: flip-to-left 2s ease-in-out;
}

但是这样只是一个平面的效果,只能看到一个窄的和宽的矩形,看不到立体的效果。然后找到一个属性添加景深transform-perspective:1000px,有了立体的感觉

image-20200617200128728.png

又发现一个问题:虽然有深度,当还是平视效果,与设计图不符合

需要有一个摄像机视角在这个门的上方,从上往下看的视角,打开的门类似于平行四边形才能与设计意图相符合

image-20200619160759187.png

找到一个冷门属性,transform-style: preserve-3d元素的子元素们是放置在 3D 空间中

transform: rotateX(15deg)就有了俯视的视角 ,还可以实现这种立体的效果

雨伞

3. 业务通用组件

3.1 背景音乐自动播放多端兼容

音频组件适应各端,ios ,安卓,微信,qq
iOS 端 safari 浏览器或者部分安卓手机的浏览器不支持 autoplay 属性。
解决方法:还是引导用户手动触发播放操作
比如绑定 touchstart 事件进行 audio.play() 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
touchInteract () {
let getTouch = () => {
this.$refs.bgm.play();
document.removeEventListener('touchstart', getTouch);
}
// 如果不能播放用户触屏 开始播放音乐
if (this.$refs.bgm && this.$refs.bgm.currentTime === 0 && this.$refs.bgm.paused) {
document.addEventListener('touchstart', getTouch);
}
}
...
// 微信特有的预播放
wxPreloadPlay () {
if (window.WeixinJSBridge) {
WeixinJSBridge.invoke('getNetworkType', {}, (e) => {
this.$refs.bgm.play();
});
} else {
let wxPlay = () => {
WeixinJSBridge.invoke('getNetworkType', {}, (e) => {
this.$refs.bgm.play();
});
document.removeEventListener('WeixinJSBridgeReady', wxPlay);
};
document.addEventListener('WeixinJSBridgeReady', wxPlay);
}
}
...
// 切换webview音效,比如缩小窗口,停止播放
document.addEventListener('visibilitychange', () => {
// 切换or最小化窗口画面,音效停止
if (this.$refs.bgm) {
if (document.hidden) {
this.$refs.bgm.pause();
} else {
this.$refs.bgm.play();
}
}
});

3.2 敏感数字过滤

敏感数据存放在Set集合里面,如果命中,就加一显示,set时间复杂度O(1),比数组查找每次遍历O(n)性能更优

通过filter管道过滤likeRankData.like | filterSensitive,实现敏感数字自动加一显示

1
2
3
4
5
6
7
const sensitiveSet = new Set([6489, 8964, 53589, 89535, 198964, 641989, 1989535, 5351989]);
filters: {
// 敏感数字过滤
filterSensitive: function (num) {
let number = Number(num)
return sensitiveSet.has(number) ? number + 1 : number;
},

3.3 组件间的过渡

所有题目的流程过渡都有渐入渐出的一个效果,为了避免每个组件都写重复的场景过渡,打算自己写一个方法,结果在vue文档找到了一个官方的过渡方法,:is="组件名",动态写入组件名,监听到data中的‘quesStep’改变后就会执行过渡转到下一个组件

1
2
3
<transition name="fade" mode="out-in">
<components :is="quesStep" :quesData="testQues"></components>
</transition>

所有场景的延迟显示细节也都可以使用transition过渡,适合流程类的需要渐入渐出场景页面

4. 总结复盘

  1. 项目开始之前,确定哪些组件是可复用的,项目复用级,全局复用级 eg: 全局的背景音乐,动效等
  2. 可以把 vue 拆开 ,避免一个文件过大 难维护
  3. 有些布局,老操作系统不兼容,写之前,多考虑兼容,不然测试出来不兼容,再修改麻烦
  4. 对于一些点击响应需要低延迟的使用touchstartclick事件可能会有延迟
  5. 项目完成计划在前一周就给设计体验,测试提测,避免在上线前来不及修改
  6. 列bug优先级,先解决逻辑体验上bug,再解决样式小问题
  7. 解决bug方面的能力有待加强,怎么解决:
    • 列bug优先级,先解决逻辑体验上bug,在解决样式小问题
    • 精准定位,是否可复现,找到bug原型机
    • 打断点看哪里数据出错了,是什么类型的问题
  8. 移动端不推荐用img标签,长按会触发保存,所以要创一个节点,然后使用背景嵌入图片background
  9. 不要把所有页面的图片加载都放在最前面的loading页,等待时间长用户会流失,在每一页动态调用下一页的图片的动态预加载函数
  10. 使用replace跳转路由,push路由会导致底部出现底部状态栏