ThreeJS——在3D地球上标记中国地图板块

Threejs3D地球标记中国地图位置

先看效果

地球预览视频效果

用到的库

  1. TweenJS (动画库)用来做相机转场的动画
  2. Jquery(这里只用到一个 each 循环方法,可以使用 js 去写)
  3. ThreeJS (3D 地球制作)
  4. 100000.json(全国城市经纬度)
  5. d3.v6.js用来设置平面转3D效果(本来考虑做成3D的中国地图板块,最后因效果看起来比较美观还是考虑用线条嵌入球体的方式去实现,这里有小伙伴考虑制作3D的地图板块可以下载这个库)

适用范围

用于获取地图的位置以及到下一个目的地的总路程,可以将实际路程转成自己配置的路程,以及正在路上的标识,可以用头像表示,经过的地方可以嵌入链接点击进行跳转

设置基础场景

html 复制代码
<div id="map">
  <canvas id="c3d" class="c2d"></canvas>
</div>
<div id="demo"></div>
js 复制代码
const Dom = document.querySelector("#c3d");
const width = Dom.clientWidth;
const height = Dom.clientHeight;

如果是 Vue 写的话需要从onMounted生命周期中获取 Dom 元素

js 复制代码
// 纹理加载器
const loader = new THREE.TextureLoader();
// 渲染器
let renderer;
// 相机
let camera;
// 场景
let scene;
// 灯光
let light;
// 相机控制
let controls;
// 动画
let tween;
// 其他
let earthMesh,
  stars,
  radius,
  labelRenderer,
  label,
  labels,
  labelsable,
  labelimg;
js 复制代码
/**
 * 初始化渲染器
 * */
function initRenderer() {
  // antialias: true, alpha: true 抗锯齿设置
  renderer = new THREE.WebGLRenderer({
    canvas: Dom,
    antialias: true,
    alpha: true,
  });
  // window.devicePixelRatio 设备像素比
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);
  labelRenderer = new CSS2DRenderer();
  labelRenderer.domElement.style.position = "absolute";
  labelRenderer.domElement.style.top = "0px";
  labelRenderer.domElement.style.pointerEvents = "none";
  labelRenderer.setSize(width, height);
  document.getElementById("map").appendChild(labelRenderer.domElement);
}

/**
 * 初始化相机
 */
function initCamera() {
  camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
  camera.position.set(0, 0, 10);
  camera.lookAt(0, 0, 0);
  window.camera = camera;
}
/**
 * 初始化场景
 */
function initScene() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x1c3262);
  // 雾
  // scene.fog = new THREE.Fog(0x020924, 200, 1000)
  window.scene = scene;
}

/**
 * 初始化 相机控制
 */
function initControls() {
  controls = new OrbitControls(camera, renderer.domElement);
  // 阻尼惯性
  controls.enableDamping = true;
  controls.dampingFactor = 0.1;
  controls.enableZoom = true;
  controls.autoRotate = false;
  controls.rotateSpeed = 0.1;
  controls.autoRotateSpeed = 1;

  controls.enablePan = true;
  controls.addEventListener("change", function () {
    //相机位置与目标观察点距离
    const dis = controls.getDistance();
    console.log(camera.position);
  });
}

/**
 * 初始化光
 */
function initLight() {
  // 环境光
  const ambientLight = new THREE.AmbientLight(0xcccccc, 0.2);
  scene.add(ambientLight);
  // 平行光
  let directionalLight = new THREE.DirectionalLight(0xffffff, 0.2);
  directionalLight.position.set(1, 0.1, 0).normalize();
  // 平行光2
  let directionalLight2 = new THREE.DirectionalLight(0xff2ffff, 0.2);
  directionalLight2.position.set(1, 0.1, 0.1).normalize();
  scene.add(directionalLight);
  scene.add(directionalLight2);
  // 半球光
  let hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1);
  hemiLight.position.set(0, 1, 0);
  scene.add(hemiLight);
  // 平行光3
  let directionalLight3 = new THREE.DirectionalLight(0xffffff);
  directionalLight3.position.set(1, -200, -20);
  let directionalLight4 = new THREE.DirectionalLight(0xffffff);
  directionalLight4.position.set(0, 500, 500);
  // 开启阴影
  directionalLight3.castShadow = true;
  // 设置光边界
  directionalLight3.shadow.camera.top = 18;
  directionalLight3.shadow.camera.bottom = -10;
  directionalLight3.shadow.camera.left = -52;
  directionalLight3.shadow.camera.right = 12;
  scene.add(directionalLight3);
}
js 复制代码
// 旋转队列
const rotateSlowArr = [];
// 放大并透明 队列
const bigByOpacityArr = [];
// 移动 队列
const moveArr = [];

// 边界 绘制点集合
const lines = [];
// 炫光粒子 几何体
const geometryLz = new THREE.BufferGeometry();
// 炫光粒子 透明度
let opacitys = [];
/**
 * 渲染函数
 * */
function renders(time) {
  time *= 0.0;

  // 3D对象 旋转
  // _y 初始坐标 _s 旋转速度
  rotateSlowArr.forEach((obj) => {
    obj.rotation.y = obj._y + time * obj._s;
  });
  bigByOpacityArr.forEach(function (mesh) {
    //  目标 圆环放大 并 透明
    mesh._s += 0.01;
    mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
    if (mesh._s <= 2) {
      mesh.material.opacity = 2 - mesh._s;
    } else {
      mesh._s = 1;
    }
  });
  moveArr.forEach(function (mesh) {
    mesh._s += 0.01;
    let tankPosition = new THREE.Vector3();
    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
  });

  if (geometryLz.attributes.position) {
    geometryLz.currentPos += geometryLz.pointSpeed;
    for (let i = 0; i < geometryLz.pointSpeed; i++) {
      opacitys[(geometryLz.currentPos - i) % lines.length] = 0;
    }

    for (let i = 0; i < 200; i++) {
      opacitys[(geometryLz.currentPos + i) % lines.length] =
        i / 50 > 2 ? 2 : i / 50;
    }
    geometryLz.attributes.aOpacity.needsUpdate = true;
  }

  renderer.clear();
  labelRenderer.render(scene, camera);
  renderer.render(scene, camera);

  // requestAnimationFrame(renders)

  // earthMesh.rotation.y += 0.01
  stars.rotation.y += 0.001;
}

动画渲染函数

用于制作开始场景镜头动画(由远到近并附带略微旋转)

js 复制代码
let p1 = { x: 100, y: 200, z: 200 };
function initTWEEN() {
  let tweena = cameraCon({ x: 100, y: 200, z: 200 }, 1000);
  let tweenb = cameraCon({ x: 0, y: 0, z: 10 }, 4000);
  tweena.chain(tweenb);
  // tweenb.chain(tweenc);
  tweenb.onComplete(function () {
    console.log("结束");
    drawChart();
    //相机位置与观察目标点最小值
    controls.minDistance = 7;
    //相机位置与观察目标点最大值
    controls.maxDistance = 50;
    // 上下旋转范围
    /* 		controls.minPolarAngle = -Math.PI /6;
                        controls.maxPolarAngle = Math.PI /4; */
    // 左右旋转范围
    controls.minAzimuthAngle = -Math.PI / 6;
    controls.maxAzimuthAngle = Math.PI / 6;
  });
  tweena.start();
}
function cameraCon(p2 = { x: p1.x, y: p1.y, z: p1.z }, time = 5000) {
  var tween1 = new TWEEN.Tween(p1)
    .to(p2, time)
    .easing(TWEEN.Easing.Sinusoidal.InOut);
  var update = function () {
    camera.position.set(p1.x, p1.y, p1.z);
  };
  tween1.onUpdate(update);

  return tween1;
}
function cameraCon2(
  p2 = { x: camera.position.x, y: camera.position.y, z: camera.position.z },
  time = 2000
) {
  var tween1 = new TWEEN.Tween(camera.position)
    .to(p2, time)
    .easing(TWEEN.Easing.Sinusoidal.Out);
  var update = function () {
    camera.position.set(
      camera.position.x,
      camera.position.y,
      camera.position.z
    );
  };
  tween1.onUpdate(update);

  return tween1;
}
function animate() {
  window.requestAnimationFrame((time) => {
    if (controls) controls.update();
    TWEEN.update();
    renders(time);
    animate();
  });
}

cameraCon2这个动画在后面会用到,是根据滚动下方内容进行左右镜头旋转的动画效果

星空背景

js 复制代码
/**
 * 创建 方形纹理
 * */
function generateSprite() {
  const canvas = document.createElement("canvas");
  canvas.width = 16;
  canvas.height = 16;

  const context = canvas.getContext("2d");
  // 创建颜色渐变
  const gradient = context.createRadialGradient(
    canvas.width / 2,
    canvas.height / 2,
    0,
    canvas.width / 2,
    canvas.height / 2,
    canvas.width / 2
  );
  gradient.addColorStop(0, "rgba(255,255,255,1)");
  gradient.addColorStop(0.2, "rgba(0,255,255,1)");
  gradient.addColorStop(0.4, "rgba(0,0,64,1)");
  gradient.addColorStop(1, "rgba(0,0,0,1)");

  // 绘制方形
  context.fillStyle = gradient;
  context.fillRect(0, 0, canvas.width, canvas.height);
  // 转为纹理
  const texture = new THREE.Texture(canvas);
  texture.needsUpdate = true;
  return texture;
}

/**
 * 背景绘制
 * */
function bg() {
  const positions = [];
  const colors = [];
  // 创建 几何体
  const geometry = new THREE.BufferGeometry();

  for (let i = 0; i < 10000; i++) {
    let vertex = new THREE.Vector3();
    vertex.x = Math.random() * 2 - 1;
    vertex.y = Math.random() * 2 - 1;
    vertex.z = Math.random() * 2 - 1;
    positions.push(vertex.x, vertex.y, vertex.z);
  }
  // 对几何体 设置 坐标 和 颜色
  geometry.setAttribute(
    "position",
    new THREE.Float32BufferAttribute(positions, 3)
  );
  // 默认球体
  geometry.computeBoundingSphere();

  // ------------- 1 ----------
  // 星星资源图片
  // ParticleBasicMaterial 点基础材质
  var starsMaterial = new THREE.ParticleBasicMaterial({
    map: generateSprite(),
    size: 2,
    transparent: true,
    opacity: 1,
    //true:且该几何体的colors属性有值,则该粒子会舍弃第一个属性--color,而应用该几何体的colors属性的颜色
    // vertexColors: true,
    blending: THREE.AdditiveBlending,
    sizeAttenuation: true,
  });
  // 粒子系统 网格
  stars = new THREE.ParticleSystem(geometry, starsMaterial);
  stars.scale.set(300, 300, 300);
  scene.add(stars);
}

此时星空就已经搭建好了,可以左右旋转试试效果

搭建 3D 地球

其实也就是创建一个球,然后贴个准确的贴图放在圆上调整一些光感即可

js 复制代码
// 地球,月亮 3D层
const landOrbitObject = new THREE.Object3D();
// 地球3D层
const earthObject = new THREE.Object3D();
// 月亮3D层
const moonObject = new THREE.Object3D();
// 地球半径
const globeRadius = 5;
/**
 * 球相关加载
 * */
function earth() {
  radius = globeRadius;
  const widthSegments = 100;
  const heightSegments = 100;
  const sphereGeometry = new THREE.SphereGeometry(
    radius,
    widthSegments,
    heightSegments
  );
  function shine() {
    var texture = loader.load("./images/blue.png");
    var spriteMaterial = new THREE.SpriteMaterial({
      map: texture,
      transparent: true,
      opacity: 0.5,
      depthWrite: false,
    });
    var sprite = new THREE.Sprite(spriteMaterial);
    sprite.scale.set(radius * 3, radius * 3, 1);
    sprite.rotation.set(-Math.PI / 2, 0, 0);
    scene.add(sprite);
  }
  shine();

  // 地球
  const earthTexture = loader.load("./images/微信图片_20230711093004 (1).jpg");
  const earthMaterial = new THREE.MeshStandardMaterial({
    map: earthTexture,
  });
  earthMesh = new THREE.Mesh(sphereGeometry, earthMaterial);

  // 月球
  const moonTexture = loader.load("./images/yueqiu.jpg");
  const moonMaterial = new THREE.MeshPhongMaterial({ map: moonTexture });
  const moonMesh = new THREE.Mesh(sphereGeometry, moonMaterial);
  moonMesh.scale.set(0.1, 0.1, 0.1);
  moonMesh.position.x = 10;

  moonObject.add(moonMesh);
  // 加入动画队列
  moonObject._y = 0;
  moonObject._s = 1;
  rotateSlowArr.push(moonObject);

  // 地球加入 地球3D层
  earthObject.add(earthMesh);
  earthObject.rotation.set(0.5, 2.9, 0.1);
  earthObject._y = 2.0;
  earthObject._s = 0.1;
  // 加入动画队列
  // rotateSlowArr.push(earthObject)

  // 加入 地球3D层
  landOrbitObject.add(earthObject);
  // 加入 月亮3D层
  landOrbitObject.add(moonObject);

  scene.add(landOrbitObject);
}

/**
 * 经维度 转换坐标
 * THREE.Spherical 球类坐标
 * lng:经度
 * lat:维度
 * radius:地球半径
 */
function lglt2xyz(lng, lat, radius) {
  // 以z轴正方向为起点的水平方向弧度值
  const theta = (90 + lng) * (Math.PI / 180);
  // 以y轴正方向为起点的垂直方向弧度值
  const phi = (90 - lat) * (Math.PI / 180);
  return new THREE.Vector3().setFromSpherical(
    new THREE.Spherical(radius, phi, theta)
  );
}

绘制红色圆点和点与点之间的飞线效果

js 复制代码
/**
 * 绘制 目标点
 * */
function spotCircle(spot) {
  // 圆
  const geometry1 = new THREE.CircleGeometry(0.02, 100);
  const material1 = new THREE.MeshBasicMaterial({
    color: 0xff0000,
    side: THREE.DoubleSide,
  });
  const circle = new THREE.Mesh(geometry1, material1);
  circle.position.set(spot[0], spot[1], spot[2]);
  // mesh在球面上的法线方向(球心和球面坐标构成的方向向量)
  var coordVec3 = new THREE.Vector3(spot[0], spot[1], spot[2]).normalize();
  // mesh默认在XOY平面上,法线方向沿着z轴new THREE.Vector3(0, 0, 1)
  var meshNormal = new THREE.Vector3(0, 0, 1);
  // 四元数属性.quaternion表示mesh的角度状态
  //.setFromUnitVectors();计算两个向量之间构成的四元数值
  circle.quaternion.setFromUnitVectors(meshNormal, coordVec3);
  earthObject.add(circle);

  // 圆环
  const geometry2 = new THREE.RingGeometry(0.03, 0.04, 100);
  // transparent 设置 true 开启透明
  const material2 = new THREE.MeshBasicMaterial({
    color: 0xff0000,
    side: THREE.DoubleSide,
    transparent: true,
  });
  const circleY = new THREE.Mesh(geometry2, material2);
  circleY.position.set(spot[0], spot[1], spot[2]);

  // 指向圆心
  circleY.lookAt(new THREE.Vector3(0, 0, 0));
  earthObject.add(circleY);
  label.position.set(spot[0] - 0.15, spot[1] + 0.04, spot[2]);

  // 加入动画队列
  bigByOpacityArr.push(circleY);
}
js 复制代码
/**
 * 绘制 两个目标点并连线
 * */
function lineConnect(posStart, posEnd) {
  const v0 = lglt2xyz(posStart[0], posStart[1], globeRadius);
  const v3 = lglt2xyz(posEnd[0], posEnd[1], globeRadius);

  // angleTo() 计算向量的夹角
  const angle = v0.angleTo(v3);
  let vtop = v0.clone().add(v3);
  // multiplyScalar 将该向量与所传入的 标量进行相乘
  vtop = vtop.normalize().multiplyScalar(globeRadius);

  let n;
  if (angle <= 1) {
    n = (globeRadius / 3) * angle;
  } else if (angle > 1 && angle < 2) {
    n = (globeRadius / 3) * Math.pow(angle, 2);
  } else {
    n = (globeRadius / 3) * Math.pow(angle, 1.5);
  }

  const v1 = v0
    .clone()
    .add(vtop)
    .normalize()
    .multiplyScalar(globeRadius + n);
  const v2 = v3
    .clone()
    .add(vtop)
    .normalize()
    .multiplyScalar(globeRadius + n);
  // 三维三次贝塞尔曲线(v0起点,v1第一个控制点,v2第二个控制点,v3终点)
  const curve = new THREE.CubicBezierCurve3(v0, v1, v2, v3);

  // 绘制 目标位置
  spotCircle([v0.x, v0.y, v0.z]);
  spotCircle([v3.x, v3.y, v3.z]);
  //   线上移动物体
  moveSpot(curve);

  const lineGeometry = new THREE.BufferGeometry();
  // 获取曲线 上的50个点
  var points = curve.getPoints(50);
  var positions = [];
  var colors = [];
  var color = new THREE.Color();

  // 给每个顶点设置演示 实现渐变
  for (var j = 0; j < points.length; j++) {
    if (j < 25 || j == 25) {
      color.set(0xffffff); // 粉色
    } else if (j < 50 && j > 25) {
      color.set(0xfffdaa); // 粉色
    }

    colors.push(color.r, color.g, color.b);
    positions.push(points[j].x, points[j].y, points[j].z);
  }
  // 放入顶点 和 设置顶点颜色
  lineGeometry.addAttribute(
    "position",
    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
  );
  lineGeometry.addAttribute(
    "color",
    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
  );

  const material = new THREE.LineBasicMaterial({
    vertexColors: true,
    side: THREE.DoubleSide,
  });
  const line = new THREE.Line(lineGeometry, material);
  earthObject.add(line);
}
js 复制代码
/**
 * 线上移动物体
 * */
function moveSpot(curve) {
  // 线上的移动物体
  const aGeo = new THREE.SphereGeometry(0.04, 0.04, 0.04);
  const aMater = new THREE.MeshPhongMaterial({
    color: 0xff0000,
    side: THREE.DoubleSide,
  });
  const aMesh = new THREE.Mesh(aGeo, aMater);
  // 保存曲线实例
  aMesh.curve = curve;
  aMesh._s = 0;

  moveArr.push(aMesh);
  earthObject.add(aMesh);
}

用画布渲染城市信息以及每个点到点的路程的总距离

js 复制代码
/**
 * 画图
 * */

function drawChart() {
  const loader = new THREE.FileLoader();
  let centers;
  loader.load("./js/100000_full.json", (data) => {
    // 点与点
    let objName = [
      { name: "黑龙江省", url: "https://www.baidu.com" },
      { name: "内蒙古自治区" },
      { name: "四川省" },
      { name: "" },
    ];
    // 线与线
    let city = [{ to: "黑龙江省" }, { to: "内蒙古自治区" }, { to: "四川省" }];
    // 当前所在地
    let location = [{ type: 0 }];
    const jsondata = JSON.parse(data);
    let transformedData = [];
    // 循环
    $.each(jsondata.features, function (index, item) {
      const { centroid, center, name } = item.properties;
      /*    const point = centroid || center || [0, 0];
                       const depth = Math.random() * 0.3 + 0.3; */
      let proName = item.properties.name;
      let proName1 = item.properties.name;
      centers = item.properties.center;
      objName.forEach((v) => {
        if (v.name == proName) {
          labels = createLabel(name, v.url);
          earthObject.add(labels);
          if (centers != undefined) {
            let markPos = lglt2xyz(centers[0], centers[1], 5);
            spotCircle([markPos.x, markPos.y, markPos.z]);
          }

          // lineConnect(item.properties.center,[150,100,100])
        }
      });
      let lastIndex = city.length - 2;
      city.map((v, index) => {
        if (v.to == proName1) {
          v.tojwd = item.properties.center;
          let indexNum = index + 1;
          if (indexNum < city.length) {
            let formCity = city[index];
            let toCity = city[index + 1];
            setTimeout(() => {
              let combinedCity = {
                to: formCity.to,
                tojwd: v.tojwd,
                form: toCity.to,
                formjwd: toCity.tojwd,
              };

              if (combinedCity.formjwd != []) {
                let distance = getDistance(
                  combinedCity.tojwd[0],
                  combinedCity.tojwd[1],
                  combinedCity.formjwd[0],
                  combinedCity.formjwd[1]
                );
                combinedCity.dist = distance;
                transformedData.push(combinedCity);

                const coordinates = kilometersToCoordinates(
                  distance,
                  combinedCity.tojwd[1],
                  combinedCity.tojwd[0]
                );
                console.log(
                  "从" +
                    combinedCity.to +
                    "到" +
                    combinedCity.form +
                    "的距离为" +
                    distance +
                    "公里"
                ); // 输出两个坐标之间的距离,单位为公里

                // 示例用法
                if (index == lastIndex) {
                  sessionStorage.setItem("last", JSON.stringify(combinedCity));
                  let par = JSON.parse(sessionStorage.getItem("last"));
                } else {
                  lineConnect(combinedCity.tojwd, combinedCity.formjwd);
                }
                // lineConnect(transformedData[0].tojwd, transformedData[0].formjwd)
              }
            }, 100);
          }
        }
      });
      location.forEach((i) => {
        if (i.type == item.properties.subFeatureIndex) {
          setTimeout(() => {
            let last = JSON.parse(sessionStorage.getItem("last"));
            console.log(last.dist);
            // 更换自定义路线(公里)
            last.dist = 300;
            // 将经纬度转换为三维坐标
            const point1 = last.tojwd;
            const point2 = last.formjwd;
            // 个人的总步数(公里)
            let lucheng = 100;
            let bfb = lucheng / last.dist;
            let s = point1[0] + (point2[0] - point1[0]) * bfb * 1;
            let d = point1[1] + (point2[1] - point1[1]) * bfb * 1;
            const div = document.createElement("div");
            div.style.color = "#fff";
            div.style.fontSize = "14px";
            div.style.textShadow = "1px 1px 2px #047cd6";
            div.innerHTML = `<img style="width:30px;border-radius:50%" src="./images/head.jpg" />`;
            labelimg = new CSS2DObject(div);
            labelimg.scale.set(0.01, 0.01, 0.01);

            let markPos = lglt2xyz(s, d, 5);
            labelimg.position.set(markPos.x - 0.1, markPos.y + 0.04, markPos.z);
            earthObject.add(labelimg);

            // 定义距离为1000公里
            setTimeout(() => {
              lineConnect(point1, [s, d]);
            }, 101);
          }, 200);
        }
      });
    });
  });
}
function getDistance(lat1, lon1, lat2, lon2) {
  const R = 6371; // 地球半径,单位为公里

  const rLat1 = toRadians(lat1);
  const rLat2 = toRadians(lat2);
  const deltaLat = toRadians(lat2 - lat1);
  const deltaLon = toRadians(lon2 - lon1);

  const a =
    Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
    Math.cos(rLat1) *
      Math.cos(rLat2) *
      Math.sin(deltaLon / 2) *
      Math.sin(deltaLon / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

  const distance = R * c;
  return distance;
}
function kilometersToCoordinates(distance, latitude, longitude) {
  const earthRadius = 6371.393; // 地球半径,单位为公里

  // 计算纬度差值
  const latDiff = distance / earthRadius;

  // 根据纬度计算经度差值
  const lonDiff =
    distance / (earthRadius * Math.cos((Math.PI * latitude) / 180));

  // 计算新的经度和纬度
  const newLatitude = latitude + latDiff * (180 / Math.PI);
  const newLongitude = longitude + lonDiff * (180 / Math.PI);

  // 返回经度和纬度
  return { latitude: newLatitude, longitude: newLongitude };
}
js 复制代码
// 将角度转换为弧度
function toRadians(degree) {
  return degree * (Math.PI / 180);
}

创建可点击跳转的 url

js 复制代码
const createLabel = (name, url) => {
  const div = document.createElement("div");
  div.style.color = "#fff";
  div.style.fontSize = "10px";
  div.style.textShadow = "1px 1px 2px #047cd6";
  div.textContent = name;
  div.style.pointerEvents = "auto";
  div.style.cursor = "pointer";
  label = new CSS2DObject(div);
  div.addEventListener("click", function (event) {
    if (url != undefined) {
      window.location.href = url;
    } else {
      return;
    }
  });
  label.scale.set(0.01, 0.01, 0.01);
  return label;
};

创建描边炫光路径

js 复制代码
const vertexShader = `
			attribute float aOpacity;
			uniform float uSize;
			varying float vOpacity;
			void main(){
				gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
				gl_PointSize = uSize;
	
				vOpacity=aOpacity;
			}
			`;
const fragmentShader = `
			  varying float vOpacity;
			  uniform vec3 uColor;
	
			  float invert(float n){
				  return 1.-n;
			  }
	
			  void main(){
				if(vOpacity <=0.2){
					discard;
				}
				vec2 uv=vec2(gl_PointCoord.x,invert(gl_PointCoord.y));
				vec2 cUv=2.*uv-1.;
				vec4 color=vec4(1./length(cUv));
				color*=vOpacity;
				color.rgb*=uColor;
	
				gl_FragColor=color;
			  }
			  `;

/**
 * 边界炫光路径
 * */
function dazzleLight() {
  const loader = new THREE.FileLoader();
  loader.load("./js/100000.json", (data) => {
    const jsondata = JSON.parse(data);
    console.log(jsondata);
    // 中国边界
    const feature = jsondata.features[0];
    const province = new THREE.Object3D();
    province.properties = feature.properties.name;
    // 点数据
    const coordinates = feature.geometry.coordinates;

    coordinates.forEach((coordinate) => {
      // coordinate 多边形数据
      coordinate.forEach((rows) => {
        // 绘制线
        // const line = lineDraw(rows, 0xaa381e)
        const line = lineDraw(rows, 0xaa381e);
        province.add(line);
      });
    });
    // 添加地图边界
    earthObject.add(province);

    // 拉平 为一维数组
    const positions = new Float32Array(lines.flat(1));
    // 设置顶点
    geometryLz.setAttribute(
      "position",
      new THREE.BufferAttribute(positions, 3)
    );

    // 设置 粒子透明度为 0
    opacitys = new Float32Array(positions.length).map(() => 0);

    geometryLz.setAttribute("aOpacity", new THREE.BufferAttribute(opacitys, 1));

    geometryLz.currentPos = 0;
    // 炫光移动速度
    geometryLz.pointSpeed = 10;

    // 控制 颜色和粒子大小
    const params = {
      pointSize: 2.0,
      pointColor: "#4ec0e9",
    };

    // 创建着色器材质
    const material = new THREE.ShaderMaterial({
      vertexShader: vertexShader,
      fragmentShader: fragmentShader,
      transparent: true, // 设置透明
      uniforms: {
        uSize: {
          value: params.pointSize,
        },
        uColor: {
          value: new THREE.Color(params.pointColor),
        },
      },
    });
    const points = new THREE.Points(geometryLz, material);
    earthObject.add(points);
  });
}

/**
 * 边框 图形绘制
 * @param polygon 多边形 点数组
 * @param color 材质颜色
 * */
let indexBol = true;
function lineDraw(polygon, color) {
  const lineGeometry = new THREE.BufferGeometry();
  const pointsArray = new Array();
  polygon.forEach((row) => {
    // 转换坐标
    const xyz = lglt2xyz(row[0], row[1], globeRadius);
    // 创建三维点
    pointsArray.push(xyz);

    if (indexBol) {
      // 为了好看 这里只要内陆边界
      lines.push([xyz.x, xyz.y, xyz.z]);
    }
  });
  indexBol = false;
  // 放入多个点
  lineGeometry.setFromPoints(pointsArray);
  const lineMaterial = new THREE.LineBasicMaterial({
    color: color,
  });
  return new THREE.Line(lineGeometry, lineMaterial);
}

最后当 ul 中的 li 发生滚动时调用前面所执行的相机转场动画的效果

js 复制代码
let { scrollY } = window;
let currentSection = 0;
window.addEventListener("scroll", () => {
  scrollY = window.scrollY;
  const newSection = Math.round(scrollY / height);
  console.log(newSection);
  console.log(camera.position);
  if (newSection !== currentSection) {
    currentSection = newSection;
    console.log(currentSection);
    if (currentSection == 0) {
      currentSection = newSection;
      // console.log('changed', currentSection)
      let tweena = cameraCon2(camera.position, 1000);
      console.log(camera.position);
      let tweenb = cameraCon2({ x: 0, y: 1, z: 10 }, 1000);
      tweena.chain(tweenb);
      tweena.start();
    } else {
      currentSection = newSection;
      // console.log('changed', currentSection)
      let tweena = cameraCon2(camera.position, 1000);
      console.log(camera.position);
      let tweenb = cameraCon2({ x: 0, y: -1, z: 10 }, 1000);
      tweena.chain(tweenb);
      tweena.start();
    }
  }
});

全局初始化

js 复制代码
window.onload = () => {
  // 初始化
  initTWEEN();
  initRenderer();
  initCamera();
  initScene();
  initLight();
  initControls();
  // 绘制
  bg();
  earth();
  dazzleLight();
  // 渲染
  animate();
};

完整代码地址 earthling ,此项目仅用于交流学习

相关推荐
小妖6666 分钟前
css 中 content: “\e6d0“ 怎么变成图标的?
前端·css
L耀早睡1 小时前
mapreduce打包运行
大数据·前端·spark·mapreduce
咖啡の猫1 小时前
JavaScript基础-创建对象的三种方式
开发语言·javascript·ecmascript
HouGISer1 小时前
副业小程序YUERGS,从开发到变现
前端·小程序
outstanding木槿1 小时前
react中安装依赖时的问题 【集合】
前端·javascript·react.js·node.js
小吕学编程2 小时前
Jackson使用详解
java·javascript·数据库·json
霸王蟹2 小时前
React中useState中更新是同步的还是异步的?
前端·javascript·笔记·学习·react.js·前端框架
霸王蟹2 小时前
React Hooks 必须在组件最顶层调用的原因解析
前端·javascript·笔记·学习·react.js
专注VB编程开发20年2 小时前
asp.net IHttpHandler 对分块传输编码的支持,IIs web服务器后端技术
服务器·前端·asp.net
爱分享的程序员3 小时前
全栈项目搭建指南:Nuxt.js + Node.js + MongoDB
前端