模仿网易四字魔咒:PixiJS实现h5一镜到底

目录

前言

1.代码地址与demo效果图

2.项目技术架构分析

3. PixiJS

3.1 pixi常见概念介绍

3.2 创建应用

3.3 预加载资源

3.4 初始化场景

3.5 初始化精灵

4. AlloyTouch

5. TimelineMax

6. 初始化动画

7. 舞台的整体缩放与旋转

8.添加背景音乐

总结

后记

// 续更 2019-09-08

(1)运动属性的最小值应该怎么计算?

(2)为什么后面会剧烈抖动??

(3)动画的delay与duration如何计算???

// 续更 2021-06-18

(1)怎么设置一个元素不会随着画布的滚动而跟随它滚动 而是让他放大?

怎么设置一个元素不会随着画布的滚动而跟随它滚动 ,让他一直固定在屏幕中间呢?

(2)pixi里怎么做轮播图?


 

前言

网易四字魔咒:http://news.163.com/special/fdh5_tolerance/(手机扫描以下二维码查看)

偶然机会看到网易的四字魔咒,觉得so cool!一开始以为只是简单的CSS3动画,后来一研究,确实不简单。CSS3动画有一定的局限性,实现不了这么流畅的一镜到底的效果。那就入坑吧,来研究一波如何实现一镜到底。

 

 

1.代码地址与demo效果图

先挂上最终实现的效果图跟仓库地址:https://gitee.com/wuchunling/pixiJS.git

 

 

2.项目技术架构分析

项目主要用到了PixiJS(https://www.pixijs.com/),一个2D动画引擎,看官方文档稍微吃力,也有一个翻译的中文教程:https://github.com/Zainking/learningPixi,中英文配合看,基本pixiJS的API就能初步掌握。

 

先来总结一下项目的实现整体的实现步骤:

(1) 创建PIXI应用预加载图片资源,资源加载完成后将PIXI应用插入真实DOM中,进行下一步

(2) 初始化场景:定义每个场景的宽高、位置等属性数据,通过new PIXI.Container()函数创建每个场景,将他们加入PIXI舞台中

(3) 初始化精灵:定义每个精灵的位置等属性数据,将其加到对应的场景中

(4) 进行到第三步,所有的精灵都绘制出来了,但是此时屏幕还不可以拖动,这里需要用到一个滑动的库,我用的是AlloyTouch,网易四字魔咒用的是ScrollerJS。通过AlloyTouch可以检测用户滑动的距离,在对应的滑动的change回调函数里可以改变舞台的位置app.stage.position.x来实现舞台的拖动效果。这样用户就可以通过滑动查看了。

(5) 进行到第四步,一切的效果都是静态的,我们的元素还没有动起来。要实现随着用户的滑动播放对应的动画效果,这里需要用到一个库TimelineMax。这是管理动画播放进度条的库。直接new 一个主时间轴timeline。

(6) 给精灵加上动画,这里用到一个库TweenMax,可以用来创建补间动画。我们只需定义精灵的起始状态,最终状态,它能轻松帮助我们进行状态的过渡。用TweenMax创建完动画,将动画加到时间轴timeline对应的位置。delay:0.1表示在动画长度的百分十处开始播放。

(7) 在第三步的时候,用户滑动的回调函数加上 timeline.seek(progress)就可以实现滑动到某个位置播放对应的动画。

 

这里,先解释一下每个库的作用

1) PixiJS: 绘图,其中包括舞台、场景、普通精灵、动画精灵、平铺精灵、定时器等。

2) TweenMax:制作过渡动画

3) TimelineMax:管理整个舞台的动画播放进度

4) AlloyTouch:实现滑动效果,监听用户滑动

PixiJS+TweenMax+TimelineMax+AlloyTouch就能实现一镜到底的套路,每个库发挥它自己应有的作用,相互配合。

 

 

3. PixiJS

3.1 pixi常见概念介绍

①舞台/Stage:舞台只有一个,app.stage就是我们的舞台。所有要被显示的东西最终都是放到舞台里去的。

②精灵/Sprite:精灵就是舞台里的每一个元素,例如一只鸟、一张背景图、一段文本等。可以通过操作精灵的某些属性达到我们想要的动画效果。同时,精灵也分很多种类,普通精灵Sprite、平铺精灵TilingSprite,动画精灵AnimatedSprite。平铺精灵一般用于小图片需要平铺成背景,动画精灵一般用于制作帧动画。

③容器/Container:容器,可以用来放置精灵,舞台的本质也是一个容器,所以容器有的属性舞台也有,舞台继承自容器。当我们舞台里的精灵元素太多太杂,可以通过设置多个容器,把精灵放到对应的容器里,再把容器放到舞台里,这样就方便管理了。这也就是一镜到底会用到的多场景切换。每个场景其实就是一个容器,每个场景有自己的精灵。舞台只有一个(舞台继承自容器),但容器可以有多个。

④加载器/Loader:因为要用到大量的图片,图片的加载比较费时间,所以需要进行一次性的预加载,这也就是在网易四字魔咒开头看到的进度条的由来,利用Loader可以预加载图片,同时跟踪进度。

 

3.2 创建应用

项目的一开始,需要创建一个pixi应用,直接通过一个语句 new PIXI.Application(),这样我们的PIXI应用就创建完成了。

    let app = new PIXI.Application({
      width:1334, 
      height:750
    });

一般把舞台的宽高设置成跟设备屏幕宽高等同。注意舞台的背景色是0x写法。例如十六进制色值#000555;在这里写成 0x000555

    // 创建PIXI应用
    const w = document.body.clientWidth,
        h = document.body.clientHeight;
    const app = new PIXI.Application({
      width:w,
      height:h,
      backgroundColor:0xd7a664
    });

 

3.3 预加载资源

loader.add可以链式调用,将全部的资源进行预加载。

progress回调函数帮助我们监听加载的进度;complete回调函数表示资源全部加载完成,这时候就可以把PIXI应用插入到真实DOM中了。

定义好加载器的所有东西,loader.load()就开始加载资源了。加载好的资源可以通过loader.resources调用

(代码中涉及到舞台的整体缩放跟旋转的问题留到最后讲。)

 // 创建资源加载器loader ,进行资源预加载
  const loader = new PIXI.loaders.Loader();

  // 链式调用添加图片资源
  loader.add('bg1', './imgs/bg1.png')
       .add('bg_desk', './imgs/bg_desk.png')
       .add('bg_person','./imgs/bg1_person.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);   // 将pixi应用插入真实DOM中

    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();
   

3.4 初始化场景

根据设计图,得出每个场景的长宽与位置。新建场景容器,等建完场景,才可以进行下一步的操作,将精灵放进场景里。

建场景的操作比较简单,定义每个场景的数据,新建Container对象并加入舞台中。这里的实现主要有三个东西:

①场景数据 scenesOptions:定义每个场景的数据

②对象集合 scenes:PIXI.Container对象,在初始化精灵的时候需要用到,所以需要将每个场景对象进行存储

③循环函数 initScenes:初始化场景并加入舞台里

  const scenesOptions = [ // 场景数据:定义每个场景的宽高,x/y距离
    {
      name:"scene1",
      x:0,y:0,
      width:2933,height:750
    },
    {
      name:"scene2",
      x:2933,y:0,
      width:1617,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]);
    }
  }

 

3.5 初始化精灵

这一步的操作与上一步操作一致:数据集,对象集,循环函数。

只是在这里我把精灵的初始化拆分成两个函数,一个是循环精灵数组,一个是循环每一个精灵的属性。再加了一个特殊属性的函数。根据需要,可以对某些精灵进行特殊的操作,都统一放在initSpecialProp函数里。

  const spritesOptions = [ // 精灵数据:定义每个精灵的坐标
    { // 第一个场景的精灵
      bg1:{
        position:{x:0,y:0}
      },
      bg_person:{
        position:{x:0,y:19},
        anchor:{x:0.5,y:0.5}
      },
      ....
    },
    { // 第二个场景的精灵
      bg_desk:{
        position:{x:2213,y:38}
      },
      ....
    }
  ];
  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)
  }

 

4. AlloyTouch

进行到上面那一步,舞台场景精灵一应俱全,可以看到绘制出来的效果,但是此时页面还不可以拖动,还需要一个滑动的库来配合。

new AlloyTouch({ ... }):

①touch定义触摸的DOM对象,在这里我们就直接是body了;

②vertical定义触摸的方向(横向滑动,还是竖向滑动。这里涉及设备是横屏还是竖屏,所以通过值vertical传进来。横屏则为false,竖屏则为true);

③可滚动的最大距离max跟最小距离min。因为我们都是往左滑,往上滑,所以为负距离,所以最大值max为0。最小值为舞台的整体宽度再减去一整屏的宽度,然后再取负值;

④关键点在于change函数,他可以返回实时滚动的距离。通过计算可以得到当前滚动的距离占全部距离的百分比,这个百分比就是我们当前的进度。拿到这个百分比。(默认总进度为1)就可以通过timeline.seek函数就可以随时改变播放的进度。这就是为什么往回滑动动画会往回撤的原因。想一下我们平时看视频,我们滚动进度条的行为就是用户滑动页面的行为,所以这就是实现的关键点。

  let alloyTouch;
   
  function initTouch(vertical, val) {
    let scrollDis = app.stage.width-max;
    alloyTouch = new AlloyTouch({
      touch:"body", //反馈触摸的dom
      vertical: vertical, //不必需,默认是true代表监听竖直方向touch
      min: -app.stage.width + max, //不必需,运动属性的最小值
      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);      
      }
   })
  }

 

5. TimelineMax

TimelineMax是GSAP动画库中的动画组织、排序、管理工具,可创建时间轴(timeline)作为动画或其他时间轴的容器,这使得整个动画控制和精确管理时间变得简单。

  const timeline = new TimelineMax({  // 整个舞台的时间轴
    paused: true
  });

最后,将4跟5步骤合在一起如下

  const w = document.body.clientWidth,
      h = document.body.clientHeight;
   
  const min = (w<h)?w:h;
  const max = (w>h)?w:h;
   
   
  const timeline = new TimelineMax({  // 整个舞台的时间轴
    paused: true
  });
   
  let alloyTouch;
   
  function initTouch(vertical, val) {
    let scrollDis = app.stage.width - max;
    alloyTouch = new AlloyTouch({
      touch:"body", //反馈触摸的dom
      vertical: vertical, //不必需,默认是true代表监听竖直方向touch
      min: -app.stage.width + max, //不必需,运动属性的最小值
      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);      
      }
   })
  }

 

6. 初始化动画

时间轴准备好了。就可以定义动画,将动画加到时间轴上了,表示在时间轴的哪个位置开始播放动画。动画这里用到的是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的计算规则:

1) delay = 开始播放动画时的滚动距离 / 可滚动总长度

delay是动画开始的时间

2) duration = (结束播放动画时的滚动距离 - 开始播放动画时的滚动距离) / 可滚动总长度

duration是动画持续的时间

 const animationsOptions = {  // 精灵动画集合
    windows:[{
      prop:'scale',  // 这里有个prop,有些人没有注意到这是什么
      delay:0.05,
      duration:0.3,
      to:{x:3,y:3,ease:Power0.easeNone}  // 在这里注意一下 ease:Power0.easeNone 是缓动函数
    },{
      delay:0.1,
      duration:0.1,
      to:{alpha:0}
    }],
    talk_1:[{
      delay:0.15,
      duration:0.1,
      from:{width:0,height:0,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);
        }
      }
    }
    // 特殊动画特殊处理
    let act = TweenMax.to(scenes.scene1,0.3,{x:2400});
    let tm = new TimelineMax({delay:0.25});
    tm.add(act,0);
    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];
          }

 

7. 舞台的整体缩放与旋转

设计图是 1334*750. 不修改图片大小,直接将图片精灵加入舞台里,最后对这个舞台进行整体缩放,这样就省心省力很多。需要对当前设备的横屏还是竖屏进行检测。

实现步骤:

1)获取整个屏幕的宽高;

  const w = document.body.clientWidth,   // 或者  window.innerWidth
      h = document.body.clientHeight;  // 或者  window.innerHeight

 

2)判断宽高大小,取最小的边 作为舞台的高,与 750做比较,得出缩放比例;

  const min = (w<h)?w:h;
  const max = (w>h)?w:h;
  let scale = min/750;  // 根据设计稿尺寸进行缩放比例调整
  console.log(w,h,min,"放大系数:",scale);
  app.stage.scale.set(scale,scale);  // 根据屏幕实际宽高放大舞台

 

3)对舞台进行旋转;

  if (w<h) {   // 根据横屏竖屏效果旋转舞台   // 当前是竖屏
    app.stage.rotation = 1.5708;  // 这个角度很迷。微调出来最好的效果了
    app.stage.pivot.set(0.5);
    app.stage.x = w;
    initTouch(true,'y');
  } else {   // 当前是横屏
    initTouch(false,'x');
  }

 

4)判断设备状态的切换

  //判断手机横竖屏状态:
  window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", function() {
      if (window.orientation === 180 || window.orientation === 0) {
          alert('竖屏状态!');
          location.reload();
      }
      if (window.orientation === 90 || window.orientation === -90 ){
          alert('横屏状态!');
          location.reload();
      }
  }, false); 

 

8.添加背景音乐

在不同的进度处播放不同的背景音乐,只需在之前的initTouch的change回调函数里调用播放背景音乐的函数 playAudio(progress),重点处理一下playAudio(progress)函数的编写即可。

 function initTouch(vertical,val){
    let scrollDis = app.stage.width-max-max;
    alloyTouch = new AlloyTouch({
      touch:"body",//反馈触摸的dom
      vertical: vertical,//不必需,默认是true代表监听竖直方向touch
      min:-app.stage.width+max+max,
      maxSpeed: 1,
      max: 0, //不必需,滚动属性的最大值
      bindSelf: false,
      initialValue: 0,
      change:function(value){
        app.stage.position[val] = value - max;
        let progress = -value/scrollDis;
        progress = progress < 0 ? 0 : progress;
        progress = progress > 1 ? 1 : progress;
        timeline.seek(progress);
        console.log(value,progress);
       
        // 播放背景音乐
        playAudio(progress);
   
      }
   })
  }

 

播放背景音乐的处理相对比较简单粗暴,将就看吧。

function playAudio(progress){
  if (progress>=0.08 && progress<=0.081) {
    playBgmAfterLoading('wechat');
    setTimeout(function(){
      tickerPhone.stop();
      playBgmAfterLoading('talk_5','talk_6',2000);
    },2000)
  }
  if (progress>=0.227 && progress<=0.23) {
    playBgmAfterLoading('talk_7');
  }
  if (progress>=0.357&& progress<=0.36) {
    playBgmAfterLoading('talk_8','talk_9',3000);
  }
  if (progress>=0.444 && progress<=0.45) {
    playBgmAfterLoading('talk_10');
  }
  if (progress>=0.433 && progress<=0.44) {
    actTrumpet.play();
    playBgmAfterLoading('train','train1',4000);
    setTimeout(function(){
      actTrumpet.pause();
    },8000)
  }
}
 
function playBgmAfterLoading(e,next,wait) {
    playBgm(e);
    if (next) {
      setTimeout(function(){
        playBgm(next);
      },wait);
    }
}
 
function playBgm(e){
  let audio = document.getElementById(e);
  if (typeof WeixinJSBridge == "object" && typeof WeixinJSBridge.invoke == "function") {
      WeixinJSBridge.invoke('getNetworkType', {}, function (res) {
          // 在这里拿到 e.err_msg, 这里面就包含了所有的网络类型
          // alert(res.err_msg);
          audio.play();
      });
  } else {
    audio.play();
  }
}

 

总结

完结撒花,待优化。下次遇到新奇玩意就来续更。

 

后记

还有一个更加完整的demo作品(代码仓库地址):https://gitee.com/wuchunling/life-amp-activity

(示例:扫码查看完整demo)

或者直戳:http://pixi.chunling.online/

 

// 续更 2019-09-08

最近陆陆续续收到网友的疑问,对于一些值的计算不甚清楚,我也觉得自己这篇博文写得不够好,没有把一些细节展开说。有必要整理一下这些疑问。

很高兴收到一封邮件,有人对pixiJS的demo提出了疑问,我也回头看了自己的代码,代码确实有bug。因为这只是我的练手demo,很多细节没有去深究,我还是建议大家看https://gitee.com/wuchunling/life-amp-activity这个仓库的代码。代码差不多,但是这个更完整,更完善,也修复了一些小bug,源代码看public文件夹即可,不需要看dist文件夹哦,要看dist文件夹,建议clone代码后再gulp dist一下。

(1)运动属性的最小值应该怎么计算?

  function initTouch(vertical,val){
    let scrollDis = app.stage.width-max-max;
    alloyTouch = new AlloyTouch({
      touch:"body",//反馈触摸的dom
      vertical: vertical,//不必需,默认是true代表监听竖直方向touch
      min: -app.stage.width + max,  // 这个值应该怎么计算呢??????????????????????
      maxSpeed: 1,
      max: 0, 
      bindSelf: false,
      initialValue: 0,
      change:function(value){
        app.stage.position[val] = value - max;
        let progress = -value/scrollDis;
        progress = progress < 0 ? 0 : progress;
        progress = progress > 1 ? 1 : progress;
        timeline.seek(progress);
        console.log(value,progress);
       
        // 播放背景音乐
        playAudio(progress);
   
      }
   })
  }

看看这段代码,new AlloyTouch({})里的min-max其实就是可滚动的最大值跟最小值。

 

向左滚动,滚动距离为负数,向右滚动,滚动距离为正数。所以,假如我们的整个动画场景的可滚动距离为x。则 min为-x,max为0。

剩下的疑问就是怎么计算这个可滚动距离??

 

以demo的四字魔咒举例,场景一二的长宽如下:

 

 

本来如果是简单的设计,整个舞台的长度就是场景一的长度加场景二的长度。

但是因为场景一与场景二存在叠加,母亲的精灵图要放置在窗户处。所以整个舞台的长度就是4971

 

 

从上图可以得出设计图的舞台总长度就是4971 ,但是实际的总长度经过缩放比的计算后有所变化,通过app.stage.width可以取得舞台的实际总长度。

所以,我们要知道舞台的实际总长度,不需要去计算设计图每个场景的长度,直接通过app.stage.width去获取就可以了。

知道了舞台的实际总长度,那么舞台的可滑动距离就是舞台的总长度减去一整屏的长度。(不知道这句话好理解不。。。。)

屏幕长度如何获取呢?

// 创建PIXI应用
const w = document.body.clientWidth,
      h = document.body.clientHeight;
let app = new PIXI.Application({
  width:w,
  height:h,
  backgroundColor:0xd7a664,
  forceCanvas:true
});

// 获取屏幕宽高,判断横屏还是竖屏
const min = (w<h)?w:h;
const max = (w>h)?w:h;

let scale = min/750;  // 根据设计稿尺寸进行缩放比例调整
console.log(w,h,min,"放大系数:",scale);

 

这个max就是我们的屏幕长度,所以可滑动距离就是 app.stage.width - max

而min需要取负值,就是 -app.stage.width + max

 

(2)为什么后面会剧烈抖动??

剧烈抖动是因为可滚动距离计算错误,这个问题就回到了第一个问题。

我当初在写练手dmeo时,很多错误没有及时纠正,因为后续工作忙就没有去改正。我也没想料想到有那么多人关注我的博文。现在代码已经更正错误。

但是我还是建议大家看“春节温暖回家路”的作品的源代码。相信大家看了就会有疑问,为什么这个作品的min,反而是 -app.stage.width + max + max??因为这个作品的设计,第一屏是不允许滚动,而是通过一个点击事件,直接进入第二屏。所以可滚动距离会减掉两个屏的长度。

没有特殊设计,一般情况下就是-app.stage.width + max

 

(3)动画的delay与duration如何计算???

delay:动画开始的时间

duration:动画持续的时间

在h5一镜到底中,时间的概念其实就是滑动距离。总滑动距离就是时间1。举个例子,假如可滑动的总距离是100m,滑动距离为20m时开始播放动画,即delay为 0.2。滑动到50m时停止播放动画,那么动画持续时间就是0.5-0.2=0.3

其实这个相对简单,我是建议大家不要去计算太麻烦了,直接通过console.log去获取。滑动的回调函数会实时返回滑动的距离,通过计算可得出当前的滑动进度progress。我们通过拖动,可以得出输出结果。拖动到要开始播放动画的地方,获取progress1,这个动画的delay就是progress;拖动到动画要停止的地方,获取progress2。所以动画的duration就是progress2-progress1

function initTouch(vertical,val){
  let scrollDis = app.stage.width - max
  console.log('舞台总长度', app.stage.width)
  console.log('屏幕长度', max)
  console.log('可滚动的距离', scrollDis)
  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;
      console.log(progress)  // 这个console超级有用!!!!帮助我们得出动画的delay与duration!!!!!
      progress = progress < 0 ? 0 : progress;
      progress = progress > 1 ? 1 : progress;
      timeline.seek(progress);
    }
 })
}

 

 

// 续更 2021-06-18

(1)怎么设置一个元素不会随着画布的滚动而跟随它滚动 而是让他放大?

怎么设置一个元素不会随着画布的滚动而跟随它滚动 ,让他一直固定在屏幕中间呢?

 

陆续收到别人的疑问。这里追加一下解答以及我的处理方法(PS:有更好处理方法的希望大家评论区分享出来)

 

先说清楚一个概念

元素画在画布里,本来是在画布里的固定的某个位置
我们滑动画布,自然元素消失在视窗(因为元素位置是固定的)

【不会随着画布的滚动而跟随它滚动 ,让他一直在中间】
所以这个效果的背后的本质是,滑动画布,让元素也跟着滑动,
让元素的滑动速度跟画布的滑动速度保持一致

这样元素就会跟画布保持相对静止,即肉眼看到的相对视窗的固定效果
 

所以我们要让它动起来呀

从位移动画下手,计算好delay和duration,x的值,(根据实际效果调整ease)

让他似动非动,跟视窗保持相对静止,完美~

 

这里要特别注意一点,如果我们要让元素滑动一直保持在画布中间,不应该把元素放在某个场景里,而是要把元素独立出来,单独放在舞台上,这样子它才能穿梭过很多个场景

这是比较容易犯的错误

 

如何把精灵元素加到舞台上?前面的概念我提到舞台也是容器的一种,可以改造一下我们的通用函数

只要我们在配置精灵参数的时候加上 stage:true 就表示它独立在舞台上

 

 

另外,如何更快的找到duration值呢?

元素肯定有开始位置和结束位置,先画出它开始位置的x,y坐标,画出他结束位置的x,y坐标

模拟滑动到他的结束位置,此时控制台输出的进度就是他的duration值

(对于线性动画,请使用Power0.easeNone)

 

(2)pixi里怎么做轮播图?

这个问题咋一看难度就很大!总感觉实现不了,目前研究比较浅,暂时没有解决思路。有后续答案再更新。

也希望有解决方案的小伙伴评论区分享一下

 

  • 17
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 44
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 44
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值