这个是根据three现有案例来模仿实现,[原网址]()
效果图:
template部分
<div class="content1" ref="containerRef">
<div id="container" style="background-color: transparent"></div>
</div>
script部分
import { onMounted,onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import * as THREE from 'three';
import TWEEN from 'three/addons/libs/tween.module.js';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
import { getSiteList } from '@/api/periodictable';
import { detailsInfo } from '@/views/mapDetail/data';
const props = defineProps({
id: {
type: Number,
defaults: '',
},
});
const autoRotate = ref(true);
const router = useRouter();
const containerRef = ref();
let camera, scene, renderer;
let controls;
let objects = [];
const targets = { table: [], sphere: [], helix: [], grid: [], tablelist: [] };
// 图形初始化
function init() {
console.log('containerRef', containerRef.value.clientWidth, containerRef.value.clientHeight);
camera = new THREE.PerspectiveCamera(40, containerRef.value.clientWidth / containerRef.value.clientHeight, 1, 10000);
camera.position.z = 3000;
scene = new THREE.Scene();
renderer = new CSS3DRenderer({ alpha: true });
renderer.setSize(containerRef.value.clientWidth, containerRef.value.clientHeight);
document.getElementById('container').style.background = 'transparent';
document.getElementById('container').appendChild(renderer.domElement);
controls = new TrackballControls(camera, renderer.domElement);
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener('change', render);
controls.noPan = true
controls.mouseButtons = {
LEFT: THREE.MOUSE.RIGHT,
RIGHT: THREE.MOUSE.LEFT,
};
getList({ type: 4, siteId: props.id });
window.addEventListener('resize', onWindowResize);
animate();
}
// 变换
function transform(targets, duration) {
TWEEN.removeAll();
for (let i = 0; i < objects.length; i++) {
const object = objects[i];
const 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();
controls.reset();
controls.noRotate = false;
}
//窗口监听
function onWindowResize() {
console.log('2222');
camera.aspect = containerRef.value.clientWidth / containerRef.value.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(containerRef.value.clientWidth, containerRef.value.clientHeight);
render();
}
// 图形刷新
function animate() {
requestAnimationFrame(animate);
if (autoRotate.value) {
scene.rotation.y += 0.001; // 旋转速度
}
objects.forEach((object) => {
object.lookAt(camera.position); //卡片取消翻转
});
TWEEN.update();
controls.update();
render();
}
// 图形渲染
function render() {
renderer.render(scene, camera);
}
// 查找数据
async function getList(query) {
const { site } = await getSiteList(query);
console.log('site', site);
helixRender(site);
}
// 圆圈形状
function helixRender(data) {
scene.clear();
objects = [];
targets.helix = [];
targets.circle = [];
Object.keys(detailsInfo).forEach((key, index) => {
// 构建元素
const element = document.createElement('div');
element.className = 'element1';
element.style.backgroundColor = 'rgba(0,127,127,' + (Math.random() * 0.5 + 0.25) + ')';
element.onmousedown = function (e) {
e.ctrlKey && getList({ type: 1, yearName: key });
};
// const number = document.createElement('div');
// number.className = 'number';
// number.textContent = index + 1;
// element.appendChild(number);
const symbol = document.createElement('div');
symbol.className = 'symbol1';
symbol.textContent = data[key] ;
element.appendChild(symbol);
const details = document.createElement('div');
details.className = 'details';
details.innerHTML = detailsInfo[key].name;
element.appendChild(details);
const objectCSS = new CSS3DObject(element);
objectCSS.position.x = Math.random() * 4000 - 2000;
objectCSS.position.y = Math.random() * 4000 - 2000;
objectCSS.position.z = Math.random() * 4000 - 2000;
scene.add(objectCSS);
objects.push(objectCSS);
});
const radius = 400; // 设置圆形布局的半径
const vector = new THREE.Vector3(20, 20, 20);
for (let i = 0, l = objects.length; i < l; i++) {
const phi = (i / l) * 2 * Math.PI; // 分配每个对象在圆上的角度
const object = new THREE.Object3D();
object.position.x = radius * Math.cos(phi);
object.position.y = 0;
object.position.z = radius * Math.sin(phi);
// 设置对象朝向圆心
vector.x = object.position.x;
vector.y = object.position.y;
vector.z = object.position.z;
object.lookAt(vector);
targets.circle.push(object);
}
transform(targets.circle, 0);
camera.position.z = 1100;
}
const setControls = (bool) => {
controls.noZoom = bool; // 启用缩放功能
controls.noRotate = bool;
};
onMounted(() => {
setTimeout(() => {
init();
animate();
},200);
});
onUnmounted(()=>{
window.removeEventListener('resize', onWindowResize);
})
defineExpose({
setControls,
});
style
<style lang="less" scoped>
.content1 {
height: 600px;
width: 1000px;
background-color: transparent !important;
position: absolute;
top: -290px;
left: -122px;
}
</style>
<style lang="less" >
a {
color: #8ff;
}
#menu {
position: absolute;
bottom: 20px;
width: 100%;
text-align: center;
}
.element1 {
width: 120px;
height: 130px;
box-shadow: 0 0 12px rgb(0 255 255 / 50%);
border: 1px solid rgb(127 255 255 / 25%);
font-family: Helvetica, sans-serif;
text-align: center;
line-height: normal;
cursor: default;
display: flex;
align-items: center;
justify-content: center;
}
.element1:hover {
box-shadow: 0 0 12px rgb(0 255 255 / 75%);
border: 1px solid rgb(127 255 255 / 75%);
}
.element1 .number {
position: absolute;
top: 20px;
right: 20px;
font-size: 12px;
color: rgb(127 255 255 / 75%);
}
.element1 .symbol1 {
// position: absolute;
// top: 15px;
// left: 0;
// right: 0;
font-size: 16px;
padding: 0 10px;
margin-bottom: 20px;
// height: 70px;
// border:1px solid red;
font-weight: bold;
color: rgb(255 255 255 / 75%);
text-shadow: 0 0 10px rgb(0 255 255 / 95%);
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box; //将对象作为弹性伸缩盒子模型显示。
-webkit-box-orient: vertical; // 从上到下垂直排列子元素
-webkit-line-clamp: 3; //显示的行数
}
.element1.grid {
width: 160px;
height: 180px;
}
.element1 .grid-symbol {
position: absolute;
top: 35px;
padding: 0 2px;
left: 0;
right: 0;
font-size: 30px;
font-weight: bold;
color: rgb(255 255 255 / 75%);
text-shadow: 0 0 10px rgb(0 255 255 / 95%);
display: -webkit-box;
-webkit-box-orient: vertical; /* 垂直排列子元素 */ /* 限制在两行 */
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
}
.element1 .table-symbol {
position: absolute;
top: 35px;
padding: 0 2px;
left: 0;
right: 0;
font-size: 20px;
font-weight: bold;
color: rgb(255 255 255 / 75%);
text-shadow: 0 0 10px rgb(0 255 255 / 95%);
display: -webkit-box;
-webkit-box-orient: vertical; /* 垂直排列子元素 */ /* 限制在两行 */
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
}
.element1.publishname {
width: 400px;
}
.element1.imageUrl {
width: 400px;
height: 340px;
}
.publishname .table-symbol {
font-size: 36px;
}
.imageUrl .table-symbol {
top: 3px;
bottom: 3px;
}
.table-symbol .table-img {
height: 100%;
width: 100%;
}
.element1 .years {
position: absolute;
left: 6px;
top: 6px;
font-size: 14px;
color: rgb(127 255 255 / 75%);
}
.element1 .subsymbol {
position: absolute;
top: 96px;
left: 0;
right: 0;
font-size: 10px;
color: rgb(255 255 255 / 75%);
text-shadow: 0 0 10px rgb(0 255 255 / 95%);
}
.element1 .details {
position: absolute;
bottom: 15px;
left: 0;
right: 0;
font-size: 14px;
color: rgb(127 255 255 / 75%);
}
.element1 .table-details {
position: absolute;
bottom: 16px;
left: 0;
right: 0;
font-size: 16px;
color: rgb(127 255 255 / 75%);
overflow: hidden;
text-overflow: ellipsis;
}
.grid-name {
font-size: 40px;
font-weight: bold;
color: rgb(255 255 255 / 75%);
text-shadow: 0 0 10px rgb(0 255 255 / 95%);
background-color: rgb(0 127 127 / 59%);
padding: 20px 30px;
border-radius: 6px;
position: relative;
}
.grid-name .level-num {
position: absolute;
border: 1px solid rgb(127 255 255 / 75%);
background-color: rgb(0 127 127 / 59%);
display: inline-block;
font-size: 12px;
padding: 2px 4px;
border-radius: 4px;
right: 1px;
top: 1px;
}
.show-more {
font-size: 22px;
// font-weight: bold;
color: rgb(255 255 255 / 75%);
text-shadow: 0 0 10px rgb(0 255 255 / 95%);
background-color: rgb(0 127 127 / 59%);
padding: 10px 30px;
border-radius: 6px;
}
</style>
<style lang="less" scoped>
.type-picker {
position: absolute;
bottom: 20px;
left: 50%;
margin-left: -52px;
z-index: 99;
:deep(.el-radio-button.is-active) {
.el-radio-button__inner {
background-color: rgb(88 88 88 / 80%);
}
}
:deep(.el-radio-button__inner) {
background-color: rgb(36 36 36 / 50%);
border: none !important;
color: rgb(255 255 255 / 90%);
padding: 10px 14px;
box-shadow: none;
}
:deep(.el-radio-button:first-child .el-radio-button__inner) {
border-radius: 45px 0 0 45px;
}
:deep(.el-radio-button:last-child .el-radio-button__inner) {
border-radius: 0 45px 45px 0;
}
}
</style>