饼图直接用echarts,但下面的只能自己实现了。
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>canvasLine</title>
</head>
<style>
.parent{
height: 90vh;
width: 90%;
border: 10px solid #ccc;
padding: 10px;
box-sizing: border-box;
}
#canvas{
border: 1px solid burlywood;
box-sizing: border-box;
}
#scrollbar {
overflow-y: hidden;
overflow-x: auto;
margin: 0 auto;
}
</style>
<body>
<div class="parent">
<canvas id="canvas"></canvas>
<div id="scrollbar">
<div style="width: 2000px;height: 1px;"></div>
</div>
</div>
</body>
<script>
class Utils {
/* px转number, 向上取整,防止出现小数,因为canvas基于像素绘制,像素没有小数,如果有小数绘制就会出现模糊 */
static Px2Number(numpx) {
numpx = numpx + '' // 防止replace报错,强制转换成string类型
return Math.round(Number(numpx.replace('px', '')))
}
/* 防止1px模糊 */
static fixLineFuzzy(num) {
return parseInt(num) - 0.5
}
}
class InitCanvas {
static dpr = window.devicePixelRatio; // 私有静态属性屏幕dpr
static cicleY = 1.25; // 私有静态属性屏幕dpr
constructor(dom) {
if (!dom) {
return console.error('未找到对应的dom节点!')
}
this.scrollEle = document.getElementById('scrollbar')
this.canvas = dom
this.ctx = this.canvas.getContext('2d')
this.canvasParent = this.canvas.parentNode
this.canvasParentWidth = '600px';
this.canvasParentBorderLeftWidth = 0;
this.canvasParentBorderRightWidth = 0;
this.canvasParentPaddingLeftWidth = 0;
this.canvasParentPaddingRightWidth = 0;
this.canvasParentBoxSizing = 'none';
this.canvasStyleWidth = Utils.Px2Number(this.canvasParentWidth)
this.canvasStyleHeight = 300
this.trueW_PX = this.canvasStyleWidth
this.trueH_PX = this.canvasStyleHeight
this.leftRightPanel = 10 // 左右面板宽度 各占10%,按百分比制
this.canvasPanel = 100 - this.leftRightPanel * 2 // 按百分比制
this.persentOneForPX = 0
this.leftRightPanelForPX = 0
this.canvasPanelForPX = 0
this.panelPonits = null
this.cicleEventEle = []
this.centerPanelPoint = null // 中间绘制面板坐标
this.init()
}
init() {
this.setCanvasStyleWH()
this.panelPonits = this.splitCanvasPanel()
this.REFERENCE_LINE() // 参考线
this.cicleEventEle.push(...this.DRAW_LEFT_CIRCLE())
this.DRAW_RIGHT_CIRCLE() // 参考右面板元素
this.cicleEventEle.push(...this.DRAW_RIGHT_CIRCLE())
this.centerPanelPoint = this.DRAW_CENTER_PANEL() // 参考中间面板元素
this.addEventClick() // 给4个按钮添加点击事件
this.addEventScroll() // 添加滚动事件
}
REFERENCE_LINE() {
this.ctx.lineWidth = 1
this.ctx.strokeStyle = 'gray'
this.ctx.moveTo(Utils.fixLineFuzzy(this.leftRightPanelForPX), 0);
this.ctx.lineTo(Utils.fixLineFuzzy(this.leftRightPanelForPX), Utils.fixLineFuzzy(this.trueH_PX));
this.ctx.stroke();
this.ctx.closePath();
this.ctx.moveTo(Utils.fixLineFuzzy(this.leftRightPanelForPX + this.canvasPanelForPX), 0);
this.ctx.lineTo(Utils.fixLineFuzzy(this.leftRightPanelForPX + this.canvasPanelForPX), Utils.fixLineFuzzy(this.trueH_PX));
this.ctx.stroke();
this.ctx.closePath();
}
DRAW_LEFT_CIRCLE() {
const { point3 } = this.panelPonits.leftPanel
const [x, y] = point3
const cricle1Orign = [this.leftRightPanelForPX / 4, y / InitCanvas.cicleY]
const cricle2Orign = [this.leftRightPanelForPX / 4 * 3, y / InitCanvas.cicleY]
this.ctx.strokeStyle = "#ff3d51"
this.ctx.lineWidth = 1;
this.ctx.beginPath()
const radius = 20
this.ctx.arc(cricle1Orign[0], cricle1Orign[1], radius, 0, Math.PI * 2)
this.ctx.stroke();
this.ctx.closePath();
this.ctx.beginPath()
this.ctx.arc(cricle2Orign[0], cricle2Orign[1], radius, 0, Math.PI * 2)
this.ctx.stroke();
this.ctx.closePath();
return [{
x: cricle1Orign[0],
y: cricle1Orign[1],
radius,
type: EventsSachieve.CICLE_EVENTS.PREV_PAGE
}, {
x: cricle2Orign[0],
y: cricle2Orign[1],
radius,
type: EventsSachieve.CICLE_EVENTS.PREV_STEP
}]
}
DRAW_RIGHT_CIRCLE() {
const { point4: [leftx, y] } = this.panelPonits.rightPanel
const cricle1Orign = [leftx + this.leftRightPanelForPX / 4, y / InitCanvas.cicleY]
const cricle2Orign = [leftx + this.leftRightPanelForPX / 4 * 3, y / InitCanvas.cicleY]
this.ctx.strokeStyle = "#ff3d51"
this.ctx.lineWidth = 1;
const radius = 20
this.ctx.beginPath()
this.ctx.arc(cricle1Orign[0], cricle1Orign[1], radius, 0, Math.PI * 2)
this.ctx.stroke();
this.ctx.closePath();
this.ctx.beginPath()
this.ctx.arc(cricle2Orign[0], cricle2Orign[1], radius, 0, Math.PI * 2)
this.ctx.stroke();
this.ctx.closePath();
return [{
x: cricle1Orign[0],
y: cricle1Orign[1],
radius,
type: EventsSachieve.CICLE_EVENTS.NEXT_STEP
}, {
x: cricle2Orign[0],
y: cricle2Orign[1],
radius,
type: EventsSachieve.CICLE_EVENTS.NEXT_PAGE
}]
}
DRAW_CENTER_PANEL() {
const { point1, point3 } = this.panelPonits.centerPanel
this.ctx.lineWidth = 1
this.ctx.strokeStyle = 'gray'
this.ctx.beginPath()
this.ctx.moveTo(Utils.fixLineFuzzy(point1[0]), point3[1] / 2);
this.ctx.lineTo(Utils.fixLineFuzzy(point3[0]), point3[1] / 2);
this.ctx.stroke();
this.ctx.closePath();
this.ctx.beginPath()
this.ctx.strokeStyle = 'green'
const y = point3[1] / 1.65
this.ctx.strokeRect(point1[0], y, this.canvasPanelForPX, this.trueH_PX - y)
this.ctx.closePath();
// this.ctx.beginPath()
// this.ctx.arc(point1[0], y, 6, 0, Math.PI * 2)
// this.ctx.arc(point1[0] + this.canvasPanelForPX, y, 6, 0, Math.PI * 2)
// this.ctx.arc(point1[0] + this.canvasPanelForPX, y + this.trueH_PX - y, 6, 0, Math.PI * 2)
// this.ctx.arc(point1[0], y + this.trueH_PX - y, 6, 0, Math.PI * 2)
// this.ctx.stroke();
// this.ctx.closePath();
return {
point1: [point1[0], y],
point2: [point1[0] + this.canvasPanelForPX, y],
point3: [point1[0] + this.canvasPanelForPX, y + this.trueH_PX - y],
point4: [point1[0], y + this.trueH_PX - y],
w: this.canvasPanelForPX,
h: this.trueH_PX - y
}
}
splitCanvasPanel() {
// 百分之一占比多所像素
this.persentOneForPX = this.trueW_PX / 100
this.leftRightPanelForPX = this.persentOneForPX * this.leftRightPanel
this.canvasPanelForPX = this.persentOneForPX * this.canvasPanel
return {
leftPanel: {
point1: [0, 0],
point2: [this.leftRightPanelForPX, 0],
point3: [this.leftRightPanelForPX, this.trueH_PX],
point4: [0, this.trueH_PX]
},
centerPanel: {
point1: [this.leftRightPanelForPX, 0],
point2: [this.leftRightPanelForPX + this.canvasPanelForPX, 0],
point3: [this.leftRightPanelForPX + this.canvasPanelForPX, this.trueH_PX],
point4: [this.leftRightPanelForPX, this.trueH_PX]
},
rightPanel: {
point1: [this.leftRightPanelForPX + this.canvasPanelForPX, 0],
point2: [this.leftRightPanelForPX + this.canvasPanelForPX * 2, 0],
point3: [this.leftRightPanelForPX + this.canvasPanelForPX * 2, this.trueH_PX],
point4: [this.leftRightPanelForPX + this.canvasPanelForPX, this.trueH_PX]
},
}
}
setCanvasStyleWH() {
const { sw, sh, TW, TH } = this.getCanvasStyleWH()
this.canvas.style.width = sw + 'px'
this.canvas.style.height = sh + 'px'
this.trueW_PX = TW
this.trueH_PX = TH
this.canvas.width = TW;
this.canvas.height = TH;
// 设置底部滚动条宽度
this.scrollEle.style.width = sw / 100 * this.canvasPanel + 'px'
}
getCanvasStyleWH() {
if (this.canvasParent) {
let { width, borderLeftWidth, borderRightWidth, boxSizing, paddingLeft, paddingRight } = getComputedStyle(this.canvasParent)
this.canvasParentPaddingLeftWidth = Utils.Px2Number(paddingLeft)
this.canvasParentPaddingRightWidth = Utils.Px2Number(paddingRight)
this.canvasParentWidth = Utils.Px2Number(width)
this.canvasParentBorderLeftWidth = Utils.Px2Number(borderLeftWidth)
this.canvasParentBorderRightWidth = Utils.Px2Number(borderRightWidth)
this.canvasParentBoxSizing = boxSizing
this.canvasStyleWidth = this.canvasParentWidth
if (this.canvasParentBoxSizing === 'border-box') {
this.canvasStyleWidth = this.canvasStyleWidth - this.canvasParentBorderLeftWidth - this.canvasParentBorderRightWidth - this.canvasParentPaddingLeftWidth - this.canvasParentPaddingRightWidth
}
}
return {
sw: this.canvasStyleWidth,
sh: this.canvasStyleHeight,
TW: Math.floor(this.canvasStyleWidth * InitCanvas.dpr),
TH: Math.floor(this.canvasStyleHeight * InitCanvas.dpr)
}
}
addEventClick() {
this.canvas.addEventListener('click', (event) => {
const rect = canvas.getBoundingClientRect();
const xClick = event.clientX - rect.left;
const yClick = event.clientY - rect.top;
this.cicleEventEle.forEach(ele => {// 判断点击位置是否在圆内
const { x, y, radius, type } = ele
if ((xClick - x) ** 2 + (yClick - y) ** 2 <= radius ** 2) {
console.log(type, '点击在圆内');
this[type]()
}
});
});
}
addEventScroll() {
this.rendering(0)
this.scrollEle.addEventListener('scroll', (event) => {
const scrollLeft = event.target.scrollLeft
window.requestAnimationFrame(() => {
this.rendering(scrollLeft)
})
})
}
rendering(scrollLeft) {
const [x, y] = this.centerPanelPoint.point1
const {w, h} = this.centerPanelPoint
this.ctx.clearRect(x, y, w, h)
this.ctx.fillStyle = "green";
this.ctx.fillRect(x + scrollLeft, y, 100, h)
this.ctx.strokeRect(x, y, w, h)
console.log(111)
}
getLeftScroll() {
return this.scrollEle.scrollLeft
}
setLeftScroll(scnum) {
this.scrollEle.scrollTo({
top: 0,
left: scnum,
behavior: 'smooth'
})
}
}
/* 事件实现 */
class EventsSachieve extends InitCanvas{
constructor(dom) {
super(dom)
}
static CICLE_EVENTS = { // 事件列表
NEXT_PAGE: 'NEXT_PAGE', // 下一页
NEXT_STEP: 'NEXT_STEP', // 下一步
PREV_STEP: 'PREV_STEP', // 上一步
PREV_PAGE: 'PREV_PAGE', // 上一页
}
PREV_PAGE() {
const scnum = this.scrollEle.scrollLeft - 100
this.setLeftScroll(scnum)
}
PREV_STEP() {
console.log('上一步')
const scnum = this.scrollEle.scrollLeft - 10
this.setLeftScroll(scnum)
}
NEXT_STEP() {
console.log('下一步')
const scnum = this.scrollEle.scrollLeft + 10
this.setLeftScroll(scnum)
}
NEXT_PAGE() {
console.log('下一页')
const scnum = this.scrollEle.scrollLeft + 100
this.setLeftScroll(scnum)
}
}
new EventsSachieve(document.getElementById('canvas'))
</script>
</html>
实现的效果如下:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。