快捷搜索:  汽车  科技

3d动画投影特效(一起炫起来--)

3d动画投影特效(一起炫起来--)创建粒子对象// pos为粒子的数据,单项数据分别含x y z坐标值 {x y z} const all = []; const yObjs = {}; const xObjs = {}; const zObjs = {}; // 对所有点按照x y z值进行分组,分组后的数据分别存在xObjs yObjs zObjs for (var i = pos.length-1; i>=0; i--) { const p = pos[i]; const yKey = getKey(p.y); const xKey = getKey(p.x); const zKey = getKey(p.z); if (!yObjs[yKey]) { yObjs[yKey] = []; } if (!xObjs[xKey]) { xObjs[xKey] = []; } if (!zObjs[

效果演示

3D粒子动画效果如下: embed: sky.mov

本示例所构成并不复杂,主要分成以下几部分:

  • 渐变的背景
  • 不断切换的3D粒子模型(入场的散点也是粒子模型的一种形态)
  • 同时切换的文字

实现主要是基于threejs做的,接下来我会分别讲解各部分的实现,不过不会介绍基础。基础内容可以去官网找到~

渐变的背景

scene的background可以接收Color、Texture或CubeTexture。在本示例中,我们使用Texture就可以达到渐变效果。Texture可以接收canvas或图片,此处我们使用canvas画出渐变效果,具体代码如下:

const canvas = document.createElement('canvas'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; const context = canvas.getContext('2d'); const gradient = context.createLinearGradient(0 0 width 0); gradient.addColorStop(0 '#4e22b7'); gradient.addColorStop(1 '#3292ff'); context.fillStyle = gradient; context.fillRect(0 0 canvas.width canvas.height); const texture = new THREE.Texture(canvas); texture.needsUpdate = true; const scene = new THREE.Scene(); scene.background = texture;

减少模型粒子数

接下来给大家介绍的是一个黑科技——减少模型粒子数。通常情况下设计师会给你符合设计稿内所需的模型,粒子数不会比你所需的多,但凡是有例外的时候。另外,粒子的多少是影响性能的关键之一,在这个示例中我测试过1W个粒子就使动画有卡顿感(当然这还跟运行的本本性能有很大的关系)。如果你正好有这方面的烦恼,那这个方法或许对你有用。

实现思路:在X Y Z轴上分别计算相邻点之间的距离,如果大于距离小于设定值则删除。具体代码如下:

// pos为粒子的数据,单项数据分别含x y z坐标值 {x y z} const all = []; const yObjs = {}; const xObjs = {}; const zObjs = {}; // 对所有点按照x y z值进行分组,分组后的数据分别存在xObjs yObjs zObjs for (var i = pos.length-1; i>=0; i--) { const p = pos[i]; const yKey = getKey(p.y); const xKey = getKey(p.x); const zKey = getKey(p.z); if (!yObjs[yKey]) { yObjs[yKey] = []; } if (!xObjs[xKey]) { xObjs[xKey] = []; } if (!zObjs[zKey]) { zObjs[zKey] = []; } const item = {x y z r g b}; yObjs[yKey].push(item); xObjs[xKey].push(item); zObjs[zKey].push(item); all.push(item); } // 对x y z值进行排序 const xKeys = orderAscKeys(xObjs); const yKeys = orderAscKeys(yObjs); const zKeys = orderAscKeys(zObjs); // 提取在x y z轴上需删除的坐标值 const xDels = getDelKeys(xKeys 4); // 4为点与点的间距 const yDels = getDelKeys(yKeys 4); const zDels = getDelKeys(zKeys 4); const result = []; for (var i = all.length-1; i>=0; i--) { const item = all[i]; if ( !(xDels.indexOf(getKey(item.x))>-1 || yDels.indexOf(getKey(item.y))>-1 || zDels.indexOf(getKey(item.z))>-1 )) { result.push(item); } } return result; function getKey(x) { let r = x.toFixed(1); if (r==='-0.0') { r = '0.0' } return r; } function orderAscKeys (obj) { return Object.keys(obj).sort((a b)=>{ return Number(a) - Number(b); }); } function getDelKeys(keys d) { const dels = []; keys.reduce((prev curr idx)=>{ let res = curr; if (curr-prev < d) { dels.push(curr); res = prev; } return res; }); return dels; }

注:此方法只有对粒子排序比较整齐的模型,如果模型本身的粒子排序混乱使用此方法的效果可能会不理想。

模型切换动画

创建粒子对象

状态切换

整个动画过程中有以下几种状态:

  1. 入场前奏动画(即散点状态),用于数据准备和动画过渡效果
  2. 模型切换动画进行时
  3. 模型切换间隔休息时

状态的管理与切换具体代码如下:

// 一切模型都已经准备妥当 if (this.objectsData && this.points && this.index !== undefined) { const config = this.config; const len = config.objs.length; const idx = this.index % len; // 当前模型序号 const item = this.objectsData[idx]; const geometry = this.points.geometry; this.points.material.needsUpdate = true; geometry.verticesNeedUpdate = true; geometry.colorsNeedUpdate = true; // 前奏因数据准备时间不较长,不额外加延迟时间 const delay = (this.index === 0) ? config.delay * 60 * 0 : config.delay * 60; if (item.waiting < delay) { // 等待时间,包含了前奏(1)和切换间隔时间(3) item.waiting ; } else if (item.waiting >= delay && item.count < config.totalCount - 1) { // 动画进行时(2) let nd; if (item.count === 0) { config.onLeave && typeof config.onLeave === 'function' && config.onLeave(); nd = idx; const prevIdx = (idx - 1 > -1) ? idx - 1 : len - 1; const prev = this.objectsData[prevIdx]; if (prev) { // 执行下一个模型切换动画时,将前一个的执行时计数和休息时计数都归零 prev.count = 0; prev.waiting = 0; } } // 执行动画 this.particleAnimation(this.points item nd); } else if (item.waiting >= delay && item.count >= config.totalCount - 1) { // 切换到下一个模型 this.index ; } }

执行动画

模型切换动画执行时时按批执行的,并不是所有粒子一起开始运动的。 这样做的好处是:

  1. 避免出现因单位时间(16.67ms)内因计算量过大而出现画面卡顿感;单位时间是由requestAnimationFrame执行的频率决定的。
  2. 分组动画会让动画更有层次感

/** * points: 粒子对象 * item: 当前模型数据 * idx: 当前模型序号 */ particleAnimation(points item idx) { const geometry = points.geometry; const vertices = geometry.vertices; const colors = geometry.colors; const len = vertices.length; if (item.count >= len) { return; } if (!item.vTween) { item.vTween = []; } const config = this.config; let isOdd = this.index % 2===0; // 每组动画执行的粒子个数 const cnt = 1000; for (let j = item.count l = item.count cnt; j < l && j < len - 1; j ) { const n = j % item.length; const p = item.data[n]; if (!p) { return; } // TWEEN只new一次,减少多次实例化的开销 if (!item.vTween[j] && vertices[j]) { item.vTween.push(new TWEEN.Tween(vertices[j]) .easing(TWEEN.Easing.Exponential.In) ); } item.vTween[j].stop(); // 奇偶序号的模型位置不一样,调整x坐标数据 const x = (isOdd) ? p.x : p.x-400; item.vTween[j].to({ x y: p.y z: p.z } config.speed).start(); } item.count = item.count cnt; }

TWEEN是一个补间动画库,使用方法也很简单,详情参见说明文档。TWEEN提供了以下几种方式,可以自行替换使用,如图:

3d动画投影特效(一起炫起来--)(1)

结束语

文字部分的动画是用css3实现的,网络上已经很多有关css3动画相关的文档就不再详述,这里就简单列出相关样式代码以作参考。

.leaving { transition: transform .7s opacity .7s; transform: translate3d(0px -205% 0); opacity: 0; } .entering { transition: opacity .7s; opacity: 1; }

至此整个动画效果中可能遇到的问题都已经一一阐述,有兴趣的同学可以自动动手尝试一下。如有更好的实现方式欢迎提出来共享。

希望本文能帮助到您!

点赞 转发,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓-_-)

关注 {我},享受文章首发体验!

每周重点攻克一个前端技术难点。更多精彩前端内容私信 我 回复“教程”

原文链接:https://github.com/ProtoTeam/blog/blob/master/201711/1.md

作者:蚂蚁金服数据体验技术团队

猜您喜欢: