横向长图h5动画实践

1,911 阅读5分钟

简介

随着H5应用的兴起和发展,交互式一镜到底逐渐成为广告新宠。这种不用翻页、无需硬切换场景的营销手段,可以根据时间的推进或故事的开展为设计灵感,将某个过程、某段经历、某个场景循序渐进地铺陈开来。

一镜到底式H5的具体玩法是多种多样的,99%都有套路可循,主要有以下几类:

一、视频类

以第一视角展示完整故事或宏大场景,先把视频做好,再将其融入到H5作品中。

二、长页面类

以X轴或Y轴运动突出整体的连续性,我们还可在上下左右滑动的基础上,将视差动画等创意形式融入其中,以便丰富画面的层次感并让作品更加立体。

三、立体穿梭类

以Z轴运动或画中画实现空间穿梭或场景穿梭

一镜到底示例集合

实践

今年年终h5三娃回家,使用了长页面的形式,随着右滑的操作,场景逐帧呈现,动画也随之出现,用户与三娃共同体验春节回家的两种历程。

技术上采用了Pixi.js + Tween.js + Animate.js

体验二维码

image

绘制场景内容——Pixi.js

创建舞台(stage)和渲染器 (renderer)

舞台:所有要渲染的对象都必须连接到舞台中才能被显示出来,舞台处于整个树形展示结构的最底层,可以理解为背景。

渲染器:选用canvas或webGL进行渲染的一个区域

//创建一个容器对象:舞台
var objPixiContainer = new PIXI.Container();
//创建渲染器
var pixiRender = new PIXI.CanvasRenderer(windowWidth, windowHeight);
//告诉渲染器去渲染舞台
pixiRender.render(objPixiContainer);

创建精灵(sprite)

图片对象被叫做精灵图。你可以控制它们的位置,尺寸以及其他许多有用的可以制作交互式动画图形的属性。Pixi有一个 Sprite 类,它是创建游戏精灵的通用方式。

Pixi的loader对象可以用来做图片的预加载,position属性设置x、y坐标来精准还原设计稿,使用addChild添加到舞台。

//图片预加载
PIXI.loader
    .add("https://p4.ssl.qhimg.com/t01f118b79154278482.png")
    .add("https://p1.ssl.qhimg.com/t01495e763abe4d0ce9.png")
    .on("progress", function(){
        //do sth when loading
    })
    .load(loadingFinish);

//加载完成回调
function loadingFinish() {
    //创建一个精灵
    var sprite = new PIXI.Sprite(
        PIXI.loader.resources["https://p1.ssl.qhimg.com/t01495e763abe4d0ce9.png"].texture
    );
    sprite.position.set(290, 297)
    //添加到舞台
    objPixiContainer.addChild(sprite)

    //渲染到渲染器
    pixiRender.render(objPixiContainer);
}

每次添加精灵或者精灵有动作之后,都需要重新去渲染舞台

function pageUpdate() {
    requestAnimationFrame(pageUpdate);
    pixiRender.render(objPixiContainer);
}

时差滚动

实现层次分明的背景动画效果,我们考虑用相对位移实现,原理是给不同的图层设置不同的运动系数,在用户划动幕布时,产生不相等的位移,从而在视觉上产生层次分明的效果。主要计算公式是:

// p.data.position.x为元素初始位置 ,p.data.speed为运动系数(不一致),distance为相对于初始位置的运动位移
   p.x = p.data.position.x + (p.data.speed.x) * distance;

连续播放动画

使用PIXI.extras.AnimatedSprite来实现,并通过animationSpeed精准控制运动速度、play/stop控制播放/暂停、gotoAndStop精细到第几帧等来满足设计的动效需求

//声明AnimatedSprite的序列帧
var urlPadding = "https://p4.ssl.qhimg.com/d/inn/bdfcc19c48d8/",
  act_animate_bg_img_arr = [];
for (let $e = 0; $e < 2; $e++) {
  act_animate_bg_img_arr.push(urlPadding + "baba" + ($e + 1) + ".png");
}

//精灵
var sprite = new PIXI.extras.AnimatedSprite.fromImages(act_1_animate_bg_img_arr)
//精灵添加到舞台
  objPixiContainer.addChild(sprite4)

//找到序列帧所在的图层,放到对应位置,并设置移动速度
sprite.animationSpeed = 0.1; //控制速度
sprite.play();//自动播放序列帧

多个场景管理

我们发现有多个场景,可以在objPixiContainer这个舞台下创建多个舞台来管理多个场景

//声明一个舞台
var stageContainer = new pixiContainer();

//多个场景(画布)
let scenes = [Act_1, Act_2, Act_3];
//每个场景又是一个舞台,依次渲染初始位置和舞台精灵
for (let j = 0; j < scenes.length; j++) {
    scenes[j] = new pixiContainer();
    scenes[j].pivot.set(act_sprites_list[j][0].position.x, 0);
    scenes[j].position.set(act_sprites_list[j][0].position.x, 0);
}
 //每个actContainerArr依次赋值初始位移和渲染背景
 actContainerArr = scenes;
 //todo 精灵渲染

  //场景舞台添加到背景 
  objPixiContainer.addChild(stageContainer);
  //每个场景添加到场景舞台
  stageContainer.addChild(Act_1, Act_2, Act_3);

场景切换——ScrollerJS

多个画布,选择用ScrollerJs来实现画布的横向滚动效果,多个画布的管理,我们选择将数据抽象成对象,尽量使用循环实现来减少代码量。

思路

当用户手指滑动屏幕时,触发滚动,通过监听滚动事件,在滚动一定距离触发事件,事件参数包含位置信息等,根据位置信息决定渲染内容(画布显示隐藏,图片位置等)。

实现

首先,需要给scroller设置容器宽高限制:

scrollerObj.setDimensions(clientWidth, clientHeight, contentWidth, contentHeight);

然后实时监听鼠标滚动的距离:

//控制位移
function scrollerCallback(left, top, zoom) {
    console.log(`当前滚动条位置:${left},${top}`)
    
	//当前场景相对移动
    stageContainer.position.x = -cur_X;
    stageContainer.position.y = -cur_Y;
}
var objScroller = new Scroller(scrollerCallback, {
    zooming: false,
    animating: true,
    bouncing: false,
    animationDuration: 1000
});
objScroller.__enableScrollY = true;

我们还需要把滚动区域与渲染器关联起来

objPixiContainer.on("touchstart", onTouchStart)
    .on("touchmove", onTouchMove)
    .on("touchend", onTouchEnd);

function onTouchStart(e) {
    var i = e.data.originalEvent;
    isTouching = true;
    objScroller.doTouchStart(i.touches, i.timeStamp);
}

function onTouchMove(e) {
    if (isTouching) {
        var i = e.data.originalEvent;
        objScroller.doTouchMove(i.touches, i.timeStamp, i.scale);
    }
}

function onTouchEnd(e) {
    var i = e.data.originalEvent;
    objScroller.doTouchEnd(i.timeStamp);
    isTouching = false;
}

动画——TweenJS

动画效果实现思路:

  • 可以回溯的动效使用AnimatedSprite + ScrollerJS结合相对位移逐帧渲染。
  • 不能中断的使用 TweenJs 来填充实现。

tweenJS,类似于jQuery的animate方法和CSS3动画,定义起始、结束状态和时间,中间的过渡状态由TweenJS自动填充缓动效果

new TWEEN.Tween({ y: 34 })
    .to({ y: 54 }, 1000)
    .onUpdate(function () {
     //
    })
    .start()

自动缩放

原来的写法

objPixiContainer.scale.set(screenScaleRito, screenScaleRito);
pixiRender.resize(windowWidth, windowHeight);//屏幕宽高

缩放造成图片模糊,改成resolution设置为2

objPixiContainer.scale.set(screenScaleRito, screenScaleRito);
pixiRender.resize(windowHeight / screenScaleRito, 720);//设计稿宽高
pixiRender.view.style.width = windowHeight / screenScaleRito + 'px';//设计稿宽高
pixiRender.view.style.height = 720 + 'px';