当前位置: 首页 > 工具软件 > Floatlabel.js > 使用案例 >

一个展示百分比的动态可视化组件(three.js实战11)

满玉泽
2023-12-01

1. demo效果

可视化组件百分比

2. demo代码js

为了方便后期使用,把demo效果抽成组件,写在component.js中,下面是这个文件内容

var CirclePercent = function () {
  var self = this
  this.percentNum = 0
  this.circlePercent = new THREE.Group();
  this.materialColor = new THREE.Color().setStyle('#0782F5')
  this.lightMaterialColor = new THREE.Color().setStyle('#24FFFF')

  this.circleGroup = new THREE.Group();
  this.circlePointsPositions = null;
  this.circlePointOne = null;
  this.circlePointTwo = null;
  this.circlePointThree = null;
  this.currentAngle = 0;
  const circleRadius = 5;
  const circlePointMoveSpeed = 0.03;


  this.centerText = null;
  this.font = null;

  //外侧梯形组成的圈
  this.trapezoidsCircleGroup = new THREE.Group();
  this.trapezoidPositions = null;
  this.trapezoidTopPoints = null;
  this.trapezoidBottomPoints = null;
  this.trapezoidLightLineIndex = 10;

  function getPointsFromOneCircle(circleRadius, pointNum) {
    const curve = new THREE.EllipseCurve(
      0, 0,
      circleRadius, circleRadius,
      Math.PI / 2, 5 * Math.PI / 2,
      false,
      0
    );

    return curve.getPoints(pointNum);
  }


  function createTrapezoid() {
    const topWidth = 2;
    const bottomWidth = 1.4;
    const height = 2.2;
    const path = new THREE.Shape();
    path.moveTo(-topWidth / 2, height / 2);
    path.lineTo(topWidth / 2, height / 2);
    path.lineTo(bottomWidth / 2, -height / 2);
    path.lineTo(-bottomWidth / 2, -height / 2);
    path.moveTo(-topWidth / 2, height / 2);
    path.closePath()

    const material = new THREE.LineBasicMaterial({
      color: 0x0782F5
    });
    // 拉伸几何体配置项说明
    /* curveSegments — int,曲线上点的数量,默认值是12。
    steps — int,用于沿着挤出样条的深度细分的点的数量,默认值为1。
    depth — float,挤出的形状的深度,默认值为100。
    bevelEnabled — bool,对挤出的形状应用是否斜角,默认值为true。
    bevelThickness — float,设置原始形状上斜角的厚度。默认值为6。
    bevelSize — float。斜角与原始形状轮廓之间的延伸距离,默认值为bevelThickness-2。
    bevelSegments — int。斜角的分段层数,默认值为3。
    extrudePath — THREE.CurvePath对象。一条沿着被挤出形状的三维样条线。
    UVGenerator — Object。提供了UV生成器函数的对象。 */

    // 拉伸几何体配置项
    const options = {
      depth: 0.1, //厚度
      bevelEnabled: false, //是否要斜角
      curveSegments: 1,
      steps: 1
    }

    // 创建拉伸几何体
    const geometry = new THREE.ExtrudeGeometry(path, options)

    const trapezoid = new THREE.Mesh(geometry, material);
    return trapezoid
  }

  this.initTrapezoidsCircle = (() => {

    //绘制外层圆用来获取坐标
    this.trapezoidPositions = getPointsFromOneCircle(circleRadius + 2, 20);
    this.trapezoidPositions.forEach((position, index) => {

      let trapezoid = createTrapezoid(index)
      trapezoid.position.set(position.x, position.y, 0)
      trapezoid.rotation.z = index * 2 * Math.PI / 20
      this.trapezoidsCircleGroup.add(trapezoid);
    })

    this.circlePercent.add(this.trapezoidsCircleGroup)
  })()

  this.initCircle = (() => {

    this.circlePointsPositions = getPointsFromOneCircle(circleRadius, 200);
    const geometry = new THREE.BufferGeometry().setFromPoints(this.circlePointsPositions);

    const material = new THREE.LineBasicMaterial({
      color: this.lightMaterialColor
    });

    const circle = new THREE.Line(geometry, material);
    this.circleGroup.add(circle);

    const smallPointGeom = new THREE.SphereBufferGeometry(0.2, 32, 32);
    this.circlePointOne = new THREE.Mesh(smallPointGeom, material)
    this.circlePointOne.position.set(this.circlePointsPositions[0].x, this.circlePointsPositions[0].y, 0)
    this.circleGroup.add(this.circlePointOne)

    this.circlePointTwo = this.circlePointOne.clone()
    this.circlePointTwo.position.set(this.circlePointsPositions[67].x, this.circlePointsPositions[67].y, 0)
    this.circleGroup.add(this.circlePointTwo)

    this.circlePointThree = this.circlePointOne.clone()
    this.circlePointThree.position.set(this.circlePointsPositions[133].x, this.circlePointsPositions[133].y, 0)
    this.circleGroup.add(this.circlePointThree)

    this.circlePercent.add(this.circleGroup)

  })()

  this.circleTwoPointsAnimation = () => {
    const angle = this.percentNum / 100 * (2 * Math.PI)

    if (angle > this.currentAngle) {
      this.currentAngle += 0.01
    } else {
      this.currentAngle -= 0.01
    }

    this.circlePointOne.position.x = Math.cos(this.currentAngle) * circleRadius
    this.circlePointOne.position.y = Math.sin(this.currentAngle) * circleRadius
    this.circlePointTwo.position.x = Math.cos(this.currentAngle + 2 * Math.PI / 3) * circleRadius
    this.circlePointTwo.position.y = Math.sin(this.currentAngle + 2 * Math.PI / 3) * circleRadius
    this.circlePointThree.position.x = Math.cos(this.currentAngle + 4 * Math.PI / 3) * circleRadius
    this.circlePointThree.position.y = Math.sin(this.currentAngle + 4 * Math.PI / 3) * circleRadius
  }

  this.trapezoidsAnimation = () => {
    //根据新值计算梯形块的索引
    const lightLineIndex = parseInt(this.percentNum / 5)

    if (lightLineIndex < this.trapezoidLightLineIndex) {

      for (let i = this.trapezoidLightLineIndex; i > lightLineIndex; i -= 0.01) {
        const index = 20 - parseInt(i)
        this.trapezoidsCircleGroup.children[index].material.color = this.materialColor
      }

    } else {

      for (let i = 0; i < lightLineIndex; i += 0.01) {
        const index = 20 - parseInt(i)
        this.trapezoidsCircleGroup.children[index].material.color = this.lightMaterialColor
      }

    }

    this.trapezoidLightLineIndex = lightLineIndex

  }


  this.circlePercent.updatePercent = (percentNum) => {
    this.percentNum = percentNum
    this.circleTwoPointsAnimation()
    this.trapezoidsAnimation()
  }

  return this.circlePercent
}

3. demo代码HTML

<!DOCTYPE html>

<html>

<head>
  <title>可视化小元素</title>
  <script type="text/javascript" src="../three/build/three.js"></script>
  <script type="text/javascript" src="../three/examples/js/controls/OrbitControls.js"></script>
  <script type="text/javascript" src="../three/examples/js/renderers/CSS2DRenderer.js"></script>
  <script type="text/javascript" src="../three/examples/js/libs/stats.min.js"></script>
  <!-- EffectComposer要先于其他后期处理文件引入,否则其他文件无法正确引入 -->
  <script type="text/javascript" src="../three/examples/js/postprocessing/EffectComposer.js"></script>
  <script type="text/javascript" src="../three/examples/js/postprocessing/RenderPass.js"></script>
  <script type="text/javascript" src="../three/examples/js/postprocessing/ShaderPass.js"></script>
  <script type="text/javascript" src="../three/examples/js/postprocessing/UnrealBloomPass.js"></script>
  <script type="text/javascript" src="../three/examples/js/shaders/LuminosityHighPassShader.js"></script>
  <script type="text/javascript" src="../three/examples/js/shaders/CopyShader.js"></script>
  <script type="text/javascript" src="./js/components.js"></script>

  <style>
    body {
      margin: 0;
      overflow: hidden;
    }

    .label {
      color: #FFF;
      font-family: sans-serif;
      font-size: 50px;
      padding: 2px;
      background: rgba(0, 0, 0, .6);
    }
  </style>
</head>

<body>

  <div id="Stats-output"></div>
  <div id="WebGL-output"></div>

  <script type="text/javascript">
    var scene, camera, renderer, labelRenderer, labelDiv, stats, controls, clock;
    var composer, unrealBloomPass;

    var circlePercentComponent = null;
    var currentNum = 20;

    function initScene() {
      scene = new THREE.Scene();
      //用一张图加载为纹理作为场景背景
      //scene.background = new THREE.TextureLoader().load("../assets/textures/starry-deep-outer-space-galaxy.jpg");
    }

    function initCamera() {
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
      camera.position.set(0, 0, 50);
      camera.lookAt(new THREE.Vector3(0, 0, 0));
    }

    function initLight() {
      //添加环境光
      const ambientLight = new THREE.AmbientLight(0x0c0c0c);
      scene.add(ambientLight);

      const directionalLight = new THREE.DirectionalLight('#fff')
      directionalLight.position.set(20, 30, 50)
      scene.add(directionalLight)

      //添加聚光灯
      const spotLight = new THREE.SpotLight(0xffffff);
      spotLight.position.set(-40, 60, -10);
      spotLight.castShadow = true;
      scene.add(spotLight);
    }

    function initModel() {
      initPlane();

      circlePercentComponent = new CirclePercent()
      scene.add(circlePercentComponent)

    }



    //创建底面
    function initPlane() {
      const planeGeometry = new THREE.PlaneGeometry(1, 1, 1, 1); //创建一个平面几何对象

      //材质
      const planeMaterial = new THREE.MeshLambertMaterial({
        transparent: true,
        opacity: 0.0
      });
      const plane = new THREE.Mesh(planeGeometry, planeMaterial);

      //平面添加到场景中
      scene.add(plane);

      //添加label
      labelDiv = document.createElement('div');
      labelDiv.className = 'label';
      //labelDiv.textContent = currentNum+' %';
      labelDiv.style.background = 'none';
      const label = new THREE.CSS2DObject(labelDiv);
      label.position.set(0, 0, -1);
      plane.add(label);
    }

    //初始化渲染器
    function initRender() {
      renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true
      });
      renderer.setClearColor(0x111111, 1); //设置背景颜色
      renderer.setSize(window.innerWidth, window.innerHeight);
      //renderer.shadowMap.enabled = true; //显示阴影
      document.getElementById("WebGL-output").appendChild(renderer.domElement);
      //创建CSS2DRenderer渲染器
      labelRenderer = new THREE.CSS2DRenderer();
      labelRenderer.setSize(window.innerWidth, window.innerHeight);
      labelRenderer.domElement.style.position = 'absolute';
      labelRenderer.domElement.style.top = '0px';
      document.getElementById("WebGL-output").appendChild(labelRenderer.domElement);
    }
    //初始化轨道控制器
    function initControls() {
      clock = new THREE.Clock(); //创建THREE.Clock对象,用于计算上次调用经过的时间
      controls = new THREE.OrbitControls(camera, renderer.domElement);
      //controls.autoRotate = true; //是否自动旋转
    }


    //性能监控
    function initStats() {
      stats = new Stats();
      stats.setMode(0); //0: fps, 1: ms
      document.getElementById("Stats-output").appendChild(stats.domElement);
    }


    function render() {
      const delta = clock.getDelta(); //获取自上次调用的时间差
      controls.update(delta); //控制器更新

      circlePercentComponent.updatePercent(currentNum)
      labelDiv.textContent = currentNum + ' %';

      stats.update();
      requestAnimationFrame(render);
      labelRenderer.render(scene, camera);
      renderer.render(scene, camera);
      if (composer) {
        composer.render();
      }
    }

    function initCustomShader() {
      //使用场景和相机创建RenderPass通道
      const renderPass = new THREE.RenderPass(scene, camera)

      /****UnrealBloomPass泛光通道构造函数参数****/
      /* 
      resolution:表示泛光所覆盖的场景大小,是Vector2类型的向量
      strength:表示泛光的强度,值越大明亮的区域越亮,较暗区域变亮的范围越广
      radius:表示泛光散发的半径
      threshold:表示产生泛光的光照强度阈值,如果照在物体上的光照强度大于该值就会产生泛光 
      */

      //创建UnrealBloomPass泛光通道
      unrealBloomPass = new THREE.UnrealBloomPass(
        new THREE.Vector2(256, 256),
        0.5,
        0.8,
        0.12
      )
      unrealBloomPass.renderToScreen = true

      const effectCopy = new THREE.ShaderPass(THREE.CopyShader);

      effectCopy.renderToScreen = true;

      //创建效果组合器
      composer = new THREE.EffectComposer(renderer)

      //将创建的通道添加到EffectComposer(效果组合器)对象中
      composer.addPass(renderPass)
      composer.addPass(unrealBloomPass)
      composer.addPass(effectCopy)

    }

    //页面初始化
    function init() {
      initScene();
      initCamera();
      initLight();
      initModel();
      initRender();
      initStats();
      initControls();
      initCustomShader();
      render();
      setInterval(() => {
        currentNum = parseInt(Math.random() * 100)
      }, 1000)
    }

    window.onload = init;
  </script>
</body>

</html>
 类似资料: