在vue3中使用threejs实现3D卡片水平旋转效果

发布于:2024-04-30 ⋅ 阅读:(33) ⋅ 点赞:(0)

这个是根据three现有案例来模仿实现,[原网址]()

效果图:

GIF 2024-4-28 17-24-13.gif

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>

网站公告

今日签到

点亮在社区的每一天
去签到