其他
Three.js 实现年会3D抽奖页面
作者:狗贼
https://juejin.cn/post/7057759297811251231
突然翻到在之前公司写的抽奖软件(用于公司年会)。觉得挺感慨的,TM的一共30+人,抽15左右,代码还是我写的,就是抽不中我。(真的是,涨了人品,失了智)
元素周期表
最重要的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 );
}
星空底图、星星动画
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();
抽奖的照片
// 创建切换图片
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);
},
五、结束语