可滑动日期时间控件

223 阅读4分钟

简介

20241229162124_rec_-convert.gif

运用场景:选择对应的时间,或者滑动时间轴,控制摄像头的时间,查看摄像头记录

实现:js+canvas

功能

上下日期时间联动,选择日期或者时间,时间轴会展示相应的时间。同样左右滑动时间轴,可以向上同步时间,方便观察。可双击进入到某天。还有一个idea是使用鼠标滚动方式进入到某天以及回退到日期,这个有空再实现

实现思路

绘制:先设定好ctx.lineWidth,然后根据canvas.width和天数,计算出每个间隔是多少( const pace = (l / days + 1) / 24;),绘制时间也是同样的道理

滑动:然后通过监听事件mousedown mouseup mousemove,mousemove监听移动,滑过的距离,就改变绘制起点,滑出画布的部分将放置画布的另外一侧(比如绘制区域x大于画布的宽度,那么将x-canvas.width,小于同理)

日期时间向下同步:日期同步:选择对应日期,调用drawDays函数,重新绘制画布,起点设置为0,时间设置为00:00较简单,时间同步:根据百分比算出该滑过的距离,将drawDays函数的起点设置为对应的负值。

日期时间向上同步:每次mouseup的时候在绘制一次,记录时间轴的上的日期以及x,y左右,和最小的x,根据最小的x,去找到日期,再减一天就是当前日期,时间:根据 最小的x和daywidth计算出时间(百分比方式)。

代码部分

state,配置数据,工具方法

      ctx.strokeStyle = "black"; // 设置线条颜色
      ctx.lineWidth = 2; // 设置线条宽度
      ctx.textAlign = "center";
      ctx.font = "bold 14px serif";
      //
      const textH = 14;
      const textW_half = 63; // 日期文字长度的一半

      const height = 30; // 分钟刻度高度 
      const heightZ = 40; // 12点刻度高度
      const heightData = 50; // 日期刻度高度
      const l = 1000; // 画布长度

      let y0 = 100; // 绘制刻度的y轴起点
      const dayMin = 24 * 3600 * 1000; // 一天的秒数
      const drawdays = 3; //整个canvas满屏绘制多少天
      const dayWidth = l / drawdays;  // 一天站的宽度
       let cacheDateText = {}; // 缓存展示中的日期的位置,
       const state = {
        isStartMove: false, // 是否在绘制中
        startDrawX: 1,//绘制x起点
        interval: 0,//每次移动鼠标的间隔 
        lastX: 0,// 上一次鼠标位置
        mode: "day", // day | time //绘制模式
        timeDay: "",// 日期时间的模式时 的日期
        minix: l, // 记录距离0点最近的日期的x坐标
      };

      function formatDateFmt(pdata, fmt="yyyy-MM-dd") {... // 日期转化 传入日期,返回日期字符串}
       function clear() { 
        ctx.clearRect(0, 0, canvas.width, canvas.height);
      }
        function isInWord(x0, x1, error) {
          // 判断鼠标点击是否是日期
          // error 误差
        const xstart = x1 - error;
        const xend = x1 + error;
        return xstart <= x0 && xend >= x0;
      }

绘制日期时间刻度

    // 绘制起点,日期,绘制天数,是否是最后一次绘制
     function drawDays(xStart, date1, days, isLastTime) {
        let x = xStart;
        let drawDate = date1;
        const pace = (l / days + 1) / 24;
        for (let i = 0; i < days; i++) {
          let { x0, nextDate, lastDate } = drawDateFont(
            x,
            drawDate,
            pace,
            isLastTime,
            days
          );
          x = x0;
          drawDate = nextDate;
        }
      }
        // 绘制一天
          function drawDateFont(x0, date1, pace, isLastTime,days) {
        for (let i = 0; i < 24; i++) {
          let h = heightData;
          let text = date1;
          if (i % 12 == 0) {
            if (i == 0) {
              h = heightData;
              text = date1;
            } else {
              h = heightZ;
              text = i + ":00";
            }
          } else {
            h = height;
            text = "";
          }
          drawLine(x0, y0, h, text, isLastTime,days);
          x0 += pace;
        }
        const nextDate = formatDateFmt(new Date(+new Date(date1) + dayMin));
        const lastDate = formatDateFmt(new Date(+new Date(date1) - dayMin));
        // 返回用于 下一天的绘制
        return { x0, nextDate, lastDate };
      }
      
       function drawLine(x, y, h, text, isLastTime,viewDays) {
        let x1 = x;
        const y1 = y - h;
        let diff = 0;
        ctx.beginPath();
        if (x > l) { // 超出部分
          diff = Math.floor(x1 / l); //获取超出第几个视图
          x1 = x1 % l;
        }
        if (x < 0) {
          diff = Math.floor(x1 / l);
          x1 = (x1 % l) + l;
        }
        ctx.moveTo(x1, y);
        ctx.lineTo(x1, y1);
        ctx.stroke();
        x1 = x1.toFixed(2) - 0;
        y = y.toFixed(2) - 0;
        if (text) {
          date1 = new Date(text);
          const isDate = date1.toString() !== "Invalid Date";
          if (isDate) {
            if (diff !== 0) {
               //划过多少个视图,再乘以视图中有几天。                 
              text = formatDateFmt(new Date(+date1 - diff * dayMin * viewDays));
            }
               // 缓存日期位置和最小x值
            if (isLastTime) {
              state.minix = Math.min(state.minix, x1);
              cacheDateText[`${x1}_${y + 30}`] = text;
            }
          }
          ctx.fillText(text, x1, y + 30);
        }
      }

绘制时间刻度,

      function drawTimes(xStart, date1,isLastTime) {
        let x = xStart;
        let drawDate = date1;
        const pace = l / 24;
        drawTimeFont(x, drawDate, pace,isLastTime);
      }

      function drawTimeFont(x0, date1, pace,isLastTime) {
        for (let i = 0; i < 24; i++) {
          let h = height;
          let text = "";
          if (i % 2 == 0) {
            if (i < 10) {
              text = "0" + i + ":00";
            } else {
              text = i + ":00";
            }
          }
          if (i % 12 == 0) {
            h = heightZ;
          }
          if (i == 0) {
            h = heightData;
            text = date1;
          }
          drawLine(x0, y0, h, text,isLastTime,1);
          x0 += pace;
        }
      }

事件监听

       canvas.onmousedown = (e) => {
        t1 = +new Date();
        const { offsetY, offsetX } = e;
        state.isStartMove = true;
        state.lastX = offsetX;
      };
      canvas.onmouseup = (e) => {
        state.isStartMove = false;
        let t2 = +new Date();
        if (t2 - t1 < 100) {
            cancelAnimationFrame(t0)
          return;
        }
        const { offsetY, offsetX } = e;
        state.startDrawX = state.startDrawX + state.interval;
        cacheDateText = {};
        state.minix = l;
        clear();
        if (state.mode == "day") {
          drawDays(state.startDrawX, dateChoose.value, drawdays, true);
          setDateTimebyXs(dayWidth)
        }else{
            drawTimes(state.startDrawX, state.timeDay,true);
            setDateTimebyXs(l)
        }
      };

      canvas.onmousemove = (e) => {
        t0=requestAnimationFrame(()=>{
            if (!state.isStartMove) return;
        const { offsetY, offsetX } = e;
        state.interval = offsetX - state.lastX;
        clear();
        if (state.mode == "day") {
          drawDays(
            state.startDrawX + state.interval,
            dateChoose.value,
            drawdays
          );
        } else {
          drawTimes(state.startDrawX + state.interval, state.timeDay);
        }
        },16.7)
      };