书接上回,我们在上文中的基础之上添加飞线、扩散波来表示具体业务 - 资金的流向,再添加一些大屏元素让整个页面看起来更加成型。
飞线
绘制飞线工具方法:createFlyLine.js
此工具主要分为三个功能点
- 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,
};
}
- 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;
}
- 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(目前在这里只用到了绘制扩散波、六边形之类的方法)
此工具主要分为五个功能点
- 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
}
}
- 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;
}
- 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备注“全球图源码”