Three.js 实现年会3D抽奖页面

 
 
 
 
 
 

大厂技术  高级前端  Node进阶

点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群

突然翻到在之前公司写的抽奖软件(用于公司年会)。觉得挺感慨的,TM的一共30+人,抽15左右,代码还是我写的,就是抽不中我。(真的是,涨了人品,失了智)

一、效果

效果如下:

a0204b3b7cd84bfe560251ad9f882d11.gif

二、基础效果

元素周期表


照片墙?抽奖?写之前的那段时间,刚好逛博客,看到别个大神写的threejs版《元素周期表》,这效果大体有点近似。

ab4371f79de56cf7f2f5c87c3279f909.png

99dfb3d2e4b0a8b26d56ba5240401d80.png

最重要的2块有了。

然后再把元素、tip啥的改成抽奖用的照片和名字,使用CSS3DObject 进行渲染;

// table
  for ( var i = 0; i < table.length; i += 2 ) {
    // 每个图标的盒子
    var element = document.createElement( 'div' );
    element.className = 'element';
    element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';
    // 索引
    var number = document.createElement( 'div' );
    number.className = 'number';
    number.textContent = (i/2) + 1;
    element.appendChild( number );
    // 图片
    var symbolBox = document.createElement( 'div' );
    var symbol = document.createElement( 'img' );
    symbolBox.className = 'symbolBox';
    symbol.className = 'symbol';
    symbol.src = table[i];
    symbolBox.appendChild(symbol);
    element.appendChild( symbolBox );
    // 姓名
    var details = document.createElement( 'div' );
    details.className = 'details';
    details.innerHTML = table[ i + 1 ];
    element.appendChild( details );
    // 图标变成3d内的对象,放入场景中
    var object = new THREE.CSS3DObject( element );
    object.position.x = Math.random() * 3400 - 1700;
    object.position.y = Math.random() * 3400 - 1700;
    object.position.z = Math.random() * 3400 - 1700;
    object.name=table[ i + 1 ];
    scene.add( object );
    objects.push( object );
    // 根据索引,定制位置
    var object = new THREE.Object3D();
    var iy = Math.floor((i/2)/9);
    var ix = (i/2)%9;
    object.position.x = (ix * 140 ) -540;
    object.position.y = - ( iy * 180 ) + 480;
    targets.table.push( object );
  }

星空底图、星星动画

光是元素周期表的,太素了。背景肯定要选个星空啥的,最好再来一条银河!

高清的星空壁纸,太大了,如果是有点动效的gif,那体积更大。而像素低点的,效果根本不行。(适应1920屏幕的)

于是,我使用 低像素图 + canvas 星星动图来完善背景!

4e5851efaa795185202b0ea23236fa1e.png

e225b0c2798341261b67deb6141961cc.gif

星星canvas 动画。(也是网上找的,链接忘了)(感谢之前的大神 + 1)

var canvas = document.getElementById('canvas'), 
  ctx = canvas.getContext('2d'), 
  w = canvas.width = window.innerWidth, 
  h = canvas.height = window.innerHeight, 


  hue = 217, 
  stars = [], 
  count = 0, 
  maxStars = 1300;//星星数量 


var canvas2 = document.createElement('canvas'), 
  ctx2 = canvas2.getContext('2d'); 
canvas2.width = 100; 
canvas2.height = 100; 
var half = canvas2.width / 2, 
  gradient2 = ctx2.createRadialGradient(half, half, 0, half, half, half); 
gradient2.addColorStop(0.025, '#CCC'); 
gradient2.addColorStop(0.1, 'hsl(' + hue + ', 61%, 33%)'); 
gradient2.addColorStop(0.25, 'hsl(' + hue + ', 64%, 6%)'); 
gradient2.addColorStop(1, 'transparent'); 


ctx2.fillStyle = gradient2; 
ctx2.beginPath(); 
ctx2.arc(half, half, half, 0, Math.PI * 2); 
ctx2.fill(); 


// End cache 


function random(min, max) { 
  if (arguments.length < 2) { 
    max = min; 
    min = 0; 
  } 


  if (min > max) { 
    var hold = max; 
    max = min; 
    min = hold; 
  } 


  return Math.floor(Math.random() * (max - min + 1)) + min; 
} 


function maxOrbit(x, y) { 
  var max = Math.max(x, y), 
    diameter = Math.round(Math.sqrt(max * max + max * max)); 
  return diameter / 2; 
  //星星移动范围,值越大范围越小, 
} 


var Star = function() { 


  this.orbitRadius = random(maxOrbit(w, h)); 
  this.radius = random(60, this.orbitRadius) / 8;  
  //星星大小 
  this.orbitX = w / 2; 
  this.orbitY = h / 2; 
  this.timePassed = random(0, maxStars); 
  this.speed = random(this.orbitRadius) / 50000;  
  //星星移动速度 
  this.alpha = random(2, 10) / 10; 


  count++; 
  stars[count] = this; 
} 


Star.prototype.draw = function() { 
  var x = Math.sin(this.timePassed) * this.orbitRadius + this.orbitX, 
    y = Math.cos(this.timePassed) * this.orbitRadius + this.orbitY, 
    twinkle = random(10); 


  if (twinkle === 1 && this.alpha > 0) { 
    this.alpha -= 0.05; 
  } else if (twinkle === 2 && this.alpha < 1) { 
    this.alpha += 0.05; 
  } 


  ctx.globalAlpha = this.alpha; 
  ctx.drawImage(canvas2, x - this.radius / 2, y - this.radius / 2, this.radius, this.radius); 
  this.timePassed += this.speed; 
} 


for (var i = 0; i < maxStars; i++) { 
  new Star(); 
} 


function animation() { 
  ctx.globalCompositeOperation = 'source-over'; 
  ctx.globalAlpha = 0.5; //尾巴 
  ctx.fillStyle = 'hsla(' + hue + ', 64%, 6%, 2)'; 
  ctx.fillRect(0, 0, w, h) 


  ctx.globalCompositeOperation = 'lighter'; 
  for (var i = 1, l = stars.length; i < l; i++) { 
    stars[i].draw(); 
  }; 


  window.requestAnimationFrame(animation); 
} 


animation();

抽奖的照片

抽奖的照片,还得在照片球的中间,位置。(也是在3d内)

同样的方式,使用CSS3DObject进行渲染

// 创建切换图片
var elements = document.createElement( 'div' );
elements.className = 'changeImgBoxs';
// element.style.backgroundImage = "url(./img/pic.png)";
elements.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';




var symbolBox = document.createElement( 'div' );
var symbol = document.createElement( 'img' );
symbol.setAttribute("id", "changeImg");
symbolBox.className = 'symbolBox2';
symbol.className = 'symbol2';
symbol.src = table[0];
symbolBox.appendChild(symbol);
elements.appendChild( symbolBox );




var details = document.createElement( 'div' );
details.setAttribute("id", "detailss");
details.className = 'details';
details.innerHTML = table[1];
elements.appendChild( details );




objectsss = new THREE.CSS3DObject( elements );
objectsss.position.x = 0;
objectsss.position.y = 20000;
objectsss.position.z = 0;
scene.add( objectsss );

三、动画

一切基础准备就绪,就差动画了。

照片墙、照片球、照片散乱状态的补间动画

点击抽奖:照片墙--->照片球

点击停止:照片球--->照片散乱   (感觉就像,照片球爆炸了一样)

threejs里面,补间动画,一般使用 tween.js

先存下照片墙、球、散乱三种状态的坐标点,然后切换抽奖状态时,同时切换,通过tween切换照片的位置,这样就实现了动画。

// 表格坐标 (在初始化)
object.position.x = Math.random() * 3400 - 1700;
    object.position.y = Math.random() * 3400 - 1700;
    object.position.z = Math.random() * 3400 - 1700;
    object.name=table[ i + 1 ];
    scene.add( object );
    objects.push( object );
    // 根据索引,定制位置
    var object = new THREE.Object3D();
    var iy = Math.floor((i/2)/9);
    var ix = (i/2)%9;
    object.position.x = (ix * 140 ) -540;
    object.position.y = - ( iy * 180 ) + 480;
    targets.table.push( object );




// 球的 照片坐标
var vector = new THREE.Vector3();
for ( var i = 0, l = objects.length; i < l; i ++ ) {
var phi = Math.acos( -1 + ( 2 * i ) / l );
var theta = Math.sqrt( l * Math.PI ) * phi;
var object = new THREE.Object3D();
object.position.x = 750 * Math.cos( theta ) * Math.sin( phi );
object.position.y = 750 * Math.sin( theta ) * Math.sin( phi );
object.position.z = 750 * Math.cos( phi );
vector.copy( object.position ).multiplyScalar( 2 );
object.lookAt( vector );
targets.sphere.push( object );
}
// 散乱随机位置
var vector = new THREE.Vector3();
for ( var i = 0, l = objects.length; i < l; i ++ ) {
var phi = Math.acos( -1 + ( 2 * i ) / l );
var theta = Math.sqrt( l * Math.PI ) * phi;
var object = new THREE.Object3D();
var py = Math.random() * 3400 - 1700;
if(py<400&&py>-400){    //防止停止时,图片位置不好挡住中央的图片中间空出点位置
if(py<0){
py-=400;
}else{
py+=400;
}
}
object.position.x = Math.random() * 3400 - 1700;
object.position.y = py;
object.position.z = Math.random() * 3400 - 1700;
object.lookAt( vector );
targets.chaos.push( object );
}

tweenjs 补件动画

// 切换状态时动画
function transform( targets, duration ,type) {
  var scale = 1;
  if(type==undefined){
    type=0;
  }
  TWEEN.removeAll();
  for ( var i = 0; i < objects.length; i ++ ) {
    var object = objects[ i ];
    var target = targets[ i ];
    new TWEEN.Tween( object.position )
      .to( { x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration )
      .easing( TWEEN.Easing.Exponential.InOut )
      .start();
    new TWEEN.Tween( object.rotation )
      .to( { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration )
      .easing( TWEEN.Easing.Exponential.InOut )
      .start();                                 
  }
  new TWEEN.Tween( this )
    .to( {}, duration * 2 )
    .onUpdate( render )
    .start();
}

抽奖时动画

这个时候的动画,主要是两个:照片切换、照片球的转动

照片切换,中间图片src  随机切换就好了

照片球转动?何必呢,转动相机不是更轻松?

于是使用:TrackballControls.js轨道控制器。转相机就好了嘛

var numsss =0,srcss='',txtsss='';
function movings(){
  // 相机旋转
  ang += Math.PI/50;
  camera.position.x = Math.cos(ang)*2000;
  camera.position.z = Math.sin(ang)*2000; 
  camera.position.y = 0;
  // 相机方向重置
  camera.up.x = 0;
  camera.up.y = 1;
  camera.up.z = 0;
  // 图片方向固定
  objectsss.rotation.y =-ang+Math.PI/2;
  //中间图片切换
  numsss = Math.floor(Math.random()*tableLens);
  if(numsss==tableLens){
    numsss = tableLens-1;
  }
  srcss = table[numsss*2];
  txtsss =  table[numsss*2+1];
  changeImg.src = srcss;
  detailss.innerHTML = txtsss;
}

抽奖结束后中奖图片放大

抽奖结束,停止转动,中奖图片放大

// 停止时,图片放大动画
function objDeal(obj,nums){
  var option = {
    x: obj.scale.x,  
    y: obj.scale.y, 
    z: obj.scale.z,                 
  };
  var tween = new TWEEN.Tween(option).to({
    x:nums,
    y:nums,
    z:nums,
  },300).delay(100).onUpdate(function() {
    obj.scale.x = this.x;
    obj.scale.y = this.y;
    obj.scale.z = this.z;
    isMoving = true;
  }).onComplete(function(){
    isMoving = false;                
  }).start();
}

四、完善细节、添加音乐

抽奖嘛,光效果是不够的,还得有点音乐才好。(音乐是当时人事姐姐提供的)

目前添加2个音乐:点击抽奖后,激动人心的音乐《猪突猛进》。中奖时的音乐,一段布灵布灵的铃声。

<!--抽奖音乐标签-->
<audio id="music" src="./music/04-抽奖音乐 猪突猛進.mp3" loop></audio>
<!--抽奖音乐标签-->
<audio id="music2" src="./music/9629.mp3"></audio>




//开始
start:function(){
  if(vm.spic.img!=''){
    alert("抽完咯~~~完咯~~~咯~~~,在中奖名单中清空,再来一次?");
    return;
  }
  moving = true;              
  objectsss.position.y = 0;
  transform( targets.sphere, 1000 );
  objDeal(objectsss,1);
  music.play(); // 开始音乐
  this.ckPrice();           
},
//结束
closes:function(){
  music.pause(); // 关闭音乐
  if(!moving){
    return;
  } 
  music2.play(); // 播放中奖音乐
  moving = false;
  this.choosePerson();
  transform( targets.chaos, 250 ,1);
  objDeal(objectsss,1.8);   
},

五、结束语

代码几年前写的,挺垃圾的,没考虑性能,也没有做啥优化。只是当时做的思路还算比较清晰。给大家看下,提供些相关业务的思路。

源码:

https://github.com/baiDog/something/tree/master/test

作者:狗贼
链接:https://juejin.cn/post/7057759297811251231

Node 社群

 
 

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

8e4be9e5ee41318676ecc199fc54a177.png

“分享、点赞、在看” 支持一下

d58f7de2f2cf557d273c68bba3ec08ad.gif

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
全部代码为纯html+JS完成的,友通数码港IT团队成员-TONY编写。 1、号码抽取等级、数量自由定义 可在系统上配置或修改ini.js 2、全屏幕显示1024X768像素,界面美观大方,适合于投影仪投射晚会晚宴豪华场所抽奖系统。 3、可自由选择键盘及鼠标双重操作方式。 4、只需要IE浏览器即可完成所有执行。 5、未到场人员可以双击数字单独重新抽取。 6、自由配置一次抽一组或者抽一个。 7、自由定义过滤没有发到的号码段。 8、屏蔽键盘上无用键以免人员误操作。 9、抽奖完成后可以选择保存结果以便日后统计分析。 环境要求: 系统:Winodws 环境:正常PC系统即可。 分辨率:默认(1024 X 768),样式可自定义 历史版本V1.X概况: V1.0 增加了键鼠屏蔽参数 ----------------------------------------------- V1.1 2010-12-28 增加了 是否可以重复抽取配置选项 ----------------------------------------------- V1.2 2011-01-05 增加了 每组是否逐个抽取配置选项 2011-01-05 对逐个抽取的号码显示做了美化 ----------------------------------------------- V1.2.1 2011-01-08 修复了window.obo = 1;时出现重复数字的错误,V1.1之前版本无此错误 ~感谢石路街道的领导提醒:) 2011-01-08 增加了可选滚动姓名的功能 配置 m_name=[]; 即可 2011-01-08 增加双击数字单独重抽时的提示选项“此号码在以后的环节是否还有机会抽到!” 2011-01-08 修改了结果显示部分的错位样式 2011-01-08 修复了组抽号码重复错误问题 ----------------------------------------------- V1.2.2 2011-01-14 兼容了火狐浏览器,但本人建议仍然用IE,要用火狐只有按F11手动全屏了 2011-01-14 部分键 F5、退格、Ctrl+R、Ctrl+N、Shift+F10、Alt+F4 被屏蔽,避免一些意外发生 2011-01-14 优化了一些繁琐的结构,效率有所提升! 2011-01-16 去掉了配置参数 r、r_name,奖项配置变为window.ini,使配合样式表更加灵活 ----------------------------------------------- V1.2.2 2011-02-10 在配置文件增加了速度控制变量,以便于在不同机器环境中做速度微调 ----------------------------------------------- V1.2.3 2011-08-10 政府部门使用未对外发布定制增强版本 ----------------------------------------------- V2.0 2011-09-13 突破性的解决了手工修改代码烦恼增加了图形化界面 2011-09-13 可以通过图形化设置界面保存配置文件(只在IE下有效) 2011-09-13 配置文件从HTML内提取出来,变为ini.js 2011-09-13 图形化配置界面做了一些兼容性优化 2011-09-13 奖项框除了可以填写样式名还可以直接填写样式代码,程序可以自动识别 2011-09-13 增加了手动和自动整理列表功能 ----------------------------------------------- V2.0.1 2011-12-09 bug处理:列表获取处理错误,已修正! ----------------------------------------------- V2.0.2 2011-12-31 bug处理:window.obo = 1;解决了不该有的错误提示!(未发布,直接发布下一版) ----------------------------------------------- V2.1 2012-01-05 革命性的采用exe直接执行hta抛弃常见的浏览器模式,以免兼容性问题太多 ----------------------------------------------- V3.0.0 2017-01-23 应用户需求解决了,解决了每次抽一个的bug逻辑 好消息:本人近期有时间做代码了,今年将适时放出移动端互动版本,基于python核心开发,脱离本机端浏览器限制,并增加照片,微信互动等对应的功能…请期待

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值