avatar

解决canvas在移动端上绘制模糊的问题

解决canvas在移动端上绘制模糊的问题

在移动端盛行,高清屏基本上已经普及的现在,1px的css像素实际上代表了4个甚至更多的物理像素。但是由于我们的代码问题,我们1px的css像素和1个canvas像素画上了等号,也就导致了1个canvas像素实际需要填充4个甚至更多物理像素,为了保证图像平滑处理,在填充剩余的物理像素时采用了原先颜色的近似值,导致了图像的模糊。解决该问题最重要的一点是让1个canvas像素和一个物理像素挂等号

原始效果

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// 原始代码
// 绘制图形数据
drawChart() {
const canvasBox = document.getElementById('canvas-box1')
if (!canvasBox) {
return
}
const ctx = canvasBox.getContext('2d')
const ctxWidth = parseInt(
window.getComputedStyle(canvasBox, null).width,
10
)
const ctxHeight = parseInt(
window.getComputedStyle(canvasBox, null).height,
10
)
let step = ctxWidth / 24 // 除以24小时
let textAlign
const labelHeight = 20
const barRadius = (ctxHeight - labelHeight) / 2
canvasBox.width = ctxWidth
canvasBox.height = ctxHeight
ctx.clearRect(0, 0, ctxWidth, ctxHeight)
// 绘制x轴文字
ctx.save()
ctx.font = '12px serif'
ctx.textBaseline = 'bottom'
for (let index = 0; index <= 24; index += 4) {
if (index === 0) {
textAlign = 'left'
} else if (index === 24) {
textAlign = 'right'
} else {
textAlign = 'center'
}
ctx.textAlign = textAlign
ctx.fillText(`${index}:00`, index * step, ctxHeight)
}
ctx.restore()
// 绘制背景
ctx.save()
ctx.fillStyle = '#f5f5f5'
ctx.beginPath()
ctx.moveTo(barRadius, 0)
ctx.lineTo(ctxWidth - barRadius, 0)
ctx.arc(
ctxWidth - barRadius,
barRadius,
barRadius,
-Math.PI / 2,
Math.PI / 2
)
ctx.lineTo(barRadius, barRadius * 2)
ctx.arc(barRadius, barRadius, barRadius, Math.PI / 2, (3 * Math.PI) / 2)
ctx.closePath()
ctx.fill()
ctx.restore()

step = ctxWidth / (24 * 60) // 按分钟分区
if (
this.workData.length === 0 ||
this.workData.length <= this.currentPeriod
) {
return
}
// 绘制所有数据
ctx.save()
ctx.beginPath()
ctx.fillStyle = '#ebebeb'
this.workData.forEach(item => {
const { startTime, endTime } = item
const startDate = new Date(startTime)
const stopDate = new Date(endTime)
const px1 =
step *
(60 * startDate.getHours() +
startDate.getMinutes() +
startDate.getSeconds() / 60)
const px2 =
step *
(60 * stopDate.getHours() +
stopDate.getMinutes() +
stopDate.getSeconds() / 60)
if (px2 > px1) {
ctx.rect(px1, 0, px2 - px1, barRadius * 2)
}
})
ctx.fill()
ctx.restore()
// 绘制高亮数据
ctx.save()
ctx.beginPath()
ctx.fillStyle = '#70b913'
const currentItem = this.workData[this.currentPeriod]
{
const { startTime, endTime } = currentItem
const startDate = new Date(startTime)
const stopDate = new Date(endTime)
const px1 =
step *
(60 * startDate.getHours() +
startDate.getMinutes() +
startDate.getSeconds() / 60)
const px2 =
step *
(60 * stopDate.getHours() +
stopDate.getMinutes() +
stopDate.getSeconds() / 60)
if (px2 > px1) {
ctx.rect(px1, 0, px2 - px1, barRadius * 2)
}
}
ctx.fill()
ctx.restore()
// 给高亮数据加描边,不然工作时间太短看不出来
ctx.save()
ctx.beginPath()
ctx.strokeStyle = '#70b913'
ctx.lineWidth = 5
{
const { startTime, endTime } = currentItem
const startDate = new Date(startTime)
const stopDate = new Date(endTime)
const px1 =
step *
(60 * startDate.getHours() +
startDate.getMinutes() +
startDate.getSeconds() / 60)
const px2 =
step *
(60 * stopDate.getHours() +
stopDate.getMinutes() +
stopDate.getSeconds() / 60)
if (px2 > px1) {
ctx.rect(px1, 0, px2 - px1, barRadius * 2 - 2.5)
}
}
ctx.stroke()
ctx.restore()
// 绘制x轴刻度
step = ctxWidth / 24 // 除以24小时
ctx.save()
ctx.strokeStyle = '#d5d5d5'
ctx.beginPath()
for (let index = 4; index < 24; index += 4) {
const px = index * step
ctx.moveTo(px, barRadius * 2)
ctx.lineTo(px, ctxHeight - labelHeight - 10)
}
ctx.stroke()
ctx.restore()
},

修改后效果

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// 修改后的代码
// 绘制图形数据
drawChart() {
const canvasBox = document.getElementById('canvas-box1')
if (!canvasBox) {
return
}
const ctx = canvasBox.getContext('2d')
const dpr = window.devicePixelRatio
const {
width: ctxWidth,
height: ctxHeight
} = canvasBox.getBoundingClientRect()
let step = ctxWidth / 24 // 除以24小时
let textAlign
const labelHeight = 20
const barRadius = (ctxHeight - labelHeight) / 2
canvasBox.width = ctxWidth * dpr
canvasBox.height = ctxHeight * dpr
ctx.scale(dpr, dpr)
ctx.clearRect(0, 0, ctxWidth, ctxHeight)
// 绘制x轴文字
ctx.save()
ctx.font = '12px serif'
ctx.textBaseline = 'bottom'
for (let index = 0; index <= 24; index += 4) {
if (index === 0) {
textAlign = 'left'
} else if (index === 24) {
textAlign = 'right'
} else {
textAlign = 'center'
}
ctx.textAlign = textAlign
ctx.fillText(`${index}:00`, index * step, ctxHeight)
}
ctx.restore()
// 绘制背景
ctx.save()
ctx.fillStyle = '#f5f5f5'
ctx.beginPath()
ctx.moveTo(barRadius, 0)
ctx.lineTo(ctxWidth - barRadius, 0)
ctx.arc(
ctxWidth - barRadius,
barRadius,
barRadius,
-Math.PI / 2,
Math.PI / 2
)
ctx.lineTo(barRadius, barRadius * 2)
ctx.arc(barRadius, barRadius, barRadius, Math.PI / 2, (3 * Math.PI) / 2)
ctx.closePath()
ctx.fill()
ctx.restore()

step = ctxWidth / (24 * 60) // 按分钟分区
if (
this.workData.length === 0 ||
this.workData.length <= this.currentPeriod
) {
return
}
// 绘制所有数据
ctx.save()
ctx.beginPath()
ctx.fillStyle = '#ebebeb'
this.workData.forEach(item => {
const { startTime, endTime } = item
const startDate = new Date(startTime)
const stopDate = new Date(endTime)
const px1 =
step *
(60 * startDate.getHours() +
startDate.getMinutes() +
startDate.getSeconds() / 60)
const px2 =
step *
(60 * stopDate.getHours() +
stopDate.getMinutes() +
stopDate.getSeconds() / 60)
if (px2 > px1) {
ctx.rect(px1, 0, px2 - px1, barRadius * 2)
}
})
ctx.fill()
ctx.restore()
// 绘制高亮数据
ctx.save()
ctx.beginPath()
ctx.fillStyle = '#70b913'
const currentItem = this.workData[this.currentPeriod]
{
const { startTime, endTime } = currentItem
const startDate = new Date(startTime)
const stopDate = new Date(endTime)
const px1 =
step *
(60 * startDate.getHours() +
startDate.getMinutes() +
startDate.getSeconds() / 60)
const px2 =
step *
(60 * stopDate.getHours() +
stopDate.getMinutes() +
stopDate.getSeconds() / 60)
if (px2 > px1) {
ctx.rect(px1, 0, px2 - px1, barRadius * 2)
}
}
ctx.fill()
ctx.restore()
// 给高亮数据加描边,不然工作时间太短看不出来
ctx.save()
ctx.beginPath()
ctx.strokeStyle = '#70b913'
ctx.lineWidth = 5
{
const { startTime, endTime } = currentItem
const startDate = new Date(startTime)
const stopDate = new Date(endTime)
const px1 =
step *
(60 * startDate.getHours() +
startDate.getMinutes() +
startDate.getSeconds() / 60)
const px2 =
step *
(60 * stopDate.getHours() +
stopDate.getMinutes() +
stopDate.getSeconds() / 60)
if (px2 > px1) {
ctx.rect(px1, 0, px2 - px1, barRadius * 2 - 2.5)
}
}
ctx.stroke()
ctx.restore()
// 绘制x轴刻度
step = ctxWidth / 24 // 除以24小时
ctx.save()
ctx.strokeStyle = '#d5d5d5'
ctx.beginPath()
for (let index = 4; index < 24; index += 4) {
const px = index * step
ctx.moveTo(px, barRadius * 2)
ctx.lineTo(px, ctxHeight - labelHeight - 10)
}
ctx.stroke()
ctx.restore()
},
文章作者: pengweifu
文章链接: https://www.pengwf.com/2020/10/17/web/JS-Mobile-Canvas/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 麦子的博客
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论