前言
前段时间笔者学习了threejs,了解了一些基本的使用方法,引起了想要做个相关的3Ddemo的兴趣,于是想到了做个3D的相框册,同时参考了网上一些其他笔者的方法自己手动实现了一下,以下是这个demo的相关步骤。为了方便主要在HTML中实现,并导入相应JS模块文件。
demo效果如下:
主要思路
- 首先先初始化下照片的大小、全局样式等,创建一个带有id为container空的容器,用来装3D化后的元素以及点击任意一张照片后放大显示此照片的容器,再进行主要的3D操作。
- 从npm官网中查找threejs、tweenjs包,并下载到本项目中,再引入相应的文件。
- 创建3D基本的三要素——场景(Scene)、相机(Camera)、渲染器(Renderer)。其中渲染器使用CSS3DRenderer,主要是为了通过CSS3的transform属性, 将层级的3D变换应用到DOM元素上;然后再引入轨道控制器OrbitControls,可以使得相机围绕目标进行轨道运动;初始化三位坐标Vector3和球的坐标Spherical,主要用来得到照片摆放的位置。
- 动态添加所需要的放置照片的dom结构——设置照片路径、大小以及类名(方便添加图片样式)。由于此结构为HTML元素,不能直接被添加到场景中,需要将其转化为3D对象(借助CSS3DObject),最后再将每个照片元素添加到场景中。
- 设置完图片的大小样式后,就要开始摆放它们对应的位置了。套用数学公式设置每张图片的位置,由于初始是这些图片在同一个位置,为了使得这些图片动画到指定位置,这里用到了TWEEN补间动画——tween引擎就可以计算从开始动画点到结束动画点之间值,来产生平滑的动画效果。这样就能形成一个球形的照片墙了!
- 然后编写animate函数,实现TWEEN、controls、renderer更新渲染,并进行递归重复调用。
- 最后要是想实现点击任意一张照片就能放大此照片的效果,主要靠监听鼠标的点击事件,只要点击了照片就触发一个编写好的一个函数(此函数主要实现的是放大后的照片的位置、大小)。
主要实现
HTML结构
<div id="container"></div> //放球体照片墙的结构
<div id='popup'></div> //主要用来放照片放大后的结构
CSS样式
<style>
html, body {
height: 100%;
}
body {
background-color: #000000;
margin: 0;
overflow: hidden;
}
.element { //照片大小
width: 120px;
height: 160px;
}
.element:hover { //鼠标悬浮在照片上的样式
box-shadow: 0px 0px 12px rgba(0,255,255,0.75);
border: 1px solid rgba(127,255,255,0.75);
}
</style>
JS实现
1. 准备
首先引入相关动画的包。使用npm i three
命令下载threejs包,使用npm i @tweenjs/tween.js
命令可以下载tweenjs包,这些都可以去npm官网自行查看用法。
由于在HTML中引入threejs、tweenjs动画,所以要额外使用JS模板。
<script type="importmap">
{
"imports": {
"three": "./three/build/three.module.js",
"three/addons/": "./three/examples/jsm/",
"@tweenjs/tween.js": "/node_modules/@tweenjs/tween.js/dist/tween.esm.js"
}
}
</script>
接下来的代码都是在以下代码块内实现动画效果。
<script type="module">
//实现动画
</script>
2. 引入文件并创建基本元素
import * as THREE from "three";
import * as TWEEN from '@tweenjs/tween.js'
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { CSS3DRenderer } from 'three/addons/renderers/CSS3DRenderer.js';
import { CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
// 场景
const scene = new THREE.Scene();
// 镜头
const camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 3400;
// 渲染器
const renderer = new CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );
//坐标初始化
const vector = new THREE.Vector3(); //三维坐标
const spherical = new THREE.Spherical();//球坐标
//轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.autoRotate = true; //为true时,相机自动围绕目标旋转,但必须在animation循环中调用update()
var objects = []; //存放转化为3D的照片对象
var spheres=[] //用来存放目标对象的位置
init(); //初始化并形成球体照片墙
animate();//每隔一段时间渲染
3. 主要相关函数编实现
实现init()函数。此函数实现照片的大小、样式以及摆放位置,并在项目目录中提前存放好需要显示的照片。
由于往添加的图片都是HTML元素,要想加入场景中,需要将其转换为3D对象,于是借助了CSS3DObject构造函数。
function init() {
//放图片
for (i=1; i < 125; i++ ) { //125为照片的个数,随便放多少,不过要适中
var element = document.createElement( 'div' );
element.className = 'element'; //给图片加类名即设置对应的图片大小
element.style.backgroundImage = 'url(./photo/' + i + '.jpg)'; // 背景图片 图片名称是 1...118.jpg
element.style.backgroundSize = 'cover'; //保持图像的宽高比例,将图片缩放到正好完全覆盖定义的背景区域
element.name = i ; // 给元素的name属性赋值,以便获取鼠标点击的当前值
var object = new CSS3DObject( element );//可以将HTML元素作为纹理添加到3D对象中,从而创建有趣的3D特效
scene.add( object );
objects.push( object );//为了知道被添加到照片元素的个数
}
var l = objects.length
// 根据球形排列公式计算每个元素的位置
for( var i = 0; i <= l; i ++ ) {
//该部分为固定的数学公式
var phi = Math.acos( -1 + ( 2 * i ) / l );
var theta = Math.sqrt( l * Math.PI ) * phi;
// 计算元素在球面上的坐标
var x = 800 * Math.cos(theta) * Math.sin(phi);//800代表球的半径
var y = 800 * Math.sin(theta) * Math.sin(phi);
var z = 800 * Math.cos(phi);
var object = new THREE.Object3D();
object.position.set(x,y,z)//设置对象的位置
vector.copy( object.position ).multiplyScalar( 2 );//将该向量与所传入的标量2进行相乘。
object.lookAt( vector );//vector这个变量的作用,它用来作为'目标位置',使用这个方法让这个位置的对象object看向vector这一点所在的方向
spheres.push( object );
}
transform( spheres, 2000 );//动画转换
}
在设置 object.position.set(x,y,z)
位置后几乎就可以形成一个球了,但是此时每张照片都是竖立起来的,需要设置下object的看向点,从而达到相机照过来是有弧度的。
说实话这部分的数学公式我也不是很清楚,这是从其他笔者写的贴过来的。
实现transform函数。此函数主要利用了tweenjs补间动画,主要是为了由于初始时所有照片在同一个位置移动到相应的指定位置,通过tween.js过渡这些数据产生动画。
function transform( spheres, duration ) {
for ( var i = 0; i < objects.length; i ++ ) {
var object = objects[ i ];
var target = spheres[ 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();
}
}
补间功能方法:start——开启补间动画;to ——控制补间的运动形式及方向;Easing——缓动函数。其详细方法可自行去TWEENJS官网查看或者去其他途径查看即可。
实现animate函数,通过requestAnimationFrame每隔一段时间调用此函数。
function animate() {
TWEEN.update();
controls.update();
renderer.render(scene, camera);
requestAnimationFrame( animate );
}
到这里球体照片墙就基本完成!接下来要是实现点击某个图片进行放大效果,需要给window绑定鼠标按下事件。
// 监听鼠标单击事件
window.addEventListener('mousedown', clickMouse);
// 鼠标单击事件
function clickMouse(e) {
if(!e){
let e = window.event;
}
let tname = e.target.name; //获取点击图片的名称
if (typeof(tname) == "undefined" || tname =='') {//鼠标点击的不是照片
let div = document.getElementById("popup");
div.style.display = 'none'; //隐藏元素
}else{
Popup(tname);
}
// 放大的图片
function Popup(tname) {
let w = window.innerWidth;
let h = window.innerHeight;
let div = document.getElementById("popup");
div.style.display = 'block'; //显示元素
div.style.backgroundImage = 'url(./photo/' + tname + '.jpg)';
div.style.backgroundSize = 'cover';
div.style.height = h*0.7 + 'px';
div.style.width = h*0.6 +'px';
div.style.position = 'absolute';
div.style.left = '0px';
div.style.right = '0px';
div.style.top = '0px';
div.style.bottom = '0px';
div.style.margin = 'auto'; //居中位置
div.style.borderRadius = '5px'; // 圆角
}
}
到此3D的球体照片墙就完成啦~ 相关知识详情可自行去Threejs官网查看哟!
结束语
本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤,您的点赞是持续写作的动力,感谢支持。要是您觉得有更好的方法,欢迎评论,提出建议!