2、地球图 - 添加飞线、扩散波及vue大屏元素(整体效果)

本文介绍如何在前端项目中利用Vue.js和Three.js实现飞线和扩散波效果,以呈现资金流向的视觉展示。通过createFlyLine.js绘制动态发光飞线,开启飞线动画,以及使用createLightBar.js创建扩散波并开启动画。同时,利用Vue的transition标签实现大屏元素的动态过渡效果,提升页面的整体呈现效果。
摘要由CSDN通过智能技术生成

书接上回,我们在上文中的基础之上添加飞线、扩散波来表示具体业务 - 资金的流向,再添加一些大屏元素让整个页面看起来更加成型。

飞线

绘制飞线工具方法:createFlyLine.js

此工具主要分为三个功能点

  1. drawFlyLine:接收scene实体、地图url、具体发送点和接收点数据信息,生成骨架线和飞线添加到scene上,并且把这些group添加到scene上外加return出去供外部使用。
import { util } from './util.js';

drawFlyLine(scene, mapUrl, data) {
  let mapData = util.decode(mapUrl);
  this.dataKeys = {};
  mapData.features.forEach((d) => {
    const { name, cp } = d.properties;
    this.dataKeys[name] = [...cp];
  });
  let lineGroup = new THREE.Group();
  let flyLineGroup = new THREE.Group();
  data.forEach((d) => {
    // 处理起始位置坐标转换
    let sSiteName = d.source.name;
    let tSiteName = d.target.name;
    sSiteName = sSiteName.replace(RegExp("站", "g"), "市");
    tSiteName = tSiteName.replace(RegExp("站", "g"), "市");
    const slnglat = this.dataKeys[sSiteName];
    const tlnglat = this.dataKeys[tSiteName];
    const [x1, y1, z1] = this.lnglatToMector(slnglat);
    const [x2, y2, z2] = this.lnglatToMector(tlnglat);
    const curve = new THREE.QuadraticBezierCurve3(
      new THREE.Vector3(x1, y1, z1 + 0.28),
      new THREE.Vector3((x1 + x2) / 2, (y1 + y2) / 2, 3.5),
      new THREE.Vector3(x2, y2, z2 + 0.28)
    );

    let line = this.createLine(curve);
    let color = new THREE.Vector3(
      0.5999758518718452,
      0.7798940272761521,
      0.6181903838257632
    );
    // 创建骨架线和飞线
    let flyLine = this.createFlyLine(
      curve,
      {
        speed: 0.4,
        color: color,
        number: 1, //同时跑动的流光数量
        length: 0.3, //流光线条长度
        size: 3, //粗细
      },
      5000
    );
    lineGroup.add(line);
    flyLineGroup.add(flyLine);
  });

  scene.add(lineGroup);
  scene.add(flyLineGroup);

  return {
    lineGroup,
    flyLineGroup,
  };
}
  1. createLine:根据数据创建骨架线
// 创建骨架线
createLine (curve) {
  const points = curve.getPoints(100);
  let geometry = new LineGeometry();
  let positions = []
  let colors = [];
  let color = new THREE.Color();
  /**
     * HSL中使用渐变
     * h — hue value between 0.0 and 1.0
     * s — 饱和度 between 0.0 and 1.0
     * l — 亮度 between 0.0 and 1.0
     */
  for ( let j = 0; j < points.length; j ++ ) {
    // color.setHSL( .31666+j*0.005,0.7, 0.7); //绿色
    color.setHSL( .81666 + j, 0.88, 0.715 + j * 0.0025 ); //粉色
    colors.push( color.r, color.g, color.b );
    positions.push( points[ j ].x, points[ j ].y, points[ j ].z + 0.01 );
  }
  geometry.setPositions( positions );
  geometry.setColors( colors );
  let matLine = new LineMaterial( {
    linewidth: 0.0006,
    vertexColors: true,
    dashed: false
  } );
  let line = new Line2( geometry, matLine )
  return line;
}

在这里插入图片描述

  1. createFlyLine、initLineMaterial:通过ShaderMaterial材质生成飞线材质,再通过Points创建点来达到飞线的效果
import { Line2 } from "three/examples/jsm/lines/Line2";
import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";

// 顶点着色器
let vertexShader = `    
  varying vec2 vUv;
  attribute float percent;
  uniform float u_time;
  uniform float number;
  uniform float speed;
  uniform float length;
  varying float opacity;
  uniform float size;

  void main()
  {
      vUv = uv;
      vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
      float l = clamp(1.0-length,0.0,1.0);//空白部分长度

      gl_PointSize = clamp(fract(percent*number + l - u_time*number*speed)-l ,0.0,1.) * size * (1./length);

      opacity = gl_PointSize/size;
      gl_Position = projectionMatrix * mvPosition;
  }`;

// 分片着色器
let fragmentShader = `
  #ifdef GL_ES
  precision mediump float;
  #endif

  varying float opacity;
  uniform vec3 color;

  void main(){
      if(opacity <=0.2){
          discard;
      }
      gl_FragColor = vec4(color,1.0);
  }`;

let commonUniforms = {
  u_time: { value: 0.0 },
};

/**
   * @param curve {THREE.Curve} 路径,
   * @param matSetting {Object} 材质配置项
   * @param pointsNumber {Number} 点的个数 越多越细致
   * */
createFlyLine (curve, matSetting, pointsNumber) {
  var points = curve.getPoints( pointsNumber );
  var geometry = new THREE.BufferGeometry().setFromPoints( points );

  let length = points.length;
  var percents = new Float32Array(length);
  for (let i = 0; i < points.length; i+=1){
    percents[i] = (i/length);
  }

  geometry.setAttribute('percent', new THREE.BufferAttribute(percents,1));

  let lineMaterial = this.initLineMaterial(matSetting);

  var flyLine = new THREE.Points( geometry, lineMaterial );
  return flyLine
}

// 首先要写出一个使用fragmentshader生成的material并赋在点上
initLineMaterial(setting){
  let number = setting ? (Number(setting.number) || 1.0) : 1.0;
  let speed = setting ? (Number(setting.speed) || 1.0) : 1.0;
  let length = setting ? (Number(setting.length) || 0.5) : 0.5;
  let size = setting ?(Number(setting.size) || 3.0) : 3.0;
  let color = setting ? setting.color || new THREE.Vector3(0,1,1) : new THREE.Vector3(0,1,1);
  let singleUniforms = {
    u_time: commonUniforms.u_time,
    number: {type: 'f', value:number},
    speed: {type:'f',value:speed},
    length: {type: 'f', value: length},
    size: {type: 'f', value: size},
    color: {type: 'v3', value: color}
  };
  let lineMaterial = new THREE.ShaderMaterial({
    uniforms: singleUniforms,
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    transparent: true,
    //blending:THREE.AdditiveBlending,
  });
  return lineMaterial;
}

使用createFlyLine.js绘制动态发光飞线
let posArr = [
  { 'x': - 1.7049594735603837, 'y': 3.208354470512221, 'z': - 3.4350509144786985 },
  { 'x': - 2.1965610576118175, 'y': 2.1955955192304506, 'z': - 3.9184792759587768 },
  { 'x': - 2.2290975556080355, 'y': 2.6054406912933263, 'z': - 3.639066211507457 },
  { 'x': 0.5738958419746141, 'y': - 0.44114968930852216, 'z': 4.9473255920938985 },
  { 'x': - 0.9326350073394328, 'y': 2.8399222968004114, 'z': - 4.00812091773949 },
  { 'x': 3.469198597393574, 'y': 1.2295167303380952, 'z': - 3.3842206934036057 },
  { 'x': - 2.4019084876611916, 'y': - 2.190220428765315, 'z': 3.7991801866087123 },
  { 'x': - 2.49363689878109, 'y': - 4.099696049856375, 'z': 1.4050862307450966 },
  { 'x': - 2.3729307780326305, 'y': 2.840227787960863, 'z': 3.3618901878497454 },
  { 'x': - 2.0636200279017873, 'y': 0.7444294629976027, 'z': - 4.493027615657812 },
  { 'x': 0.47725894517680106, 'y': 2.4327372143508037, 'z': - 4.34212085796347 },
  { 'x': - 2.4777001955161246, 'y': - 1.2092952460724242, 'z': 4.171163716394502 },
  { 'x': - 0.03915748918627658, 'y': - 0.008362945319338826, 'z': 4.999839672648135 },
  { 'x': 1.5223738738260317, 'y': - 1.032865814102439, 'z': - 4.649254348640267 },
  { 'x': - 0.26640112020426315, 'y': - 4.314854187280748, 'z': 2.5121830716848077 },
  { 'x': - 4.031470206741836, 'y': - 2.606648761952297, 'z': - 1.3973654511134501 },
  { 'x': 0.8544382232162094, 'y': 1.5274953155132989, 'z': 4.683662390031124 },
  { 'x': 3.0409624989238546, 'y': 1.76433738825175, 'z': - 3.555230043268055 },
  { 'x': - 4.721251023266457, 'y': 1.2354922989397954, 'z': - 1.0878177947459262 },
  { 'x': 2.1518961827021106, 'y': 3.891904027152385, 'z': - 2.285262755638206 },
  { 'x': 0.8501960736517479, 'y': - 2.851729208821255, 'z': - 4.018060123480341 },
  { 'x': 2.5631840141785176, 'y': 4.263234820997851, 'z': - 0.5048926326370041 },
  { 'x': - 0.4580143454812531, 'y': - 2.6523265200067385, 'z': 4.213714144386437 }
];

// 绘制飞线
let targetFlyLineDatas = { 'x': - 1.7049594735603837, 'y': 3.208354470512221, 'z': - 3.4350509144786985 };
flyLineModel = flyLineUtil.drawFlyLine(posArr, targetFlyLineDatas);
earthGroup.add(flyLineModel.lineGroup)
earthGroup.add(flyLineModel.flyLineGroup)

开启飞线动画
// 飞线model
let flyLineModel = null;
let commonUniformsValue = 0.0;

render() {
  // 飞线动画
  this.flyLineAnimation();
  // 地球自转动画
  this.earthRotationAnimation();

  scene.traverse(this.darkenNonBloomed) // 隐藏不需要辉光的物体
  bloomComposer.render()
  scene.traverse(this.restoreMaterial) // 还原

  // 更新性能插件
  stats.update();
  TWEEN.update();
  renderer.render(scene, camera);
  requestAnimationFrame(this.render);

  // 呼吸灯效果要放到最后渲染,要不然没效果
  if (composer) {
    composer.render();
  }
}

// 飞线动画
flyLineAnimation() {
  commonUniformsValue -= 0.02;
  if (flyLineModel) {
    flyLineModel.flyLineGroup.children.forEach(item => {
      item.material.uniforms.u_time.value = commonUniformsValue
    })
  }
}

飞线效果

在这里插入图片描述

扩散波

绘制光柱工具方法:createLightBar.js(目前在这里只用到了绘制扩散波、六边形之类的方法)

此工具主要分为五个功能点

  1. drawLightBar:接收scene实体、地图url、具体光柱数据信息,然后创建多个Group调用创建不同实体效果的方法,并且把这些group添加到scene上外加return出去供外部使用。
drawLightBar(scene, mapUrl, datas) {
  let mapData = util.decode(mapUrl);
  this.dataKeys = {};
  mapData.features.forEach(d => {
    const { name, cp } = d.properties;
    this.dataKeys[name] = [...cp];
  });
  this.colors = ['#fff', '#ff0'];
  this.colorIndex = 0;
  this.textures = [new THREE.TextureLoader().load(img1), new THREE.TextureLoader().load(img2), new THREE.TextureLoader().load(img3)];
  this.pointsLength = 20;

  const sixMeshgroup = new THREE.Group();
  const sixLineGroup = new THREE.Group();
  const waveGroup = new THREE.Group();
  const lightBarGroup = new THREE.Group();
  const lightCurtainGroup = new THREE.Group();
  const labelGroup = new THREE.Group();

  datas.forEach((d, i) => {
    let siteName = d.name
    siteName = siteName.replace(RegExp('站', 'g'), '市');
    const lnglat = this.dataKeys[siteName];
    const [x, y, z] = this.lnglatToMector(lnglat);

    let color = '#fff';
    if (d.main) {
      color = '#ff0'
    }

    // 绘制六边体
    sixMeshgroup.add(this.drawSixMesh(x, y, z, color));
    // 绘制6边线
    sixLineGroup.add(this.drawSixLineLoop(x, y, z, color));
    // 绘制扩散波
    waveGroup.add(this.drawWaveMesh(x, y, z, color));

    // 绘制柱子
    const [plane1, plane2] = this.drawPlane(x, y, z, d.value, color);
    lightBarGroup.add(plane2);
    lightBarGroup.add(plane1);
    // 绘制柱子光幕
    lightCurtainGroup.add(this.drawPlanelightCurtain(x, y, z, d.value, color));

    // 绘制站名
    labelGroup.add(this.drawLabel(x, y, 2.3, d, color));
  });

  /**
     * 这里有个坑:不知道为啥,如果把mosh或line放到lightCurtainGroup前面加载到scene里面,透明度会被遮挡
     * 所以,这里是按照光柱-光柱光罩-底部元素-标题来添加到scene中
     */
  scene.add(lightBarGroup);
  scene.add(lightCurtainGroup);
  scene.add(sixMeshgroup);
  scene.add(sixLineGroup);
  scene.add(waveGroup);
  scene.add(labelGroup);

  return {
    lightBarGroup: lightBarGroup,
    lightCurtainGroup: lightCurtainGroup,
    sixMeshgroup: sixMeshgroup,
    sixLineGroup: sixLineGroup,
    waveGroup: waveGroup,
    labelGroup: labelGroup
  }
}
  1. drawSixMesh、drawSixLineLoop:绘制六边形和六边线
drawSixMesh(x, y, z, color) {
  let radius = 0.06;
  let segments = 6;
  if (color !== '#fff') {
    radius = 0.1
  }
  const geometry = new THREE.CircleGeometry(radius, segments);
  const material = new THREE.MeshBasicMaterial({ color: color });
  const mesh = new THREE.Mesh(geometry, material);
  mesh.position.set(x, y, z + 0.28);
  return mesh;
}

drawSixLineLoop(x, y, z, color) {
  // 绘制六边型
  let innerRadius = 0.12;
  let outerRadius = 0.15;
  let thetaSegments = 36;
  if (color !== '#fff') {
    innerRadius = 0.15
    outerRadius = 0.2
  }
  const geometry = new THREE.RingGeometry(innerRadius, outerRadius, thetaSegments);
  const material = new THREE.MeshBasicMaterial({ color: color, transparent: true });
  // geometry.vertices.shift();
  const line = new THREE.Mesh(geometry, material);
  line.position.set(x, y, z + 0.28);
  return line;
}

在这里插入图片描述

  1. drawWaveMesh:绘制扩散波,当外层获取到此mesh后,会添加到动画中,这样就会出现扩散波动画的效果了
drawWaveMesh(x, y, z, color) {
  let material = new THREE.MeshBasicMaterial( {
    color: color === '#fff' ? '#22ffcc' : '#ff0',
    map: this.textures[2],
    transparent: true, //使用背景透明的png贴图,注意开启透明计算
    opacity: 1.0,
    // side: THREE.DoubleSide, //双面可见
    depthWrite: false, //禁止写入深度缓冲区数据
  } );
  const geometry = new THREE.PlaneGeometry(1, 1);
  let mesh = new THREE.Mesh( geometry, material );
  let size = 5 * 0.06;//矩形平面Mesh的尺寸
  if (color !== '#fff') {
    size = 5 * 0.09
  }
  mesh.size = size;//自顶一个属性,表示mesh静态大小
  mesh.scale.set( size, size, size );//设置mesh大小
  mesh._s = 0.5;//自定义属性._s表示mesh在原始大小基础上放大倍数  光圈在原来mesh.size基础上1~2倍之间变化
  mesh.position.set(x, y, z + 0.28);
  mesh.layers.enable(1);
  return mesh;
}

在这里插入图片描述

使用createLightBar.js绘制扩散波
let lightBarModel = null;

let posArr = [
  { 'x': - 1.7049594735603837, 'y': 3.208354470512221, 'z': - 3.4350509144786985 },
  { 'x': - 2.1965610576118175, 'y': 2.1955955192304506, 'z': - 3.9184792759587768 },
  { 'x': - 2.2290975556080355, 'y': 2.6054406912933263, 'z': - 3.639066211507457 },
  { 'x': 0.5738958419746141, 'y': - 0.44114968930852216, 'z': 4.9473255920938985 },
  { 'x': - 0.9326350073394328, 'y': 2.8399222968004114, 'z': - 4.00812091773949 },
  { 'x': 3.469198597393574, 'y': 1.2295167303380952, 'z': - 3.3842206934036057 },
  { 'x': - 2.4019084876611916, 'y': - 2.190220428765315, 'z': 3.7991801866087123 },
  { 'x': - 2.49363689878109, 'y': - 4.099696049856375, 'z': 1.4050862307450966 },
  { 'x': - 2.3729307780326305, 'y': 2.840227787960863, 'z': 3.3618901878497454 },
  { 'x': - 2.0636200279017873, 'y': 0.7444294629976027, 'z': - 4.493027615657812 },
  { 'x': 0.47725894517680106, 'y': 2.4327372143508037, 'z': - 4.34212085796347 },
  { 'x': - 2.4777001955161246, 'y': - 1.2092952460724242, 'z': 4.171163716394502 },
  { 'x': - 0.03915748918627658, 'y': - 0.008362945319338826, 'z': 4.999839672648135 },
  { 'x': 1.5223738738260317, 'y': - 1.032865814102439, 'z': - 4.649254348640267 },
  { 'x': - 0.26640112020426315, 'y': - 4.314854187280748, 'z': 2.5121830716848077 },
  { 'x': - 4.031470206741836, 'y': - 2.606648761952297, 'z': - 1.3973654511134501 },
  { 'x': 0.8544382232162094, 'y': 1.5274953155132989, 'z': 4.683662390031124 },
  { 'x': 3.0409624989238546, 'y': 1.76433738825175, 'z': - 3.555230043268055 },
  { 'x': - 4.721251023266457, 'y': 1.2354922989397954, 'z': - 1.0878177947459262 },
  { 'x': 2.1518961827021106, 'y': 3.891904027152385, 'z': - 2.285262755638206 },
  { 'x': 0.8501960736517479, 'y': - 2.851729208821255, 'z': - 4.018060123480341 },
  { 'x': 2.5631840141785176, 'y': 4.263234820997851, 'z': - 0.5048926326370041 },
  { 'x': - 0.4580143454812531, 'y': - 2.6523265200067385, 'z': 4.213714144386437 }
];

// 绘制标点
lightBarModel = lightBarUtil.drawLightBar(posArr)
earthGroup.add(lightBarModel.sixMeshgroup)
earthGroup.add(lightBarModel.sixLineGroup)
earthGroup.add(lightBarModel.waveGroup)

开启扩散波动画
render() {
  // 光柱扩散波动画
  this.lightBarAnimation();
  // 飞线动画
  this.flyLineAnimation();
  // 地球自转动画
  this.earthRotationAnimation();

  scene.traverse(this.darkenNonBloomed) // 隐藏不需要辉光的物体
  bloomComposer.render()
  scene.traverse(this.restoreMaterial) // 还原

  // 更新性能插件
  // stats.update();
  TWEEN.update();
  renderer.render(scene, camera);
  requestAnimationFrame(this.render);

  // 呼吸灯效果要放到最后渲染,要不然没效果
  if (composer) {
    composer.render();
  }
}

// 光柱扩散波动画
lightBarAnimation() {
  if (lightBarModel) {
    lightBarModel.waveGroup.children.forEach(mesh => {
      mesh._s += 0.008;
      mesh.scale.set( mesh.size * mesh._s, mesh.size * mesh._s, mesh.size * mesh._s );
      if (mesh._s <= 1.5) {
        //mesh._s=1,透明度=0 mesh._s=1.5,透明度=1
        mesh.material.opacity = ( mesh._s - 1 ) * 2;
      } else if (mesh._s > 1.5 && mesh._s <= 2) {
        //mesh._s=1.5,透明度=1 mesh._s=2,透明度=0
        mesh.material.opacity = 1 - ( mesh._s - 1.5 ) * 2;
      } else {
        mesh._s = 1.0;
      }
    } );
  }
}

扩散波效果

在这里插入图片描述

大屏元素

这一篇文章非常简单,但是也非常出效果
阅读前面几篇文章,都可以看到有顶部、左侧、右侧的面板,主要我不会录gif图片,大屏元素面板还有一些动态加载的效果。

vue的transition标签

Vue 提供了 transition 的封装组件,可以给任何元素和组件添加进入/离开过渡;在进入/离开的过渡中,会有 6 个 class 切换:v-enter 、v-enter-active 、v-enter-to 、v-leave 、v-leave-active 、v-leave-to。
我们正好使用这个特性来达到面板进入的效果

<div class="page">
  <transition
              enter-active-class="animated fadeInDown"
              leave-active-class="animated fadeOutUp"
              appear
              >
    <navigation />
  </transition>
  <transition
              enter-active-class="animated fadeInLeft"
              leave-active-class="animated fadeOutLeft"
              appear
              >
    <div class="left">
      <yyzsr />
      <lrze />
      <jlr />
      <sfzj />
      <yysrbl />
    </div>
  </transition>
  <div
       class="content"
       >
    <div class="c-left">
      <div class="c-l-1">
        <div class="label">
          勘探与生产
        </div>
        <div class="value">
          8,287.6
        </div>
      </div>
      <div class="c-l-2">
        <div class="label">
          炼油与化工
        </div>
        <div class="value">
          5,342.2
        </div>
      </div>
      <div class="c-l-3">
        <div class="label">
          销售
        </div>
        <div class="value">
          11,846.5
        </div>
      </div>
      <div class="c-l-4">
        <div class="label">
          天然气
        </div>
        <div class="value">
          4,214.9
        </div>
      </div>
      <div class="c-l-5">
        <div class="label">
          中油国际
        </div>
        <div class="value">
          11,377.4
        </div>
      </div>
      <div class="c-l-6">
        <div class="label">
          世界贸易
        </div>
        <div class="value">
          23,887.8
        </div>
      </div>
    </div>
    <div class="c-right">
      <div class="c-r-1">
        <div class="label">
          亚太合作区
        </div>
        <div class="value">
          8,287.6
        </div>
      </div>
      <div class="c-r-2">
        <div class="label">
          中亚-俄罗斯合作区
        </div>
        <div class="value">
          14,738.9
        </div>
      </div>
      <div class="c-r-3">
        <div class="label">
          中东合作区
        </div>
        <div class="value">
          34,784.6
        </div>
      </div>
      <div class="c-r-4">
        <div class="label">
          美洲合作区
        </div>
        <div class="value">
          5,979.8
        </div>
      </div>
      <div class="c-r-5">
        <div class="label">
          非洲合作区
        </div>
        <div class="value">
          44,468.6
        </div>
      </div>
      <div class="c-r-6">
        <div class="label">
          特殊地区
        </div>
        <div class="value">
          2,986.6
        </div>
      </div>
    </div>
  </div>
  <transition
              enter-active-class="animated fadeInRight"
              leave-active-class="animated fadeOutRight"
              appear
              >
    <div class="right">
      <yqcl />
      <gnyyjgl />
      <gncpyxsl />
      <gntrqxsl />
      <yftrqd />
    </div>
  </transition>
</div>

整体效果

在这里插入图片描述

在这里插入图片描述

PS:项目源码及3d模型会在第一篇文章给出下载地址,或者添加wx:z13964122832备注“全球图源码”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

threejscesium项目实战

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值