本篇文章次要波及的内容有
canvas根本理解
什么是canvas?
canvas是HTML5新增的元素,可用于通过javascript中的脚本来绘制图形;例如它能够用于绘制图形,创立动画。canvas最早由Apple引入webkit;
咱们能够应用canvas标签来定义一个canvas元素,应用canvas标签时,倡议成对呈现,不要应用闭合的模式。canvas默认具备宽高,width: 300px;height: 150px;
canvas中的替换内容
canvas标签很容易定义一些替换内容,因为某些较老的浏览器(尤其是IE9之前的IE浏览器),不反对HTML元素canvas,然而在这些浏览器上你应该给用户展现一些替换内容。这非常简单,咱们只须要在canvas标签中提供替换文本就能够了,反对canvas的浏览器将会疏忽容器中蕴含的内容,并且只是失常渲染canvas,不反对canvas的浏览器会显示替换内容。
canvas标签中的两个属性
canvas看起来和img标签看起来很像,惟一不同的是它并没有src和alt属性。实际上canvas标签只有两个属性,width和height;这些都是可选的,当没有设置高宽时,canvas会初始化宽度为300px和高度150px。
画布高宽
html属性设置width height 时只影响画布自身不影响画布内容css属性设置width height时岂但会影响画布自身的高宽,还会使画布中的内容等比例缩放(缩放参照于画布默认的尺寸)
!!!所以不要通过css设置画布的尺寸
渲染上下文
canvas元素只是创立了一个固定大小的画布,要想在它下面绘制内容,咱们须要找到它的渲染上下文;
canvas元素中有一个getContext()办法,这个办法是用来取得渲染上下文和它的绘画性能。
getContext只有一个参数,上下文格局
上下文格局类型:
"2d", 建设一个 CanvasRenderingContext2D 二维渲染上下文。
"webgl" (或"experimental-webgl") 这将创立一个 WebGLRenderingContext 三维渲染上下文对象。只在实现WebGL 版本1(OpenGL ES 2.0)的浏览器上可用。
"webgl2" (或 "experimental-webgl2") 这将创立一个 WebGL2RenderingContext 三维渲染上下文对象。只在实现 WebGL 版本2 (OpenGL ES 3.0)的浏览器上可用。
"bitmaprenderer" 这将创立一个只提供将canvas内容替换为指定ImageBitmap性能的ImageBitmapRenderingContext 。
const canvas = document.querySelector('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
}
绘制矩形
HTML中的元素canvas只反对一种原生(调用一个api)的图形绘制:矩形;所有其余的图形的绘制都至多须要生成一条门路。
绘制矩形
canvas提供三种办法绘制矩形:
1. 绘制一个填充矩形
ctx.fillRect(x, y, width, height);
2. 绘制一个矩形的边框
ctx.strokeRect(x, y, width, height);
3. 革除指定矩形的区域,让革除局部齐全通明
ctx.clearRect(x, y, width, height);
x,y指定了在canvas画布上所绘制的左上角(绝对于原点)的坐标。
width和height设置绘制矩形的尺寸(存在边框的话,边框会在width上占据一个边框的宽度,高度同理)
strokeRect时,边框像素渲染问题
按理来说渲染进去的边框应该是1px,然而canvas在渲染矩形边框时,边框的宽度是平均分在偏移地位的两侧。
ctx.strokeRect(10, 10, 50, 50);
阐明:
canvas渲染边框在10.5到9.5之间
然而浏览器是不会让一个像素只用本人的一半,所有这个边框渲染时在9至11之间。
解决办法:
1. ctx.strokeRect(10.5, 10.5, 50, 50);
2. 绘制一个通明的矩形进行笼罩
ctx.strokeRect(10, 10, 50, 50);
ctx.clearRect(10, 10, 50, 50);
增加款式和色彩
fillStyle 设置图形的填充色彩,默认#000000
strokeStyle 设置图形轮廓的色彩,默认#000000
lineWidth 设置以后绘线的粗细,属性值必须为正值。默认1.0
lineJoin
设定线条与线条间接合处的款式(默认是miter),与填充没有关系
都有哪些值,含意是什么
1. round: 圆角
2. bevel: 斜角
3. miter: 直角
绘制门路
图形的根本元素是门路。门路是通过不同色彩和宽高的线段或曲线相连造成的不同形态的点的汇合。
大体绘制的步骤
1. 首先,你须要创立门路起始点
2. 而后应用画图命令去画门路
3. 而后你把门路进行关闭
4. 一旦门路生成,你就能通过描边或者填充门路区域来渲染图形
绘制门路之绘制三角形
画笔上的api
1. beginPath()
新建一条门路,生成之后,图形绘制命令指向到门路上筹备生成门路。
生成门路的第一步叫做beginPath()。
实质上,门路是由多个子门路形成,这些子门路都在一个列表中,
所有的子门路形成图形,而每次这个办法调用之后,列表清空重置。
2. moveTo(x, y)
将笔触挪动到指定坐标 x y上
3. lineTo(x, y)
将笔触挪动到指定坐标 x y上
4. closePath()
闭合门路之后图形绘制命令又从新指向到上下文
5. stroke()
须要手动合并门路,要么closePath(),要么lineTo到起始点
6. fill()
会主动合并门路
画一个描边三角形
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(100, 200);
ctx.lineTo(200, 200);
ctx.lineTo(100, 100);// 或者 closePath()
ctx.stroke(); // 描边
ctx.fill(); // 填充三角形
如何了解beginPath呢?看一个实例,以及效果图
我只是像绘制一个描边三角形和另外一个填充的三角形,然而见效果图,把后面的本只是描边的三角形也从新填充了一遍;
ctx.strokeStyle = 'pink';
ctx.lineWidth = 10;
// ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(100, 200);
ctx.lineTo(200, 200);
ctx.closePath();
ctx.stroke();
ctx.moveTo(200, 200);
ctx.lineTo(200, 300);
ctx.lineTo(300, 300);
ctx.fill();
绘制门路之绘制矩形
rect(x, y, width, height)
ctx.rect(100, 100, 100, 100);
ctx.fill();// 绘制一个填充矩形
// ctx.stroke(); // 绘制一个描边矩形
lineCap
lineCap 是canvas 2D API 指定如何绘制没一条线段末端的属性。
存在的值
1. butt(默认值) 线段末端以方形完结
2. round 线段末端以圆形完结
3. square 线段末端以方形完结,然而减少了一个宽度和线段雷同,高度是线段厚度一半的矩形区域
save
save() 是Canvas 2D API 通过将以后状态入栈中,保留canvas全副状态的办法
保留到栈中的绘制状态有上面局部组成:
以后的变换矩阵。
以后的剪切区域
以后的虚线列表
以下属性以后的值:
strokeStyle
fillStyle
lineWidth
lineCap
lineJoin
restore
save & restore & beginPath套路步骤(多个图形之间齐全独立时)
ctx.save();
款式相干的
ctx.beginPath(); // 每次绘制门路之前都先beginPath一下
门路
ctx.restore();
而后开始绘制图形
看一个简略的小demo联合图解了解save&restore
ctx.save();
ctx.fillStyle = 'pink';
ctx.save();
ctx.fillStyle = 'deeppink';
ctx.fillStyle = 'blue';
ctx.save();
ctx.fillStyle = 'red';
ctx.save();
ctx.fillStyle = 'green';
ctx.save();
ctx.restore(); // 绿色
ctx.restore(); // 红色
ctx.restore(); // blue
ctx.restore(); // deeppink
ctx.restore(); // pink
ctx.fillRect(50, 50, 100, 100); // pink
理解了以上根本的用法,这时候能够插入一个小demo——签名
const canvas = document.querySelector('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
canvas.addEventListener('mousedown', function(e) {
e = e || window.event;
ctx.moveTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
document.onmousemove = function(e) {
e = e || window.event;
ctx.lineTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
ctx.stroke();
}
document.onmouseup = function(e) {
e = e || window.event;
document.onmousedown = document.onmousemove = null;
if (document.releaseCapture) {
document.releaseCapture();
}
}
});
绘制曲线
绘制圆形
arc(x, y, radius, startAngle, endAngle, anticlockwise)
该api形容的是以(x, y)为圆心radius为半径的圆弧,从star开始到endAngle完结,
依照anticlockwise给定的方向(默认是顺时针)生成一个圆弧。
anticlockwise: false(默认顺时针)
x,y 绘制圆弧所在圆上的圆心坐标
radius 半径
startAngle, endAngle: 值是弧度,这些值都是以x轴为基准参数
arcTo绘制圆弧
arcTo(x1, y1, x2, y2, radius)
依据给定的控制点和半径画一段圆弧(至多须要三个控制点)
ctx.arcTo(50, 50, 100, 50, 50);
ctx.arcTo(100, 50, 100, 100, 50);
ctx.stroke();
二次贝塞尔
quadraticCurveTo(cp1x, cp1y, x, y)
绘制二次贝塞尔尔曲线,cp1x,cp1y为一个控制点,x,y为完结点。
// 连贯三个控制点,不便看出曲线是如何绘制的
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(100, 50);
ctx.lineTo(100, 100);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.quadraticCurveTo(100, 50, 100, 100); // 没有指定半径,绘制的圆弧必须过这个控制点
ctx.stroke();
三次贝塞尔
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x, cp2y为控制点二,x,y为完结点
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(100, 50);
ctx.lineTo(0, 300);
ctx.lineTo(200, 200);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.bezierCurveTo(100, 50, 0, 300, 200, 200); // 没有指定半径,绘制的圆弧必须过第一和第三个控制点
ctx.stroke();
canvas中变换
translate(x, y)
这个办法是用来挪动canvas的原点到一个指定的地位。translate办法承受两个参数。x是左右偏移量,y是高低偏移量,并且在canvas中translate是累加的
ctx.translate(50, 50);
ctx.strokeRect(0, 0, 50, 50);
ctx.translate(50, 50);
ctx.strokeRect(0, 0, 50, 50);
从效果图不难看出这个translate办法是扭转了画布的原点并且是有累加成果的,如果不想要这个累加这个成果,这时候就须要应用后面提及的save办法和restore办法了,试试看
ctx.save();
ctx.translate(50, 50);
ctx.strokeRect(0, 0, 50, 50);
ctx.restore();
ctx.save();
ctx.strokeStyle = 'red';
ctx.translate(70, 70);
ctx.strokeRect(0, 0, 50, 50);
rotate(angle)
这个办法只承受一个参数,旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。旋转的中心点始终是canvas的原地,如果要扭转原点,咱们能够应用translate办法,并且在canvas中roate也是累加的。
ctx.rotate(Math.PI * 45 / 180);
ctx.fillRect(50, 50, 50, 50);
ctx.rotate(Math.PI * 45 / 180);
ctx.fillRect(50, 50, 50, 50);
ctx.rotate(Math.PI * (360 - 45) / 180); // 如果是叠加的让它转回默认的地位
ctx.fillRect(50, 50, 50, 50); // 这个绘制进去的是效果图中残缺矩形
scale(x, y)
scale办法承受两个参数,x,y,别离是横轴和纵轴的缩放因子,它们必须是正值。值比1.0小示意放大,比1.0大则示意放大,值为1.0什么成果都没有。缩放个别咱们用它增减图形在canvas中的像素数目,对形态,位图进行放大或者放大。在canvas中scale是累加的
css像素是一个形象单位,它所占据理论尺寸会随着缩放发生变化
放大时,区域物理尺寸没有变,区域内(画布)css像素个数变少,每一个css像素的面积变大
放大时,区域物理尺寸没有变,区域内(画布)css像素个数变多,每一个css像素的面积变小
变换和旋转以及缩放相结合
ctx.scale(2, 2);
ctx.translate(10, 10);
ctx.rotate(Math.PI * 10 / 180);
ctx.fillRect(50, 50, 50, 50); // 也就是说参数指定的像素点的个数
来一个变换相干的实例找找感觉
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
canvas {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
background: grey;
}
</style>
</head>
<body>
<canvas width="300" height="300"></canvas>
</body>
<script>
const canvas = document.querySelector('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
let flag = 0;
let scaleValue = 0;
let scaleFlag = 1;
setInterval(function() {
flag += 2;
if (scaleValue >= 100) {
scaleFlag = -2;
} else if (scaleValue === 0) {
scaleFlag = 2;
}
scaleValue += scaleFlag; // scaleValue值的区间[0, 2]
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
ctx.save();
ctx.translate(150, 150);
ctx.rotate(Math.PI*flag/180);
ctx.scale(scaleValue/50, scaleValue/50);
ctx.fillRect(-50, -50, 100, 100);
ctx.restore();
}, 1000/60);
}
</script>
</html>
理解了canvas中的变动,这时候能够来写一个简易版的时钟啦
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
canvas {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
background: grey;
}
</style>
</head>
<body>
<canvas width="400" height="400">
您的浏览器不反对canvas,请应用萌萌哒谷歌:https://www.google.cn/chrome/
</canvas>
</body>
<script>
window.onload = function() {
const canvas = document.querySelector('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
setInterval(function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
move();
}, 1000);
move();
function move() {
ctx.save();
ctx.lineWidth = 8;
ctx.lineCap = 'round';
ctx.translate(200, 200);
ctx.rotate(-90*Math.PI/180);
ctx.scale(.5, .5);
ctx.beginPath();
// 外层空心圆盘
ctx.save();
ctx.strokeStyle = '#325FA2';
ctx.lineWidth = 14;
ctx.beginPath();
ctx.arc(0,0, 140, 0, Math.PI*2);
ctx.stroke();
ctx.restore();
// 时针刻度
ctx.save();
for (let i = 0; i < 12; i++) {
ctx.rotate(30*Math.PI/180); // canvas中变换是累加的
ctx.beginPath();
ctx.moveTo(100, 0);
ctx.lineTo(120, 0);
ctx.stroke();
}
ctx.restore();
// 分针刻度
ctx.save();
ctx.lineWidth = 4;
for (let i = 0; i < 60; i++) {
// 解决时钟的地位不反复画分针刻度
if (i % 5 !== 0) {
ctx.beginPath();
ctx.moveTo(117, 0);
ctx.lineTo(120, 0);
}
ctx.rotate(6*Math.PI/180); // canvas中变换是累加的
ctx.stroke();
}
ctx.restore();
// 时针 分针 秒针 表座
const date = new Date();
const s = date.getSeconds();
const m = date.getMinutes() + s / 60;
let h = date.getHours() + m / 60;
h > 12 ? h - 12 : h;
console.log(s, m, h);
// 时针
ctx.save();
ctx.lineWidth = '14';
ctx.rotate(h*30*Math.PI/180);
ctx.beginPath();
ctx.moveTo(-20, 0);
ctx.lineTo(80, 0);
ctx.stroke();
ctx.restore();
// 分针
ctx.save();
ctx.lineWidth = '10';
ctx.rotate(m*6*Math.PI/180);
ctx.beginPath();
ctx.moveTo(-28, 0);
ctx.lineTo(112, 0);
ctx.stroke();
ctx.restore();
// 秒针 表座
ctx.save();
ctx.strokeStyle = '#D40000';
ctx.fillStyle = '#D40000';
ctx.lineWidth = '6';
ctx.rotate(s*6*Math.PI/180);
ctx.beginPath();
ctx.moveTo(-30, 0);
ctx.lineTo(83, 0);
ctx.stroke();
// 表座
ctx.beginPath();
ctx.arc(0,0,10,0,Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(96,0,10,0,Math.PI*2);
ctx.stroke();
ctx.restore();
ctx.restore();
}
}
}
</script>
</html>
canvas中应用图片(背景、突变)
在canvas中插入图片
1.canvas操作图片时,必须要等图片加载完能力操作
2.drawImage(image, x, y, width, height)
image: 图像源;是imgae对象 或者 canvas 对象
x, y: 图片在canvas中开始绘制的起始坐标
width & height: 这两个参数用来管制 当向canvas画入时应该缩放的大小
const image = new Image();
image.src = './image.jpg';
image.onload = function() {
ctx.drawImage(image, 0, 0, image.width, image.height);
}
在canvas中设置背景
1. createPattern(image, repetition)
image: 图像源
repetition: repeat | repeat-x | repeat-y | no-repeat
个别状况下会将createPattern()返回的对象作为fillStyle的值
const image = new Image();
image.src = './image.jpg';
image.onload = function() {
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
在canvas中应用突变
线性突变
1. createLinearGradient(x1, y1, x2, y2)
x1,y1: 突变的终点
x2,y2: 突变的起点
2. gradient.addColorStop(position, color)
gradient: createLinearGradient办法的返回值
position: 必须是0 - 1 之间的数值,示意突变中色彩所在的绝对地位。
color: 必须是一个无效的css色彩值(如: #fff rgba(255, 255, 255, 1)等)
径向突变
1. createRadialGradient(x1, y1, r1, x2, y2, r2)
x1, y1, r1: 定义一个以(x1, y1)为原点,半径为r1的圆
x2, y2, r2: 定义一个以(x2, y2)为原点,半径为r2的圆
线性突变
const gradient = ctx.createLinearGradient(0, 0, 300, 200);
gradient.addColorStop(0, 'red');
gradient.addColorStop(.5, 'yellow');
gradient.addColorStop(1, 'green');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
径向突变
// 两个圆之间是突变的
const gradient = ctx.createRadialGradient(150, 150, 50, 150, 150, 150);
gradient.addColorStop(0, 'red');
gradient.addColorStop(.5, 'yellow');
gradient.addColorStop(1, 'green');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
绘制文本
canvas提供了两种办法来渲染文本
1. fillText(text, x, y): 在指定的(x, y)地位填充指定的文本
2. strokeText(text, x, y): 在指定的(x, y)地位绘制描边文字
还提供api设置文本款式
font = value
默认字体是 '10px sans-serif'
这个字符串应用和 css font属性的语法雷同
留神:font属性在指定时,必须要有字体大小 和 字体 缺一不可,并且只有sans-serif这种字体可用,写错了也默认应用sans-serif
textAlign = value
设置文本程度对齐形式
文本对齐选项:
left: 文本左对齐
right: 文本右对齐
center: 文本居中对齐。
值得注意的是,这里的文本居中是基于你在fillText的时候所给x值,文本的一半在x地位的右边一半内容在左边
textBaseline = value
设置文本基线的属性
top|middle|bottom
文本基线在文本块的顶部|两头|底部
measureText: 返回TextMetrics对象,蕴含文本尺寸信息
const ctx = canvas.getContext('2d');
ctx.font = '26px sans-serif';
ctx.textAlign = 'left';
ctx.fillText('hello world', 150, 150);
计算文本的宽度,这个比拟有用
ctx.font = '26px sans-serif';
ctx.fillText('hello world', 150, 150);
const textMetrics = ctx.measureText('hello world');
console.log(textMetrics);
文本暗影 & 盒模型暗影
1. shadowOffsetX = float 默认为0
shadowOffsetX用来设定暗影在X轴的延长间隔
2. shadowOffsetY = float 默认为0
shadowOffsetY用来设定暗影在Y轴的延长间隔
3. shadowOffsetBlur = float 默认为0
用于设定暗影的含糊水平,与像素数量没有关系,不受变换矩阵的影响
4. shadowOffsetColor = color(必须) 默认全透明的彩色
ctx.font = '26px sans-serif';
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.shadowBlur = 0.5;
ctx.shadowColor = 'rgba(0, 0, 0, .6)';
ctx.fillText('hello world', 150, 150);
像素操作
到目前为止,咱们还尚未理解canvas画布实在像素的原理,事实上,咱们能够通过ImageData对象操纵像素数据,间接读取或者将数据数组写入该对象中
获取场景像素数据
getImageData() 取得一个含画布场景像素数据的ImageData对象
ctx.getImageData(sx, sy, sw, sh)
sx 将要被提取的图形像素数据矩形区域左上角x坐标
sy 将要被提取的图形像素数据矩形区域左上角y坐标
sw 将要被提取的图形像素数据矩形区域的宽度
sh 将要被提取的图形像素数据矩形区域的高度
返回的ImageData对象
width: 横向上像素点的个数
height: 纵向上像素点的个数
data: Unit8ClampeArray类型的一维数组,蕴含着RGBA格局的整型数据
R: 0 - 255 (彩色到红色)
G: 0 - 255 (彩色到红色)
B: 0 - 255 (彩色到红色)
A: 0 - 255 (通明到不通明)
// 为了不便看出画布,图中画布灰色是通过css设置的
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
console.log(imageData);
console.log(imageData.data);
由此能够看出画布默认是彩色通明的
在场景中写入像素数据
1. putImageData(myImageData, dx, dy)
dx和dy参数示意在场景内左上角绘制的像素所失去的设施坐标
ctx.fillRect(0, 0, 50, 50); // 默认绘制的矩形是彩色
const imageData = ctx.getImageData(0, 0, 50, 50); // 获取场景矩形的像素数据
for (let i = 0; i < imageData.data.length; i++) {
const r = i*4;
const g = i*4 + 1;
const b = i*4 + 2;
const a = i*4 + 3;
imageData.data[r] = 100;
}
ctx.putImageData(imageData, 0, 0); // 批改了场景所有像素点G为100变成了酒红色
创立一个ImageData对象
ctx.createImageData(width, height)
width: ImageData新对象的宽度
height: ImageData新对象的高度
默认状况下创立的是彩色通明的
像素操作之马赛克
铺垫
单像素操作,定义一个工具函数,给定一个imageData对象和一个坐标获取该坐标点的像素信息
// 给定偏移量拿像素点的信息
function getPXInfo (imageData, x, y) {
const color = [];
const data = imageData.data;
const width = imageData.width;
const height = imageData.height;
const theFirst = (y * width) + x; // 以后像素后面有多少个像素点
// r
color[0] = data[theFirst * 4];
// g
color[1] = data[theFirst * 4 + 1];
// b
color[2] = data[theFirst * 4 + 2];
//a
color[3] = data[theFirst * 4 + 3];
return color;
}
单像素操作,定义一个工具函数,给定一个坐标和像素信息设置该坐标
ctx.save();
ctx.fillStyle = 'pink';
ctx.beginPath();
ctx.fillRect(0, 0, 100,100);
ctx.restore();
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
console.log(getPXInfo(imageData, 100, 0));
// 将矩形区域的第一行改成了彩色
for (let i = 0; i < 100; i++) {
var newImageData = changePXInfo(imageData, i, 0, [0, 0, 0, 255]);
}
ctx.putImageData(newImageData, 0, 0);
// 扭转指定地位上的像素点信息
function changePXInfo (imageData, x, y, color) {
const data = imageData.data;
const width = imageData.width;
const height = imageData.height;
const theFirst = (y * width) + x; // 以后像素后面有多少个像素点
// r
data[theFirst * 4] = color[0];
// g
data[theFirst * 4 + 1] = color[1];
// b
data[theFirst * 4 + 2] = color[2];
//a
data[theFirst * 4 + 3] = color[3];
return imageData;
}
马赛克实现
思路
1. 将图片区域划分成若干个马赛克矩形区域
2. 从马赛克矩形区域中随机抉择一个像素点,并且将该像素点信息赋予马赛克矩形区域内其余像素点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
}
canvas {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
/* border: 1px solid; */
background: grey;
}
</style>
</head>
<body>
<canvas></canvas>
</body>
<script>
const canvas = document.querySelector('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = 'pink';
ctx.beginPath();
ctx.fillRect(0, 0, 100,100);
ctx.restore();
const image = new Image();
image.src = './image.jpeg';
image.onload = function() {
canvas.width = image.width * 2;
canvas.height = image.height;
draw();
}
function draw() {
ctx.drawImage(image, 0, 0, image.width, image.height);
const oldImageData = ctx.getImageData(0, 0, image.width, image.height);
// 依据 oldImageData 数据的批改成
let newImageData = ctx.createImageData(image.width, image.height);
// 1. 选取马赛克矩形
// 2. 从马赛克矩形中随机抽出一个像素点的信息(rgba)
// 3. 将整个马赛克矩形中的像素点信息对立调成随机抽取的那个(2中的)
const size = 5;
for (let i = 0; i < oldImageData.width/size;i++) {
// 列的马赛克矩形
for (let j = 0; j < oldImageData.height/size;j++) {
// (i, j) 每一个马赛克矩形的坐标
/*
(0, 0) (0, 0) - (4, 4) (1, 0) (5, 0) - (9, 4)
(0, 1) (0, 5) - (4, 9) (1, 1) (5, 5) - (9, 9)
*/
// Math.random ---> [0, 1)
const color = getPXInfo(oldImageData, Math.floor(Math.random()*size + size*i), Math.floor(Math.random()*size + size*j));
// 循环每一个马赛克矩形 将整个马赛克矩形中的像素点信息对立调成随机抽取的那个(2中的)
for (let a = 0; a < size; a++) {
for (let b = 0; b < size; b++) {
newImageData = setPXInfo(newImageData, size * i + a, size * j + b, color);
}
}
}
}
ctx.putImageData(newImageData, image.width, 0);
}
}
// 给定偏移量拿像素点的信息
function getPXInfo (imageData, x, y) {
const color = [];
const data = imageData.data;
const width = imageData.width;
const height = imageData.height;
const theFirst = (y * width) + x; // 以后像素后面有多少个像素点
// r
color[0] = data[theFirst * 4];
// g
color[1] = data[theFirst * 4 + 1];
// b
color[2] = data[theFirst * 4 + 2];
//a
color[3] = data[theFirst * 4 + 3];
return color;
}
function setPXInfo (imageData, x, y, color) {
const data = imageData.data;
const width = imageData.width;
const height = imageData.height;
const theFirst = (y * width) + x; // 以后像素后面有多少个像素点
// r
data[theFirst * 4] = color[0];
// g
data[theFirst * 4 + 1] = color[1];
// b
data[theFirst * 4 + 2] = color[2];
//a
data[theFirst * 4 + 3] = color[3];
return imageData;
}
</script>
</html>
局部图解
实现的效果图:
合成
全局通明的的设置
globalAlpha = value(默认值是1)
这个属性影响到canvas里所有图形的通明的
有效值范畴0(齐全通明)到1(齐全不通明)
ctx.save();
ctx.fillStyle = 'red';
ctx.globalAlpha = .5; // 把画布上的所有内容都透明度都批改了
ctx.beginPath();
ctx.fillRect(0, 0, 100, 100);
ctx.restore();
笼罩合成
多个图像叠加在一起时,如何展现的操作
source: 新的图像源(前面绘制的图像)
destination: 曾经绘制过的图形(指标)
ctx.globalCompositeOperation: (属性值不止下方八种,这八种比拟罕用)
source-over(默认值): 源在下面,新的图像层级比拟高
source-in: 只留下源与指标的重叠局部(源的那局部)
source-out: 只留下源超出指标的局部
source-atop: 砍掉源溢出的局部
destination-over(默认值): 指标在下面,旧的图像层级比拟高
destination-in: 只留下源与指标的重叠局部(指标的那局部)
destination-out: 只留下指标超过源的局部
destination-atop: 砍掉指标溢出的局部
ctx.fillStyle = 'darkred';
ctx.fillRect(0, 0, 100, 100);
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = 'black';
ctx.fillRect(50, 50, 100, 100);
下图是globalCompositeOperation八种值所对应的效果图
刮刮卡的制作
剖析构造:
底层是背景图片,下面是canvas
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
overflow: hidden;
}
#wrap, #wrap ul, ul li {
height: 100%;
}
ul li {
background-image: url(down.jpg);
background-size: 100% 100%;
background-repeat: no-repeat;
}
canvas {
position: absolute;
top: 0;
left: 0;
transition: 1s;
}
</style>
</head>
<body>
<div id='wrap'>
<canvas></canvas>
<ul>
<li></li>
</ul>
</div>
</body>
<script type="text/javascript">
window.onload = function() {
const canvas = document.querySelector('canvas');
canvas.width = document.documentElement.clientWidth;// 获取视口的宽度
canvas.height = document.documentElement.clientHeight;// 获取视口的宽度
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
const image = new Image();
image.src = './up.jpg';
image.onload = function() {
draw();
}
function draw() {
let count = 0;
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.lineWidth = 40;
canvas.addEventListener('touchstart', function(e) {
e = e || window.e;
const touchC = e.changedTouches[0]; // 挪动端获取手指列表的第一根手指
const x = touchC.clientX - canvas.offsetLeft;
const y = touchC.clientY - canvas.offsetTop;
ctx.save();
ctx.globalCompositeOperation = 'destination-out'; // 只留下指标操过源的局部
ctx.beginPath();
// ctx.arc(x, y, 20, 0, 2*Math.PI);
ctx.moveTo(x, y);
ctx.lineTo(x + 1, y + 1);
ctx.stroke();
ctx.restore();
});
// 手指挪动的时候 必须touchstart触发后才会触发
canvas.addEventListener('touchmove', function(e) {
e = e || window.event;
const touchC = e.changedTouches[0]; // 挪动端获取手指列表的第一根手指
const x = touchC.clientX - canvas.offsetLeft;
const y = touchC.clientY - canvas.offsetTop;
ctx.save();
ctx.globalCompositeOperation = 'destination-out'; // 只留下指标操过源的局部
// ctx.arc(x, y, 20, 0, 2*Math.PI);
ctx.moveTo(x, y);
ctx.lineTo(x + 1, y + 1);
ctx.stroke();
ctx.restore();
});
canvas.addEventListener('touchend', function(e) {
e = e || window.event;
// 当划掉的内容超过一半的时候,把canvas那一层有过渡的干掉
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const allPx = imageData.width * imageData.height; // 所有的像素点个数
// 统计像素点是通明的
for (let i = 0; i < allPx;i++) {
if (imageData.data[4*i+3] === 0) {
count++;
}
}
if (count >= allPx/2) {
canvas.style.opacity = 0;
}
});
// 当canvas实现过渡时触发
canvas.addEventListener('transitionend', function() {
this.remove();
});
}
}
}
</script>
</html>
其余
将画布导出为图像(挪动端)
toDataURL() canvas元素上的办法
返回: base64格局的门路
事件操作
ctx.isPointInPath(x, y) 判读在以后门路中是否蕴含检测点
x 检测点x坐标
y 检测点y坐标
此办法只作用于最新画出的canvas图像
ctx.arc(100, 100, 50, 0, 2*Math.PI);
ctx.fill();
ctx.beginPath();
ctx.arc(200, 200, 50, 0, 2*Math.PI);
ctx.fill();
canvas.onclick = function(e) {
// 点击画布时都触发
e = e || window.event;
const x = e.clientX - canvas.offsetLeft;
const y = e.clientY - canvas.offsetTop;
if (ctx.isPointInPath(x, y)) {
alert('点在了最初绘制指标图形上了');
}
发表回复