前言
事件是这样的,上个月接了一个活儿。客户要咱们模拟一个国外的网站 https://www.vanmoof.com/en-NL... (须要迷信上网)
为他们做几个展现动效。
的确很酷!这个我的项目一共6个动效,客户给的工期是15天,咱们理论花在真正写代码上的工期大略一个礼拜左右。报酬2个W
。
可能有同学感觉赚得也不多...确实不算多,但接活儿赚钱只是一方面,更重要的是在这个我的项目里我带着很多同学们一起实战取得了贵重的我的项目实战经验。
并且这种动效开发,很多只会用vue
,react
写页面的前端同学很少能接触到。
想一起接外包私活儿的敌人加v吧:dashuailaoyuan
最终成果
先看最终成果
还原得还是很靠近的吧,上面我就来一步步教大家如何写
筹备好根底环境
https://github.com/ezshine/YC...
我也给大家筹备好了一个空白我的项目,大家能够clone
这个仓库一步步来实现。
空白我的项目很简略,引入了两个库,一个叫PIXI
,是性能十分优良的webGL2d
渲染引擎。另一个GSAP
,是十分优良的前端动画引擎,这个库十分老牌,从我还在做FLASH
的时代就始终用它。
<script src="https://pixijs.download/release/pixi.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"></script>
而后大家装置一下 VSCode
里的 live preview
插件,肯定要留神是微软官网出品的,不要装了山寨版。
本教程视频版
https://www.bilibili.com/vide...
创立PIXI利用
class BrakeBanner{ constructor(selector){ //初始化PIXI利用,将舞台设置为1920x1080 this.app = new PIXI.Application({ width:window.innerWidth, height:window.innerHeight, backgroundColor: 0xffffff, resizeTo:window }); document.querySelector(selector).appendChild(this.app.view); this.stage = new PIXI.Container(); this.app.stage.addChild(this.stage); }}
加载图片资源
//创立资源加载器this.loader = new PIXI.Loader();//向资源加载器增加资源 key,paththis.loader.add('btn.png', 'images/btn.png');this.loader.add('btn_circle.png', 'images/btn_circle.png');this.loader.add('brake_bike.png', 'images/brake_bike.png');this.loader.add('brake_handlerbar.png', 'images/brake_handlerbar.png');this.loader.add('brake_lever.png', 'images/brake_lever.png');this.loader.load();this.loader.onComplete.add(()=>{ this.show();});this.loader.resources[key];
通过图片创立精灵元素
const btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);btnContainer.addChild(btnImage);const btnCircleImage = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);btnContainer.addChild(btnCircleImage);
转换中心点设置
btnImage.pivot.x = btnImage.pivot.y = btnImage.width/2;btnCircleImage.pivot.x = btnCircleImage.pivot.y = btnCircleImage.width/2;btnContainer.x = 200;btnContainer.y = 200;
初尝动效GSAP
btnCircleImage.scale.x = btnCircleImage.scale.y = .8;gsap.to(btnCircleImage,{duration:1,alpha:0,repeat:-1})gsap.to(btnCircleImage.scale,{duration:1,x:1.1,y:1.1,repeat:-1})
把按钮相干的代码整合进 createActionButton 函数中
车架和刹车把手
const bikeContainer = new PIXI.Container();this.stage.addChild(bikeContainer);//整体进行一个缩放bikeContainer.scale.x = bikeContainer.scale.y = .3;const bikeImage = new PIXI.Sprite(this.loader.resources['brake_bike.png'].texture);bikeContainer.addChild(bikeImage);const bikeHandlerImage = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture);bikeContainer.addChild(bikeHandlerImage);
车把手
const bikeLeverImage = new PIXI.Sprite(this.loader.resources['brake_lever.png'].texture);bikeContainer.addChild(bikeLeverImage);bikeLeverImage.x = 255+454;bikeLeverImage.y = 450+462;bikeLeverImage.pivot.x = 454;bikeLeverImage.pivot.y = 462;bikeLeverImage.rotation = Math.PI/180*-35;
当初咱们这个刹车的旋转角度曾经OK了。
增加按钮交互
actionBtn.on("mousedown",()=>{ bikeLeverImage.rotation = Math.PI/180*-35;})actionBtn.on("mouseup",()=>{ bikeLeverImage.rotation = 0;})
退出GSAP
actionBtn.on("mousedown",()=>{ gsap.to(bikeLeverImage,{duration:.6,rotation : Math.PI/180*-35});})actionBtn.on("mouseup",()=>{ gsap.to(bikeLeverImage,{duration:.6,rotation : 0});})
是不是更有感觉了?OK,到这里大家对PIXI和GSAP曾经算是入了门了,接下来咱们实现这个速度线粒子成果。
将车体永远置于画布右下角
let resize = () => { bikeContainer.x = window.innerWidth-bikeContainer.width; bikeContainer.y = window.innerHeight-bikeContainer.height;}window.addEventListener('resize', resize);resize();
速度粒子成果
怎么做?咱们再仔细观察一下参考案例
- 首先得要有粒子,也就是小圆点
- 小圆点的色彩有好几种
- 向某一个角度始终挪动
- 超出底部边界后回到顶部继续移动
- 按住鼠标进行
- 进行的时候还有一点回弹的成果
- 松开鼠标持续
OK,一条一条来实现
创立粒子容器和对象数组
let particleZoneSize = window.innerWidth;let particlesContainer = new PIXI.Container();this.stage.addChild(particlesContainer);let particles = [];for(let i=0;i<10;i++){ let gr = new PIXI.Graphics(); gr.beginFill(0xff0000); gr.drawCircle(0,0,6); gr.endFill(); let parItem = { sx:Math.random()*particleZoneSize, sy:Math.random()*particleZoneSize, } gr.x = parItem.sx; gr.y = parItem.sy; particlesContainer.addChild(gr);}
粒子随机色彩
const colors = [0xf1cf54, 0xb5cea8, 0xf1cf54, 0x818181, 0x000000];for(let i=0;i<10;i++){ //... gr.beginFill(colors[Math.floor(Math.random() * colors.length)]); //...
粒子的继续挪动
向一个角度始终挪动大家晓得算法吗?
gr.x -= cos(angle) * 1;gr.y -= sin(angle) * 1;
上述办法不过多的阐明,我教大家一个更简略的办法。旋转容器,其实咱们只有管制粒子向下挪动即可,而后将这个容器旋转一个角度,那么所有粒子的挪动方向也就都扭转了。
particlesContainer.pivot.set(particleZoneSize / 2, particleZoneSize / 2);particlesContainer.rotation = (35 * Math.PI) / 180;particlesContainer.x = particleZoneSize/2;particlesContainer.y = particleZoneSize/2;
旋转的时候,咱们依然须要留神一下将旋转的圆心设置到粒子容器的中心点上。
OK,当初让咱们让粒子挪动起来
通过gsap.ticker.add();
增加一个屏幕渲染机会的钩子,就是requestAnimationFrame
事件。
function loop(){ for (let i = 0; i < particles.length; i++) { //拿到粒子item和小圆点实例 let pItem = particles[i]; let gr = pItem.gr; //让粒子走起来 gr.y += 1; //当粒子挪动超出范围时回到顶部 if(gr.y>innerWidth)gr.y=0; }}gsap.ticker.add(loop);
这个速度显然不够快,咱们能够多调整一下每次挪动的间隔看看成果。稍微有点感觉了,对吧
而后咱们还心愿速度是由慢到快的,所以咱们定义一个speed的变量,让它从0 开始一直变大,直到一个固定值。
let speed = 0;function loop(){ speed+=.5; speed = Math.min(speed,20); for (let i = 0; i < particles.length; i++) { //拿到粒子item和小圆点实例 let pItem = particles[i]; let gr = pItem.gr; gr.y += speed; if(gr.y>innerWidth)gr.y=0; }}
感觉更加难受了一些,但还是不到位,当初我来教大家一些魔法
人的眼睛察看一个球体直线运动时,球体会产生什么变动?
它会缓缓被拉长,变细对不对?
所以咱们用代码来模仿这个拉长变细的成果。
if(speed>=20){ gr.scale.y = 40; gr.scale.x = 0.03}
OK ,接下来咱们写进行,进行的时候,有一个回弹的成果。
有进行就有启动,把之前的代码定义成一个启动函数,loop函数不变
function start(){ speed = 0; gsap.ticker.add(loop);}
进行粒子静止
function pause(){ //先移除掉requestAnimationFrame的侦听 gsap.ticker.remove(loop); for (let i = 0; i < particles.length; i++) { let pItem = particles[i]; let gr = pItem.gr; //复原小圆点的拉伸比例 gr.scale.x = gr.scale.y = 1; //复原小圆点透明度 gr.alpha = 1; //让所有的小圆点应用弹性补间动画回到初始坐标 gsap.to(gr, { duration: 0.6, x: pItem.sx, y: pItem.sy, ease: "elastic.out", }); }}
OK,把这个两个函数和咱们按钮的按下和松开事件关联上。这个动效就做好了。
最终成果
咱们还原得还是相当能够的吧,动效根本都是这样的原理,心愿大家都能够本人去练习一下。
在公众号里搜 大帅老猿
,在他这里能够学到很多货色