百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

vue3 + ts + svg + ECharts 实现双十一数据大屏

bigegpt 2024-10-17 08:06 5 浏览

本篇文章就来介绍下如何使用 vue3 + ts + svg + ECharts 实现一个如下所示的双十一数据大屏页面:

创建项目

执行命令 npm create vue@latest 创建基于 Vite 构建的 vue3 项目,功能选择如下:

我选择使用 pnpm 安装项目依赖:pnpm i,各安装包的版本号可见于下图:

在 vite.config.ts 中添加配置,以便在项目启动时能自动打开浏览器:

typescript

复制代码

export default defineConfig({ // ... server: { open: true } })

现在,就可以通过 pnpm dev 启动新建的项目了。

大屏适配

大屏适配的方案有很多,比如 rem、vw 和 flex 布局等,我选择使用缩放(scale)的方式来适配大屏,因为该方案使用起来比较简单,也不用考虑第三方库的单位等问题。

假设设计稿的尺寸为 1920 * 1080px,为了保证效果,在大屏中放大时应该保持宽高比 designRatio 不变,designRatio 为 1920 / 1080 ≈ 1.78。放大的倍数 scaleRatio,可以分为以下 2 种情况计算:

  • 当用于展示的设备屏幕的宽高比 deviceRatio 等于或小于设计稿的宽高比 designRatio 时,我们可以按照两者的宽度之比进行缩放,即让设计稿保持宽高比的情况下放大到与设备等宽,在高度上可能留有空白;
  • 当设备屏幕的宽高比 deviceRatio 大于设计稿宽高比 designRatio 时,也就是说设备为超宽屏,scaleRatio 应该按照两者的高度之比决定,即让设计稿保持宽高比的情况下放大到与设备等高,在宽度上可能留有空白,所以还要做个居中布局。

具体代码我封装成了一个 hook:

// 屏幕适配,src\hooks\useScreenAdapt.ts
import _ from 'lodash'
import { onMounted, onUnmounted } from 'vue'

export default function useScreenAdapt(dWidth: number = 1920, dHeight: number = 1080) {
  // 节流
  const throttleAdjustZoom = _.throttle(() => {
    AdjustZoom()
  }, 1000)

  onMounted(() => {
    AdjustZoom()
    // 响应式
    window.addEventListener('resize', throttleAdjustZoom)
  })

  // 释放资源
  onUnmounted(() => {
    window.removeEventListener('resize', throttleAdjustZoom)
  })

  function AdjustZoom() {
    // 设计稿尺寸及宽高比
    const designWidth = dWidth
    const designHeight = dHeight
    const designRatio = designWidth / designHeight // 1.78

    // 当前屏幕的尺寸及宽高比
    const deviceWidth = document.documentElement.clientWidth
    const devicHeight = document.documentElement.clientHeight
    const deviceRatio = deviceWidth / devicHeight

    // 计算缩放比
    let scaleRatio = 1
    // 如果当前屏幕的宽高比大于设计稿的,则以高度比作为缩放比
    if (deviceRatio > designRatio) {
      scaleRatio = devicHeight / designHeight
    } else {
      // 否则以宽度比作为缩放比
      scaleRatio = deviceWidth / designWidth
    }

    document.body.style.transform = `scale(${scaleRatio}) translateX(-50%)`
  }
}

最后是给 body 添加了 transform 属性,为了实现居中效果,还需要给 body 添加上相应样式:

/* \src\assets\base.css */
* {
  box-sizing: border-box;
}

body {
  position: relative;
  margin: 0;
  width: 1920px;
  height: 1080px;
  transform-origin: left top;
  left: 50%;
  background-color: black;
}

使用 lodash 实现节流

为避免改变屏幕尺寸时过于频繁触发 AdjustZoom,我借助 lodash 的 throttle 方法做了个节流,这就需要安装 lodash:pnpm add lodash。因为用到了 ts,如果直接引入使用 lodash 会遇到如下报错:

我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。提示里已经告诉了我们解决办法,就是去安装 @types/lodash:pnpm add -D @types/lodash,之后就能在 ts 文件中正常使用 lodash 了。

使用 svg

页面头部使用的就是一张 svg,样式中给 #top 添加绝对定位 position: absolute; ,目的在于开启一个单独的渲染层,以减少之后添加动画造成的回流损耗:

<template>
  <main class="main-bg">
    <div id="top"></div>
  </main>
</template>
<style scoped>
#top {
  position: absolute;
  width: 100%;
  height: 183px;
  background-size: cover;
  background-image: url(@/assets/imgs/top_bg.svg);
}
</style>

作为背景引入的 top_bg.svg 是我使用 Illustrator 绘制后导出的,绘制时注意做好图层的命名:

因为图层的名称会影响到导出的 svg 文件中元素的 id 名称。另外导出的 svg 文件中也可能存在一些中文命名或一些不必要的代码,我们可以自行修改:

添加动画及滤镜

使用 Illustrator 绘制的都是静态图形,现在我们以其中一个圆球为例,添加上平移的动画以及高斯模糊的滤镜:

<!-- top_bg.svg 部分代码 -->
<?xml version="1.0" encoding="UTF-8"?>
<svg id="top-bg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1920 183">
  <defs>
    <style>
      #circle-1 {
        opacity: 0;
        transform: translate(800px, -18px) scale(0.5);
        animation: circle-1-ani 1.8s ease-out forwards infinite;
      }
      @keyframes circle-1-ani {
        90%,
        100% {
          opacity: 0.95;
          transform: translate(600px, 80px) scale(1);
        }
      }
    </style>
    <filter id="blurMe">
        <feGaussianBlur stdDeviation="2" />
      </filter>
  </defs>
	<circle id="circle-1" class="cls-1" r="12.96" filter="url(#blurMe)" />
</svg>

动画使用 css 定义,可以直接写在 <defs> 里的 <style> 中。一旦用到 transform,那么圆的坐标系就会移动到圆的中心点,所以我将原本 <circle> 中的用于定义圆心坐标的 cx 和 cy 属性删除了,通过在 #circle-1 中直接使用 transform: translate(800px, -18px); 来定位圆的初始位置:

滤镜定义在 <defs> 里的 <filter> 中,使用的是高斯模糊 <feGaussianBlur>, stdDeviation 用于指定钟形(bell-curve),可以理解为模糊程度。在圆形 <circle> 上通过 filter 属性,传入滤镜的 id 应用滤镜。

使用 ECharts

安装

首先是安装 ECharts:pnpm add echarts。在 npm 的仓库搜索 echarts 可以看到其带有如下所示的 ts 标志:

说明它的库文件中已经包含了 .d.ts 文件:

所以不需要像上面使用 lodash 那样再去额外安装声明文件了。

封装组件

接着就可以封装 echarts 组件了。组件中只需要提供一个展示图表的 dom 容器 <div>,然后在 onMounted(确保可以获取到 dom 容器) 中创建一个 ECharts 实例 myChart,最后通过 myChart.setOption(option) 传入从父组件获取的图表实例的配置项以及数据 option:

<!-- src\components\BaseEChart.vue -->
<template>
  <div ref="mainRef" :style="{ width: width, height: height }"></div>
</template>

<script lang="ts" setup>
  import * as echarts from 'echarts'
  import { onMounted, onUnmounted, ref } from 'vue'

  interface IProps {
    width?: string
    height?: string
    chartOption: echarts.EChartsOption
  }
  const props = withDefaults(defineProps<IProps>(), {
    width: '100%',
    height: '100%'
  })

  const mainRef = ref(null)

  let myChart: echarts.ECharts | null = null
  onMounted(() => {
    myChart = echarts.init(mainRef.value, 'dark', { renderer: 'svg' })
    const option = props.chartOption
    myChart.setOption(option)
  })

  onUnmounted(() => {
    // 销毁 echart 实例,释放资源
    myChart?.dispose()
  })
</script>

使用示例

以左上角的“人均消费金额排名”柱状图为例,代码如下:

<!-- src\views\HomeView.vue -->
<template>
  <main class="main-bg">
    <div id="left-top">
      <div class="title">人均消费金额排名</div>
      <div class="sub-title">Ranking of per capita consumption amount</div>
      <BaseEChart :chartOption="amountRankOption" />
    </div>
  </main>
</template>

<script setup lang="ts">
import BaseEChart from '@/components/BaseEChart.vue'
import { amountRankOption } from './config/amount-rank-option'
</script>

<style scoped>
#left-top {
  position: absolute;
  top: 130px;
  left: 20px;
  width: 500px;
  height: 320px;
}
</style>

在页面引入 BaseEChart 后,传入定义好的 amountRankOption 即可:

// 人均消费金额排名柱状图配置
import * as echarts from 'echarts'
type EChartsOption = echarts.EChartsOption

export const amountRankOption: EChartsOption = {
  grid: {
    top: 20,
    bottom: 50,
    left: 40,
    right: 40
  },
  xAxis: {
    axisTick: {
      show: false // 隐藏 x 坐标轴刻度
    },
    data: ['思明', '湖里', '集美', '同安', '海沧', '翔安']
  },
  yAxis: {
    axisLabel: {
      show: false // 隐藏 y 坐标轴刻度标签
    },
    splitLine: {
      show: false // 隐藏平行于 x 轴的分隔线
    }
  },
  series: [
    {
      type: 'bar',
      data: [5, 20, 36, 10, 10, 20],
      barWidth: 20 // 设置柱形的宽度
    }
  ]
}

至于剩下的图表的实现,只是配置不同而已,如有兴趣可以去该项目的 git 仓库查看。

数字滚动动画

最后添加成交额的数字滚动动画,用到了 countup.js,需要先安装: pnpm add countup.js。

使用时,直接 new CountUp() 生成 countUp,第 1 个参数为要添加动画的 dom 的 id,第 2 个参数为动画结束时显示的数字,还可以传入第 3 个参数 options 实现一些配置,比如设置前缀,小数点等。然后通过 countUp.start() 即可实现动画效果:

<!-- src\components\Digital.vue -->
<template>
  <div>
    <span class="t1">成交额</span>
    <span id="amount" class="t2">150</span>
    <span class="t1">亿</span>
  </div>
</template>

<script lang="ts" setup>
  import { CountUp } from 'countup.js'
  import { onMounted } from 'vue'

  onMounted(() => {
    const countUp = new CountUp('amount', 150)

    if (!countUp.error) {
      countUp.start()
    } else {
      console.error(countUp.error)
    }
  })
</script>


原文链接:https://juejin.cn/post/7305434729527181322


相关推荐

LangChain4j如何自定义文档转换器实现数据清洗?

LangChain4j提供了3种RAG(Retrieval-AugmentedGeneration,检索增强生成)实现,我们通常在原生或高级的RAG实现中,要对数据进行清洗,也就是将外接...

Java 8 Stream API 详解(java stream.)

Java8StreamAPI详解一、概述在Java8中,StreamAPI是一个重要的新特性。它为处理集合(如List、Set等)中的元素提供了一种高效且富有表现力的方式。Str...

Java修炼终极指南:185 使用 Stream 过滤嵌套集合

这是面试中的一个经典问题,通常从一个模型开始,如下所示(我们假设集合是一个List):publicclassAuthor{privatefinalStringname;pri...

java8的stream使用小示例(java stream())

据JetBrains发布的2021年开发者生态系统调查,Java8在java使用的版本中仍然是当前最流行的版本。72%的专业开发人员使用Java8作为其在java开发中主要编程语言版本。现...

Node.js Stream - 实战篇(node.js in action)

本文转自“美团点评技术团队”http://tech.meituan.com/stream-in-action.html背景前面两篇(基础篇和进阶篇)主要介绍流的基本用法和原理,本篇从应用的角度,介...

Java Stream:集合处理的api(java 集合操作)

JavaStream流:高效集合处理的函数式编程利器一、什么是JavaStream?Java8引入的StreamAPI是一套用于处理集合数据的流式编程接口,通过函数式风格(无副作用的...

去除 List 中的重复元素,你知道几种实现方法?

去除List中重复元素,这在实际编程或面试中经常遇到,每个人都有习惯的写法吧,这里抛砖引玉,汇总了一些实现方案,开拓思路。准备数据假设数组中有10个数据,可能有重复,需要将重复的数据从数组中去掉。pu...

Java开发者必看!Stream流式编程10个爆款技巧,让你代码优雅飞起

为什么你的Java代码总像拧巴的麻绳?掌握这10个Stream实战技巧,代码效率与优雅度将产生质的飞跃。以下案例均来自真实电商系统场景,带你感受流式编程的降维打击!一、过滤与映射组合拳(Filter...

leetcode每日一题之存在重复元素(存在重复元素 iii)

题:给定一个整数数组,判断是否存在重复元素。如果存在一值在数组中出现至少两次,函数返回true。如果数组中每个元素都不相同,则返回false。比如:输入:[1,2,3,1]输出:true...

告别for循环!揭秘Stream API如何让你的代码简洁度提升300%

一、当传统循环遇上现代需求真实场景复现:某电商平台需要处理10万条订单数据,要求:筛选出金额>500的订单提取用户ID并去重统计VIP用户数量传统实现方案://常规写法Set<Long...

Java中List去重的N种方法:从基础到优雅

Java中List去重的N种方法:从基础到优雅在日常的Java开发中,我们经常会遇到需要对List集合去重的情况。无论是为了清理重复的数据,还是为了优化算法性能,掌握多种去重方式都是一项非常实用的技能...

Java Stream流没用过?常用高频方法

概念Stream流是Java8添加的以一种链式调用的方法处理数据,主要侧重于计算。具有以下相关特点代码简洁链式调用Stream常用方法1.将数组变为当作List操作String[]strArr=...

核医学专业名词索引(M-R)(核医学重点归纳)

M吗啡(morphia)埋藏式心律转复除颤器(implantablecardioverterdefibrillator,ICD)麦角骨化醇(VD2,calciferol)脉冲堆积(pulsepi...

CodeMeter 新版发布(codesigner下载)

威步于2022年8月4日发布CodeMeter7.50及CodeMeter软件保护套装11.10,以下为新版内容。CodeMeterRuntime7.50StreamingSIMDExten...

世界上最小的五轴铣床Pocket NC(最小的五轴加工中心)

PocketNC,由MIT学生研制,还有说法是这款产品的设计者是来自美国蒙大拿州的一对极客夫妻。目前主要有两款产品:PocketNCV2-50,9000美元;PocketNCV2-10,60...