一个由“PixiJS+TweenMax+TimelineMax+AlloyTouch”组合拳实现的“春节送好礼,温暖回家路”h5一镜到底活动宣传页面(pixiJS)。
项目启动:
npm install
安装依赖包node app.js
或 npm start
运行项目于localhost:8080gulp watch
开启热更新,开启liverload chrome 插件gulp dist
打包代码博客地址: https://blog.csdn.net/qq_30604453/article/details/86544553 OR http://www.chunling.online/#/article/5d4cdf740c9a1118e22eb674
├── public // 公共资源文件
├── audio 音频文件
├── css // 样式
├── imgs // 图片
├── js
├── Alloytouch.js
├── animation.js // 动画处理
├── index.js
├── jquery-3.3.1.min.js
├── Pixi.min.js
├── playAudio.js // 音频处理
├── resources.js // 资源处理
├── scene.js // 场景处理
├── sprites.js // 精灵处理
├── TimelineMax.min.js
├── TweenMax.min.js
├── views // 视图
├── index.html
├── app.js // 项目启动的入口文件
├── data.txt // 表单提交记录文本
├── gulpfile.js // gulp的配置文件
1)舞台/Stage:舞台只有一个,app.stage就是我们的舞台。所有要被显示的东西最终都是放到舞台里去的。
2)精灵/Sprite:精灵就是舞台里的每一个元素,例如一只鸟、一张背景图、一段文本等。可以通过操作精灵的某些属性达到我们想要的动画效果。同时,精灵也分很多种类,普通精灵Sprite、平铺精灵TilingSprite,动画精灵AnimatedSprite。平铺精灵一般用于小图片需要平铺成背景,动画精灵一般用于制作帧动画。
3)容器/Container:容器,可以用来放置精灵,舞台的本质也是一个容器,所以容器有的属性舞台也有,舞台继承自容器。当我们舞台里的精灵元素太多太杂,可以通过设置多个容器,把精灵放到对应的容器里,再把容器放到舞台里,这样就方便管理了。这也就是一镜到底会用到的多场景切换。每个场景其实就是一个容器,每个场景有自己的精灵。舞台只有一个(舞台继承自容器),但容器可以有多个。
4)加载器/Loader:因为要用到大量的图片,图片的加载比较费时间,所以需要进行一次性的预加载,这也就是在网易四字魔咒开头看到的进度条的由来,利用Loader可以预加载图片,同时跟踪进度。
项目的一开始,需要创建一个pixi应用,直接通过一个语句 new PIXI.Application(),这样我们的PIXI应用就创建完成了。
const app = new PIXI.Application({
width:1334,
height:750
});
一般把舞台的宽高设置成跟设备屏幕宽高等同。注意舞台的背景色是0x写法。例如十六进制色值#000555;在这里写成 0x000555
// 创建PIXI应用
const w = document.body.clientWidth,
h = document.body.clientHeight;
let app = new PIXI.Application({
width: w,
height: h
});
loader.add可以链式调用,将全部的资源进行预加载。 progress回调函数帮助我们监听加载的进度;complete回调函数表示资源全部加载完成,这时候就可以把PIXI应用插入到真实DOM中了。 定义好加载器的所有东西,loader.load()就开始加载资源了。加载好的资源可以通过loader.resources调用。
// 创建资源加载器,进行资源预加载
const loader = new PIXI.loaders.Loader();
loader.add('bg1', './imgs/bg1.png')
.add('tv', './imgs/tv.png')
.add('curtain', './imgs/curtain.png')
.add('tree', './imgs/tree.png')
.add('bg2', './imgs/bg2.jpg')
.add('mother', './imgs/mother.png')
.add('mother_body', './imgs/mother_body.png')
.add('mother_left', './imgs/mother_left.png')
.add('mother_right', './imgs/mother_right.png')
.add('boy', './imgs/boy.png')
.add('child', './imgs/child.png')
.add('desk', './imgs/desk.png')
loader.on("progress", function (target, resource) { // 加载进度
document.getElementById('percent').innerText = parseInt(target.progress) + "%";
});
loader.once('complete', function (target, resource) { // 加载完成
document.getElementById('loading').style.display = 'none';
document.body.appendChild(app.view);
initScenes(); // 初始化场景
initSprites(); // 初始化精灵
initAnimation(); // 初始化动画
app.stage.scale.set(scale, scale); // 根据屏幕实际宽高放大舞台
if (w < h) { // 根据横屏竖屏效果旋转舞台
app.stage.rotation = 1.57;
app.stage.pivot.set(0.5);
app.stage.x = w;
initTouch(true, 'y');
} else {
initTouch(false, 'x');
}
});
loader.load(); // 加载资源
根据设计图,得出每个场景的长宽与位置。新建场景容器,等建完场景,才可以进行下一步的操作,将精灵放进场景里。 建场景的操作比较简单,定义每个场景的数据,新建Container对象并加入舞台中。这里的实现主要有三个东西: 1)场景数据 scenesOptions:定义每个场景的数据 2)对象集合 scenes:PIXI.Container对象,在初始化精灵的时候需要用到,所以需要将每个场景对象进行存储 3)循环函数 initScenes:初始化场景并加入舞台里
const scenesOptions = [ // 场景数据:定义每个场景的宽高,x/y距离
{
name: "scene1",
x: 0, y: 0,
width: 3247, height: 750
},
{
name: "scene2",
x: 1620, y: 0,
width: 3351, height: 750
}
];
const scenes = {}; // 场景集合 - pixi对象
function initScenes() { // 初始化场景
for (let i = scenesOptions.length - 1; i >= 0; i--) {
scenes[scenesOptions[i].name] = new PIXI.Container({
width: scenesOptions[i].width,
height: scenesOptions[i].height
});
scenes[scenesOptions[i].name].x = scenesOptions[i].x;
app.stage.addChild(scenes[scenesOptions[i].name]);
}
}
这一步的操作与上一步操作一致:数据集,对象集,循环函数。 只是在这里我把精灵的初始化拆分成两个函数,一个是循环精灵数组,一个是循环每一个精灵的属性。再加了一个特殊属性的函数。根据需要,可以对某些精灵进行特殊的操作,都统一放在initSpecialProp函数里。
const spritesOptions = [ // 精灵数据:定义每个精灵的坐标
{ // 第一个场景的精灵
tv: { // 电视
position: { x: 800, y: 0 }
},
curtain: { // 窗帘
position: { x: 1220, y: 36 }
},
child: { // 儿童
position: { x: 1090, y: 400 }
},
// ......
},
{ // 第二个场景的精灵
bg2: {
position: { x: 0, y: 0 }
},
desk: {
position: { x: 0, y: 592 }
},
// ......
}
}
];
const sprites = {}; // 精灵集合 - pixi对象
function initSprites() { // new出所有精灵对象,并交给函数initSprite分别赋值
for (let i = 0; i < spritesOptions.length; i++) {
let obj = spritesOptions[i];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
sprites[key] = PIXI.Sprite.fromImage(key);
initSprite(sprites[key], obj[key], i + 1);
}
}
}
initSpecialProp();
}
function initSprite(sprite, prop, i) { // 初始化单个精灵的属性并加入对应的场景中
for (let key in prop) {
if (prop.hasOwnProperty(key)) {
sprite[key] = prop[key];
}
}
scenes['scene' + i].addChild(sprite);
}
function initSpecialProp() { // 特殊属性
sprites.mother_left.pivot.set(0, 51);
sprites.mother_right.pivot.set(95, 50)
}
进行到上面那一步,舞台场景精灵一应俱全,可以看到绘制出来的效果,但是此时页面还不可以拖动,还需要一个滑动的库来配合。 new AlloyTouch({ ... }):
TimelineMax是GSAP动画库中的动画组织、排序、管理工具,可创建时间轴(timeline)作为动画或其他时间轴的容器,这使得整个动画控制和精确管理时间变得简单。与AlloyTouch合在一起使用。
let timeline = new TimelineMax({
paused: true
});
let alloyTouch;
let max = (w > h) ? w : h;
function initTouch(vertical, val) {
let scrollDis = app.stage.width - max
alloyTouch = new AlloyTouch({
touch: "body", // 反馈触摸的dom
vertical: vertical, // 不必需,默认是true代表监听竖直方向touch
min: -scrollDis, // 不必需,运动属性的最小值
maxSpeed: 1,
max: 0, // 不必需,滚动属性的最大值
bindSelf: false,
initialValue: 0,
change: function (value) {
app.stage.position[val] = value;
let progress = -value / scrollDis;
progress = progress < 0 ? 0 : progress;
progress = progress > 1 ? 1 : progress;
timeline.seek(progress);
}
})
}
时间轴准备好了。就可以定义动画,将动画加到时间轴上了,表示在时间轴的哪个位置开始播放动画。动画这里用到的是TweenMax库,当然,也可以用别的库,只要能实现补间动画即可。TweenMax常用的三个函数如下:
①TweenMax.to(target,duration,statusObj) ——目标target从当前状态到statusObj状态过渡 ②TweenMax.from(target,duration,statusObj) ——目标target从statusObj状态到当前状态过渡 ③TweenMax.fromTo(target,duration,statusObjFrom,statusObjTo)——目标target从statusObjFrom状态到statusObjTo状态过渡
duration表示过渡时长。如果duration=0.1则表示过渡时长占滚动总长的10%,即占时间轴的10%。 delay跟duration的计算规则:
const animationsOptions = { // 精灵动画集合
mother: [{
delay: 0,
duration: 0.2,
to: { x: 1120, ease: Power0.easeNone }
}],
bg1: [
{
prop: 'scale',
delay: 0.36,
duration: 0.28,
to: { x: 3, y: 3, ease: Power0.easeNone }
},
{
delay: 0.40,
duration: 0.08,
to: { alpha: 0, ease: Power0.easeNone }
},
],
mother_left: [
{
delay: 0.28,
duration: 0.25,
to: { rotation: -1, ease: Power0.easeNone }
}
],
mother_right: [
{
delay: 0.28,
duration: 0.2,
to: { rotation: 1, ease: Power0.easeNone }
}
]
}
function initAnimation() {
// delay = 0.1 表示滚动到10%开始播放动画
// duration = 0.1 表示运动时间占滚动的百分比
for (let key in animationsOptions) {
if (animationsOptions.hasOwnProperty(key)) {
let obj = animationsOptions[key];
for (let i = 0; i < obj.length; i++) {
let act;
let target;
if (obj[i].prop) {
target = sprites[key][obj[i].prop];
} else {
target = sprites[key];
}
if (obj[i].from & obj[i].to) {
act = TweenMax.fromTo(target, obj[i].duration, obj[i].from, obj[i].to);
} else if (obj[i].from) {
act = TweenMax.from(target, obj[i].duration, obj[i].from);
} else if (obj[i].to) {
act = TweenMax.to(target, obj[i].duration, obj[i].to);
}
let tm = new TimelineMax({ delay: obj[i].delay });
tm.add(act, 0);
tm.play();
timeline.add(tm, 0);
}
}
}
}
在这里注意一下 ease:Power0.easeNone 是缓动函数,戳链接:https://www.tweenmax.com.cn/api/tweenmax/ease 我一开始没有写缓动函数,动画出现严重的卡帧效果,真丑陋!我还以为是设备问题,是pixi问题。最后发现了这个缓动函数。给动画加上线性缓动,完美解决了卡帧的丑陋效果。 tip!!!!!(有热心网友问到我上面的prop是干什么用的)
注意一下,TweenMax.from(target,duration,statusObj)等方法只识别target的属性。举个例子,精灵sprite的width从0变到100
TweenMax.to(sprite, 0.1, { width: 100}) // 即 sprite.width变为100
那么问题来了,要改变属性的属性呢?例如sprite.scale.x变为2,则target应该变为sprite.scale
TweenMax.to(sprite.scale, 0.1, { x: 2}) // 即 sprite.scale.x变为100
为了兼容这两种情况,就需要加入prop。我可真是个小机灵鬼。
拿上面的animationsOptions.window举个例子。可以观察到animationsOptions.window其实是一个数组,数组里面的一个个对象代表一个个动画,所以组成精灵window的动画集合。我们需要对精灵window做很多动画的话,例如用时位移+缩放+变透明等等,如果是delay跟duration相同并且是window的一级属性(即window.prop),可以放在一个对象里,如果delay或者duration不同,就需要设置成不同的对象。为了兼容二级属性,即window.prop.prop,我特意加了一个参数prop,达到兼容二级属性的效果。
在下面初始化动画的函数initAnimation里,才有这一步,先判断是否是二级属性。
if (obj[i].prop) {
target = sprites[key][obj[i].prop];
} else {
target = sprites[key];
}
----------- The End. 更多介绍战以下博客地址: https://blog.csdn.net/qq_30604453/article/details/86544553 OR http://www.chunling.online/#/article/5d4cdf740c9a1118e22eb674
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。