/antd-slider-parenthesis-demo

通过使用antd的slider组件,来实现用大括号标记节点之间的间距并进行显示。同时时间进度进行动态控制。

Primary LanguageJavaScript

antd-slider-parenthesis-demo

版权声明:本文为博主原创文章,未经博主允许不得转载。欢迎 Issues 留言。

点击查看 codesandbox 中 demo 示例

一、背景

在实际开发过程中,遇到个有趣的小功能,这里分享一下实现过程。主要是通过 antd 的 slider 组件进行实现,并且用括号标注一个区间进行显示。如下图所示:

示例图.png

我们要实现的效果是:

  • 动态的根据时间数据给出的时间节点进行显示
  • 如果当前时间在两个时间节点之间,显示进度在节点之间
  • 不能手动对 Slider 进行拖拽。

二、功能实现

1. 数据准备

首先我们的时间数据是动态获取的,时间顺序可能也没有排序,所以需要对获取到得时间做一个处理。 下面显示的是我们处理完后的假数据格式如下:

// 比如我们获取的时间数据如下
// data.js
export const timeArray = [
  {
    time: "2022-03-01",
    type: "start" // 标记括号区间开始位置
  },
  {
    time: "2022-03-10"
  },
  {
    time: "2022-02-26"
  },
  {
    time: "2022-06-07"
  },
  {
    time: "2022-05-08"
  }
];

2. Slider 使用

查看 antd 的slider 的 API,可以看到我们需要的大概有如下的参数:

  • marks 刻度标记
  • step 步长,取值必须大于 0
  • value 设置当前取值
  • disable 值为 true 时,滑块为禁用状态
  • tooltipVisible 值为 true 时,Tooltip 将会始终显示;否则始终不显示,哪怕在拖拽及移入时
import { Slider } from "antd";
...
<Slider
    marks={marks} // 刻度标记
    step={step}   // 步长
    value={value} // 当前取值
    disabled      // 值为 true 时,滑块为禁用状态
    tooltipVisible={false}  // 始终不显示tooltip
/>

3. 处理 Slider 需要的参数

我们需要定义一个方法getParam来对组件 Slider 需要的参数进行一个处理。

首先处理动态显示时间节点的问题:

1) 定义 marks 来存放处理好的 slider 的节点。

// utils.js
import { timeArray } from "./data.js";

export const getParam = () => {
  const marks = {}; //slider上的标记对象
  ...
}

marks 对象的格式是{ number: ReactNode } 或者{ number: { style: CSSProperties, label: ReactNode } }

// antd demo显示
const marks = {
  0: "0°C",
  26: "26°C",
  37: "37°C",
  100: { style: { color: "#f50" }, label: <strong>100°C</strong> }
};

所以后续我们需要处理后得到如 demo 所示的数据结果。

2) timeArray 先通过 sort 对时间进行排序,保证时间顺序正确

import moment from "moment";
...
// 对时间数组排序
const newTimeArray = timeArray.sort((a, b) =>
    moment(a.time).isSameOrAfter(b.time) ? 1 : -1
);

3)根据数组内的时间节点个数,动态计算节点 number 值和节点步长 step。

比如当前数组内有4 个时间节点,那么 marks 对象的 0 和 100 会分别占据 2 个节点,剩余 2 个节点要在 0 至 100 之间平均分配。也就是 0 至 100 会被平均分成 3 份,每一份的长度 就是我们要计算的nmark

因为我们的需求第 2 点提到如果当前时间在两个时间节点之间,显示进度在节点之间,所以,我们的步长 nstep 其实是前面nmark长度的一半。

// 根据时间节点计算节点位置
let nmark =
  newTimeArray.length > 1 ? (100 / (newTimeArray.length - 1)).toFixed(2) : 0;

// 两节点之间的中间值为步长,表示在两时间节点中的状态
let nstep = (nmark / 2).toFixed(2);

4) 在 marks 对象进行刻度标记

  • 当我们计算好每份的长度和步长以后,我们需要通过遍历时间数组,对每个数据显示的节点的在 marks 对象进行刻度标记。

  • 并且跟今天的日期做对比,记录当前我们应该显示的 value 值是什么。

    • 如果今天的日期和某个标记日期正好相等,value 就是标记日期的刻度值
    • 如果今天日期 在某两个刻度(比如刻度 1、2)之间,则 value 值是这两个刻度之间的刻度值(这里就是为什么前面 nstep 是 nmark 的一半的原因),既 value = 刻度1的mark值 + nstep,则正好位于刻度 1、2 之间
  let sliderValue = 0; // 记录当前时间轴的值

  // 对排序后的时间进行遍历
  newTimeArray.forEach((item, i) => {
    // 数组长度
    let length = newTimeArray.length;
    // 是否是最后一个节点
    let islast = length > 1 ? i === length - 1 : false;

    // 计算时间的标记位置数字 0-100之间的
    let markNum = (i * (nmark * 100)) / 100;

    // 今天的日期
    let todayTime = moment(moment().format("YYYY-MM-DD"));

    // 如果时间是今天,时间节点是相等时间的mark值
    if (moment(item.time).isSame(todayTime)) {
      sliderValue = Number(markNum);
    }
    // 如果今天时间在某个时间节点的后面,则mark值在这个节点和下个节点中间,所以加个step值
    else if (todayTime.isAfter(moment(item.time))) {
      sliderValue = (Number(markNum) * 100 + Number(nstep) * 100) / 100;
    }

    // 刻度标记marks对象
    marks[islast ? 100 : markNum] = {
      label: item.time
    };
    ...

  });

  return {
    timeArr: newTimeArray,
    marks,
    step: nstep,
    sliderValue,
  };
}

4. 大括号的实现

1)获取参数处理

这里处理的比较简单,我们这里默认显示两个节点之间的间距。所以给要显示的时间节点加个 type ,这里 是 type = “start”

{
    time: "2022-03-01",
    type: "start" // 标记括号区间开始位置
},

然后在上面 3 中第 4)步遍历时间数组的时候,对大括号要显示的地方做个处理。

const parenthesisArr = []; // 大括号

// 对排序后的时间进行遍历
newTimeArray.forEach((item, i) => {
     ...
    // 计算时间的标记位置数字 0-100之间的
    let markNum = (i * (nmark * 100)) / 100;

    // ------ 对大括号显示处理------//
    // 记录开始节点
    if (item.type === "start") {
        parenthesisArr.push({
            name: "显示间距",
            left: markNum // 大括号左侧位移距离
        });
    }

   ...

});

然后大括号的组件处理

import Parenthesis from "./comp/parenthesis.js";
import { getParam } from "./utils";

...

 const [value, set_value] = useState(0);  // 当前值
 const [marks, set_marks] = useState({});  // 标记
 const [step, set_step] = useState(null); // 步长
 const [parenthesisArr, set_parenthesisArr] = useState([]); // 显示大括号数组

 useEffect(() => {
    let param = getParam();

    set_marks(param.marks);
    set_step(param.step);
    set_value(param.sliderValue);
    set_parenthesisArr(param.parenthesisArr);
 }, []);

  ...

{parenthesisArr.map((item, i) => {
    return (
        <Parenthesis
            key={i}
            data={item}
            step={Number(step) * 2} // 默认显示相邻两个节点之前的间距
            day={item.name}
        />
    );
})}

2)大括号的样式实现

主要根据步长来设置大括号的宽度,left 是向左位移的距离。

// parenthesis.js

import "./index.css";

const Parenthesis = (props) => {
  let { data, step, day } = props;

  return (
    <div
      className="parenthesis-box"
      // 这里根据步长来设置大括号的宽度
      style={{ left: `${data.left}%`, width: `${step}%` }}
    >
      <div className="parenthesis-inner">
        <div className="parenthesis">
          <div className="parenthesis-text">{day}</div>
        </div>
      </div>
    </div>
  );
};

export default Parenthesis;

css 样式如下: 主要是利用 before、after 伪类进行实现。

/*index.css*/

.parenthesis-box {
  position: absolute;
  top: 40px;
}

/*显示提示文案*/
.parenthesis-text {
  position: relative;
  top: -40px;
}

/*大括号样式*/
.parenthesis-inner {
  position: relative;
  width: 100%;
  height: 40px;
}
.parenthesis-inner::before,
.parenthesis-inner::after,
.parenthesis::before,
.parenthesis::after {
  content: "";
  display: block;
  position: absolute;
  width: calc(50% - 20px);
  height: 20px;
  border-style: solid;
  border-color: rgb(9, 113, 241);
  border-width: 0;
}
.parenthesis::before,
.parenthesis::after {
  top: 0;
  border-top-width: 1px;
}
.parenthesis-inner::before,
.parenthesis-inner::after {
  top: -19px;
  border-bottom-width: 1px;
}
.parenthesis::before {
  left: 0;
  border-top-left-radius: 20px;
}
.parenthesis::after {
  right: 0;
  border-top-right-radius: 20px;
}
.parenthesis-inner::before {
  left: 20px;
  border-bottom-right-radius: 20px;
}
.parenthesis-inner::after {
  right: 20px;
  border-bottom-left-radius: 20px;
}

三、参考文章