XingYun blog
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具

XingYun

冲!
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具
  • 3个提升Vue性能的写法
  • 重读Vue文档
  • moment.js给定时间获取自然月、周的时间轴
  • Vue虚拟dom与Diff算法
  • 深入响应式原理
  • Echart样例
  • 我的Vue指令库
  • 滚动到底部加载更多
  • 实现一键换肤
  • 手写Vue数据劫持
  • vue-router核心原理与手写实现
  • computed和watch
  • vue数组为什么不是响应式
  • v-for为什么不能用index做key
  • webpack loader和plugin
  • keep-alive组件原理
  • vue插槽进化
  • Vue多层嵌套组件
  • vue生命周期hook
  • vue监听dom元素的resize事件
  • 前端打包需要gzip压缩吗
  • 实现水波浪进度球
    • Vue
    XingYun
    2022-02-09
    目录

    实现水波浪进度球

    工作上需要做一个波浪进度球展示系统资源的占用情况

    效果图如下

    # 一、实现思路

    水波球由一个圆和波浪区域组成,重点在于波浪区域的实现。 Canvas 中有提供贝塞尔曲线函数 quadraticCurveTo 可以用它画出平滑的曲线来模拟水波

    # 二、实现步骤

    首先看一下 quadraticCurveTo 函数签名

    context.quadraticCurveTo(cpx,cpy,x,y);

    quadraticCurveTo() 方法通过使用表示二次贝塞尔曲线的指定控制点,向当前路径添加一个点。

    提示:二次贝塞尔曲线需要两个点。第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath() 和 moveTo() 方法来定义开始点。

    # 1. 贝塞尔曲线模拟波浪

    我们先写一个画贝塞尔曲线的公共方法

      /**
       * ctx getContext 返回对象
       * W 画布宽
       * color 线条颜色
       * wave 波浪起伏程度
       * Y 结束点y坐标
       */
      drawCurve(ctx, X, color, wave, Y) {
        ctx.save()
        ctx.beginPath()
        ctx.moveTo(0, X)
        ctx.lineTo(0, Y)
        ctx.quadraticCurveTo(X / 4, Y - wave, X / 2, Y)
        ctx.lineTo(X / 2, Y)
        ctx.quadraticCurveTo((X * 3) / 4, Y + wave, X, Y)
        ctx.lineTo(X, X)  // lineTo 作为曲线下方的填充
        ctx.lineTo(0, X)  // lineTo 作为曲线下方的填充
        ctx.fillStyle = color
        ctx.fill()
        ctx.restore()
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    # 2.动画

    曲线有了,第二步是让它动起来

    function init() {
      let canvas1: any = document.getElementById('wave-ball-canvas')
      let mW = canvas1.clientWidth
      // 设置Canvas元素的高
      canvas1.style.height = mW
      // 设置Canvas画布的宽高
      canvas1.width = canvas1.height = mW
    
      let canvas2: any = document.createElement('canvas')
      let ctx2: any = canvas2.getContext('2d')
      canvas2.width = mW
      canvas2.height = mW
    
      let { flat, speed, rate, distance, wave, waveColor, opacity } = this
      let x = 0
      let ctx1 = canvas1.getContext('2d')
      this.drawCurve(ctx2, mW, waveColor, wave, mW - mW * rate)
    
      let rate1 = rate
      let wave1 = wave
      let that = this
    
      function animation() {
        ctx1.clearRect(0, 0, mW, mW)
        ctx1.drawImage(canvas2, x, 0, mW + flat, mW)
        ctx1.drawImage(canvas2, x - mW - flat, 0, mW + flat, mW)
        x >= mW - speed + flat ? (x = 0) : (x += speed)
        requestAnimationFrame(animation) // 浏览器每一帧执行动画
      }
      animation()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31

    这里有个关键函数 requestAnimationFrame

    比起 setTimeout、setInterval 的优势主要有两点:

    1. requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒 60 帧。
      2、在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的的 cpu,gpu 和内存使用量。

    使用 requestAnimationFrame 让动画更流畅且性能开销会小一些

    动画效果如下

    # 3. 让动画更生动

    为了波浪更加生动,可以给它加一个带透明度和偏离值的波浪

    init() {
        let canvas1: any = document.getElementById('wave-ball-canvas')
        let mW = canvas1.clientWidth
        // 设置Canvas元素的高
        canvas1.style.height = mW
        // 设置Canvas画布的宽高
        canvas1.width = canvas1.height = mW
    
        let canvas2: any = document.createElement('canvas')
        let ctx2: any = canvas2.getContext('2d')
        canvas2.width = mW
        canvas2.height = mW
    
        let canvas3: any = document.createElement('canvas')
        let ctx3: any = canvas3.getContext('2d')
        canvas3.width = mW
        canvas3.height = mW
    
        let { flat, speed, rate, distance, wave, waveColor, opacity } = this
        let x = 0
        let ctx1 = canvas1.getContext('2d')
        this.drawCurve(ctx2, mW, waveColor, wave, mW - mW * rate)
        this.drawCurve(ctx3, mW, `${this.sixteenToRgba(waveColor, opacity)}`, wave, mW - mW * rate) //
    
        let rate1 = rate
        let wave1 = wave
    
        let that = this
    
        function animation() {
          ctx1.clearRect(0, 0, mW, mW)
          ctx1.drawImage(canvas2, x, 0, mW + flat, mW)
          ctx1.drawImage(canvas2, x - mW - flat, 0, mW + flat, mW)
          ctx1.drawImage(canvas3, x + distance, 0, mW + flat, mW)  // 加入偏离值distance
          ctx1.drawImage(canvas3, x - mW + distance - flat, 0, mW + flat, mW)
          x >= mW - speed + flat ? (x = 0) : (x += speed)
          requestAnimationFrame(animation) // 浏览器每一帧执行动画
        }
        animation()
      }
    
      // 十六进制颜色转成 rgba 带 透明度
      sixteenToRgba(sixteen, opacity) {
        let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
        let sColor: any = sixteen
        if (sColor && reg.test(sColor)) {
          if (sColor.length === 4) {
            let sColorNew = '#'
            for (let i = 1; i < 4; i += 1) {
              sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
            }
            sColor = sColorNew
          }
          // 处理六位的颜色值
          let sColorChange: any[] = []
          for (let i = 1; i < 7; i += 2) {
            sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
          }
          return 'rgba(' + sColorChange.join(',') + `,${opacity})`
        } else {
          return sColor
        }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63

    波浪效果完成

    传入不同百分比参数

    # 三、封装为Vue组件

    Vue 组件完整代码

    <template>
      <div
        class="content"
        :style="{
          width: size + 'px',
          height: size + 'px',
          borderColor: waveColor
        }"
      >
        <canvas id="wave-ball-canvas"></canvas>
        <p class="value" :style="{ color: fontColor, fontSize: fontSize + 'px' }">
          {{ rate * 100 }}%
        </p>
      </div>
    </template>
    
    <script lang="ts">
    import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
    import { State, Getter, Action, Mutation, namespace } from 'vuex-class'
    
    @Component({ components: {} })
    export default class WaveBall extends Vue {
      @Prop({ type: Number, default: 0.6 }) rate!: any // 进度 比例 min=0 max=1
      @Prop({ type: Number, default: 100 }) size!: any // 球大小
      @Prop({ type: String, default: '#00c2fb' }) waveColor!: String
      @Prop({ type: String, default: '#F5EEDC' }) fontColor!: String
      @Prop({ type: Number, default: 26 }) fontSize!: any
      @Prop({ type: Number, default: 2 }) speed!: any // 波浪速度 min=1 max=12
      @Prop({ type: Number, default: 200 }) flat!: any // 波浪平滑度 min=200 max=600
      @Prop({ type: Number, default: 100 }) distance!: any // 波浪偏移量 min=0 max=200
      @Prop({ type: Number, default: 10 }) wave!: any // 波浪起伏度 min=5 max=60
      @Prop({ type: Number, default: 0.6 }) opacity!: any // 波浪起伏透明度 min=5 max=60
    
      created() {}
    
      mounted() {
        this.init()
      }
    
      /**
       * ctx getContext 返回对象
       * X 纵坐标
       * color 线条颜色
       * wave 波浪起伏程度
       * Y 结束点y坐标
       */
      drawCurve(ctx, X, color, wave, Y) {
        ctx.save()
        ctx.beginPath()
        ctx.moveTo(0, X)
        ctx.lineTo(0, Y)
        ctx.quadraticCurveTo(X / 4, Y - wave, X / 2, Y)
        ctx.lineTo(X / 2, Y)
        ctx.quadraticCurveTo((X * 3) / 4, Y + wave, X, Y)
        ctx.lineTo(X, X)
        ctx.lineTo(0, X)
        ctx.fillStyle = color
        ctx.fill()
        ctx.restore()
      }
    
      init() {
        let canvas1: any = document.getElementById('wave-ball-canvas')
        let mW = canvas1.clientWidth
        // 设置Canvas元素的高
        canvas1.style.height = mW
        // 设置Canvas画布的宽高
        canvas1.width = canvas1.height = mW
    
        let canvas2: any = document.createElement('canvas')
        let ctx2: any = canvas2.getContext('2d')
        canvas2.width = mW
        canvas2.height = mW
    
        let canvas3: any = document.createElement('canvas')
        let ctx3: any = canvas3.getContext('2d')
        canvas3.width = mW
        canvas3.height = mW
    
        let { flat, speed, rate, distance, wave, waveColor, opacity } = this
        let x = 0
        let ctx1 = canvas1.getContext('2d')
        this.drawCurve(ctx2, mW, waveColor, wave, mW - mW * rate)
        this.drawCurve(
          ctx3,
          mW,
          `${this.sixteenToRgba(waveColor, opacity)}`,
          wave,
          mW - mW * rate
        )
    
        let rate1 = rate
        let wave1 = wave
    
        let that = this
    
        function animation() {
          if (rate !== rate1 || wave1 !== wave) {
            ctx2.clearRect(0, 0, mW, mW)
            ctx3.clearRect(0, 0, mW, mW)
            rate1 = rate
            wave1 = wave
            that.drawCurve(ctx2, mW, waveColor, wave, mW - mW * rate)
            that.drawCurve(
              ctx3,
              mW,
              `${that.sixteenToRgba(waveColor, opacity)}`,
              wave,
              mW - mW * rate
            )
          }
          ctx1.clearRect(0, 0, mW, mW)
          ctx1.drawImage(canvas2, x, 0, mW + flat, mW)
          ctx1.drawImage(canvas2, x - mW - flat, 0, mW + flat, mW)
          ctx1.drawImage(canvas3, x + distance, 0, mW + flat, mW)
          ctx1.drawImage(canvas3, x - mW + distance - flat, 0, mW + flat, mW)
          x >= mW - speed + flat ? (x = 0) : (x += speed)
          requestAnimationFrame(animation) // 浏览器每一帧执行动画
        }
        animation()
      }
    
      // 十六进制颜色转成 rgba 透明度
      sixteenToRgba(sixteen, opacity) {
        let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
        let sColor: any = sixteen
        if (sColor && reg.test(sColor)) {
          if (sColor.length === 4) {
            let sColorNew = '#'
            for (let i = 1; i < 4; i += 1) {
              sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
            }
            sColor = sColorNew
          }
          // 处理六位的颜色值
          let sColorChange: any[] = []
          for (let i = 1; i < 7; i += 2) {
            sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
          }
          return 'rgba(' + sColorChange.join(',') + `,${opacity})`
        } else {
          return sColor
        }
      }
    }
    </script>
    
    <style lang="scss" scoped>
    .content {
      position: relative;
      border-radius: 50%;
      border: 1px solid #1c86d1;
      overflow: hidden;
      padding: 8px;
      box-sizing: border-box;
      background: #a5e2fc;
      .value {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        font-size: 24px;
        // font-weight: bold;
      }
    
      canvas {
        width: 100%;
        height: 100%;
        border-radius: 50%;
      }
    }
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172

    用法

    <WaveBall
      :rate="0.46"
      :size="140"
      waveColor="#00c2fb"
      fontColor="#F5EEDC"
      :fontSize="28"
    />
    
    1
    2
    3
    4
    5
    6
    7

    # 四、to do

    1. 百分比变化 -> 球体 颜色 无极或者阶梯变化
    2. 百分比变化 -> 球体波浪 起伏度 无极或者阶梯变化
    3. 百分比变化 -> 球体波浪 速度 无极或者阶梯变化
    #组件库
    上次更新: 2023/04/05, 09:41:10
    前端打包需要gzip压缩吗

    ← 前端打包需要gzip压缩吗

    最近更新
    01
    JavaScript-test
    07-20
    02
    二维码的原理
    07-20
    03
    利用ChatGPT优化代码
    07-20
    更多文章>
    Theme by Vdoing | Copyright © 2021-2023 XingYun | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式