canvas
只能在标签中用width。heigth修改大小
基本方法:
先得到canvas:
let canvas = document.querySelector('#canvas') // 得到canvas
let gd = canvas.getContext('2d')//得到canvas上下文环境
gd.moveTo(a,b): 从一个点开始绘图
gd.lineTo(c,d):绘图终点
gd.stroke():绘制
gd.fill():填充
gd.strokeStyle = ‘#555’ : 绘图颜色
gd.fillStyle = ‘#666’: 填充颜色
canvas.lineWidth=100:修改线宽
gd,beginPath(): 清除原来的痕迹
ge.closePath(): 自动完成闭合
绘制完毕后就不能修改颜色等属性,所以绘制是最后一步
绘制图之前一定先 beginPath()
绘制矩形
gd.strokeRect(x, y, 矩形width,矩形height)
gd.fillRect(x, y, 矩形width,矩形height)
clearRect(x, y, 矩形width,矩形height) 清除指定的矩形区域,然后这块区域会变的完全透明。
绘制圆形(弧)
gd.arc(cX, cY, R, 起始弧度, 终点弧度,是否逆时针) //弧度:Math.PI*角度
gd.stroke()
绘制文字
gd.font = '100px 宋体‘’ // 设置字体大小 型号
gd.strokeText(‘abc’, x, y) // 实心
gd.fillText(‘abc’, x, y) // 空心
gd.stroke()
canvas绘图以左上角的(0, 0)为基准原点 但是文字以基线为基点
画图
window.onload = function() {
let canvas = document.querySelector('#canvas')
let gd = canvas.getContext('2d')
let obj = {}
canvas.onmousedown = function (event) {
obj.lastX = event.offsetX
obj.lastY = event.offsetY
}
canvas.onmousemove = function (event) {
gd.beginPath()
gd.moveTo(obj.lastX, obj.lastY)
gd.lineTo(event.offsetX, event.offsetY)
obj.lastX = event.offsetX
obj.lastY = event.offsetY
gd.stroke()
}
canvas.onmouseup = function () {
canvas.onmousemove = null
canvas.onmouseup = null
}
}
圆弧案例:
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(50, 50); // 创建起点
//参数1、2:控制点1坐标 参数3、4:控制点2坐标 参数5:圆弧半径
ctx.arcTo(200, 50, 200, 200, 100); // 画弧
ctx.lineTo(200, 200) // 创建终点
ctx.stroke();
ctx.beginPath();
ctx.rect(50, 50, 10, 10);
ctx.rect(200, 50, 10, 10)
ctx.rect(200, 200, 10, 10)
ctx.fill()
}
draw();
arcTo方法的说明:
这个方法可以这样理解。绘制的弧形是由两条切线所决定。
第 1 条切线:起始点和控制点1决定的直线。
第 2 条切线:控制点1 和控制点2决定的直线。
其实绘制的圆弧就是与这两条直线相切的圆弧。
饼图绘制
window.onload = function() {
function draw(cX, cY, r){
var canvas = document.getElementById('canvas');
if (!canvas.getContext('2d')) return;
var ctx = canvas.getContext("2d");
//数据
let data = [100,50,25,25]
//求和
let sum = data.reduce((temp, item, index) => {return temp + item})
// 角度占比
let angs = data.map(item => {return 360 * item / sum})
console.log(angs)
// 绘制
let last = 0
angs.forEach(ang => {
pie(cX, cY, r, last, last+ang)
last += ang
})
function pie (cX, cY, r, startAng, endAng) {
ctx.beginPath()
ctx.moveTo(cX, cY) // 圆心
let x = cX + Math.sin(startAng* Math.PI / 180) * r // 求起始点
let y = cY - Math.cos(startAng* Math.PI / 180) * r
ctx.lineTo(x, y)
ctx.arc(cX, cY, r, (startAng - 90) * Math.PI / 180, (endAng - 90) * Math.PI / 180, false)
ctx.stroke()
ctx.closePath()
}
}
draw(200, 300, 100);
}
绘制贝塞尔曲线
2次贝塞尔曲线
quadraticCurveTo(cp1x, cp1y, x, y):
说明:
参数1和2:控制点坐标
参数3和4:结束点坐标
3次贝塞尔曲线
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y):
说明:
参数1和2:控制点1的坐标
参数3和4:控制点2的坐标
参数5和6:结束点的坐标
线条样式
lineCap = type
线条末端样式。
共有3个值:
butt:线段末端以方形结束
round:线段末端以圆形结束
square:线段末端以方形结束,但是增加了一个宽度和线段相同,高度是线段厚度一半的矩形区域。
lineJoin = type
同一个path内,设定线条与线条间接合处的样式。
共有3个值round, bevel 和 miter:
round:通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。 圆角的半径是线段的宽度。
bevel:在相连部分的末端填充一个额外的以三角形为底的区域, 每个部分都有各自独立的矩形拐角。
miter(默认):通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域。
图片处理
ctx.drawImage(img, x, y,width ,height)
width 和 height,这两个参数用来控制 当像canvas画入时应该缩放的大小。
要保证当图片完成加载后再实现绘制
let img = new Image() // 创建img
img.src = 'xxx.jpg' // img路径
img.onload = function(){
ctx.drawImage(img, x, y)
}
切片(slice)
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
前4个是定义图像源的切片位置和大小,后4个则是定义切片的目标显示位置和大小。
阴影
let canvas = document.getElementById('canvas')
let context = canvas.getContext('2d')
//设置阴影
context.shadowColor="rgba(0,0,0,0.5)"//设置阴影颜色
context.shadowOffsetX=5;//设置形状或路径x轴方向的阴影偏移量,默认值为0;
context.shadowOffsetY=5;//设置形状或路径y轴方向的阴影偏移量,默认值为0;
context.shadowBlur=4;//设置模糊的像素数,默认值为0,即不模糊。
//绘制红色矩形
context.fillStyle="red";
context.fillRect(10,10,50,50);
//绘制半透明的蓝色矩形
context.fillStyle="rgba(0,0,255,1)";
context.fillRect(30,30,50,50);
渐变
渐变由CanvasGradient实例表示。要创建一个新的先行渐变,可以调用createLinearGradient()方法。这个方法接收4个参数:起点的x坐标,起点的y坐标,终点的x坐标,终点的y坐标。创建渐变对象后,下一步就是使用addColorStop()方法来指定色标,这个方法接收两个参数:色标位置和css颜色值。色标位置是一个0(开始的颜色)到1(结束的颜色)之间的数据。
gradient=context.createLinearGradient(30,30,70,70); 相当于画了一条渐变直线
gradient.addColorStop(0,“red”); 渐变开始的颜色
gradient.addColorStop(1,“black”); 渐变结束的颜色
context.fillStyle=gradient; 图形填充颜色为渐变色
let canvas = document.getElementById('canvas')
let context = canvas.getContext('2d')
// 设置渐变
var gradient=context.createLinearGradient(30,30,70,70);
gradient.addColorStop(0,"red");
gradient.addColorStop(1,"black");
//绘制红色矩形
context.fillStyle="red";
context.fillRect(10,10,50,50);
//绘制渐变矩形
context.fillStyle=gradient;
context.fillRect(30,30,50,50);
径向渐变(或放射渐变)
可以使用createRadialGradient()方法,这个方法接收6个参数,对应着两个圆的圆心和半径
var gradient=context.createRadialGradient(55,55,10,55,55,30);
//设置渐变
var gradient=context.createRadialGradient(55,55,10,55,55,30);
gradient.addColorStop(0,"white");
gradient.addColorStop(1,"black");
//绘制红色矩形
context.fillStyle="red";
context.fillRect(10,10,50,50);
//绘制渐变矩形
context.fillStyle=gradient;
context.fillRect(30,30,50,50);
模式
模式其实就是重复的图像,可以用来填充或描边图形
var image=document.images[0];
pattern=context.createPattern(image,"repeat");
context.fillStyle=pattern;
context.fillRect(10,10,300,300);
游戏小人的走动
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')
let img = new Image()
img.src = '1.jpg'
img.onload = function() {
let frame = 0 // 列 控制行走动作
let d = 'down' // 行 控制行走朝向
let x = 100
let y = 100
let speed = 10
document.onkeydown = function(event) {
// switch(event.keyCode) {
// case 87 || 38:
// d = 'up'
// break;
// case 83 || 40:
// d = 'down'
// break;
// case 65 || 37:
// d = 'left'
// break;
// case 68 || 39:
// d = 'right'
// break;
// }
let keys = {87:'up', 83:'down', 65:'left', 68:'right'}
let keys2 = {38:'up', 40:'down', 37:'left', 39:'right'}
d = keys[event.keyCode] || d || keys2[event.keyCode]
}
setInterval(() => {
let rows = {'down': 0, 'left': 1, 'right': 2, 'up': 3}
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 10 + 94*frame, 100 * rows[d], 70, 90, x, y, 70, 90)
if (x < 0) {
x = 0
frame = 2
}
console.log(canvas.width)
if (x > canvas.width-70) {
x = canvas.width-70
frame = 2
}
if (y < 0) {
y = canvas.height
} else if (y > canvas.height) {
y = 0
}
switch (d) {
case 'down':
y += speed
break;
case 'up':
y -= speed
break;
case 'left':
x -= speed
break;
case 'right':
x += speed
break
}
frame++
if (frame === 3) {
frame = 0
}
}, 100)
}
像素操作
每个像素占4位:r g b a
若一张图大小为800600 则 宽=800 => 800列
高=600 => 600行
r行c列的某个像素:r800+c
r行c列的某个像素占位:(r*800+c) *4
获得像素区
let imgData = ctx.getImageData(100, 100, img.width, img.height) // 获取像素区
处理像素(黑白:使r=g=b;
昏黄:b=0)
改变像素
ctx.putImageData(imgData, 100, 100)
window.onload = function () {
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')
let btn = document.querySelector('input')
let img = new Image()
img.src = '22.jpg'
img.onload = function() {
ctx.drawImage(img, 100, 100, 400, 250)
let imgData = ctx.getImageData(100, 100, img.width, img.height) // 获取像素区
btn.onclick = function() {
for(let r = 0; r < img.height; r++) { // 行
for(let c = 0; c< img.width; c++) { // 列
let avg = (imgData.data[(r*img.width + c) * 4] + imgData.data[(r*img.width + c) * 4 + 1] + imgData.data[(r*img.width + c) * 4 + 2]) / 3
imgData.data[(r*img.width + c) * 4] = imgData.data[(r*img.width + c) * 4 + 1] =imgData.data[(r*img.width + c) * 4 + 2] = avg
}
}
ctx.putImageData(imgData, 100, 100)
}
}
}
变形
在变形之前,先保存ctx.save(),变形之后再恢复,ctx.restore()
- 1.translate
- translate(x, y) 用来移动 canvas 的原点到指定的位置
function draw(){
var canvas = document.querySelector('canvas');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.save(); //保存坐原点平移之前的状态
ctx.translate(100, 100);
ctx.strokeRect(0, 0, 100, 100)
ctx.restore(); //恢复到最初状态
ctx.translate(220, 220);
ctx.fillRect(0, 0, 100, 100)
}
draw();
2.rotate
这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。
旋转的中心是坐标原点。
3.scale
scale(x, y) 用它来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。
x,y分别是横轴和纵轴的缩放因子,它们都必须是正值。值比 1.0 小表示缩 小,比 1.0 大则表示放大,值为 1.0 时什么效果都没有。
function draw(){
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext("2d");
let rotate = 0
ctx.strokeRect(100, 100, 200, 200) //先基于原原点画一个矩形
setInterval(() => {
ctx.clearRect(0,0,canvas.width,canvas.height)
rotate++
ctx.save(); //保存坐原点平移之前的状态
ctx.translate(200, 200); // 移动原点
ctx.rotate(Math.PI/180 * rotate) // 基于后来的原点再进行旋转
ctx.fillRect(-100,-100, 200, 200) // 基于后来的原点画一个矩形 实现新矩形围绕矩形中心旋转
ctx.restore()
}, 24)
}
draw();
合成
globalCompositeOperation = type
- source-over(default)
这是默认设置,新图像会覆盖在原有图像。 - source-in
仅仅会出现新图像与原来图像重叠的部分,其他区域都变成透明的。(包括其他的老图像区域也会透明) - source-out
仅仅显示新图像与老图像没有重叠的 新图像的那一部分,其余部分全部透明。(老图像也不显示) - source-atop
显示新图像与老图像重叠区域 + 老图像其余部分。新图像其他部分不显示。 - destination-over
新图像会在老图像的下面 - destination-in
仅仅新老图像重叠部分的老图像部分被显示,其他区域全部透明。 - destination-out
仅仅老图像与新图像没有重叠的部分。 注意显示的是老图像的部分区域。 - destination-atop
老图像仅仅仅仅显示重叠部分,新图像会显示在老图像的下面。
9. lighter
新老图像都显示,但是重叠区域的颜色做加处理 - darken
保留重叠部分最黑的像素。(每个颜色位进行比较,得到最小的)
blue: #0000ff
red: #ff0000
所以重叠部分的颜色:#000000
11. lighten 与lighter 相似
保证重叠部分最量的像素。(每个颜色位进行比较,得到最大的)
blue: #0000ff
red: #ff0000
所以重叠部分的颜色:#ff00ff
12. xor
重叠部分会变成透明
裁剪路径
clip()
把已经创建的路径转换成裁剪路径。
裁剪路径的作用是遮罩。只显示裁剪路径内的区域,裁剪路径外的区域会被隐藏。
注意:clip()只能遮罩在这个方法调用之后绘制的图像,如果是clip()方法调用之前绘制的图像,则无法实现遮罩。
clip()遮住方法之前绘制的图像,显示之后的绘制的区域
动画
动画的基本步骤
1 清空canvas
再绘制每一帧动画之前,需要清空所有。清空所有最简单的做法就是clearRect()方法
2 保存canvas状态
如果在绘制的过程中会更改canvas的状态(颜色、移动了坐标原点等),又在绘制每一帧时都是原始状态的话,则最好保存下canvas的状态
3 绘制动画图形
这一步才是真正的绘制动画帧
4恢复canvas状态
如果你前面保存了canvas状态,则应该在绘制完成一帧之后恢复canvas状态
时钟绘制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
body {
padding: 0;
margin: 0;
background-color: rgba(0, 0, 0, 0.1)
}
canvas {
display: block;
margin: 200px auto;
}
</style>
<script>
window.onload = function() {
let canvas = document.getElementsByTagName('canvas')[0]
let ctx = canvas.getContext('2d')
draw(ctx)
function draw(ctx) {
requestAnimationFrame(function step() {
drawDeil(ctx) // 表盘
drawAllHand(ctx) // 指针
requestAnimationFrame(step) // 回调
})
}
function drawAllHand(ctx) {
//得到时分秒
let time = new Date()
let s = time.getSeconds()
let m = time.getMinutes()
let h = time.getHours()
// 得到指针的弧度
let pi = Math.PI
let secondAngle = pi / 180 * s * 360 / 60
let minuteAngle = pi / 180 * m * 360 / 60 + secondAngle / 60
let hourAngle = pi / 180 *h *360 / 12 + minuteAngle / 12
//drawHand(指针弧度, 指针长度, 指针宽度, 颜色, ctx); //绘制指针
drawHand(hourAngle, 60, 6, "red", ctx); //绘制时针
drawHand(minuteAngle, 106, 4, "green", ctx); //绘制分针
drawHand(secondAngle, 129, 2, "blue", ctx); //绘制秒针
}
function drawHand(angle, len, width, color, ctx) {
ctx.save()
ctx.translate(150, 150) // 原点转移到canvas中心
// 弧度与角度的0相差九十度,角度顺时针转90(-90)与弧度重合
ctx.rotate(-90 * Math.PI / 180 + angle); //旋转坐标轴。 x轴就是针
ctx.beginPath() // 绘制之前先清除路径
ctx.moveTo(-4, 0) // 以新原点为基点
ctx.lineTo(len, 0) // x周就是指针,所以与x重合即可
ctx.strokeStyle = color
ctx.lineWidth = width
ctx.stroke()
ctx.closePath()
ctx.restore()
}
// 绘制表盘
function drawDeil(ctx) {
let pi = Math.PI
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.save()
ctx.translate(150, 150) // 原点转移到canvas中心
ctx.beginPath()
ctx.arc(0, 0, 148, 0, 2 * pi) // 绘制圆周
ctx.stroke()
ctx.closePath()
// 绘制刻度
for(let i = 0; i < 60; i++) {
ctx.save()
ctx.rotate(-90 * pi / 180 + i * 360 / 60 * pi / 180)
ctx.beginPath()
ctx.moveTo(110, 0)
ctx.lineTo(140, 0)
ctx.lineWidth = i % 5 === 0 ? 4 : 2
ctx.strokeStyle = i % 5 === 0 ? 'red' : 'blue'
ctx.stroke()
ctx.closePath()
ctx.restore()
}
ctx.restore()
}
}
</script>
</head>
<body>
<canvas width="300" height="300">请更新浏览器</canvas>
</body>
</html>