目录
canvas
Canvas API 提供了一个通过JavaScript 和 HTML的元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。
Canvas API 主要聚焦于 2D 图形。而同样使用元素的 WebGL API 则用于绘制硬件加速的 2D 和 3D 图形。
初识canvas
<body>
<!--
id:标识元素的唯一性
width:画布的宽度
height:画布的高度
-->
<canvas id="c1" width="600" height="400"></canvas>
<script>
// 1. 找到画布
var c1 = document.getElementById("c1");
// 2. 获取画笔,上下文对象
var ctx = c1.getContext("2d");
// 3. 绘制图形
// 3.1 绘制矩形fillRect(位置x,位置y,宽度,高度)
ctx.fillRect(100, 200, 300, 300);
</script>
</body>
标签只有两个属性**——** width和height。
id属性并不是元素所特有的,而是每一个 HTML 元素都默认具有的属性(比如 class 属性)。
我们只是在标签中提供了替换内容。不支持的浏览器将会忽略容器并在其中渲染后备内容。而支持的浏览器将会忽略在容器中包含的内容,并且只是正常渲染 canvas。
元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容。我们将会将注意力放在 2D 渲染上下文中。
canvas 起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。 元素有一个叫做 getContext() 的方法,这个方法是用来获得渲染上下文和它的绘画功能。getContext()接受一个参数,即上下文的类型。
var canvas = document.getElementById('tutorial');
var ctx = canvas.getContext('2d');
代码的第一行通过使用 document.getElementById() 方法来为 元素得到 DOM 对象。一旦有了元素对象,你可以通过使用它的 getContext() 方法来访问绘画上下文。
上下文对象
var canvas = document.getElementById('tutorial');
var ctx = canvas.getContext('2d');
console.log(ctx);
canvas绘制基本图形
矩形绘制-填充模式
<body>
<!--
id:标识元素的唯一性
width:画布的宽度
height:画布的高度
-->
<canvas id="c1" width="600" height="400">
当前浏览器不支持canvas,请下载最新的浏览器
<a href="https://www.google.cn/intl/zh-CN/chrome/">立即下载</a>
</canvas>
<script>
// 1. 找到画布
var c1 = document.getElementById("c1");
// 判断是否有getContext
if (!c1.getContext) {
console.log("当前浏览器不支持canvas,请下载最新的浏览器");
}
// 2. 获取画笔,上下文对象
var ctx = c1.getContext("2d");
console.log(ctx);
// console.log(document.body);
// console.log(document.body.getContext);
// console.log(c1.getContext);
// 3. 绘制图形
// 3.1 绘制矩形fillRect(位置x,位置y,宽度,高度)
// ctx.fillRect(100, 200, 300, 300);
// 拆开写法
ctx.rect(100, 200, 300, 300);
ctx.fill();
</script>
</body>
fillRect(位置x,位置y,宽度,高度);
rect:矩形。
矩形绘制-路径模式
<body>
<!--
id:标识元素的唯一性
width:画布的宽度
height:画布的高度
-->
<canvas id="c1" width="600" height="400">
当前浏览器不支持canvas,请下载最新的浏览器
<a href="https://www.google.cn/intl/zh-CN/chrome/">立即下载</a>
</canvas>
<script>
// 1. 找到画布
var c1 = document.getElementById("c1");
// 判断是否有getContext
if (!c1.getContext) {
console.log("当前浏览器不支持canvas,请下载最新的浏览器");
}
// 2. 获取画笔,上下文对象
var ctx = c1.getContext("2d");
console.log(ctx);
// console.log(document.body);
// console.log(document.body.getContext);
// console.log(c1.getContext);
// 3. 绘制图形
// 3.1 绘制矩形fillRect(位置x,位置y,宽度,高度)
// ctx.strokeRect(100, 100, 20 0, 100);
// 拆开写法
ctx.rect(100, 100, 200, 100);
ctx.stroke();
</script>
</body>
strokeRect(100, 100, 20 0, 100);和fillRect()相似,但不填充内部颜色。
也可拆开写
rect(100, 100, 200, 100); 光写这个是没有图象的,必须加下面的stroke();填充模式同理。
stroke();
矩形绘制-清除模式
<body>
<canvas id="c1" width="600" height="400">
当前浏览器不支持canvas,请下载最新的浏览器
<a href="https://www.google.cn/intl/zh-CN/chrome/">立即下载</a>
</canvas>
<script>
// 1. 找到画布
var c1 = document.getElementById("c1");
// 判断是否有getContext
if (!c1.getContext) {
console.log("当前浏览器不支持canvas,请下载最新的浏览器");
}
// 2. 获取画笔,上下文对象
var ctx = c1.getContext("2d");
console.log(ctx);
// console.log(document.body);
// console.log(document.body.getContext);
// console.log(c1.getContext);
// 3. 绘制图形
// 3.2 路径绘制矩形strokeRect(x1, y1, 矩形宽度, 矩形高度)
// ctx.strokeRect(100, 100, 200, 100);
// ctx.fillRect(200, 150, 200, 100);
//beiginPath和closePath可以完成路径的分段
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 height = 0;
let t1 = setInterval(() => {
height++;
ctx.clearRect(0, 0, c1.clientWidth, height);
if (height > c1.clientHeight) {
clearInterval(t1);
}
}, 10);
</script>
</body>
clearRect(0, 0, c1.clientWidth, height); 清除矩形。
这里有个补充,当在一个画布中画多个图形,并且不需要连笔时,需要对画笔进行分段。
所用api是 ctx.beginPath();和ctx.closePath(); 这样就不会产生连笔的效果
绘制圆形
圆弧
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
// arc是绘制圆弧的方法
// ctx.arc(圆心x,圆心y,半径,开始的角度,结束的角度,逆时针还是顺时针,默认是顺时针false,设置逆时针的时候为true
ctx.arc(300, 200, 50, 0, Math.PI * 2);
ctx.fill();
</script>
</body>
笑脸
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//绘制一张脸
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
//绘制嘴巴
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();
</script>
</body>
这里问题在笔的分段。
要用beginPath()和closePath();
用moveTo()进行分段
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//绘制一张脸
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2);
ctx.stroke();
ctx.moveTo(110, 75);
//绘制嘴巴
ctx.arc(75, 75, 35, 0, Math.PI);
ctx.stroke();
ctx.moveTo(65, 65)
//绘制左眼
ctx.beginPath();
ctx.arc(60, 65, 5, 0, Math.PI * 2);
ctx.stroke();
ctx.moveTo(95, 65);
//绘制右眼
ctx.arc(90, 65, 5, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
</script>
</body>
用beginPaht()和closePath(),进行分段太繁琐,所以有了moveTo(位置x,位置y);
线段
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
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.fill();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(200, 100);
ctx.lineTo(250, 150)
ctx.lineTo(250, 100);
ctx.moveTo(200, 100);
ctx.fill();
ctx.closePath();
</script>
</body>
lineTo(X,Y) 画线用。
弧线
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
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();
</script>
</body>
arcTo(参考点1X,参考点1Y,参考点2X,参考点2Y,半径)
画圆弧要三个点,第一个点可以来处前面的操作。三个点形成两条直线,圆弧就切这两条线,
在小由半径决定。
贝塞尔二次曲线
画聊天气泡
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
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();
</script>
</body>
贝塞尔二次曲线。需要两个参考点,以及一个终点。和弧线同理,第一个可以来处前面的操作。
quardraticCruveTo(第二个参考点X,第二个参考点Y,终点X,终点Y);
贝塞尔三次曲线
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
ctx.beginPath();
ctx.moveTo(300, 200);
//两个控制点,一个终点
ctx.bezierCurveTo(350, 150, 400, 200, 300, 250);
ctx.bezierCurveTo(200, 200, 250, 150, 300, 200);
ctx.stroke();
ctx.closePath();
</script>
</body>
一共四个点,起点,两个参考点,一个终点,起点同前面一样。
bezierCurveTo(参考点1X,参考点1Y,参考点2X,参考点2Y,终点X,终点Y)
封装路径
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//heart
// #region
var newHeart = new Path2D();
newHeart.moveTo(300, 200);
//两个控制点,一个终点
newHeart.bezierCurveTo(350, 150, 400, 200, 300, 250);
newHeart.bezierCurveTo(200, 200, 250, 150, 300, 200);
ctx.stroke(newHeart);
ctx.fill(newHeart);
// #endregion
//chat
// #region
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);
// #endregion
//创建一条拆线
var polyLine = new Path2D("M10 10 h 80 v 80 h -80 z")
ctx.stroke(polyLine);
</script>
</body>
new path2D();
注意这里封装的是路径,不是画笔,最后要将路径参数放入画笔线段中。
stroke(path)
颜色设置
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//heart
// #region
var newHeart = new Path2D();
newHeart.moveTo(300, 200);
//两个控制点,一个终点
newHeart.bezierCurveTo(350, 150, 400, 200, 300, 250);
newHeart.bezierCurveTo(200, 200, 250, 150, 300, 200);
ctx.strokeStyle = "red";
ctx.stroke(newHeart);
// ctx.fill(newHeart);
// #endregion
//chat
// #region
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.strokeStyle = "#ff00ff";
ctx.stroke(chatPath);
ctx.fillStyle = "rgba(255,200,200,.3)"
ctx.fill(newHeart);
// #endregion
//创建一条拆线
var polyLine = new Path2D("M10 10 h 80 v 80 h -80 z")
ctx.strokeStyle = "rgba(0,0,255)";
ctx.stroke(polyLine);
ctx.fillStyle = "rgba(200,200,255,1)";
ctx.fillRect(280, 180, 40, 40);
</script>
</body>
ctx.strokeStyle = “red”;
ctx.strokeStyle = “#ff00ff”;
ctx.fillStyle = “rgba(255,200,200,.3)”;
颜色设置要放在画之前。注意线段和填充的区别。
线性渐变和径向渐变
线性渐变
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
let linearGradient = ctx.createLinearGradient(100, 200, 400, 500);
linearGradient.addColorStop(0, "red");
linearGradient.addColorStop(0.3, "#ffcccc");
linearGradient.addColorStop(1, "blue");
ctx.fillStyle = linearGradient;
ctx.fillRect(100, 200, 300, 300);
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, "#ffcccc");
linearGradient.addColorStop(1, "blue");
ctx.fillStyle = linearGradient;
ctx.fillRect(100, 200, 300, 300);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
</script>
</body>
createLinearGradient
①创建线性渐变对象ctx.createLinearGradient(渐变起点X, 渐变起点Y, 渐变终点X, 渐变终点Y);
②设置渐变过程addColorStop(0-1渐变比例, “颜色”); 可以多设
③把渐变对象赋值给fillStyle.
径向渐变
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
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(0.3, '#ffcccc');
radiaGradient.addColorStop(1, 'blue');
ctx.fillStyle = radiaGradient;
ctx.fillRect(0, 0, 600, 400);
</script>
</body>
createRadialGradient
它有两个点,从第一个点向第二个点辐射的过程叫径向渐变。就是两个圆之间的径向渐变。
①创建线性渐变对象ctx.createRadialGradient(中心点X, 中心点Y, 半径,中心点X, 中心点Y,半径);
②设置渐变过程addColorStop(0-1渐变比例, “颜色”); 可以多设
③把渐变对象赋值给fillStyle.
径向渐变模式3D小球
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
let radialGradient = ctx.createRadialGradient(250, 150, 10, 300, 200, 100);
radialGradient.addColorStop(0, "#ffcccc");
radialGradient.addColorStop(1, "red");
ctx.fillStyle = radialGradient;
ctx.arc(300, 200, 100, 0, Math.PI * 2);
ctx.fill();
</script>
</body>
圆锥渐变
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext('2d');
//圆锥渐变createConicGradient(角度,位置X,位置Y)
let conicGradient = ctx.createConicGradient(Math.PI / 4, 300, 200);
conicGradient.addColorStop(0, "red");
conicGradient.addColorStop(0.5, "yellow");
conicGradient.addColorStop(1, "blue");
ctx.fillStyle = conicGradient;
ctx.fillRect(0, 0, 600, 400);
</script>
</body>
createConicGradient
它有两个点,从第一个点向第二个点辐射的过程叫径向渐变。就是两个圆之间的径向渐变。
①创建线性渐变对象ctx.createConicGradient(角度,位置X,位置Y);
②设置渐变过程addColorStop(0-1渐变比例, “颜色”); 可以多设
③把渐变对象赋值给fillStyle.
pattern模式
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext('2d');
//创建图案样式pattern
var img = new Image();
img.src = "./imgs/money.png";
img.onload = function () {
//创建图案对象createPattern(图片对象(可以是image对象,也可以是canvas对象)重复方式repeat,no-repeat,repeat-x,repeat-y)
var pattern = ctx.createPattern(img, "repeat-y");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 600, 400);
}
</script>
</body>
①创建图案样式pattern,new Image();
②创建图案对象createPattern,createPattern(图片对象,重复方式)
③把图案对象赋值给fillStyle.
线条样式
线条样式
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
ctx.moveTo(290, 150);
ctx.lineTo(300, 200);
ctx.lineTo(310, 150);
//设置线条样式,默认1px
ctx.lineWidth = 40;
//设置线条端点样式,butt平齐,round半圆,正方形:square
ctx.lineCap = "round";
//设置2个线段连接处的样式,mitter处侧相连的角,,round角被磨圆了,bevel
ctx.lineJoin = "mitter";
//对斜接面进行限制
ctx.miterLimit = 5;
ctx.stroke();
</script>
</body>
lineWidth 设置线条样式,默认1px
lineCap 设置线条端点样式,butt平齐,round半圆,正方形:square
lineJoin 设置2个线段连接处的样式,mitter处侧相连的角,,round角被磨圆了,bevel
miterLimit 对斜接面进行限制
虚线设置
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
let index = 0;
function render() {
ctx.clearRect(0, 0, 600, 400);
index++;
if (index > 400) {
index = 0;
}
ctx.moveTo(150, 150);
ctx.lineTo(300, 200);
ctx.lineTo(450, 150);
//设置线条样式,默认1px
ctx.lineWidth = 1;
//设置线条端点样式,butt平齐,round半圆,正方形:square
ctx.lineCap = "square";
//设置2个线段连接处的样式,mitter处侧相连的角,,round角被磨圆了,bevel
ctx.lineJoin = "mitter";
//对斜接面进行限制
ctx.miterLimit = 5;
//设置虚线
ctx.setLineDash([40, 20]);
ctx.lineDashOffset = index;
ctx.stroke();
requestAnimationFrame(render);
}
render();
</script>
</body>
setLineDash([40, 20]);设置虚线 数组两个参数是虚线的实线和虚线的空白长度。
阴影设置
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//heart
// #region
var newHeart = new Path2D();
newHeart.moveTo(300, 200);
//两个控制点,一个终点
newHeart.bezierCurveTo(350, 150, 400, 200, 300, 250);
newHeart.bezierCurveTo(200, 200, 250, 150, 300, 200);
// #endregion
//chat
// #region
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.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 5;
ctx.shadowColor = "rgba(255,100,100,1)";
ctx.stroke(newHeart);
ctx.fill(newHeart);
ctx.stroke(chatPath);
// #endregion
//创建一条拆线
var polyLine = new Path2D("M10 10 h 80 v 80 h -80 z")
ctx.stroke(polyLine);
</script>
//设置阴影
ctx.shadowOffsetX = 10; 偏移
ctx.shadowOffsetY = 10;偏移
ctx.shadowBlur = 5; 模糊度
ctx.shadowColor = “rgba(255,100,100,1)”; 阴影颜色
绘制图片
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
let img = new Image();
img.src = "./imgs/girl.webp";
img.onload = function () {
//第一种绘制图片的方式,参数1为图片对象,参数2为将图片渲染到画布的水平位置,参数3将图片渲染到画布的垂直位置;
// ctx.drawImage(img, 0, 0);
//第二种绘制图片,参数1为图片对象,参数2为将图片渲染到画布的水平位置,参数3将图片渲染到画布的垂直位置,4将图片缩放到对应的宽度,5将图片缩放到对应的高度
// ctx.drawImage(img, 0, 0, 600, 400);
//第三种绘制图片的方式,Img参数后面的四个参数分别为源图片上要裁剪的起点位置和矩形宽高,后面四个参数分别为画布的位置和要渲染的矩形宽高。
ctx.drawImage(img, 640, 0, 1280, 720, 0, 0, 600, 400);
}
</script>
</body>
//第一种绘制图片的方式,参数1为图片对象,参数2为将图片渲染到画布的水平位置,参数3将图片渲染到画布的垂直位置;
// ctx.drawImage(img, 0, 0);
//第二种绘制图片,参数1为图片对象,参数2为将图片渲染到画布的水平位置,参数3将图片渲染到画布的垂直位置,4将图片缩放到对应的宽度,5将图片缩放到对应的高度
// ctx.drawImage(img, 0, 0, 600, 400);
//第三种绘制图片的方式,Img参数后面的四个参数分别为源图片上要裁剪的起点位置和矩形宽高,后面四个参数分别为画布的位置和要渲染的矩形宽高。
ctx.drawImage(img, 640, 0, 1280, 720, 0, 0, 600, 400);
绘制视频和叠加水印
<body>
<canvas id="c1" width="600" height="400"></canvas>
<video src="./imgs/mov_bbb.mp4" controls></video>
<button id="btn">播放/暂停</button>
<script>
var c1 = document.getElementById("c1");
if (!c1.getContext) {
console.log("plase relogin");
}
var ctx = c1.getContext("2d");
var video = document.querySelector("video");
let btn = document.querySelector("#btn");
btn.onclick = function () {
if (video.paused) {
video.play();
render();
} else {
video.pause();
}
};
let img = new Image();
img.src = "./imgs/logo.png";
function render() {
ctx.drawImage(video, 0, 0, 600, 400);
ctx.drawImage(img, 400, 350, 200, 50);
requestAnimationFrame(render);
}
</script>
</body>
文字绘制
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//文字大小/字体
ctx.font = "100px Microsoft YaHei";
ctx.strokeStyle = "#ff0000";
//填充渲染文字
// fillText(文本,文本的起点X,文本的起点Y,绘制文字最大的宽度)
// ctx.fillText("hello", 300, 200, 100);
//文本对齐选项textAlign,start(默认),end,left,right,center
ctx.textAlign = "center";
//文本基线对齐,textBaseline,top,bottom,alphabetic,middle
ctx.textBaseline = "middle";
//文本的方向
ctx.direction = "rtl";
//预测量文本宽度
let text = ctx.measureText("Hello!");
console.log(text);
ctx.strokeText("Hello!", 300, 200);
ctx.arc(300, 200, 5, 0, 2 * Math.PI);
ctx.fill();
</script>
</body>
//填充渲染文字
// fillText(文本,文本的起点X,文本的起点Y,绘制文字最大的宽度)
//文本对齐选项textAlign,start(默认),end,left,right,center
//文本基线对齐,textBaseline,top,bottom,alphabetic,middle
//文本的方向
ctx.direction = “rtl”;
//预测量文本宽度
let text = ctx.measureText(“Hello!”);
变换
translate
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
canvas {
border: 1px solid #ccc;
}
</style>
</head>
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//绘制图形
//位移,translate,位移坐标系,每次位移都是相对当前位置。
ctx.translate(100, 100);
ctx.fillRect(0, 0, 50, 50);
ctx.translate(100, 100);
ctx.fillRect(0, 0, 50, 50);
</script>
</body>
//位移,translate,位移坐标系,每次位移都是相对当前位置。
ctx.translate(100, 100);
scale
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
canvas {
border: 1px solid #ccc;
}
</style>
</head>
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//scale拉伸坐标系,横向,纵向。
ctx.scale(5, 2);
ctx.fillRect(0, 0, 50, 50);
</script>
</body>
//scale拉伸坐标系,横向,纵向。
ctx.scale(5, 2);
rotate
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
canvas {
border: 1px solid #ccc;
}
</style>
</head>
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//旋转rotate
ctx.rotate(Math.PI / 6);
ctx.fillRect(0, 0, 500, 50);
</script>
</body>
//旋转rotate
ctx.rotate(Math.PI / 6);
混合使用
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
canvas {
border: 1px solid #ccc;
}
</style>
</head>
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
ctx.translate(300, 200);
ctx.rotate(Math.PI / 4);
ctx.scale(2, 1);
ctx.fillRect(-250, -25, 500, 50);
</script>
</body>
矩阵transform
位移
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
ctx.transform(1, 0, 0, 1, 100, 100);
ctx.fillRect(0, 0, 50, 50);
</script>
</body>
ctx.transform(1, 0, 0, 1, 100, 100);
旋转
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//旋转rotate
ctx.transform(1, 1, -1, 1, 50, 0);
ctx.fillRect(0, 0, 500, 50);
</script>
</body>
ctx.transform(1, 1, -1, 1, 50, 0);
合成图像
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
ctx.fillStyle = "rgba(0,0,255,1)";
ctx.fillRect(300, 200, 100, 100);
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "rgba(255,0,0,1)";
ctx.fillRect(250, 150, 100, 100);
</script>
</body>
// ctx.globalCompositeOperation = “source-in”;
合成图像实现刮刮卡
<style>
* {
margin: 0;
padding: 0;
}
#ggk {
width: 600px;
height: 400px;
font-size: 30px;
font-weight: 900;
text-align: center;
line-height: 400px;
overflow: hidden;
position: absolute;
left: 0;
right: 0;
}
canvas {
position: absolute;
left: 0;
right: 0;
z-index: 10;
}
</style>
</head>
<body>
<div id="ggk">谢谢惠顾</div>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
let img = new Image();
img.src = "./imgs/m2.png";
img.onload = function () {
ctx.drawImage(img, 0, 0, 600, 400);
}
var isDarw = false;
c1.onmousedown = function () {
isDarw = true;
}
c1.onmouseup = function () {
isDarw = false;
}
c1.onmousemove = function (e) {
if (isDarw) {
let x = e.pageX;
let y = e.pageY;
ctx.globalCompositeOperation = "destination-out";
ctx.arc(x, y, 20, 0, Math.PI * 2);
ctx.fill();
}
}
let random = Math.random();
if (random < 0.1) {
var ggkDiv = document.querySelector("#ggk");
ggkDiv.innerHTML = "恭喜您获得IPHONE14PRO大奖!";
}
</script>
</body>
裁剪路径
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//heart
// #region
var newHeart = new Path2D();
newHeart.moveTo(300, 200);
//两个控制点,一个终点
newHeart.bezierCurveTo(350, 150, 400, 200, 300, 250);
newHeart.bezierCurveTo(200, 200, 250, 150, 300, 200);
ctx.stroke(newHeart);
ctx.fill(newHeart);
// #endregion
//chat
// #region
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.clip(chatPath);
ctx.stroke(chatPath);
// #endregion
let img = new Image();
img.src = "./imgs/girl.webp";
img.onload = function () {
//第二种绘制图片,参数1为图片对象,参数2为将图片渲染到画布的水平位置,参数3将图片渲染到画布的垂直位置,4将图片缩放到对应的宽度,5将图片缩放到对应的高度
// ctx.drawImage(img, 0, 0, 600, 400);
ctx.drawImage(img, 0, 0, 600, 400);
ctx.lineWidth = 20;
ctx.stroke(chatPath);
}
</script>
</body>
ctx.clip(chatPath);
状态的保存与恢复
<body>
<canvas id="c1" width="800" height="800"></canvas>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 100, 100);
ctx.save();
ctx.fillStyle = "blue";
ctx.fillRect(100, 100, 100, 100);
ctx.save();
ctx.fillStyle = "yellow";
ctx.fillRect(200, 200, 100, 100);
ctx.save();
ctx.fillStyle = "green";
ctx.fillRect(300, 300, 100, 100);
ctx.save();
ctx.restore();
ctx.fillRect(400, 400, 100, 100);
ctx.restore();
ctx.fillRect(500, 500, 100, 100);
ctx.restore();
ctx.fillRect(600, 600, 100, 100);
ctx.restore();
ctx.fillRect(700, 700, 100, 100);
ctx.restore();
ctx.fillRect(800, 800, 100, 100);
</script>
</body>
ctx.save();保存
ctx.restore();恢复
像素操作
这里会遇到跨域问题,解决方法为用vscod的一个插件
<body>
<canvas id="c1" width="600" height="400"></canvas>
<script>
var c1 = document.getElementById('c1');
var ctx = c1.getContext('2d');
let img = new Image();
img.src = "./imgs/girl.webp";
img.onload = function () {
//第一种绘制图片的方式,参数1为图片对象,参数2为将图片渲染到画布的水平位置,参数3将图片渲染到画布的垂直位置;
// ctx.drawImage(img, 0, 0);
//第二种绘制图片,参数1为图片对象,参数2为将图片渲染到画布的水平位置,参数3将图片渲染到画布的垂直位置,4将图片缩放到对应的宽度,5将图片缩放到对应的高度
// ctx.drawImage(img, 0, 0, 600, 400);
//第三种绘制图片的方式,Img参数后面的四个参数分别为源图片上要裁剪的起点位置和矩形宽高,后面四个参数分别为画布的位置和要渲染的矩形宽高。
ctx.drawImage(img, 0, 0, 600, 400);
//获取象素数据
let imageData = ctx.getImageData(0, 0, 600, 400);
console.log(imageData);
// 循环修改数据
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;
imageData.data[i + 3] = 255;
}
// for (let i = 0; i < imageData.data.length; i += 4) {
// imageData.data[i] = 255 - imageData.data[i];
// imageData.data[i + 1] = 255 - imageData.data[i + 1];
// imageData.data[i + 2] = 255 - imageData.data[i + 2];
// imageData.data[i + 3] = 255;
// }
//将修改后的数据重新渲染到画布上
ctx.putImageData(imageData, 0, 0);
// ctx.putImageData(imageData, 0, 0, 300, 200, 200, 200);
}
</script>
</body>
核心代码
//获取象素数据
let imageData = ctx.getImageData(0, 0, 600, 400);
//将修改后的数据重新渲染到画布上
ctx.putImageData(imageData, 0, 0);
封装绘制和物体
<body>
<canvas id="c1" width="600" height="400">
当前浏览器不支持canvas,请下载最新的浏览器
<a href="https://www.google.cn/intl/zh-CN/chrome/">立即下载</a>
</canvas>
<script>
// 1. 找到画布
var c1 = document.getElementById("c1");
// 判断是否有getContext
if (!c1.getContext) {
console.log("当前浏览器不支持canvas,请下载最新的浏览器");
}
// 2. 获取画笔,上下文对象
var ctx = c1.getContext("2d");
class Heart {
constructor(x, y) {
this.x = x;
this.y = y;
this.color = "red";
this.isIn = false;
this.eventMapList = {
hover: [],
leave: [],
};
c1.onmousemove = (e) => {
let x = e.offsetX;
let y = e.offsetY;
this.isIn = ctx.isPointInPath(this.heartPath, x, y);
if (this.isIn) {
this.eventMapList.hover.forEach((item) => {
item();
});
} else {
this.eventMapList.leave.forEach((item) => {
item();
});
}
};
}
onHover(fn) {
this.eventMapList.hover.push(fn);
}
onLeave(fn) {
this.eventMapList.leave.push(fn);
}
draw() {
this.heartPath = new Path2D();
// 起点
this.heartPath.moveTo(this.x, this.y);
// 2个控制点、1个终点
this.heartPath.bezierCurveTo(
this.x + 50,
this.y - 50,
this.x + 100,
this.y,
this.x,
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);
heart.onHover(() => {
heart.color = "blue";
// heart.draw();
});
heart.onLeave(() => {
heart.color = "red";
// heart.draw();
});
function render() {
ctx.clearRect(0, 0, c1.width, c1.height);
heart.draw();
requestAnimationFrame(render);
}
render();
</script>
</body>
签名画板
<body>
<canvas id="c1" width="800" height="600"></canvas>
<hr>
<button id="boldBtn" type="button">粗线条</button>
<button id="thinBtn" type="button">细线条</button>
<button id="saveBtn" type="button">保存签名</button>
<input type="color" name="" id="color" value="" />
<button class="clearBtn">橡皮擦</button>
<button id="nullBtn">清空画布</button>
<script>
var c1 = document.getElementById("c1");
var ctx = c1.getContext("2d");
//连接处圆润
ctx.lineJoin = "round";
//开端和结束也是圆的
ctx.lineCap = "round";
// 设置画笔的粗细
var boldBtn = document.querySelector('#boldBtn')
var thinBtn = document.querySelector('#thinBtn')
//画笔颜色
var inputColor = document.querySelector('#color')
// 保存签名
var saveBtn = document.querySelector('#saveBtn')
// 橡皮擦按钮
var clearBtn = document.querySelector('.clearBtn');
// 清空画布
var nullBtn = document.querySelector('#nullBtn');
//设置是否允许绘制
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();
}
}
boldBtn.onclick = function () {
ctx.globalCompositeOperation = "source-over";
ctx.lineWidth = 20;
boldBtn.classList.add('active');
thinBtn.classList.remove("active");
clearBtn.classList.remove("active");
}
thinBtn.onclick = function () {
ctx.globalCompositeOperation = "source-over";
ctx.lineWidth = 2;
thinBtn.classList.add("active");
boldBtn.classList.remove("active");
clearBtn.classList.remove("active");
}
clearBtn.onclick = function () {
ctx.globalCompositeOperation = "destination-out";
ctx.lineWidth = 30;
clearBtn.classList.add("active");
boldBtn.classList.remove("active");
thinBtn.classList.remove("active");
}
nullBtn.onclick = function () {
ctx.clearRect(0, 0, 800, 600);
}
saveBtn.onclick = function () {
var urlData = c1.toDataURL();
// var img = new Image();
// img.src = urlData;
// document.body.appendChild(img);
var downloadA = document.createElement("a");
downloadA.setAttribute("download", "我的签名");
downloadA.href = urlData;
downloadA.click();
}
inputColor.onclick = function () {
console.log(inputColor.value);
ctx.strokeStyle = inputColor.value;
}
</script>
</body>
时钟
<body>
<canvas id="c1" width="800" height="600"></canvas>
<script>
var c1 = document.querySelector("#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 + ((2 * 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 +
((2 * Math.PI) / 12 / 60) * min +
((2 * Math.PI) / 60 / 60) * sec);
ctx.beginPath();
ctx.moveTo(-50, 0);
ctx.lineTo(110, 0);
ctx.lineWidth = 8;
ctx.strokeStyle = "blue";
ctx.stroke();
ctx.closePath();
ctx.restore()
ctx.restore()
requestAnimationFrame(render);
}
render();
</script>
</body>