<canvas> 元素本身并没有绘制能力(它仅仅是图形的容器) - 必须使用脚本来完成实际的绘图任务。
getContext() 方法可返回一个对象,该对象提供了用于在画布上绘图的方法和属性
颜色、样式和阴影
属性 | 描述 |
---|---|
fillStyle | 设置或返回用于填充绘画的颜色、渐变或模式。 |
strokeStyle | 设置或返回用于笔触的颜色、渐变或模式。 |
shadowColor | 设置或返回用于阴影的颜色。 |
shadowBlur | 设置或返回用于阴影的模糊级别。 |
shadowOffsetX | 设置或返回阴影与形状的水平距离。 |
shadowOffsetY | 设置或返回阴影与形状的垂直距离。 |
渐变
方法 | 描述 |
---|---|
createLinearGradient() | 创建线性渐变(用在画布内容上)。 |
createPattern() | 在指定的方向上重复指定的元素。 |
createRadialGradient() | 创建放射状/环形的渐变(用在画布内容上)。 |
addColorStop() | 规定渐变对象中的颜色和停止位置。 |
线条样式
属性 | 描述 |
---|---|
lineCap | 设置或返回线条的结束端点样式。 |
lineJoin | 设置或返回两条线相交时,所创建的拐角类型。 |
lineWidth | 设置或返回当前的线条宽度。 |
miterLimit | 设置或返回最大斜接长度。 |
矩形
方法 | 描述 |
---|---|
rect() | 创建矩形。 |
fillRect() | 绘制"被填充"的矩形。 |
strokeRect() | 绘制矩形(无填充)。 |
clearRect() | 清空 / 在给定的矩形内清除指定的像素。 |
路径
方法 | 描述 |
---|---|
fill() | 填充当前绘图(路径)。 |
stroke() | 绘制已定义的路径。 |
beginPath() | 起始一条路径,或重置当前路径。 |
moveTo() | 把路径移动到画布中的指定点,不创建线条。绘制不连续的路径 |
closePath() | 创建从当前点回到起始点的路径。 |
lineTo() | 添加一个新点,然后在画布中创建从该点到最后指定点的线条。 |
clip() | 从原始画布剪切任意形状和尺寸的区域。 |
quadraticCurveTo() | 创建二次贝塞尔曲线。 |
bezierCurveTo() | 创建三次贝塞尔曲线。 |
arc() | 创建弧/曲线(用于创建圆形或部分圆)。 |
arcTo() | 创建两切线之间的弧/曲线。 |
isPointInPath() | 如果指定的点位于当前路径中,则返回 true,否则返回 false。 |
转换
方法 | 描述 |
---|---|
scale() | 缩放当前绘图至更大或更小。 |
rotate() | 旋转当前绘图。 |
translate() | 重新映射画布上的 (0,0) 位置。 |
transform() | 替换绘图的当前转换矩阵。 |
setTransform() | 将当前转换重置为单位矩阵。然后运行 transform()。 |
文本
属性 | 描述 |
---|---|
font | 设置或返回文本内容的当前字体属性。 |
textAlign | 设置或返回文本内容的当前对齐方式。 |
textBaseline | 设置或返回在绘制文本时使用的当前文本基线。 |
direction | 设置文本方向 |
measureText | 预测量文本宽度,例:let text = ctx.measureText('Canvas!'); console.log(text); |
方法 | 描述 |
---|---|
fillText() | 在画布上绘制"被填充的"文本。 |
strokeText() | 在画布上绘制文本(无填充)。 |
measureText() | 返回包含指定文本宽度的对象。 |
图像绘制
方法 | 描述 |
---|---|
drawImage() | 向画布上绘制图像、画布或视频。 |
像素操作
属性 | 描述 |
---|---|
width | 返回 ImageData 对象的宽度。 |
height | 返回 ImageData 对象的高度。 |
data | 返回一个对象,其包含指定的 ImageData 对象的图像数据。 |
方法 | 描述 |
---|---|
createImageData() | 创建新的、空白的 ImageData 对象。 |
getImageData() | 返回 ImageData 对象,该对象为画布上指定的矩形复制像素数据。 |
putImageData() | 把图像数据(从指定的 ImageData 对象)放回画布上。 |
合成
属性 | 描述 |
---|---|
globalAlpha | 设置或返回绘图的当前 alpha 或透明值。 |
globalCompositeOperation | 设置或返回新图像如何绘制到已有的图像上。 |
其他
方法 | 描述 |
---|---|
save() | 保存当前环境的状态。 |
restore() | 返回之前保存过的路径状态和属性。 |
createEvent() | 创建新的 Event 对象 |
getContext() | 获得用于在画布上绘图的对象 |
toDataURL() | 导出在 canvas 元素上绘制的图像 |
clearRect(x, y, 宽, 高) | 清空画布 |
globalCompositeOperation = 'destination-out' | 删除 / 橡皮擦 |
例:
1.创建 Canvas 元素:
<canvas id="myCanvas" width="500" height="300"></canvas>
2.获取 Canvas 上下文对象:
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
3.绘制填充矩形
方法一:
ctx.fillRect(50, 50, 100, 100); // (位置X 位置y 宽度 高度)
方法二:拆分写法
ctx.rect(50, 50, 100, 100)
ctx.fill()
4.1.填充颜色
ctx.fillStyle = 'red';
4.2路径颜色
ctx.strokeStyle = "red"
5.绘制路径矩形
方法一:
ctx.strokeRect(50, 50, 100, 100);// (位置X 位置y 宽度 高度)
方法二:拆分写法
ctx.rect(50, 50, 100, 100)
ctx.stroke()
6.清空画布:
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空整个画布
7.绘制文本:参数(文本,x,y,最大宽度)
ctx.font = '100px Microsoft YaHei'; // 设置字体样式
ctx.fillText('Hello, Canvas!', 100, 200, 100);//填充文本
ctx.strokeText('Hello, Canvas!', 100, 200, 100);//路径文本
// 文本对齐,textAlign--start(默认),end,left,right,center
ctx.textAlign = 'left';
//基线对齐,textBaseline--top,bottom,elphabetic,middle
ctx.textBaseline = 'middle'
例:设置文本和圆向左对齐
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.font = '100px Microsoft YaHei';
// 文本对齐选项textAlign,start(默认),end,left,right,center
ctx.textAlign = 'left';
ctx.strokeText('Canvas!', 200, 200);
ctx.arc(200, 200, 5, 0, Math.PI * 2);
ctx.fill();
8.绘制图像:
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
//获取图片
let img = new Image();
img.src = './img/1.webp';
//加载完毕的时候执行绘制
img.onload = function() {
//第一种绘制方式,参数一为图片,参数二为坐标x,参数三为坐标y
ctx.drawImage(img, 0, 0);
//第二种绘制方式,参数一为图片,参数二为坐标x,参数三为坐标y,参数四为绘制宽度,
//参数五为绘制高度
ctx.drawImage(img, 0, 0, 100, 100);
//第三种绘制方式,参数一为图片,参数二为裁剪的起始点x,参数三为裁剪的起始点y,
//参数四为裁剪的宽度,参数五为裁剪的高度,参数六为绘制宽度,参数七为绘制高度
ctx.drawImage(img, 100, 100, 100, 100, 10, 10, 100, 100);
}
8-1.绘制视频
<canvas id="c1" width="600" height="400"></canvas>
<video src="./img/mov.mp4" controls></video>
<button id="btn">播放/暂停</button>
<script>
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
var video = document.querySelector('video');
//获取视频播放/暂停按钮
var btn = document.getElementById('btn');
btn.onclick = function() {
video.play()//播放
render() //调用请求动画帧
}
function render() {
ctx.drawImage(video, 0, 0, 600, 400);//绘制视频
requestAnimationFrame(render);
}
</script>
8-1-1.给视频打码/添加水印
<canvas id="c1" width="600" height="400"></canvas>
<video src="./img/mov.mp4" controls></video>
<button id="btn">播放/暂停</button>
<script>
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
var video = document.querySelector('video');
var btn = document.getElementById('btn');
btn.onclick = function() {
video.play()
render()
}
var img = new Image();
img.src = './img/1.webp';
function render() {
ctx.drawImage(video, 0, 0, 600, 400);
ctx.drawImage(img, 500, 350, 100, 50);//绘制水印图像
requestAnimationFrame(render);
}
</script>
9.设置样式和属性:
ctx.strokeStyle
:设置描边 / 路径颜色ctx.fillStyle
:设置填充颜色ctx.lineWidth
:设置线条宽度ctx.globalAlpha
:设置全局透明度- ctx.fillStyle:"rgba(255,200,200,0.3)";透明度
10.渐变
10-1线性渐变
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
let linearGradient = ctx.createLinearGradient(100, 200, 400, 500);
linearGradient.addColorStop(0, 'red');
linearGradient.addColorStop(1, 'blue');
ctx.fillStyle = linearGradient;
ctx.fillRect(100, 200, 300, 300)
10-2径向 / 圆形 渐变
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
let radiaGradient = ctx.createRadialGradient(300, 200, 0, 300, 200, 100);
radiaGradient.addColorStop(0, 'red');
radiaGradient.addColorStop(1, 'blue');
ctx.fillStyle = radiaGradient
ctx.fillRect(0, 0, 600, 400)
案例:模拟3D球
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
let radiaGradient = ctx.createRadialGradient(250, 150, 10, 300, 200, 100);
radiaGradient.addColorStop(0, '#fff');
radiaGradient.addColorStop(1, 'red');
ctx.fillStyle = radiaGradient
ctx.arc(300, 200, 100, 0, Math.PI * 2);
ctx.fill()
10-3圆锥 渐变
createRadialGradient(角度,x,y)创建一个锥形渐变对象
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
let coneGradient = ctx.createConicGradient(0, 300, 200);
coneGradient.addColorStop(0, '#fff');
coneGradient.addColorStop(1, 'red');
ctx.fillStyle = coneGradient
ctx.fillRect(0, 0, 600, 400)
11.阴影
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
//设置阴影
ctx.shadowOffsetX = 8;
ctx.shadowOffsetY = 8;
ctx.shadowBlur = 5;
ctx.shadowColor = 'red'
ctx.strokeRect(50, 50, 200, 100);
案例:绘制一个矩形
一:填充矩形fillRect(x1, y1, 宽, 高)
<canvas id="myCanvas"></canvas>
var c1 = document.getElementById('myCanvas');
var ctx = c1.getContext('2d');
ctx.fillRect(10, 20, 200, 100);// (位置X 位置y 宽度 高度)
拆开写法:ctx.fillRect();=ctx.rect();ctx.fill()
ctx.rect(10, 20, 200, 100)
ctx.fill()
二:路径矩形strokeRect(x1, y1, 宽, 高)
<canvas id="c1"></canvas>
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.strokeRect(100, 100, 200, 100);
拆开写法:ctx.strokeRect();=ctx.rect();ctx.stroke()
ctx.rect(100, 100, 200, 100)
ctx.stroke()
三:动画 / 清除模式clearRect(x1, y1, 宽, 高)
<canvas id="c1"></canvas>
<script type="text/javascript">
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.strokeRect(100, 100, 200, 100);
let h = 0;
let t1 = setInterval(() => {
h++;
ctx.clearRect(0, 0, c1.clientWidth, h);
if (h > c1.clientHeight) {
clearInterval(t1);
}
}, 10)
</script>
拆开写法: 开始绘制beginPath(),结束绘制closePath(),可以完成路径的分段
beginPath() | 起始一条路径,或重置当前路径。 |
closePath() | 创建从当前点回到起始点的路径。 |
<script type="text/javascript">
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.beginPath();
ctx.rect(100, 100, 200, 100);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.rect(200, 150, 200, 100)
ctx.fill()
ctx.closePath();
let h = 0;
let t1 = setInterval(() => {
h++;
ctx.clearRect(0, 0, c1.clientWidth, h);
if (h > c1.clientHeight) {
clearInterval(t1);
}
}, 10)
</script>
案例:绘制一个圆
方法一:arc是绘制圆弧的方法,arc(圆心x,圆心y,半径,起始角度,结束角度,逆时针还是顺时针,默认是顺时针false,设置顺时针为true ) ,
Math.PI / 1就是半圆
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.arc(300, 200, 50, 0, Math.PI * 2,true)
ctx.fill()
方法二:arcTo()
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.beginPath();
ctx.moveTo(300, 200) //起点
ctx.arcTo(300, 250, 250, 250, 50) //四个点和半径
ctx.stroke()
ctx.closePath()
案例:绘制一个笑脸
方法一:
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
//绘制大圆圈
ctx.arc(75, 75, 50, 0, Math.PI * 2)
ctx.stroke()
//绘制嘴巴
ctx.beginPath();
ctx.arc(75, 75, 35, 0, Math.PI)
ctx.stroke()
ctx.closePath()
//绘制左边的眼睛
ctx.beginPath();
ctx.arc(60, 65, 5, 0, Math.PI * 2)
ctx.stroke()
ctx.closePath()
//绘制右边的眼睛
ctx.beginPath();
ctx.arc(90, 65, 5, 0, Math.PI * 2)
ctx.stroke()
ctx.closePath()
方法二:使用moveTo()绘制一条不连续的路径
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2); //大圆圈
ctx.moveTo(110, 75);
ctx.arc(75, 75, 35, 0, Math.PI) //嘴巴
ctx.moveTo(65, 65);
ctx.arc(60, 65, 5, 0, Math.PI * 2) //右边的眼睛
ctx.moveTo(95, 65);
ctx.arc(90, 65, 5, 0, Math.PI * 2) //右边的眼睛
ctx.stroke()
ctx.closePath()
案例:绘制直线和线段 lineTo(x,y)
绘制一个三角形
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.beginPath();
ctx.moveTo(300, 200)
ctx.lineTo(350, 250)
ctx.lineTo(350, 200)
ctx.lineTo(300, 200)
ctx.stroke()
ctx.closePath()
案例:位移-缩放-旋转
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.translate(100, 100) //位移
ctx.scale(0.5, 0.5) //缩放
ctx.rotate(Math.PI / 6) //旋转30度
ctx.fillRect(0, 0, 100, 100);
案例:变形Transforms
transforms(水平缩放,竖直缩放,水平倾斜偏移,竖直倾斜偏移,水平移动,竖直移动)
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.transform(1, 0, 0, 1, 100, 100)
ctx.fillRect(0, 0, 300, 50);
案例:合成globalCompositeOperation
source-over
: 默认属性,新绘制的图像位于原有内容之上。source-atop
: 新绘制的图像仅覆盖原有内容重叠的部分,并位于原有内容之上。source-in
: 新绘制的图像仅显示与原有内容重叠的部分,其他部分透明。source-out
: 新绘制的图像仅显示不与原有内容重叠的部分,与原有内容重叠的部分透明。destination-over
: 原有内容位于新绘制的图像之上。destination-atop
: 原有内容仅覆盖新绘制的图像重叠的部分,并位于新绘制的图像之上。destination-in
: 原有内容仅显示与新绘制的图像重叠的部分,其他部分透明。destination-out
: 原有内容仅显示不与新绘制的图像重叠的部分,与新绘制的图像重叠的部分透明。lighter
: 新绘制的图像与原有内容叠加,颜色变亮。copy
: 仅显示新绘制的图像,忽略原有内容。xor
: 使用异或操作组合新绘制的图像与原有内容。
例:显示两个矩形重叠的部分,其他部分透明
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(300, 200, 100, 100);
ctx.globalCompositeOperation = 'source-in';
ctx.fillStyle = 'blue';
ctx.fillRect(250, 150, 100, 100);
案例:刮刮卡
source-out
: 新绘制的图像仅显示不与原有内容重叠的部分,与原有内容重叠的部分透明
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
var img = new Image();
img.src = './img/1.webp'
img.onload = function() {
ctx.drawImage(img, 0, 0, 600, 400);
}
var isDrawing = false;
c1.onmousedown = function(e) {
isDrawing = true
}
c1.onmouseup = function() {
isDrawing = false
}
c1.onmousemove = function(e) {
if (isDrawing) {
var x = e.pageX;
var y = e.pageY;
ctx.globalCompositeOperation = 'destination-out';
ctx.arc(x, y, 20, 0, 2 * Math.PI)
ctx.fill()
}
}
案例:裁剪路径clip( )
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
var chatPath = new Path2D();
chatPath.moveTo(200, 300); //起点
chatPath.quadraticCurveTo(150, 300, 150, 200);
chatPath.quadraticCurveTo(150, 100, 300, 100);
chatPath.quadraticCurveTo(450, 100, 450, 200);
chatPath.quadraticCurveTo(450, 300, 250, 300);
chatPath.quadraticCurveTo(250, 350, 150, 350);
chatPath.quadraticCurveTo(200, 350, 200, 300);
ctx.stroke(chatPath)
ctx.clip(chatPath)
let img = new Image();
img.src = './img/12.webp'
img.onload = function() {
ctx.drawImage(img, 0, 0, 600, 400)
}
贝塞尔曲线:
一般用来绘制复杂有规律的图形,
案例:绘制聊天气泡框,
使用二次曲线:quadraticCurveTo(cp1x, cp1y, x, x); cp1x,cp1y为控制点,xy为结束点
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.beginPath();
ctx.moveTo(200, 300); //起点
ctx.quadraticCurveTo(150, 300, 150, 200);
ctx.quadraticCurveTo(150, 100, 300, 100);
ctx.quadraticCurveTo(450, 100, 450, 200);
ctx.quadraticCurveTo(450, 300, 250, 300);
ctx.quadraticCurveTo(250, 350, 150, 350);
ctx.quadraticCurveTo(200, 350, 200, 300);
ctx.stroke()
ctx.closePath()
案例:绘制一个心形
使用三次曲线:bezierCurveTo(cp1x, cp1y, cp2x,cp2y,x, x); cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,xy为结束点
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.beginPath();
ctx.moveTo(75, 40); //起点
ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
ctx.fillStyle = "#E992B9";
ctx.fill();
ctx.closePath();
图案样式pattern
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
//创建图案样式pattern
var img = new Image();
img.src = './img/1.webp';
img.onload = function() {
//创建图案对象createPattern(图片对象,重复方式repeat,no-repeat ,repeat x,repeat-y)
var pattern = ctx.createPattern(img, 'no-repeat');
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 600, 350);
}
path2D封装路径
创建一条折线:字符串写法
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
var polyline = new Path2D('M10 10 h 80 v 80 h -80 z')
ctx.stroke(polyline)
封装绘制聊天气泡框路线
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
var chatPath = new Path2D();
chatPath.moveTo(200, 300); //起点
chatPath.quadraticCurveTo(150, 300, 150, 200);
chatPath.quadraticCurveTo(150, 100, 300, 100);
chatPath.quadraticCurveTo(450, 100, 450, 200);
chatPath.quadraticCurveTo(450, 300, 250, 300);
chatPath.quadraticCurveTo(250, 350, 150, 350);
chatPath.quadraticCurveTo(200, 350, 200, 300);
ctx.stroke(chatPath)
requestAnimationFrame
函数
requestAnimationFrame
是一个由浏览器提供的用于优化动画效果的方法。它告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。
相比于使用 setTimeout
或 setInterval
来实现动画,requestAnimationFrame
具有以下优势:
- 浏览器会在适当的时间内执行动画,以确保最佳的性能和流畅度。
- 当页面处于非激活状态(例如在后台标签页)时,动画会自动暂停,节省资源。
- 能够自动适应设备的刷新率,避免不必要的绘制。
- 在支持硬件加速的浏览器中,动画效果会更加流畅。
语法:
function animate() {
// 动画更新逻辑
requestAnimationFrame(animate);
}
// 启动动画
requestAnimationFrame(animate);
案例:渐变矩形动画效果
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
let index = 0;
function render() {
ctx.clearRect(0, 0, 600, 400); //清除上一帧
index += 0.01;
if (index >= 1) {
index = 0;
}
let linearGradient = ctx.createLinearGradient(100, 200, 400, 500);
linearGradient.addColorStop(0, 'red');
linearGradient.addColorStop(index, 'blue');
ctx.fillStyle = linearGradient;
ctx.fillRect(100, 200, 300, 300)
//请求浏览器在下一帧绘制动画,实现动画效果。不断调用 render 函数,实现动画的连续播放
requestAnimationFrame(render);
}
requestAnimationFrame(render);
状态保存和恢复save() ,restore()
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
ctx.fillStyle = 'yellow'
ctx.fillRect(10, 10, 100, 100);
ctx.save() //保存黄色的状态
ctx.fillStyle = 'red'
ctx.fillRect(50, 50, 100, 100);
ctx.fillStyle = 'green'
ctx.fillRect(100, 100, 100, 100);
ctx.restore() //恢复/取出黄色的状态,恢复的为上一次保存的状态
ctx.fillRect(150, 150, 100, 100); //为黄色
像素操作:lmageData (), putImageData()
putImageData(imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
imageData
: 表示要绘制到画布上的 ImageData 对象。dx
和dy
: 表示将图像数据放置在画布上的位置坐标,即横坐标和纵坐标。dirtyX
和dirtyY
: 表示从 ImageData 对象中提取图像数据的起始位置坐标。dirtyWidth
和dirtyHeight
: 表示从 ImageData 对象中提取图像数据的宽度和高度
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
var img = new Image();
img.src = './img/12.webp';
img.onload = function() {
ctx.drawImage(img, 0, 0, 600, 400);
let imageData = ctx.getImageData(0, 0, 600, 400);
//循环修改数据
for (let i = 0; i < imageData.data.length; i += 4) {
//计算出当前像素的平均值---灰色
let avg = (imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]) / 3;
imageData.data[i] = avg
imageData.data[i + 1] = avg
imageData.data[i + 2] = avg
}
//将修改后的数据重新渲染到画布上
ctx.putImageData(imageData, 0, 0);
}
封装绘制的物体:
封装一个爱心图像,鼠标在里面的时候为红色,鼠标不在里面的时候为蓝色
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
class Heart {
constructor(x, y) {
this.x = x;
this.y = y;
this.color = "#333";
//监听鼠标移动
c1.onmousemove = (e) => {
var x = e.offsetX;
var y = e.offsetY;
var isIn = ctx.isPointInPath(this.heartPath, x, y); //判断鼠标在不在里面
if (isIn) {
this.color = "red"; //鼠标在里面
} else {
this.color = "blue"; //鼠标不在里面
}
}
}
setPosition(x, y) {
this.x = x;
this.y = y;
}
draw() {
this.heartPath = new Path2D()
this.heartPath.moveTo(this.x, this.y); //起点
this.heartPath.bezierCurveTo(this.x + 50, this.y - 50, this.x + 100, this.x, this.y, this.y + 50);
this.heartPath.bezierCurveTo(this.x - 100, this.y, this.x - 50, this.y - 50, this.x, this.y);
ctx.save()
ctx.fillStyle = this.color;
ctx.fill(this.heartPath);
ctx.restore()
}
}
let heart = new Heart(100, 100)
function render() {
ctx.clearRect(0, 0, c1.width, c1.height)
heart.draw()
requestAnimationFrame(render)
}
render()
制作画笔 / 在线画板:
<canvas id="c1" width="600" height="400"></canvas>
<button id="boldBtn">粗线条</button>
<button id="thinBtn">细线条</button>
<button id="save">保存</button>
<button id="clearBtn">橡皮擦</button>
<button id="nullBtn">清空画布</button>
<input id="color" type="color" value="" name=""></input>
<script>
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
//设置画笔连接处圆润
ctx.lineJoin = 'round';
//设置画笔开始和结束圆润
ctx.lineCap = 'round';
//设置允许绘制的变量
var isDraw = false;
//鼠标按下去开始绘制
c1.onmousedown = function() {
isDraw = true;
ctx.beginPath();
var x = event.pageX - c1.offsetLeft;
var y = event.pageY - c1.offsetTop;
ctx.moveTo(x, y);
};
//鼠标移动停止绘制
c1.onmouseleave = function() {
isDraw = false;
ctx.closePath()
};
//鼠标抬起来停止绘制
c1.onmouseup = function() {
isDraw = false;
ctx.closePath()
};
//开始绘制
c1.onmousemove = function() {
if (isDraw) {
var x = event.pageX - c1.offsetLeft;
var y = event.pageY - c1.offsetTop;
ctx.lineTo(x, y);
ctx.stroke();
}
};
//点击粗线条按钮,画笔变粗
var boldBtn = document.querySelector('#boldBtn');
boldBtn.onclick = function() {
//点击线条进行绘制的时候,从橡皮擦的模式回来
ctx.globalCompositeOperation = 'source-over';
ctx.lineWidth = 20
};
//点击细线条按钮,画笔变细
var thinBtn = document.querySelector('#thinBtn');
thinBtn.onclick = function() {
//点击线条进行绘制的时候,从橡皮擦的模式回来
ctx.globalCompositeOperation = 'source-over';
ctx.lineWidth = 0.1
};
//选择颜色
var inputColor = document.querySelector('#color');
color.onchange = function() {
ctx.strokeStyle = color.value
};
//点击保存
var saveBtn = document.querySelector('#save');
saveBtn.onclick = function() {
//先转换为一个图片地址
var urlData = c1.toDataURL();
var img = new Image();
img.src = urlData;
document.body.appendChild(img);
console.log(urlData, '图片地址');
};
//点击橡皮擦
var clearBtn = document.querySelector('#clearBtn');
clearBtn.onclick = function() {
ctx.globalCompositeOperation = 'destination-out';
ctx.lineWidth = 30;
};
//点击清空画布
var nullBtn = document.querySelector('#nullBtn');
nullBtn.onclick = function() {
ctx.clearRect(0, 0, 600, 400)
}
</script>
制作时钟:
<canvas id="c1" width="800" height="600"></canvas>
<script>
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
function render() {
ctx.clearRect(0, 0, 800, 600)
ctx.save(); //存档,保存当前坐标位置和上下文对象的状态
ctx.translate(400, 300);
ctx.rotate(-Math.PI / 2); //旋转
ctx.save();
for (let i = 0; i < 12; i++) {
//绘制小时的刻度
ctx.beginPath();
ctx.moveTo(170, 0);
ctx.lineTo(190, 0);
ctx.lineWidth = 8;
ctx.strokeStyle = 'gray';
ctx.stroke();
ctx.closePath();
ctx.rotate(2 * Math.PI / 12);
}
ctx.restore(); //恢复坐标
ctx.save();
for (let i = 0; i < 60; i++) {
//绘制小时的刻度
ctx.beginPath();
ctx.moveTo(180, 0);
ctx.lineTo(190, 0);
ctx.lineWidth = 2;
ctx.strokeStyle = 'gray';
ctx.stroke();
ctx.closePath();
ctx.rotate(2 * Math.PI / 60);
}
ctx.restore();
ctx.save();
//获取当前时间
var time = new Date();
var hour = time.getHours();
var min = time.getMinutes();
var sec = time.getSeconds();
hour = hour >= 12 ? hour - 12 : hour;
//绘制秒针
ctx.rotate(2 * Math.PI / 60 * sec);
ctx.beginPath();
ctx.moveTo(-30, 0);
ctx.lineTo(190, 0);
ctx.lineWidth = 2;
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.closePath();
ctx.restore();
ctx.save();
//绘制分针
ctx.rotate(2 * Math.PI / 60 * min + Math.PI / 60 / 60 * sec);
ctx.beginPath();
ctx.moveTo(-20, 0);
ctx.lineTo(130, 0);
ctx.lineWidth = 4;
ctx.strokeStyle = '#888';
ctx.stroke();
ctx.closePath();
ctx.restore();
ctx.save();
//绘制时钟
ctx.rotate(2 * Math.PI / 12 * hour + Math.PI / 12 / 60 * min + 2 * Math.PI / 12 / 60 / 60 * sec);
ctx.beginPath();
ctx.moveTo(-15, 0);
ctx.lineTo(110, 0);
ctx.lineWidth = 8;
ctx.strokeStyle = '#333';
ctx.stroke();
ctx.closePath();
ctx.restore();
ctx.restore();
requestAnimationFrame(render)
};
//请求动画帧
render()
</script>
案例:利用canvas设置视频封面,用video播放视频然后指定某一帧作为封面图
<video :src="currentUrl"
controls :poster="imageShow"
:autoplay="false" style="width: 100%; height: 100%;"
></video>
<canvas width="640" height="360" style="display: none;"></canvas>
captureFrame(url, file) {
const video = document.createElement('video')
video.src = url
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
video.onloadedmetadata = () => {
video.currentTime = 3
}
video.oncanplay = () => {
canvas.width = 1160
canvas.height = 570
ctx.drawImage(video, 0, 0, 1160, 570)
Vue.set(file, 'imageShow', canvas.toDataURL('image/png'))
}
},
this.captureFrame(jumpUrl, item)