Aitter's Blog

动画系列之Qzone5.0的H5技术分析

随着CSS3动画的普及,应用CSS3动画的活动页、宣传页、专题页,如雨后春笋般破土而出,达到了很好的传播与品牌宣传的效果。

H5几乎成为了移动端宣传的主流方式,不管是商业活动还是品牌推广宣传,大多都会选择制作H5页面来进行相应的宣传工作,因此在移动端,特别是微信上,几乎每天都能发现H5页面的踪影。

2015年的QQ空间5.0预约页

相关技术点

  1. CSS Transiton & Animation 动画
  2. SVG 动画
  3. 图片高清屏适配
  4. 图片预加载
  5. 竖屏提示
  6. 感应视差
  7. 分享
  8. 多媒体播放
  9. 整屏滚动
  10. PC适配

素材定位

容器

.center-obj 固定大小的盒子通过 absolute 绝对定位 垂直水平居中

.center-obj {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
width: 320px;
height: 350px;
}

元素

容器内的元素绝对定位,并且使用背景图片

  • 使用背景图片结合 CSS3 Media query 方便做高清屏的图片适配,同时也能很好的去过 CSS Sprite 雪碧图,提升图片加载效率。
  • 方便动画渲染,绝对定位可以触发独立的 Composited Layer 复合层,也就是独立的渲染层,在执行动画时只会自身重绘 repaint,不会引起界面重排 reflow/relayout

动画分析

第一屏

  • 循环 星星闪烁动画 m1start ( scale )
  • 循环 火焰闪烁动画 m1fly ( background-position + steps )
  • 入场 Button弹性动画 rubberBand ( scale )
  • 循环 Loading 加载动画 progress-bar-stripes ( background-position )

出场顺序:星星+火焰+Loading > Button

当页面素材加载时执行更新 Loading,加载完成时,删除 Loading 元素,显示 Button

Keframes

@keyframes m1start {
0% { transform: scale(1); } /* 正常大小 */
33% { transform: scale(.9); } /* 缩小到.9倍大小 */
66% { transform: scale(1.3); }/* 放大到1.3倍大小 */
100% { transform: scale(1); } /* 还原到正常大小 */
}
animation: m1start 2s .5s linear infinite backwards;
/* 背影图片X轴向左移动210px */
@keyframes m1fly {
0% { background-position: 0 0; }
100% { background-position: 210px 0; }
}
animation: m1fly .2s .5s steps(3, start) infinite alternate;
/* 背影图片X轴从20px的位置回到原点 */
@keyframes progress-bar-stripes {
0% { background-position: 40px 0; }
100% { background-position: 0 0; }
}
animation: progress-bar-stripes 2s linear infinite;
/* 大小随着关键帧缩放 */
@keyframes rubberBand {
0% { transform: scale3d(1,1,1); }
30% { transform: scale3d(1.25,.75,1); }
40% { transform: scale3d(0.75,1.25,1); }
50% { transform: scale3d(1.15,.85,1); }
60% { transform: scale3d(.95,1.05,1); }
65% { transform: scale3d(.95,1.05,1) }
75% { transform: scale3d(1.05,.95,1); }
100% {transform: scale3d(1,1,1);}
}
animation: rubberBand .6s 1 forwards;

动画说明

星星闪烁

animation: m1start 2s .5s linear infinite backwards;

运用 m1start 序列帧,动画执行2s,延迟0.5s,使用 linear 匀速动画函数,循环执行,每个循环停留在最后一帧。

火焰闪烁

animation: m1fly .2s .5s steps(3, start) infinite

运用 m1fly 序列帧,动画执行 0.2s,延迟 0.5s,使用 steps 动画,循环执行,每个循环停留在最后一帧。

steps

火焰动画不需要平滑的线性渐变动画,而需要一帧一帧显示的帧动画,steps(
3,start) ,把动画 分为三个关键帧执行,从起点开始跃阶变化。

第二屏

这一屏动画形式比较简单,主要是位移与缩放

  • 入场 火箭&人从上掉落 m2fall1
  • 入场 手机从上掉落 m2fall2
  • 入场 陆地弹性跳动 m2fall3
  • 循环 热气球上下位移 m2ball
  • 循环 黄色星球&星星闪烁 m2yball
  • 感应视差 背景 parallax.js

出场顺序:
热气球&黄色星球&星星&背景&标题 > 火箭&人&手机

通过关键帧控制动画的快慢

关键帧与关键帧之间的间隔长短,可以决定动画的快慢

@keyframes m2fall3 {
0% { transform: translateY(0); }
35% { transform: translateY(0); }
38% { transform: translateY(5px); }
50% { transform: translateY(0); }
85% { transform: translateY(5px); }
100% { transform: translateY(0); }
}
@keyframes m2ball {
0% { opacity: 1; transform: translateY(-10px); }
50% { opacity: 1; transform: translateY(0); }
100% { opacity: 1; transform: translateY(-10px); }
}
@keyframes m2yball {
0% { opacity: 1; transform: scale(1); }
60% { opacity: 1; transform: scale(1.1); }
100% { opacity: 1; transform: scale(1); }
}

模拟陆地落地有缓冲的效果,0-35,没有动画,35-38,快速掉落,38-50,向上弹起,50-85再落下,85-100回到原点。

第三屏

入场 手机&文字 fall
入场 按钮 bounce
入场 线条 dash
入场 星球 m3wave
循环 星星与点 m3star

出场顺序:手机&文字&按钮 > SVG线条动画+星球

SVG线条动画

svg线条动画主要由两个样式控制

  • stroke-dasharray:一组数组,没数量上限,每个数字交替表示划线与间隔的宽度 (线宽 间隔 线宽 间隔 … )
  • stroke-dashoffset: 虚线的偏移量

这两个属性任意一个都可以实现svg线条填充动画,如 svg 路径长度为1000:

  • 如果使用 stroke-dasharray, 则动画可以 从 stroke-dasharray: 0, 1000; 变化至 strok-dasharry: 1000, 1000; 即虚线的长度从 01000

  • 如果使用 stroke-dashoffset 偏移来实现,则动画可以从初始状态

stroke-dasharry: 1000, 1000;
stroke-dashoffset: 1000;

变化至

stroke-dashoffset: 0;

即先向左偏移100px,显示空白间隔,现减少偏移量,显示出线段来

推荐阅读 SVG 线条动画入门

第四屏

入场 手机&文字 fall
入场 按钮 bounce
入场 线条 dash
入场 猴 m4monkey
入场 蜜蜂 m4bee
入场 猫 m4cat
循环 星星与点 m4star
循环 蓝色纸人 m4paste

第五屏

入场 手机&文字 fall
入场 按钮 bounce
入场 汽泡 m5pop1 m5pop2 m5pop3
入场 热气球 m5ball

第四屏与第五屏动画效果与上面基本相同(略)

第六屏

入场 手机&文字 fall
入场 按钮 bounce

入场 云向右漂浮
入场+循环 人向右走动
入场 汽泡淡出

利用元素嵌套运用连续的动画效果

animation 允许设置多个动画效果,但是如果多个动画效果有相同的动画属性时,动画则不能达到预期效果。

可以正常执行

.element {
animation: fadeIn 2s, float 1.2s 2s ease-in infinite both;
}
@keyframes fadeIn {
from { opacity:0; }
to{ opacity: 1}
}
@keyframes float {
25%{ transform: translateY(10px)}
50%{ transform: translateY(0px)}
75%{ transform: translateY(-10px)}
}

后面相同的动画属性会覆盖掉前面的动画

.element2 {
animation: left-in 2s, zoomIn 1.2s 2s ease-in infinite both;
}
@keyframes left-in {
from { transform: translateX(0) }
to{ transform: translateX(40px) }
}
@keyframes zoomIn {
from { transform: scale(.8) }
to{ transform: scale(1.2) }
}

走路的人
walk 元素从透明度 1 变成 0.5,同时从向右移动 60px ,并停留在 60px 的位置
ele-runner 则应用 steps 帧动画,表现为走路的状态

将两个动画应用在不同的元素上,避免相同的动画属性被覆盖掉

<div class="walk">
<div class="ele-runner"></div>
</div>
.page6.active .walk {
animation: m6walk 1.5s .4s ease-in 1 forwards;
transform-origin: 0 50%;
}
.page6.active .ele-runner {
animation: m6runner .6s .6s steps(9, start) alternate infinite;
}
@keyframes m6walk {
0% { opacity: 1; transform: translateX(0) }
100% { opacity: .5; transform: translateX(60px) }
}
@keyframes m6runner {
0% { background-position: 0 0 }
100% { background-position: 540px 0 }
}

整屏滚动

jQuery的一款插件fullpage.js,可以实现全屏滚动,非常流行的效果,兼容性IE8+

因为考虑到兼容性,使用了jQuery的fullpage插件,使用方法

网易邮箱大师

页面结构

<div class="main" id="fullPage">
<section style="height: 100%" class="page page2"></section>
<section style="height: 100%" class="page page3"></section>
<section style="height: 100%" class="page page4"></section>
<section style="height: 100%" class="page page5"></section>
<section style="height: 100%" class="page page6"></section>
</div>

Fullpage初始化

$("#fullPage").fullpage({
css3: !0,
scrollingSpeed: 500,
sectionSelector: ".page",
})

图片适配与加载

图片的高清适配

一般情况下使用 @1x 一倍图缩写样式
利用 media query 检测如果高清屏,则使用 @2x 两倍图

#loading .ele-man {
position: absolute;
background-image: url(img/m1-man.32.png);
width: 70px;
height: 163px;
top: 30px;
left: 50%;
margin-left: -35px;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d
}
@media
only screen and (-webkit-min-device-pixel-ratio: 1.25),
only screen and (min-resolution:120dpi),
only screen and (min-resolution:1.25dppx) {
#loading .ele-man {
background-image:url(img/m1-man.32@2x.png);
background-size: 210px 163px
}
}

图片预加载

通过将一些大图预先加载出来,再展示页面动画
在加载的过程中,更新进度信息给予用户一定的反馈效果

var img_list = [
'/sprite/loading-imp.32',
'/sprite/m-animate-1-imp.32',
'/sprite/m-animate-2-imp.32',
'/sprite/m-animate-3-imp.32'
];
// 高清屏适配
var isRetina = window.devicePixelRatio>1.5;
img_list = img_list.map(function(v){
return v + (isRetina ? '@2x.png' : '.png');
})
console.log(img_list)
// 预加载
loadImgs(img_list, function(o){
// 更新进度
$('#J_loadTest').html((o*100)+'%');
$('#J_loadProgress').css('width', (o*100)+'%');
// 隐藏进度条
if(o === 1){
$('#J_btnGo').removeClass('hide');
$('#J_loadArea').remove();
}
});

图片预加载的原理

通过创建一个 Image 对象,给对象的src属性赋值为图片URL,来达到加载图片目的

图片预加载要考虑到的问题:

  • 如果图片被浏览器缓存,在ie和oprea中,则不会触发 onload 事件
  • 如果是动态图片如gif,则会多次触发 onload 事件
function loadImage(url, callback) {
//创建一个Image对象,实现图片的预下载
var img = new Image();
// 如果图片已经存在于浏览器缓存,直接调用回调函数
if (img.complete) {
callback(img);
return;
}
// 先绑定onload事件再赋值src
img.onload = function(){
img.onload = null;
callback(img);
}
img.src = url;
}

竖屏提示

通过监听 window 的 onorientationchange 事件,再判断用户是否是横屏,如果是横屏,则提示用户竖屏操作

window.onorientationchange = function() {
var show = "-90" == window.orientation || "90" == window.orientation;
$(".landscape-wrap")[show ? 'removeClass' : 'addClass']('hide');
}

感应视差

Parallax.js是一款功能非常强大的javascript视觉差特效引擎插件。通过这个视觉差插件可以制作出非常炫酷的视觉差特效。并可以检测智能设备的方向。

<div class="parallax-obj">
<div class="layer ele-p1" data-depth="1.00"></div>
<div class="layer ele-p2" data-depth="0.80"></div>
<div class="layer ele-p3" data-depth="0.30"></div>
<div class="layer ele-p4" data-depth="0.30"></div>
<div class="layer ele-p5" data-depth="1.10"></div>
</div>
$(".parallax-obj").parallax();

多媒体播放

H5支持 audio 多媒体标签,直接播放多媒体内容

<audio id="audio" src="audio.mp3" loop preload="auto" autoplay="true"></audio>

在IOS中,多媒体的插放不能通过JS来触发播放,需要用户手动触发

通过给 html 绑定 touchstart 事件,来触发音乐播放

var a = document.getElementById("audio");
var b = 0
a.oncanplay = function() {
$("html").on("touchstart", function() {
0 == b && (a.play(), b = 1)
})
}();
a.autoplay = !0;
a.isLoadedmetadata = !1;
a.audio = !0;

可以通过 audio.play()audio.pause() 方法来控制 播放 与 暂停

$('.J_musicIcon').on('touchend click', function(e){
e.preventDefault();
var a = a = document.querySelector("audio");
if($(this).hasClass('off')){
$(".icon-music").removeClass("off");
a.play();
}else{
$(".icon-music").addClass("off");
a.pause();
}
})

PC适配

让主要内容固定大小剧中显示,背景全屏平铺显示,以达到PC的适配效果

分享

微信分享分为两种,一种是普通的网页分享,一种是利用微信的JS-SDK分享页面。

一般分享

普通分享,只能分享页面的标题、缩略图、URL地址

  • 标题取页面 headtitle 的内容
  • URL为当前页面的地址
  • 缩略图为当前页面第一张图片

如何在分享中显示指定的图片?

在body之后添加一张隐藏的缩略图,大小大于 200 x 200

<h1 class="thumbnails" style="display:none">
<img src="img/20141202165235_IOQF2pLPOv.jpg"/>
</h1>

微信JS-SDK分享

微信JS-SDK分享需要公众号授权,并有很多的限制规则

微信分享可以分享到 朋友圈、微信好友、QQ、腾讯微博、QQ空间

wx.onMenuShareAppMessage({
title: '', // 分享标题
desc: '', // 分享描述
link: '', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: '', // 分享图标
type: '', // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});