Canvas学习日记

70 阅读3分钟

Canvas学习

今天还是熟悉项目代码,看到项目中用到的折线图组件,就很好奇里面内部实现的原理,于是去查了查发现目前市面上流行的EChartsantv基本上都是基于canvas实现的。于是就去系统学习一下canvas的用法。

1.坐标系统(2D)

canvas的坐标系统如下所示,是一个以左上角为原点,水平向右为X轴,垂直向下为Y轴,坐标单位为像素(px),所以每个点的坐标都是非负整数。

image.png

2.创建画布

let canvas = document.getElementById('container'),//获取容器
let ctx = canvas.getContext('2d');//创建2D canvas上下文

3.文字

创建文字,并添置一些样式,写法与css一致。

    ctx.shadowOffsetX = 2;//阴影x轴偏置量
    ctx.shadowOffsetY = 2;//阴影y轴偏置量
    ctx.shadowBlur = 2;//阴影模糊度
    ctx.shadowColor = '#ccc';//阴影颜色
    ctx.font = '28px Arial';//字体设置(大小,类型)
    ctx.fillStyle = '#999';//字体颜色
    ctx.fillText('带阴影的文字', 20, 40);//渲染字体

4. 图形

矩形

//左上坐标、宽度、高度
ctx.fillRect(x, y, width, height)//填充矩形
ctx.strokeRect(x, y, width, height)//描边矩形
ctx.clearRect(x, y, width, height)//清除矩形区域

圆形

ctx.beginPath();
// 圆心横坐标,圆心纵坐标,半径,起始和结束角度,是否逆时针绘制
ctx.arc(x, y, r, 0, Math.PI * 2, false); 
ctx.fillStyle = 'green';
ctx.fill(); // 填充圆形
ctx.strokeStyle = 'black';//描边颜色
ctx.stroke(); // 描边圆形

线条

ctx.beginPath();
ctx.moveTo(10, 10); // 起点
ctx.lineTo(200, 100); // 终点
ctx.strokeStyle = 'purple';
ctx.stroke(); // 绘制线条

学了这些练习题就来了

image.png

拿到这题一开始还有点不知所措,这都没有坐标轴,我该怎么确定每个点的位置,于是经过思考想到了解决思路: 1.首先绘制一个矩形用于承载这个折线图 2.为了确定各个点位的信息,来表示上升和下降的变化趋势,这时我们就需要在心中画出一个坐标轴。根据如下公式:

{垂直坐标尺度=画布高度这几天最高温度和最低温度的温差垂直坐标尺度=画布宽度天数1\left\{ \begin{aligned} &垂直坐标尺度=\frac{画布高度}{这几天最高温度和最低温度的温差} \\ &垂直坐标尺度=\frac{画布宽度}{天数-1} \\ \end{aligned} \right.

确定完点的位置,这时只需要将点通过线条连起来,然后在点的附近添加上文字即可。综上所述,最核心的还得是计算出各个点的位置,话不多说上代码。

let data = [
    { high: 35, low: 22 },
    { high: 37, low: 24 },
    { high: 37, low: 25 },
    { high: 34, low: 24 },
    { high: 33, low: 23 }
];

let canvas = document.getElementById('weather-canvas');
// TODO: 绘图
// 400x200
let ctx = canvas.getContext('2d');
ctx.fillStyle = "#fff"
const width = 400;
const height = 200;
ctx.fillRect(0, 0, width, height);

const margin = { top: 30, right: 40, bottom: 30, left: 30 };

// 计算比例尺
const chartWidth = width - margin.left - margin.right;
const chartHeight = height - margin.top - margin.bottom;
const xScale = chartWidth / (data.length - 1); // X 轴比例尺
const yScale = chartHeight / (Math.max(...data.map(d => d.high)) - Math.min(...data.map(d => d.low))); // Y 轴比例尺

// 绘制最高气温折线
ctx.strokeStyle = 'orange';
ctx.lineWidth = 2;
ctx.beginPath();
data.forEach((d, i) => {
    const x = margin.left + i * xScale;
    const y = margin.top + chartHeight - (d.high - Math.min(...data.map(d => d.low))) * yScale;
    if (i === 0) {
        ctx.moveTo(x, y);//如果是起点那么直接绘制点
    } else {
        ctx.lineTo(x, y);//不是起点,那就绘制折线
    }
});
ctx.stroke();
// 绘制最低气温折线
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.beginPath();
data.forEach((d, i) => {
    const x = margin.left + i * xScale;
    const y = margin.top + chartHeight - (d.low - Math.min(...data.map(d => d.low))) * yScale;
    if (i === 0) {
        ctx.moveTo(x, y);
    } else {
        ctx.lineTo(x, y);
    }
});
ctx.stroke();

// 绘制数据点及标注
data.forEach((d, i) => {
    const x = margin.left + i * xScale;

    // 最高气温点
    const highY = margin.top + chartHeight - (d.high - Math.min(...data.map(d => d.low))) * yScale;
    ctx.fillStyle = 'orange';
    ctx.beginPath();
    ctx.arc(x, highY, 5, 0, Math.PI * 2);
    ctx.fill();

    // 最低气温点
    const lowY = margin.top + chartHeight - (d.low - Math.min(...data.map(d => d.low))) * yScale;
    ctx.fillStyle = 'blue';
    ctx.beginPath();
    ctx.arc(x, lowY, 5, 0, Math.PI * 2);
    ctx.fill();

    // 标注温度
    ctx.fillStyle = '#000';
    ctx.font = '16px Arial';
    ctx.fillText(`${d.high}℃`, x + 0, highY - 10); // 最高气温标注
    ctx.fillText(`${d.low}℃`, x + 0, lowY + 20);  // 最低气温标注
});

// 下载:
let download = document.getElementById('weather-download');
download.href = canvas.toDataURL();

得到的效果图如下:

image.png ### 参考资料 [廖雪峰的官方网站](https://liaoxuefeng.com/)