简介
运用场景:选择对应的时间,或者滑动时间轴,控制摄像头的时间,查看摄像头记录
实现: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)
};