目录
<canvas id="canvas" width="600" height="600"></canvas>
宽高只能通过canvas对象的属性来设置,不能通过css来设置
获取画笔
canvas.getContext(‘2d’)
getContext()
方法:获取渲染上下文和绘画功能。2d图像,就传入参数’2d’
let canvas: any = document.getElementById('canvas');
let pen = canvas.getContext('2d');
moveTo()
起点
pen.moveTo(100, 100);
lineTo()
轨迹
上一次的终点是下一次轨迹的起点
pen.lineTo(200, 100);
pen.lineTo(200, 200);
stroke()
沿着轨迹绘制
pen.stroke();
closePath()
从终点连接到起点
pen.closePath();
fill()
闭合路径并填充颜色
pen.fill()
fillRect()
绘制一个填充矩形
fillRect(x, y, width, height)
strokeRect()
绘制一个矩形边框
pen.strokeRect(x, y, width, height);
arc()
绘制一段圆弧路径
pen.arc(x, y, radius, startAngle, endAngle, anticlockwise)
弧度值 = 角度值 * Math.PI / 180
- x:圆弧中心(圆心)的 x 轴坐标。
- y:圆弧中心(圆心)的 y 轴坐标。
- radius:圆弧的半径。
- startAngle:圆弧的起始角度(弧度值),x 轴方向开始计算。
- endAngle:圆弧的结束角度(弧度值)。
- anticlockwise:(可选)定义绘制的方向。
- true:逆时针绘制圆弧
- false:顺时针绘制圆弧(默认)
drawImage()
绘制图像
pen.drawImage(image, dx, dy)
pen.drawImage(image, dx, dy, dWidth, dHeight)
pen.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
image
:绘制图片的dom对象。dx
:image 的左上角在目标画布上 X 轴坐标。
dy
:image 的左上角在目标画布上 Y 轴坐标。
dWidth
:image 在目标画布上绘制的宽度。允许对绘制的 image 进行缩放。如果不说明,在绘制时 image 宽度不会缩放。
dHeight
:image 在目标画布上绘制的高度。允许对绘制的 image 进行缩放。如果不说明,在绘制时 image 高度不会缩放。
sx
:(可选)
需要绘制到目标上下文中的,image 的矩形(裁剪)选择框的左上角 X 轴坐标。sy
:(可选)
需要绘制到目标上下文中的,image 的矩形(裁剪)选择框的左上角 Y 轴坐标。sWidth
:(可选)
需要绘制到目标上下文中的,image 的矩形(裁剪)选择框的宽度。如果不说明,整个矩形(裁剪)从坐标的 sx 和 sy 开始,到 image 的右下角结束。sHeight
:(可选)
需要绘制到目标上下文中的,image的矩形(裁剪)选择框的高度。使用负值将翻转这个图像。
let img = new Image();
img.src = require('./imgs/clock-bg.jpg');
img.onload = function () {
// 绘制图片
pen.save();
pen.drawImage(img, 0, 50);
pen.restore();
}
clip()
将当前创建的路径设置为当前剪切路径的方法。
浏览器会将所有的绘图操作都限制在本区域内执行。在默认情况下,剪辑区域的大小与Canvas画布大小一致。
let img = new Image();
img.src = require('./imgs/clock-bg.jpg');
img.onload = function () {
pen.save();
pen.beginPath();
pen.arc(center.x, center.y, r, 0, 2 * Math.PI);
// pen.fill();
// 将上面的区域作为剪辑区域
pen.clip();
pen.drawImage(img, 0, 50);
pen.restore();
}
clearRect()
清除指定矩形区域,让清除部分完全透明。
pen.clearRect(x, y, width, height)
beginPath()
开始一个新的路径
pen.beginPath()
save()
保存画布状态
pen.save()
restore()
恢复save()保存的状态
pen.restore()
变换
rotate(angle)
:旋转- angle顺时针旋转的弧度。
弧度值 = 角度值 * Math.PI / 180
。 - 旋转中心点是(0,0),可以通过translate()转换来改变中心点
- angle顺时针旋转的弧度。
scale()
:放大缩小translate()
:平移
线型
lineWidth
:线的宽度。默认1lineCap
:线末端的类型。允许的值: butt (默认), round, square.lineJoin
:两线相交拐点的类型。允许的值:round, bevel, miter(默认)。
文本
fillText(text, x, y, [maxWidth])
:text:使用当前的 font, textAlign, textBaseline 和 direction 值对文本进行渲染。
x:文本起点的 x 轴坐标。
y:文本起点的 y 轴坐标。
maxWidth(可选):绘制的最大宽度。超出宽度会缩放字体。
pen.font = "48px serif"; pen.fillText("Hello world", 50, 100);
strokeText()
:在 (x,y) 位置绘制(描边)文本。
样式
font
:当前字体样式的属性。pen.font = "10px sans-serif"; // 默认值
textAlign
:文本对齐设置。允许的值: start (默认), end, left, right 或 center.direction
:文本的方向。允许的值: ltr, rtl, inherit (默认).fillStyle
:图形内部的颜色和样式。默认 #000 (黑色).strokeStyle
:图形边线的颜色和样式。默认 #000 (黑色).
阴影
shadowColor
:阴影的颜色。默认 fully-transparent black.shadowBlur
:模糊效果。默认 0shadowOffsetX
:阴影水平方向的偏移量。默认 0.shadowOffsetY
:阴影垂直方向的偏移量。默认 0.
pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
pen.shadowBlur = 20;
pen.shadowOffsetX = -12;
pen.shadowOffsetY = 20;
用例
绘制线段
pen.moveTo(100, 100);
pen.lineTo(200, 100);
pen.lineTo(200, 200);
pen.stroke();
绘制网格
// 绘制网格
const step = 100;
const h = 600;
const w = 300;
const w_l = w / step;
const h_l = h / step;
// 横着的线
for (let i = 0; i <= h_l; i++) {
pen.beginPath();
pen.moveTo(0, i * step);
pen.lineTo(w, i * step);
pen.stroke();
}
// 竖着的线
for (let i = 0; i <= w_l; i++) {
pen.beginPath();
pen.moveTo(i * step, 0);
pen.lineTo(i * step, h);
pen.stroke();
}
改变旋转中心
/**
* canvas旋转中心偏移值
* @param width canvas宽
* @param center 中心坐标点 center:{x:100,y:100}
* @param arc 旋转的弧度值
*/
function rotateOriginOffset(
width: number,
center: { x: number; y: number },
arc: any
) {
const r1 = width - center.x;
const xRes1 = Math.cos(arc) * r1;
const yRes1 = Math.sin(arc) * r1;
const x1 = center.x + xRes1;
const y1 = center.y + yRes1;
const x0 = width;
const y0 = center.y;
const c0 = Math.sqrt(Math.pow(x0, 2) + Math.pow(y0, 2));
const arc0 = Math.atan2(y0, x0);
const arc_0 = arc0 + arc;
const y2 = Math.sin(arc_0) * c0;
const x2 = y2 / Math.tan(arc_0);
const xLength = x1 - x2;
const yLength = y1 - y2;
return {
x: xLength,
y: yLength,
};
}
const offset = rotateOriginOffset(width, center, arc);
pen.translate(offset.x, offset.y); // 改变旋转中心
pen.rotate(arc);
绘制时钟
这个用例基本把上面的方法都用了一遍。
使用到的框架是QuasarCLI
vue3+quasar2.0+ts
Template
<template>
<div class="fit">
<div>canvas</div>
<div>
<q-btn label="显示网格" color="primary" @click="showGrid" />
</div>
<div class="relative-position">
<canvas
id="canvas"
width="600"
height="600"
style="border: 1px solid red; position: absolute; top: 0; left: 0"
></canvas>
<canvas
id="canvas-seconds"
width="600"
height="600"
style="
border: 1px solid transparent;
position: absolute;
top: 0;
left: 0;
"
></canvas>
<canvas
id="canvas-grid"
width="600"
height="600"
style="
border: 1px solid transparent;
position: absolute;
top: 0;
left: 0;
"
></canvas>
</div>
</div>
</template>
Script
<script setup lang="ts">
import { onMounted, ref, reactive, onBeforeUnmount } from 'vue';
const isShowGrid = ref(false);
const state = reactive({
center: {
x: 300,
y: 300,
}, // 圆心
r: 200, // 半径,
timer: null as any,
canvasSeconds: null as any,
});
onBeforeUnmount(() => {
if (state.timer) {
clearInterval(state.timer);
state.timer = null;
}
});
onMounted(() => {
state.canvasSeconds = document.getElementById('canvas-seconds');
let canvas: any = document.getElementById('canvas');
let pen = canvas.getContext('2d');
const { center, r } = state;
let img = new Image();
img.src = require('./imgs/clock-bg.jpg');
img.onload = function () {
// 背景色
pen.save();
pen.fillStyle = '#2d2d2d';
pen.fillRect(0, 0, 600, 600);
pen.restore();
// 绘制图片
pen.save();
pen.beginPath();
pen.arc(center.x, center.y, r, 0, 2 * Math.PI);
// pen.fill();
// 将上面的区域作为剪辑区域
pen.clip();
pen.drawImage(img, 241, 68, 300, 300, 100, 100, 2 * r, 2 * r);
// pen.drawImage(img, -90, 80);
pen.restore();
pen.save();
// 大刻度
for (let i = 0; i < 12; i++) {
pen.beginPath();
const a1 = Math.sin(toArc(i * 30)) * (r - 10); // (r - 10) 10是刻度与圆弧边线的间隙
const b1 = Math.cos(toArc(i * 30)) * (r - 10);
const x0 = center.x + a1;
const y0 = center.y - b1;
const a2 = Math.sin(toArc(i * 30)) * 8; // 8是大刻度线的长度
const b2 = Math.cos(toArc(i * 30)) * 8;
const x1 = x0 - a2;
const y1 = y0 + b2;
pen.moveTo(x0, y0);
pen.lineTo(x1, y1);
pen.lineWidth = 4;
pen.lineCap = 'round';
pen.strokeStyle = '#5CD8F9';
// pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
// pen.shadowBlur = 10;
// pen.shadowOffsetX = -8;
// pen.shadowOffsetY = 10;
pen.stroke();
}
pen.restore();
// 小刻度
pen.save();
for (let i = 0; i < 60; i++) {
pen.beginPath();
const a1 = Math.sin(toArc(i * 6)) * (r - 10); // (r - 10) 10是刻度与圆弧边线的间隙
const b1 = Math.cos(toArc(i * 6)) * (r - 10);
const x0 = center.x + a1;
const y0 = center.y - b1;
const a2 = Math.sin(toArc(i * 6)) * 4; // 4是大刻度线的长度
const b2 = Math.cos(toArc(i * 6)) * 4;
const x1 = x0 - a2;
const y1 = y0 + b2;
pen.moveTo(x0, y0);
pen.lineTo(x1, y1);
pen.lineWidth = 1;
pen.lineCap = 'round';
pen.strokeStyle = '#48E6FE';
pen.stroke();
}
pen.restore();
// 绘制文本
pen.save();
for (let i = 0; i < 12; i++) {
pen.beginPath();
const a1 = Math.sin(toArc(i * 30)) * (r - 40);
const b1 = Math.cos(toArc(i * 30)) * (r - 40);
const num = i || 12;
const x0 = center.x + a1;
const y0 = center.y - b1;
pen.font = '26px sans-serif';
pen.textAlign = 'center';
pen.textBaseline = 'middle';
pen.fillStyle = '#5CD8F9';
pen.fillText(`${num}`, x0, y0);
}
pen.restore();
// 外圆
pen.save();
pen.beginPath();
pen.arc(center.x, center.y, r, 0, 2 * Math.PI);
pen.lineWidth = 10;
pen.strokeStyle = '#0E4A6F';
// 阴影
pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
pen.shadowBlur = 20;
pen.shadowOffsetX = -12;
pen.shadowOffsetY = 20;
pen.stroke();
pen.restore();
// 圆心
pen.save();
pen.beginPath();
pen.arc(center.x, center.y, 16, 0, 2 * Math.PI);
pen.fillStyle = '#204969';
pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
pen.shadowBlur = 5;
pen.shadowOffsetX = -10;
pen.shadowOffsetY = 10;
pen.fill();
pen.restore();
state.timer = setInterval(() => {
drawSeconds();
}, 1000);
};
});
function drawSeconds() {
let pen = state.canvasSeconds.getContext('2d');
const { center, r } = state;
const date = getDate();
// console.log('---', date);
const secondsDeg = date.seconds * 6 + 270;
const minDeg = date.minutes * 6 + 270 + date.seconds * 0.1;
const hoursDeg =
date.hours * 30 + 270 + date.minutes * 0.5 + date.seconds * (1 / 120);
pen.clearRect(0, 0, 600, 600);
// 时间显示
pen.save();
pen.font = '26px sans-serif';
pen.textAlign = 'center';
pen.textBaseline = 'middle';
pen.fillStyle = '#5CD8F9';
const hStr = date.hours < 10 ? `0${date.hours}` : date.hours;
const mStr = date.minutes < 10 ? `0${date.minutes}` : date.minutes;
const sStr = date.seconds < 10 ? `0${date.seconds}` : date.seconds;
const timeStr = `${hStr}:${mStr}:${sStr}`;
pen.fillText(timeStr, center.x, center.y - (2 / 5) * r);
pen.restore();
// 时针
pen.save();
const offseHour = rotateOriginOffset(600, center, toArc(hoursDeg));
pen.translate(offseHour.x, offseHour.y); // 改变旋转中心
pen.rotate(toArc(hoursDeg));
pen.beginPath();
pen.moveTo(center.x - 20, center.y);
pen.lineTo(center.x + r - 110, center.y);
pen.lineWidth = 9;
pen.lineCap = 'round';
pen.strokeStyle = '#1E88D2';
pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
pen.shadowBlur = 5;
pen.shadowOffsetX = -10;
pen.shadowOffsetY = 10;
pen.stroke();
pen.restore();
pen.save();
pen.beginPath();
pen.arc(center.x, center.y, 12, 0, 2 * Math.PI);
pen.fillStyle = '#71C6F7';
pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
pen.shadowBlur = 2;
pen.shadowOffsetX = -2;
pen.shadowOffsetY = 2;
pen.fill();
pen.restore();
// 分针
pen.save();
const offsetMin = rotateOriginOffset(600, center, toArc(minDeg));
pen.translate(offsetMin.x, offsetMin.y); // 改变旋转中心
pen.rotate(toArc(minDeg));
pen.beginPath();
pen.moveTo(center.x - 20, center.y);
pen.lineTo(center.x + r - 80, center.y);
pen.lineWidth = 5;
pen.lineCap = 'round';
pen.strokeStyle = '#71C6F7';
pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
pen.shadowBlur = 5;
pen.shadowOffsetX = -10;
pen.shadowOffsetY = 10;
pen.stroke();
pen.restore();
// 秒针
pen.save();
const offset = rotateOriginOffset(600, center, toArc(secondsDeg));
pen.translate(offset.x, offset.y); // 改变旋转中心
pen.rotate(toArc(secondsDeg));
pen.beginPath();
pen.moveTo(center.x - 40, center.y);
pen.lineTo(center.x + r - 10, center.y);
pen.lineWidth = 2;
pen.lineCap = 'round';
pen.strokeStyle = '#71C6F7';
pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
pen.shadowBlur = 5;
pen.shadowOffsetX = -10;
pen.shadowOffsetY = 10;
pen.stroke();
pen.restore();
pen.save();
pen.beginPath();
pen.arc(center.x, center.y, 5, 0, 2 * Math.PI);
pen.fillStyle = '#204969';
pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
pen.shadowBlur = 2;
pen.shadowOffsetX = -2;
pen.shadowOffsetY = 2;
pen.fill();
pen.restore();
}
// 绘制网格
function drawGrid(color: string, w: number, h: number, pen: any) {
const step = 100;
const w_l = w / step;
const h_l = h / step;
pen.save();
// 横着的线
for (let i = 0; i <= h_l; i++) {
pen.beginPath();
pen.strokeStyle = color;
pen.moveTo(0, i * step);
pen.lineTo(w, i * step);
pen.stroke();
}
// 竖着的线
for (let i = 0; i <= w_l; i++) {
pen.beginPath();
pen.moveTo(i * step, 0);
pen.lineTo(i * step, h);
pen.stroke();
}
pen.restore();
}
function showGrid() {
let canvas: any = document.getElementById('canvas-grid');
let pen = canvas.getContext('2d');
isShowGrid.value = !isShowGrid.value;
if (isShowGrid.value) {
drawGrid('#FD7013', 600, 600, pen); // 网格
} else {
pen.clearRect(0, 0, 600, 600);
}
}
function toArc(degree: number) {
return (degree * Math.PI) / 180;
}
function rotateOriginOffset(
width: number,
center: { x: number; y: number },
arc: any
) {
const r1 = width - center.x;
const xRes1 = Math.cos(arc) * r1;
const yRes1 = Math.sin(arc) * r1;
const x1 = center.x + xRes1;
const y1 = center.y + yRes1;
const x0 = width;
const y0 = center.y;
const c0 = Math.sqrt(Math.pow(x0, 2) + Math.pow(y0, 2));
const arc0 = Math.atan2(y0, x0);
const arc_0 = arc0 + arc;
const y2 = Math.sin(arc_0) * c0;
const x2 = y2 / Math.tan(arc_0);
const xLength = x1 - x2;
const yLength = y1 - y2;
return {
x: xLength,
y: yLength,
};
}
function getDate() {
const myDate = new Date();
return {
hours: myDate.getHours(),
minutes: myDate.getMinutes(),
seconds: myDate.getSeconds(),
};
}
</script>