Canvas实现时间刻度标尺功能
前言:项目中需要实现一个时间标尺的功能,展示某个人在某段时间内已经被订单占用的时间,并且展示当前订单在标尺中的占用位置,大概为下图的样子
大概的实现逻辑如下:
- 计算时间刻度跨度。canvas总宽度为320,高度为40,注意,canvas的宽度不能使用css来写,会导致计算混乱,直接写在标签内,一共显示8个时间区域,所以每个刻度之间的宽度为320/8=40
- 利用canvas画一条线,X轴起点为0,终点点为320,Y轴起终点都是25,只所以不放到最底下,是因为需要给显示时间的文字一定的区域
- 计算显示的时间跨度,正常跨度为1个小时,行程超过4个小时跨度为2个小时,超过12小时跨度为6个小时.利用行程的结束时间减去开始的时间,得出行程的时长,再做判断得出时间的跨度
- 利用moment格式化行程的开始时间,得出第一个刻度上的时间,然后再根据跨度递增并且显示即可.
- 绘画已有行程的矩形,拿到司机已有的行程的时间.用司机行程的开始时间跟当前订单的开始时间都转化成时间戳去判断,如果司机行程的开始时间早于订单的开始时间,那矩形的X轴起点为0,反之用相差的时间/60000转化成分钟数.
- 利用时间跨度*60,得出时间跨度的分钟数,使用前面的时间差的分数/每个跨度代表的分钟数,得出需要展示的比例,乘于每个跨度中的宽度,也就是40.得出需要X轴起点位置
- X轴的终点位置也是一样,只需要在计算行程差的时候,使用司机行程的结束时间去减订单的开始时间,然后算法同上,得出X轴的终点位置即可绘制.
实现代码如下
initDraw:function(){
// 首先执行此方法,计算起终点的时间
let start = this.modifyStartTime ? this.modifyStartTime : this.orderStartTime;
let end = this.modifyEndTime ? this.modifyEndTime : this.orderEndTime;
// 先计算刻度的数值
this.calculateSpan(start,end);
// 初始化时间刻度尺
this.initTimeArr(start);
// 每个司机的行程都不相同,所以遍历去绘画每个司机的行程
this.driverJourneyArr.forEach((item,index)=>{
this.drawDriverRect("d" + item.id,index);
})
},
calculateSpan:function(start,end){
// 计算时间跨度orderEndTime
let hours = (new Date(end).valueOf() - new Date(start).valueOf()) / 60000 / 6 ;
if(hours < 4){
this.timeSpan = 1;
}else if(hours > 4 && hours < 12){
this.timeSpan = 2;
}else{
this.timeSpan = 6;
}
},
initTimeArr:function(start){
// 获取第一个时间刻度上的时间
this.timeArr = [];
// * -3 是因为刻度尺展示的时间是开始时间的前XX个小时,跟后XX个小时,所以start这个行程的开始时间是在刻度尺中间的位置展示的
this.begin = moment(start).add(this.timeSpan * -3 , "hour").format("YYYY-MM-DD HH:mm:ss");
// 初始化显示在时间刻度表上的时间
for (let i = 1; i < 9; i++) {
this.timeArr.push(moment(start).add(this.timeSpan * (i - 3), "hour").format("HH:mm"));
}
},
initTimeScale:function(ctx){
// 先移动至线的起点处
ctx.moveTo(0,25);
// 设定线的起点跟终点
ctx.lineTo(320,25);
// 设置线的颜色
ctx.strokeStyle = "#070707";
// 设置字体的颜色
ctx.fillStyle = "#070707";
for (let i = 1; i < 8; i++) {
ctx.moveTo(i * 40,17);
ctx.lineTo(i * 40,25);
ctx.textAlign = "center";
ctx.fillText(this.timeArr[i-1],i * 40,35)
}
// 绘画
ctx.stroke();
},
drawDriverRect:function(domId,i){
let ctx = document.getElementById(domId).getContext("2d");
// 清除画布
ctx.clearRect(0,0,320,40);
this.driverJourneyArr[i].times.forEach((time,index)=>{
// 获取每个司机
let obj = this.getDrawLocation(time);
// 绘制矩形
ctx.fillStyle = "rgb(161,161,161)";
ctx.fillRect (obj.start, 10, obj.end - obj.start, 15);
})
this.drawOrderRect(ctx);
this.initTimeScale(ctx);
},
getDrawLocation:function(item){
// 通过计算得出需要绘制起终点
let timeDifference,startLocation,endLocation;
// 计算出司机行程和订单的开始时间的时间差,用于计算绘制的位置
timeDifference = new Date(item.start).valueOf() - new Date(this.begin).valueOf() ;
startLocation = 0;
if(timeDifference > 0 ){
// 使用前面计算出的时间差的分数除于每个跨度代表的分钟数,得出需要展示的比例,乘于每个跨度中的宽度,也就是40.得出需要X轴起点位置
startLocation = timeDifference / 60000 /(60 * this.timeSpan) * 40
}
// X轴终点的位置,是计算司机行程的结束时间和订单开始的时间差,由于计算出来是时间戳,除于60000得到分钟数,除于一个刻度区域里代表的分钟数,得出需要展示的比例,乘以固定的刻度区域宽度40,得出需要绘制的X轴终点
endLocation = (new Date(item.end).valueOf() - new Date(this.begin).valueOf()) / 60000 / (60 * this.timeSpan) * 40;
return {start:startLocation,end:endLocation}
},
drawOrderRect(c){
let start = this.modifyStartTime ? this.modifyStartTime : this.orderStartTime;
let end = this.modifyEndTime ? this.modifyEndTime : this.orderEndTime;
let obj = this.getDrawLocation({start:start,end:end});
// 绘制矩形
c.fillStyle = "rgba(0,204,255,0.5)";
c.fillRect (obj.start, 0, obj.end - obj.start, 25);
},