MyVisualMap实现思路(不包含完整代码)

效果类似这种,实现方式想必非常多,接下来仅讲解我的思路,可做一种参考。

img_v3_02e8_87c910d1-37c0-4302-8028-af56849c74bg

描述

实现一个类echarts visualMap的工具bar,可以调整一个高度自定义的echarts图形的values和colors

分析

实现一个带两种模式的多角标(网上普遍叫法是multiple handles)Slider。

模式一(渐变模式)是固定values,修改对应的colors,此时滑动条是一个渐变色块;

模式二(非渐变模式)是固定colors,修改对应的values,此时滑动条是一个多个色块组成的均匀条。

思路

咱们慢慢来,手挫肯定是要画点时间的,喝口水慢慢来。

绘图

  • 前置共识:values和colors一一对应,拖拽牵扯到的变动属性(top/height等)在绘图的时候需要考虑到,别框死了。
  • 固定一个div作为盛放色块和角标的容器,灵活使用css-position-relative/absolute,对应给div两块子元素。
    • 颜色块-colorRef
      • 渐变模式时,可以直接给colorRef设置一个linear-gradient(to bottom, ${colorStr})的背景色。
      • 非渐变模式,可以给colorRef均分成colors.length数量的色块,也就是若干个高度为height/len的不同颜色的div,其top值为idx * height/len。
    • 角标块-textRef
      • 渐变模式时,textRef均分成height/(len-1)个,那么每个角标的top就 *idx 有了。
      • 非渐变模式,textRef均分成height/len个,同样页确定好了每个角标的位置。
  • 画角标图形:一般使用绘制三角形的方法,或者使用clip-path: polygon(),接下来简单介绍一下后者的使用方法
    • 每一个’,’为一组,每组结构是(x长度 y长度):clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  • 其他

不带计算的交互

  • 前置共识:最上角标和最下角标不可拖拽,每个角标的拖拽区域在其前后角标位置之间。
  • 给遍历的角标孩子添加mousedown监听,绑定开始拖拽函数
  • 开始拖拽(startDrag)
    • 记录当前角标index
    • 记录当前角标距视口顶部距离event.clientY
    • 记录当前角标style.top,从textRef.children[index].style.top中拿
    • 给当前角标添加mousemove和mouseup监听,分别绑定拖拽中和结束拖拽函数
  • 拖拽中(Drag)
    • 根据当前新event.clientY和之前记录的初始clientY,计算偏移量
    • 根据偏移量和之前记录的角标top,计算出新的top值
    • 边界判定(最前后两个不拖拽,)给当前角标的style.top赋新top值
  • 结束拖拽(endDrag)
    • 将开始拖拽函数中涉及到的几个状态重置
    • 将mousemove和mouseup两个监听销毁

计算

  • 非渐变模式,拖拽更新color对应的value

    • 根据其上下角标间距(top1-top2)和其相对上角标间距(top-),计算新value值。这里注意数据类型和精确度,精确度完全可以作为一个组件配置项
    • 显示:将角标文本部分的innerText更新为新value值
    • 显示:将牵扯到的色块高度变一下,角标的上下色块,也就是children[index]和[index-1]
  • 渐变模式,拖拽更新value对应的color

    • 判断角标在哪个区间移动,根据移动量、平均高度、两头的颜色来计算插值颜色

    • 判断移动区间:根据角标的newtop距原top的偏移,确定上下颜色,也能确定偏移的区间。想象一下,如果是向下偏移,那么上下颜色就是本颜色和下颜色;如果是向上偏移,那么上下颜色就是上颜色和本颜色,偏移量一正一负很方便代入计算

    • 插值颜色:可能用到hex-rgb的相互转换,插值计算就是将rgb格式颜色按照比例计算,整点源代码

      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
      export const interpolateColor = function (color1, color2, factor) {
      // TODO: 这里还需要将factor规范一下,使其介于0-1范围
      // hex => rgb 1上颜色2下颜色
      const color1RGB = hexToRgb(color1);
      const color2RGB = hexToRgb(color2);

      // 线性插值 比例-factor要根据偏移量和区间总高度来计算
      const result = color1RGB.map((val, index) => {
      return Math.round(val + (color2RGB[index] - val) * factor);
      });

      // rgb => hex
      return rgbToHex(result[0], result[1], result[2]);
      };

      const hexToRgb = function (hex) {
      const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
      hex = hex.replace(shorthandRegex, (m, r, g, b) => {
      return r + r + g + g + b + b;
      });

      const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
      return result
      ? [
      parseInt(result[1], 16),
      parseInt(result[2], 16),
      parseInt(result[3], 16),
      ]
      : null;
      };
      const rgbToHex = function (r, g, b) {
      return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
      };