![](https://img.haomeiwen.com/i8535415/b684fe4d20a882b8.gif)
GIF 2023-10-30 18-46-11.gif
<div>
<canvas id="c1" width="500" height="255"></canvas>
<button onclick="download()">download</button>
<img id="img" src="" />
</div>
<style>
* {
margin: 0;
padding: 0;
}
div {
padding: 20px;
}
body {
background-color: #333;
}
canvas {
border-radius: 6px;
background-color: #fff;
}
</style>
<script src="./my-canvas.js"></script>
<script>
function download() {
line.download()
}
</script>
class Line {
/** 边距 */
padding = {
left: 0,
top: 0,
bottom: 0,
right: 0
}
/** Y轴标尺 */
axias = { max: 100, show: true, number: 5 }
/** 间隔 */
gap = 0
margin = 12
shadowX = 7
shadowWidth = 4
shadowOpacity = 0.1
constructor(dom, data, options) {
let element = document.querySelector(dom)
this.element = element
this.ctx = this.element.getContext('2d')
this.data = JSON.parse(JSON.stringify(data))
let { padding, axias, labels } = options
padding = { ...this.padding, ...padding }
this.padding = padding
let { width, height } = element
this.width = width
this.height = height
this.contentHight = height - padding.top - padding.bottom
this.contentWidth = width - padding.left - padding.right
// this.drawLines()
this.setGap()
this.axias = { ...this.axias, ...axias }
this.AniPoints = this.setPoints()
this.AniShadows = this.setPoints(true)
this.points = []
this.shadows = []
// console.log(this.points)
this.labels = labels
// this.drawXLine()
// this.drawxLabels()
// this.drawYLabels()
this.animation()
// this.drawSmoothLines()
// this.drawSmoothLines(true)
}
animation() {
let { AniPoints, AniShadows, ctx, width, height } = this
this.points.push(AniPoints.shift())
this.shadows.push(AniShadows.shift())
ctx.clearRect(0, 0, width, height)
this.drawXLine()
this.drawxLabels()
this.drawYLabels()
this.drawSmoothLines()
this.drawSmoothLines(true)
window.requestAnimationFrame(() => {
if (AniPoints.length) {
this.animation()
}
})
}
/** 横向底部信息 */
drawxLabels() {
let { labels, padding: { left, bottom }, gap, ctx, height, margin } = this
labels.forEach((item, index) => {
ctx.fillStyle = '#555'
ctx.font = '10px Arial'
let x = gap * index + left - 10 + margin
let y = height - bottom + 16
ctx.fillText(item, x, y)
})
}
/** 横轴线 */
drawXLine() {
let { axias: { max, show, number }, padding: { left, right, top }, width, contentHight, ctx } = this
if (!show) { return }
let g = max / (number - 1) * (contentHight / max)
this.yGap = g
ctx.save()
for (let i = 0; i < number; i++) {
let endPoint = Number((i * g + top).toFixed(0)) + 0.5
ctx.beginPath()
ctx.moveTo(left, endPoint)
ctx.lineTo(width - right, endPoint)
ctx.strokeStyle = '#ddd'
ctx.lineWidth = 1
ctx.stroke()
ctx.closePath()
}
ctx.restore()
}
/** 竖轴线 */
drawYLabels() {
let { axias: { max, show, number }, padding: { left, top }, ctx } = this
if (!show) { return }
let g = max / (number - 1)
let toLeft = {
1: 0,
2: 8,
3: 16
}
ctx.save()
for (let i = 0; i < number; i++) {
let text = (max - i * g).toFixed(0)
ctx.fillStyle = '#555'
ctx.font = '14px Arial'
// console.log(top + i * this.yGap)
ctx.textAlign = 'end'
ctx.textBaseline = 'middle'
ctx.fillText(text, left - 8, top + i * this.yGap + 0.5)
}
ctx.restore()
}
/** 横向间隔 */
setGap() {
let { contentWidth, data: { length }, margin } = this
this.gap = (contentWidth - margin * 2) / (length - 1)
}
/** 结合Y轴标尺,转化为px数值 */
setPoints(isShadow) {
let { gap, padding: { left, top }, axias: { max }, data, contentHight, margin, shadowX } = this
return data.map((item, index) => {
return [index * gap + left + margin, contentHight - (item * contentHight / max) + top + (isShadow ? shadowX : 0)]
})
}
/** 画线 */
drawLines(isShadow) {
let { ctx, points, shadows } = this
points = isShadow ? shadows : points
ctx.save()
ctx.beginPath()
ctx.moveTo(points[0][0], points[0][1])
if (points[1]) {
ctx.lineTo(points[1][0], points[1][1])
}
ctx.stroke()
ctx.closePath()
ctx.restore()
}
/** 画平滑线 */
drawSmoothLines(isShadow) {
let { ctx, points, drawLines, width, height, shadows, shadowWidth } = this
points = isShadow ? shadows : points
// ctx.clearRect(0, 0, 400, 205)
if (points.length < 3) {
return drawLines.call(this)
}
let f = 0.3
ctx.save()
ctx.beginPath()
ctx.moveTo(points[0][0], points[0][1])
let dx1 = 0
let dy1 = 0
let dx2 = 0
let dy2 = 0
let prevPoint = points[0]
let nextPoint = null
for (let i = 1; i < points.length; i++) {
let currtPoint = points[i]
nextPoint = points[i + 1]
if (nextPoint) {
dx2 = -(nextPoint[0] - currtPoint[0]) * f
} else {
dx2 = 0
dy2 = 0
}
ctx.bezierCurveTo(prevPoint[0] - dx1, prevPoint[1] - dy1, currtPoint[0] + dx2, currtPoint[1] + dy2, currtPoint[0], currtPoint[1])
dx1 = dx2
dy1 = dy2
prevPoint = currtPoint
}
let grd = ctx.createLinearGradient(0, height / 2, width, height / 2)
grd.addColorStop(0, `rgba(14, 185, 160, ${isShadow ? 0.1 : 1})`)
grd.addColorStop(1, `rgba(160, 209, 118, ${isShadow ? 0.1 : 1})`)
// 将渐变赋值给线的样式
ctx.strokeStyle = grd
ctx.lineWidth = isShadow ? shadowWidth : 2
ctx.stroke()
ctx.closePath()
ctx.restore()
}
download() {
this.element.toBlob(blob => {
console.log(blob)
let img = new Image()
let url = URL.createObjectURL(blob)
document.querySelector('#img').src = url
console.log(url)
img.onload = function () {
URL.revokeObjectURL(url)
}
img.src = url
console.log(img)
}, "image/png", 1)
}
}
let data = [70, 45, 120, 100, 110, 80, 70, 102, 120, 80, 80, 90]
let line = new Line('#c1', data, {
padding: {
left: 40,
right: 30,
top: 28,
bottom: 38
},
axias: { max: 200, show: true, number: 6 },
labels: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
})
class Bg {
/** Y轴标尺 */
axias = { max: 100, show: true }
constructor(dom, options) {
let element = document.querySelector(dom)
this.element = element
this.ctx = this.element.getContext('2d')
let { axias } = options
this.axias = { ...this.axias, ...axias }
let { width, height } = element
this.width = width
this.height = height
}
}
网友评论