【Vue3+vite+Echarts】完美解决3D图表旋转卡顿问题,警告: ‘requestAnimationFrame‘ handler 用时 <N> 毫秒

hi brother!你是否还在为3D图表旋转卡顿而烦恼?不用担心,跟着本文步骤走,让咱们的图表丝滑旋转上天!

【警告内容 】

‘requestAnimationFrame’ handler 用时 <N> 毫秒在这里插入图片描述
顺嘴说一句,有些朋友开发阶段还出现geo3D exists的警告。不用担心!打包后就不会出现了!!!

【解决方案】

1、下载 worker-loader
(自行了解worker-loader是什么,这哥们总结的挺好:http://t.csdnimg.cn/tMQM1)

npm i worker-loader

2、写一个worker.js文件

// worker.js
// 此文件的作用类似于咱们封装aixos的interceptors,过滤器效果
// 可以做其他处理
addEventListener("message", (e) => {
  const { data } = e;
  // console.log(data)
  return postMessage(data);
});

3、使用worker

  • 开线程
  let myChart = null 
  let myWorker = null
  onMounted(() => {
    myChart = $echarts.init(pie3d.value); 
    myWorker = new Worker(new URL('../../../utils/worker.js',
      import.meta.url)) //这里的url写自己的worker.js的地址
	myWorker.postMessage(option); // 开线程,传递Echarts的option数据
    myWorker.onmessage = function (event) {
      // 这里的数据已经在worker.js处理过一遍
      // 在这里更新Echarts
      myChart.setOption(event.data);
      
    };
  })
  • 关线程
    开了要记得关!
onBeforeUnmount(() => {
    // console.log("摧毁之前")
    myWorker.terminate() //关闭线程
  })

【案例】

话不多说,上案例!
自己封装的组件,

<template>
  <div>
	<pie :dataList="dataList"></pie>
  </div>
</template>
<script setup>
import Pie from './pie.vue'
const dataList=[1,2,3,4]

</script>

pie.vue组件

<!-- pie.vue -->
<template>
  <div class="pie">
    <div ref="pie3d" style="width:200px;height:160px;"></div>
  </div>
</template>
<script setup>
  import {
    onMounted,
    onBeforeUnmount,
    watch,
    nextTick,
    ref,
    reactive,
    getCurrentInstance
  } from 'vue';
  
  const props = defineProps({
    dataList: {
      type: Object,
      default: () => {
        return {}
      }
    }
  })
  const {
    $echarts
  } = getCurrentInstance().appContext.config.globalProperties

  const optionsData = reactive({
    list: []
  })
  const pie3d = ref()
  let myChart = null
  let myWorker = null
  const opacity = 0.8;
  onMounted(() => {
    myChart = $echarts.init(pie3d.value);
    myWorker = new Worker(new URL('../../../utils/worker.js',
      import.meta.url))
    setWorker()
  })

  const setWorker = () => {
    myWorker.onmessage = function (event) {
      // 这里的数据已经在worker.js处理过一遍
      // 在这里更新Echarts
      if (event.data) {
        // 开启线程
        init()
      } else {
        // 线程结束
        myWorker.terminate() // 关闭线程
      }
    };
  }
  //格式化数据
  const formatingData = () => {
    optionsData.list = [{
        name: "数据1",
        value: props.dataList[0] || 0,
        itemStyle: {
          opacity: opacity,
          color: "rgba(219,112,147,1)",
        },
      },
      {
        name: "数据2",
        value: props.dataList[1] || 0,
        itemStyle: {
          opacity: opacity,
          color: "rgba(240,230,140, 1)",
        },
      },
      {
        name: "数据3",
        value: props.dataList[2] || 0,
        itemStyle: {
          opacity: opacity,
          color: "rgba(0, 184, 246, 1)",
        },
      },
      {
        name: "数据4",
        value: props.dataList[3] || 0,
        itemStyle: {
          opacity: opacity,
          color: "rgba(144,238,144, 1)",
        },
      },
    ]
  }
  //初始化构建
  const init = () => {
    let series = getPie3D(optionsData.list, 0.85);
    let option = {
      animation: true,
      backgroundColor: "transparent",
      label: {
        show: true,
        position: "outside",
        formatter: function (optionsData) {
          return (
            "{name|" +
            optionsData.name +
            "}" +
            " {value|" +
            optionsData.percent.toFixed(0) +
            "%}"
          );
        },
        rich: {
          name: {
            fontSize: 14,
            color: "#ffffff",
          },
          value: {
            fontSize: 14,
            color: "#ffffff",
          },
        },
        textStyle: {
          color: "#fff",
          fontSize: "14px",
        },
      },
      xAxis3D: {
        min: -1,
        max: 1,
      },
      yAxis3D: {
        min: -1,
        max: 1,
      },
      zAxis3D: {
        min: -1,
        max: 1,
      },
      grid3D: {
        show: false,
        boxHeight: 2,
        boxDepth: "160",
        boxWidth: '160',
        top: '0%',
        left: "0%",
        viewControl: {
          distance: 250,
          alpha: 22,
          beta: 15,
          autoRotate: true, // 自动旋转
        },
      },
      series: series,
    };

    //将配置项设置进去
    myChart.setOption(option);
  }
  // 设置3DPie
  const getPie3D = (pieData, internalDiameterRatio) => {
    let series = [];
    let sumValue = 0;
    let startValue = 0;
    let endValue = 0;
    let legendData = [];
    let k =
      typeof internalDiameterRatio !== "undefined" ?
      (1 - internalDiameterRatio) / (1 + internalDiameterRatio) :
      1 / 3;

    // 为每一个饼图数据,生成一个 series-surface 配置
    for (let i = 0; i < pieData.length; i++) {
      sumValue += pieData[i].value;

      let seriesItem = {
        name: typeof pieData[i].name === "undefined" ?
          `series${i}` : pieData[i].name,
        type: "surface",
        parametric: true,
        wireframe: {
          show: false,
        },
        pieData: pieData[i],
        pieStatus: {
          selected: false,
          hovered: false,
          k: k,
        },
      };

      if (typeof pieData[i].itemStyle != "undefined") {
        let itemStyle = {};

        typeof pieData[i].itemStyle.color != "undefined" ?
          (itemStyle.color = pieData[i].itemStyle.color) :
          null;
        typeof pieData[i].itemStyle.opacity != "undefined" ?
          (itemStyle.opacity = pieData[i].itemStyle.opacity) :
          null;

        seriesItem.itemStyle = itemStyle;
      }
      series.push(seriesItem);
    }
    for (let i = 0; i < series.length; i++) {
      endValue = startValue + series[i].pieData.value;
      series[i].pieData.startRatio = startValue / sumValue;
      series[i].pieData.endRatio = endValue / sumValue;
      series[i].parametricEquation = getParametricEquation(
        series[i].pieData.startRatio,
        series[i].pieData.endRatio,
        false,
        false,
        k,
        // 调整扇形高度
        i === 0 ? 10 : 10,
        i,
        series[i].pieData.value
      );

      startValue = endValue;

      legendData.push(series[i].name);
    }
    return series;
  }
  const getParametricEquation = (startRatio, endRatio, isSelected, isHovered, k, height, i) => {
    // 计算
    let midRatio = (startRatio + endRatio) / 2;
    let startRadian = startRatio * Math.PI * 2;
    let endRadian = endRatio * Math.PI * 2;
    let midRadian = midRatio * Math.PI * 2;
    // 如果只有一个扇形,则不实现选中效果。
    if (startRatio === 0 && endRatio === 1) {
      isSelected = false;
    }
    // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
    k = typeof k !== "undefined" ? k : 1 / 3;
    // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
    let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
    let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
    let offsetZ = i == 1 ? 2 : 0;
    // 计算高亮效果的放大比例(未高亮,则比例为 1)
    let hoverRate = isHovered ? 1.05 : 1;
    // 返回曲面参数方程
    return {
      u: {
        min: -Math.PI,
        max: Math.PI * 3,
        step: Math.PI / 32,
      },
      v: {
        min: 0,
        max: Math.PI * 2,
        step: Math.PI / 20,
      },
      x: function (u, v) {
        if (u < startRadian) {
          return (
            offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
          );
        }
        if (u > endRadian) {
          return (
            offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
          );
        }
        return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
      },
      y: function (u, v) {
        if (u < startRadian) {
          return (
            offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
          );
        }
        if (u > endRadian) {
          return (
            offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
          );
        }
        return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
      },
      z: function (u, v) {
        if (u < -Math.PI * 0.5) {
          return Math.sin(u);
        }
        if (u > Math.PI * 2.5) {
          return Math.sin(u);
        }
        return Math.sin(v) > 0 ? 1 * height : -1;
      },
    };
  }
  watch(() => props.dataList, (val) => {
    formatingData()
    nextTick(() => {
      myWorker.postMessage(1); // 1开启 0关闭
    })
  }, {
    deep: true,
    immediate: true
  })
  onBeforeUnmount(() => {
    // console.log("摧毁之前")
    myWorker.postMessage(0); // 1开启 0关闭
  })
</script>

<style scoped>
  .pie {
    position: relative;
  }
</style>

worker.js

addEventListener("message", (e) => {
  const { data } = e;
  // console.log(data)
  return postMessage(data);
});

【成果】

来试试看吧!
在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值