echarts给自定义地图添加流光效果

echarts yekong

我们之前实现过一个echarts和地图图片叠加实现数据渲染的效果,今天我们要实现一个效果就是给地图底图增加一个流光效果。

echarts给自定义地图添加流光效果

动态效果

在线演示

帧动画资源文件比较大,加载时间会比较长。

在线演示

准备图片

首先我们需要准备地图底图,地图尺寸1023*783,文件大小705kb

准备图片

图片处理

我们需要创建一个图层,图层是当前图片宽高的3倍,也就是3069×2349,之所以这样创建是给流光增加展示空间,避免我们创建的流光因为展示空间不足被硬生生截断。

图片处理

ae处理

将我们的图片拖入到ae中,使用钢笔工具将地图轮廓勾勒出来,然后添加saber流光插件

ae处理添加流光

添加saber配置

这里我们使用saber创建来实现我们的动画效果,下面是基本的配置参数

添加saber配置

帧动画导出

这里我们的帧动画图片一共有8秒,200张图片,这个图片本身有705k,加上流光大搞800多k,200张帧图片的话,导出图片就要高达160M,单单这个帧动画就这么大的话,前端就没办法正常渲染了,所以我们需要优化,我们将地图和流光分为两层,导出的时候,我们只导出流光,我们导出为200张流光图,大小一共为32.9M,虽然依然很大,但相比于160M地图,这里已经很小了。

帧动画导出

前端渲染

图片导出后,我们需要可以正常渲染,保证地图渲染出来不会出现错位。下面是帧动画的渲染,我们将200张帧动画图片渲染,因为图片四周各有100%的空白区域,所以我们需要调整一下定位。因为帧动画有空白区域的影响,可能会干扰到其他区域,这里我们需要让这一层不会干扰到鼠标点击,这里给这一层增加一个鼠标穿透pointer-events: none;

<template>
  <div class="canvas-container">
    <div class="canvas-wrapper">
      <canvas ref="animationCanvas" class="animation_canvas" id="animation_canvas"></canvas>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    fileLength: {
      type: Number,
      default: 199
    },
    IntervalTime: {
      type: Number,
      default: 60
    }
  },
  data() {
    return {
      animationCanvas: null,
      isLoading: true
    };
  },
  methods: {
    // 加载所有图片
    async loadImages2() {
      const sources = [];
      for (let i = 0; i <= this.fileLength; i++) {
        const image = await import(`./bg/${i}.png`);
        sources.push(image.default);
      }
      return sources;
    },

    // 预加载图片
    loadImages(sources) {
      return new Promise((resolve) => {
        let loadedImages = 0;
        const numImages = sources.length;
        const images = [];

        for (let i = 0; i < numImages; i++) {
          images[i] = new Image();
          images[i].onload = () => {
            loadedImages++;
            if (loadedImages >= numImages) {
              resolve(images);
            }
          };
          images[i].src = sources[i];
        }
      });
    },

    // 播放动画
    playImages(images, ctx, width, height) {
      let imageNow = 0;
      return setInterval(() => {
        ctx.clearRect(0, 0, width, height);
        ctx.drawImage(images[imageNow], 0, 0, width, height);
        imageNow = (imageNow + 1) % images.length;
      }, this.IntervalTime);
    },

    // 初始化画布
    initCanvas() {
      const canvas = this.$refs.animationCanvas;
      if (!canvas) return null;

      const ctx = canvas.getContext('2d');
      if (!ctx) {
        console.error('Failed to get canvas context');
        return null;
      }

      const width = canvas.offsetWidth;
      const height = canvas.offsetHeight;
      canvas.width = width;
      canvas.height = height;

      return { ctx, width, height };
    }
  },
  async mounted() {
    try {
      // 初始化画布
      const canvasData = this.initCanvas();
      if (!canvasData) return;
      const { ctx, width, height } = canvasData;

      // 加载所有图片源
      const sources = await this.loadImages2();

      // 预加载所有图片
      const images = await this.loadImages(sources);

      // 开始播放动画
      this.playImages(images, ctx, width, height);

    } catch (error) {
      console.error('Animation initialization error:', error);
    }
  }
};
</script>

<style scoped>
.canvas-container {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% + 200%);
  height: calc(100% + 200%);
  overflow: hidden;
  z-index: -1;
  pointer-events: none;
}

.animation_canvas {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
}
</style>

echarts地图代码渲染

下面是地图的渲染,我们需要调整层级,最下面时地图图片层,中间是流光帧动画层,最上面是echarts层。

<template>
  <div class="item1" :style="{width:width?width+'px':'100%',height:height?height+'px':'100%'}">
    <div class="mapPopWin" ref="mapPopWin" v-if="popShow"
         :style="{left:left-(popWidth/2)+'px',top:top-(popHeight/2)+'px'}">
      <img class="close" @click="popShow=false" src="../assets/close.png" alt="">
      <h3>{{ address }}</h3>
      <div class="info2">
        <p>接入车辆数:{{ num }}</p>
      </div>
    </div>
    <div class="centerMap" :style="{width:width+'px',height:height+'px'}" ref="centerMap" id="centerMap">
    </div>
    <div class="map map2">
      <sequence></sequence>
    </div>
    <div class="map">
      <img ref="img" src="../assets/centerMap.png" alt="">
    </div>
  </div>
</template>

<script>
import * as echarts from "echarts";
import data from '../assets/data.json'
import mapTag from '../assets/labelImg.png'
import sequence from '../sequence/index.vue'
export default {
  name: "item1",
  data() {
    return {
      data,
      height: 0,
      width: 0,
      popShow: false,
      left: 0,
      top: 0,
      address: '',
      num: 10,
      popWidth: 0,
      popHeight: 0,
    }
  },
  components: {sequence},
  watch: {
    popShow() {
      this.getPopInfo()
    }
  },
  mounted() {
    var that = this;
    const viewElem = document.body;
    // 监听窗口变化,重绘echarts
    const resizeObserver = new ResizeObserver(() => {
      setTimeout(() => {
        that.drawEcharts();
      }, 300)
    });
    resizeObserver.observe(viewElem);
  },
  methods: {
    getPopInfo() {
      this.$nextTick(() => {
        this.popWidth = this.$refs?.mapPopWin?.offsetWidth
        this.popHeight = this.$refs?.mapPopWin?.offsetHeight * 2
      })
    },
    drawEcharts() {
      this.width = this.$refs.img.offsetWidth
      this.height = this.$refs.img.offsetHeight
      this.$nextTick(() => {
        this.getEcharts()
      })
    },
    getEcharts() {
      var that = this;
      var chartDom = that.$refs.centerMap;
      var myChart = echarts.init(chartDom);
      myChart.clear()
      myChart.resize()
      var nameMap = '地图数据';
      var geoCoordMap = {};
      var mapData = [];
      var serverdata = []
      // 图标数据
      var iconData = [];
      echarts.registerMap(nameMap, this.data);
      var mapFeatures = echarts.getMap(nameMap).geoJson.features;
      myChart.hideLoading();
      var mapName = ''
      var mapInfo = []
      mapFeatures.forEach(function (v, index) {
        // 地区名称
        mapData.push({
          name: v.properties.name,
          value: Math.random() * 100
        });
        geoCoordMap[v.properties.name] = v.properties.center;
        mapName = mapName + (mapName ? ',' : '') + v.properties.name
        mapInfo.push({
          name: v.properties.name,
          code: v.properties.adcode
        })
        var data = {
          "value": v.properties.center,
          "id": index,
          "name": v.properties.name,
          "num": (Math.random() * 100).toFixed(0)
        }
        iconData.push(data)
      });
      // 生成地图图标
      iconData.forEach((type, index) => {
        var datamap = {
          type: 'scatter',
          tooltip: {
            show: true,
            formatter: function (params) {
              return params.data.name;
            }
          },
          name: type.name,
          coordinateSystem: 'geo',
          symbol: 'image://' + mapTag,
          symbolSize: [68, 28],
          symbolOffset: [-0, -0],
          label: {
            normal: {
              show: true,
              position: 'top',
              offset: [0, 25],
              formatter: function (params) {
                console.log(params)
                var text = `{num|${params.data.num}}\n{name|${params.name}}`
                return text
              },
              color: '#fff',
              rich: {
                name: {
                  padding: [0, 0],
                  color: '#FEFEFE',
                  fontSize: 17,
                  fontWeight: 500,
                  fontFamily: 'YouSheBiaoTiHei'
                },
                num: {
                  padding: [10, 0],
                  color: '#11fffe',
                  fontSize: 20,
                  fontWeight: 500,
                  textAlign: 'center',
                  fontFamily: 'DIN-Bold'
                },
              },
            },
          },
          hoverAnimation: true,
          z: 6,
          data: [type]
        }
        serverdata.push(datamap)
      });
      var optionMap = {
        geo: {
          map: nameMap,
          show: true,
          aspectScale: 0.80,
          layoutCenter: ["50%", "65%"],
          layoutSize: '120%',
          roam: false,
          itemStyle: {
            normal: {
              borderColor: 'rgba(147, 235, 248, 0)',
              borderWidth: 0.5,
              areaColor: 'rgba(147, 235, 248, 1)',
              opacity: 0,
            },
            emphasis: {
              borderColor: 'rgba(147, 235, 248, 0)',
              borderWidth: 0.5,
              areaColor: 'rgba(147, 235, 248, 0)',
              opacity: 0,
            }
          },
          z: 0,
          label: {
            normal: {
              show: false
            },
            emphasis: {
              show: false
            }
          }
        },
        series: [
          ...serverdata,
        ]
      };
      myChart.clear()
      myChart.resize()
      myChart.setOption(optionMap);
      myChart.off('click')
      myChart.on('click', function (params) {
        console.log(params)
        that.left = params.event.event.offsetX;
        that.top = params.event.event.offsetY;
        that.popShow = false
        that.$nextTick(() => {
          that.popShow = true
        })
        that.address = params.name
        that.num = params.data.num
        let data = myChart.convertFromPixel('geo', [that.left, that.top])
        // that.getPositionByLonLats(data[0], data[1])
        // myChart.off('click')
        // myChart.setOption(optionMap, false)
        // myChart.off('click')
      })
      that.address = '四川'
      let datas = myChart.convertToPixel('geo', [102.02347029661686, 30.232391836704323]);
      that.left = datas[0];
      that.top = datas[1];
      that.popShow = false
      iconData.forEach((type) => {
        if (type.name == '四川') {
          that.num = type.num
        }
      });
      that.$nextTick(() => {
        that.popShow = true
      })
    }
  },
}
</script>

<style lang="scss" scoped>
.item1 {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  margin: auto;
  align-items: center;
  flex-wrap: nowrap;
  flex-direction: row;
  align-content: flex-start;
}

.map {
  //background: url("../../../../../assets/centerMap.png") center center no-repeat;
  //background-size: 1024px 783px;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: nowrap;
  flex-direction: row;
  align-content: flex-start;
  margin: 0 auto;
  position: absolute;
  z-index: 0;

  img {
    height: 100%;
    max-width: 100%;
    max-height: 100%;
  }
}

.centerMap {
  width: 100%;
  height: 100%;
  position: absolute;
  z-index: 2;
  top: 0;
}

.titleTop {
  position: absolute;
  top: 0;
  width: 100%;
  left: 0;
  z-index: 100;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: nowrap;
  flex-direction: column;
  align-content: flex-start;

  :deep(.titleTopDesc) {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: nowrap;
    flex-direction: row;
    align-content: flex-start;

    .real-time-num {
      font-size: 22px;
      font-family: DIN;
      font-weight: 500;
      width: auto;
      margin: 0;
      color: #1AFFFF;
    }

  }

  .unit {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: nowrap;
    flex-direction: row;
    align-content: flex-start;
    margin-top: -3px;
    margin-left: 3px;
  }

  .titleTopDesc {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: nowrap;
    flex-direction: row;
    align-content: flex-start;
  }
}

.infoWin {
  background: red;
  width: 100px;
  height: 100px;
}

.mapPopWin {
  position: absolute;
  left: 0;
  top: 0;
  background: url("../assets/activeIcon.png") no-repeat;
  background-size: 100% 100%;
  width: 220PX;
  height: 141PX;
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  flex-wrap: nowrap;
  flex-direction: row;
  align-content: flex-start;
  z-index: 10000;

  .close {
    position: absolute;
    right: 0;
    top: 0;
    cursor: pointer;
    width: 20px;
  }

  h3 {
    margin-left: 15px;
    font-size: 22px;
    font-family: PangMenZhengDao;
    font-weight: 400;
    color: #FEFEFE;
  }

  .info2 {
    font-size: 14px;
    font-family: PingFang;
    font-weight: 500;
    color: #FFFFFF;
    display: flex;
    justify-content: flex-start;
    align-items: flex-start;
    flex-wrap: nowrap;
    flex-direction: column;
    margin-top: 24px;
    margin-left: 10px;
    align-content: flex-start;

    p {
      margin: 0px;
    }
  }
}
.map2{
  z-index: 1;
  pointer-events: none;
}
</style>

实例下载

代码包括ae源文件
vue vite js 项目实例代码

相关文件下载地址
此资源需支付 ¥5 后下载
支付宝购买扫右侧红包码购买更优惠,如无法下载请联系微信:17331886870
喜欢
echarts给自定义地图添加流光效果