1

饼图直接用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>

实现的效果如下:
image.png


smallStone
419 声望71 粉丝

前端一枚^_-