最近接到一个需求,做一个活动页上下滑动翻页,并且有很多动画效果,比如:翻页后文字渐显、流星划过、火车不断驶过、人物的头发和领导不断飘动,门里有云彩不断飘过等等。
众所周知,css动画效果可以通过两种方法实现:transition 和 animation+keyframes。
transition: 用来给元素添加过渡效果,无法循环播放。
animation+keyframes: 定义开始和结束状态,系统自动添加过渡效果,可以循环播放。
不足之处是:
1、性能不好
2、兼容性存在问题
3、实现起来比较繁琐
4、效果与设计有差异,需设计师反复校验,会增加开发时间。
现在还有另一种实现方式:json动画。这种方式需要设计师在AE中制作动画并导出json,前端开发人员使用lottie-web即可。
这种方式需要设计人员知道如何制作AE动画。
我这个需求最后采用的方案是css动画。下面记录一些效果的实现方法和开发过程中踩过的坑。
动画效果无法在翻页动画结束后触发
swiper提供的 slideChangeTransitionEnd 事件在淡入切换时无法触发(effect: 'fade'),只能使用* slideChange*方法,这样造成翻页动画结束时,我们定义的动画已经开始了。我的方法是给将动效写在active中,给所有需要动效的元素一个属性,然后延迟改变classname。
.a {
&.active {
animation: spreadOut 1s linear infinite;
}
}
const duration = 300;
function fnName($el) {
// 其他操作
$el && setTimeout(function () {
$el.find('[data-trigger="true"]').addClass('active');
}, duration);
}
siwper 中存在自由翻页
已知swiper阻止了默认的touch事件,所以我们主需要在子元素中阻止touch冒泡即可。
副作用是阻止冒泡区域内无法滑动翻页,优化方法是只有在子内容高度超出父元素是才组织冒泡。
$('[data-selector="stop-swiper"]')
.on('touchstart', function (e) {
e.stopPropagation();
})
.on('touchmove', function (e) {
e.stopPropagation();
});
相同的动画效果在后几页出现闪烁、跳动
因为要给设计展示实现效果,所以显示了全部页面。结果在前几页中正常的动画在后几页无法正常展示。这个问题也困扰了我很久。后来发现去掉几个页面之后问题就不存在。猜测是因为页面dom结构太多导致的。
人物头发和领带飘动效果
这个效果通过切换背景图的方式实现的。
遇到的问题是,每张背景图第一次渲染的时候出现闪烁的情况,猜测是图片展示时处在渲染过程中导致的。解决思路是图片预渲染。
中间尝试通过将背景图全部一次性渲染出来,通过translate改变父元素的位置,但是改变过程中会出现过渡效果。
在部分机型上可以通过带小数的百分比来取消过度,但是ios上还是不行。
25% {
transform: translate(0, -100%);
}
25.001% {
transform: translate(0, -200%);
}
最后的解决方案是,将图偏设置为透明元素的背景图,来达到预渲染的效果。
行驶的列车
设计的效果是页面展示时一辆车从右侧驶出,然后火车一直行驶。
最初方案是两倍原图宽度的div让火车做从左至右的循环运动,当运动到一半即刚驶过一辆车的位置时返回到初始位置。
0% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(50%, 0, 0);
}
但是实际测试中发现ios手机上出现跳动,移动的距离还未到自身的一半就返回了第一帧,只播放一次的时没问题。
如果需要循环播放则需要在开始和结束增加一段时间来维持状态。
0% {
transform: translate3d(0, 0, 0);
}
1% {
transform: translate3d(0, 0, 0);
}
99% {
transform: translate3d(50%, 0, 0);
}
100% {
transform: translate3d(50%, 0, 0);
}
但是需求这么做会导致动画有一瞬间的停止,所以我将一个div车拆成了两个
.animate-train {
position: absolute;
bottom: 0;
right: 0;
width: 1075px;
height: 188px;
&.behind {
right: 1075px;
&.active {
animation-name: trainBehindLeft2Right;
}
}
&.active {
animation: trainLeft2Right 2.2s linear infinite forwards;
}
}
@keyframes trainLeft2Right {
0% {
right: 0;
transform: translate3d(0, 0, 0);
}
50% {
right: 0;
transform: translate3d(110%, 0, 0);
}
51% {
right: 1075px;
transform: translate3d(0, 0, 0);
}
100% {
right: 0;
transform: translate3d(0, 0, 0);
}
}
@keyframes trainBehindLeft2Right {
0% {
right: 1075px;
transform: translate3d(0, 0, 0);
}
100% {
right: 0;
transform: translate3d(105%, 0, 0);
}
}
水波纹扩散效果
水波纹扩散效果重点在于波纹扩散效果,先快速扩大然后扩散速度降低并逐渐消失。
@keyframes spreadOut {
0% {
opacity: 0.1;
transform: scale(0.4, 0.4);
}
80% {
opacity: 1;
transform: scale(0.9, 0.9);
}
100% {
opacity: 0.1;
transform: scale(1, 1);
}
}
音乐无法自动播放、无法循环播放
audio部分手机上需要触发用户操作才能播放,但是微信内是可以自动播放的。
所以我们只在微信内外打开的场景下才会自动播放。音乐使用mp3格式即可兼容全部机型。
ios上无法循环则是通过在audio结束通过js再次播放来解决的。
const audo = document.querySelector('audo');
audo.addEventListener('ended', function () {
audo.load();
audo.play();
});
audio标签不能加loop属性,否则不会触发ended事件
隐藏网页后音乐仍然播放问题
这个问题其实存在
解决思路是,通过监听visibilitychange事件获取当前页面的状态
// 隐藏时暂停
(function () {
let vibibleState = '';
let visibleChange = '';
const { visibilityState, webkitVisibilityState } = document;
if (typeof visibilityState !== 'undefined') {
visibleChange = 'visibilitychange';
vibibleState = 'visibilityState';
} else if (typeof webkitVisibilityState !== 'undefined') {
visibleChange = 'webkitvisibilitychange';
vibibleState = 'webkitVisibilityState';
}
if (visibleChange) {
document.addEventListener(visibleChange, function () {
const status = document[vibibleState];
if (status === 'hidden') {
sound.pause();
}
});
}
}());
// 退出时暂停
window.addEventListener('unload', function () {
sound.pause();
});
用户杀掉浏览器进程后音乐仍然会播放,对于这种情况是没有办法处理的
今后css动画过程中需要注意的地方
- 优先使用json动画;
- 减少循环播放的动画效果;
- 减少动效数量、同时展示的动效数量;
- 注意页面复杂度,减少页面数量、精简dom结构;
- 不要滑动翻页中嵌入自由滑动;