theme: smartblue
highlight: a11y-dark
本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
大家好我是ndz,很高兴也很荣幸成为了一名稀土掘金技术社区签约作者,在这里真的很感谢平台给予的肯定和各位读者的支持,感谢 🙏 🙏 🙏。
本文为稀土掘金技术社区签约作者专栏 – 《从Canvas到PixiJs》 的第七篇文章,喜欢的小伙伴记得点赞加关注,以防需要用时回来不迷路 😂
前言
在前面的文章中我们已经介绍了 Canvas 基础的内容:
在之前的文章中我们学习了Canvas的基础内容和进阶内容中的动画和事件,接下来我们将进入Canvas最后的应用篇。
本文主要要以日常开发中我们常见的案例为主,说一下canvas在我们前端开发中必不可少的一些应用。人狠话不多,直接进入主题吧。
应用一:图片保存
在我们的日常的摸鱼中,我们会看到一些H5小游戏,或者类似支付宝年度账单之类的小应用,其中就会有保存图片的按钮,或者说长按保存图片之类的功能,下面我们就看看这些功能如何实现。
所需方法
首先我们需要知道在保存图片的案例中,我们需要用到哪些方法?
toDataURL
toDataURL()
方法可以返回一个包含图片的Data URL
。
Data URL
也就是前缀为 data:
协议的URL,其允许内容创建者向文档中嵌入小文件。
语法:
toDataURL(type, encoderOptions)
参数:
- type:
type
为图片格式,默认为image/png
,也可指定为:image/jpeg
、image/webp
等格式 - encoderOptions:
encoderOptions
为图片的质量,默认值0.92
。在指定图片格式为image/jpeg
或image/webp
的情况下,可以从0
到1
的区间内选择图片的质量。如果不在这个范围内,则使用默认值0.92
。
下面咱们以上篇文章 PixiJs学前篇(五):Canvas进阶【事件篇】🔥🔥 中的相册拖拽为例,把每次拖拽好的相册截屏保存起来。
// 点击截图函数
function clickFn(){
// 将canvas转换成base64的url
let url = canvas.toDataURL("image/png");
// 把Canvas 转化为图片
Img.src = url;
// 将base64转换为文件对象
let arr = url.split(",")
let mime = arr[0].match(/:(.*?);/)[1] // 此处得到的为文件类型
let bstr = atob(arr[1]) // 此处将base64解码
let n = bstr.length
let u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
// 通过以下方式将以上变量生成文件对象,三个参数分别为文件内容、文件名、文件类型
let file = new File([u8arr], "filename", { type: mime });
// 将文件对象通过a标签下载
let aDom = document.createElement("a"); // 创建一个 a 标签
aDom.download = file.name; // 设置文件名
let href = URL.createObjectURL(file); // 将file对象转成 UTF-16 字符串
aDom.href = href; // 放入href
document.body.appendChild(aDom); // 将a标签插入 body
aDom.click(); // 触发 a 标签的点击
document.body.removeChild(aDom); // 移除刚才插入的 a 标签
URL.revokeObjectURL(href); // 释放刚才生成的 UTF-16 字符串
};
整个保存为图片并下载的方法如上,其中的canvas就为之前的相册,整体代码如下:
<!DOCTYPE html>
<html lang="en">
<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>canvas - 保存并下载</title>
<style>
* { margin: 0; padding: 0; }
canvas {
box-shadow: 0px 0px 5px #ccc;
border-radius: 8px;
float: left;
}
img {
width: 800px;
height: 500px;
float: right;
}
button {
position: absolute;
top: 550px;
left: 50%;
margin-left: -40px;
}
</style>
</head>
<body>
<canvas id="canvas" width="800" height="500">
当前浏览器不支持canvas元素,请升级或更换浏览器!
</canvas>
<img id="img" src="" />
<button id="btn">转化为图片且下载</button>
<script>
// 获取Canvas
const canvas = document.getElementById('canvas');
var Img = document.getElementById('img');
var Btn = document.getElementById('btn');
const width = canvas.width;
const height = canvas.height;
// 获取绘制上下文
const ctx = canvas.getContext('2d');
const images = [
{
name: "白月魁",
url: "https://img1.baidu.com/it/u=4141276181,3458238270&fm=253&fmt=auto&app=138&f=JPEG?w=281&h=500"
},
{
name: "鸣人",
url: "https://img2.baidu.com/it/u=1548765981,166433699&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889",
},
{
name: "路飞",
url: "https://img2.baidu.com/it/u=1700240772,3511789617&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
},
{
name: "哪吒",
url: "https://img2.baidu.com/it/u=4044887937,3129736188&fm=253&fmt=auto&app=138&f=JPEG?w=640&h=392",
},
{
name: "千寻",
url: "https://img1.baidu.com/it/u=3907076642,679964949&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=293",
},
];
let imagesData = []
let clickCoordinate = { x: 0, y: 0 }
let target;
images.forEach((item)=>{
// 创建image元素
const image = new Image()
image.crossOrigin = "Anonymous";
image.src = item.url;
const name = item.name;
image.onload = () => {
// 控制宽度为200(等宽)
const w = 200;
// 高度按宽度200的比例缩放
const h = 200 / image.width * image.height;
const x = Math.random() * (width - w) ;
const y = Math.random() * (height - h);
const imageObj = { image, name, x, y, w, h }
imagesData.push(imageObj)
draw(imageObj)
}
})
// 渲染图片
function draw(imageObj) {
ctx.drawImage(imageObj.image, imageObj.x, imageObj.y, imageObj.w, imageObj.h);
ctx.beginPath();
ctx.strokeStyle = "#fff";
ctx.rect(imageObj.x, imageObj.y, imageObj.w, imageObj.h);
ctx.stroke();
}
// 为canvas添加鼠标按下事件
canvas.addEventListener("mousedown", mousedownFn, false)
// 为按钮添加点击事件
Btn.addEventListener("click", clickFn, false)
// 鼠标按下触发的方法
function mousedownFn(e) {
// 获取元素按下时的坐标
clickCoordinate.x = e.pageX - canvas.offsetLeft;
clickCoordinate.y = e.pageY - canvas.offsetTop;
// 判断选中的元素是哪一个
checkElement()
// 未选中元素则return
if(target === undefined) return;
// 为canvas添加鼠标移动和鼠标抬起事件
canvas.addEventListener("mousemove", mousemoveFn, false)
canvas.addEventListener("mouseup", mouseupFn, false)
}
// 鼠标移动触发
function mousemoveFn(e) {
const moveX = e.pageX
const moveY = e.pageY
// 计算移动元素的坐标
imagesData[target].x = imagesData[target].x + ( moveX - clickCoordinate.x );
imagesData[target].y = imagesData[target].y + ( moveY - clickCoordinate.y );
// 清空画布
ctx.clearRect(0, 0, width, height);
// 清空画布以后重新绘制
imagesData.forEach((i) => draw(i))
// 赋值
clickCoordinate.x = moveX;
clickCoordinate.y = moveY;
}
// 鼠标抬起触发
function mouseupFn() {
// 鼠标抬起以后移除事件
canvas.removeEventListener("mousemove", mousemoveFn, false)
canvas.removeEventListener("mouseup", mouseupFn, false)
// 销毁选中元素
target = undefined
}
// 检测选中的元素是哪一个
function checkElement() {
imagesData.forEach((item, index)=>{
draw(item)
if(ctx.isPointInPath(clickCoordinate.x, clickCoordinate.y)) {
target = index
console.log("点击的元素是:", item.name)
}
})
}
// 点击截图函数
function clickFn(){
// 将canvas转换成base64的url
let url = canvas.toDataURL("image/png");
// 把Canvas 转化为图片
Img.src = url;
// 将base64转换为文件对象
let arr = url.split(",")
let mime = arr[0].match(/:(.*?);/)[1] // 此处得到的为文件类型
let bstr = atob(arr[1]) // 此处将base64解码
let n = bstr.length
let u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
// 通过以下方式将以上变量生成文件对象,三个参数分别为文件内容、文件名、文件类型
let file = new File([u8arr], "filename", { type: mime });
// 将文件对象通过a标签下载
let aDom = document.createElement("a"); // 创建一个 a 标签
aDom.download = file.name; // 设置文件名
let href = URL.createObjectURL(file); // 将file对象转成 UTF-16 字符串
aDom.href = href; // 放入href
document.body.appendChild(aDom); // 将a标签插入 body
aDom.click(); // 触发 a 标签的点击
document.body.removeChild(aDom); // 移除刚才插入的 a 标签
URL.revokeObjectURL(href); // 释放刚才生成的 UTF-16 字符串
};
</script>
</body>
</html>
看一下具体效果:
应用二:主题(滤镜)
滤镜大家应该比较熟悉,尤其女生应该会更加熟悉,比如每次拍照完成以后 P图 不!应该说修图的时候,我们都会换各种主题(滤镜),比如说暖色、冷色、复古等等。而这些主题就可以用滤镜来实现。下面我们来实现个简单的例子看一下。
实现滤镜的方式有很多种方式,这里既然咱们介绍的是canvas的应用,那么就用canvas的方式来实现看看。
具体实现我们可以遍历所有像素然后改变他们的数值,再将被修改的像素数组通过 putImageData()
方法放回到画布中去,以达到反相颜色
。
所需方法:
首先我们需要知道在制作主题的案例中,我们需要用到哪些方法?
getImageData()
getImageData()
方法可以返回一个ImageData
对象。
ImageData
对象用来描述canvas
区域隐含的像素数据,此区域通过矩形表示,起始点为(sx, sy)
、宽为sw
、高为sh
语法:
getImageData(sx, sy, sw, sh)
参数:
- sx:将要被提取的图像数据矩形区域的左上角 x 坐标。
- sy:将要被提取的图像数据矩形区域的左上角 y 坐标。
- sw:将要被提取的图像数据矩形区域的宽度。
- sh:将要被提取的图像数据矩形区域的高度。
putImageData()
putImageData()
方法和getImageData()
方法正好相反,可以将数据从已有的ImageData
对象绘制为位图。如果提供了一个绘制过的矩形,则只绘制该矩形的像素。
语法:
putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
参数:
- ImageData:包含像素值的数组对象。
- dx:源图像数据在目标画布中 x 轴方向的偏移量。
- dy:源图像数据在目标画布中 y 轴方向的偏移量。
- dirtyX:可选参数,在源图像数据中,矩形区域左上角的位置。默认是整个图像数据的左上角(x 坐标)。
- dirtyY:可选参数,在源图像数据中,矩形区域左上角的位置。默认是整个图像数据的左上角(y 坐标)。
- dirtyWidth:可选参数,在源图像数据中,矩形区域的宽度。默认是图像数据的宽度。
- dirtyHeight:可选参数,在源图像数据中,矩形区域的高度。默认是图像数据的高度。
知道了这两个方法以后,下面我们简单编写两个方法来做两个主题(滤镜)效果。
黑白主题
黑白主题咱们用一个:blackWhite
函数来实现,具体是减掉颜色的最大色值255来实现
代码如下:
const blackWhite = function() {
ctx.drawImage(img, 0, 0, 450, 800);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // red
data[i + 1] = avg; // green
data[i + 2] = avg; // blue
}
ctx.putImageData(imageData, 0, 0);
};
曝光主题
曝光主题咱们用一个:exposure
函数来实现,具体是用红绿和蓝的平均值来实现
代码如下:
const exposure = function() {
ctx.drawImage(img, 0, 0, 450, 800);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // red
data[i + 1] = 255 - data[i + 1]; // green
data[i + 2] = 255 - data[i + 2]; // blue
}
ctx.putImageData(imageData, 0, 0);
};
直接上例子吧:
<!DOCTYPE html>
<html lang="en">
<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>canvas - 主题</title>
<style>
canvas {
box-shadow: 0px 0px 5px #ccc;
border-radius: 8px;
}
</style>
</head>
<body>
<canvas id="canvas" width="450" height="800">
当前浏览器不支持canvas元素,请升级或更换浏览器!
</canvas>
<div class="btnBox">
<button id="original">还原</button>
<button id="blackWhite">黑白主题</button>
<button id="exposure">曝光主题</button>
</div>
<script>
// 获取 canvas 元素
var canvas = document.getElementById('canvas');
var originalEl = document.getElementById('original');
var blackWhiteEl = document.getElementById('blackWhite');
var exposureEl = document.getElementById('exposure');
var sepiaEl = document.getElementById('sepia');
// 通过判断getContext方法是否存在来判断浏览器的支持性
if(canvas.getContext) {
// 获取绘图上下文
var ctx = canvas.getContext('2d');
var img = new Image();
img.crossOrigin = 'anonymous';
img.src = 'https://img1.baidu.com/it/u=4141276181,3458238270&fm=253&fmt=auto&app=138&f=JPEG';
img.onload = function() {
ctx.drawImage(img, 0, 0, 450, 800);
};
var original = function() {
ctx.drawImage(img, 0, 0, 450, 800);
};
var exposure = function() {
ctx.drawImage(img, 0, 0, 450, 800);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // red
data[i + 1] = 255 - data[i + 1]; // green
data[i + 2] = 255 - data[i + 2]; // blue
}
ctx.putImageData(imageData, 0, 0);
};
var blackWhite = function() {
ctx.drawImage(img, 0, 0, 450, 800);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // red
data[i + 1] = avg; // green
data[i + 2] = avg; // blue
}
ctx.putImageData(imageData, 0, 0);
};
originalEl.addEventListener("click", function(evt) {
original()
})
blackWhiteEl.addEventListener("click", function(evt) {
blackWhite()
})
exposureEl.addEventListener("click", function(evt) {
exposure()
})
}
</script>
</body>
</html>
我们看一下具体效果:
应用三:拾色器
拾色器也是比较常见的一个案例,尤其在现在很多在线编辑的项目中很常见。
所需方法:
拾色器案例用的方法还是上面我们介绍过的getImageData()
方法。这里就不再赘述,记不得了可以返回去看看😄。
需要补充的是:getImageData()
方法会返回一个 ImageData
对象,它是画布区域的数据,画布的四个角分别表示为 (left, top)、(left + width, top)、(left, top + height)和(left + width, top + height) 四个点。这四个坐标点被设定为画布坐标空间元素。
接下来我们再用getImageData()
方法做一个拾色器
<!DOCTYPE html>
<html lang="en">
<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>canvas - 拾色器</title>
<style>
/* 给画布增加一个阴影和圆角的样式 */
canvas {
box-shadow: 0px 0px 5px #ccc;
border-radius: 8px;
}
div {
width: 430px;
height: 30px;
color: #fff;
box-shadow: 0px 0px 5px #ccc;
border-radius: 8px;
line-height: 30px;
padding: 10px;
}
</style>
</head>
<body>
<canvas id="canvas" width="450" height="800">
当前浏览器不支持canvas元素,请升级或更换浏览器!
</canvas>
<div id="hovered"></div>
<div id="selected"></div>
<script>
// 获取 canvas 元素
var canvas = document.getElementById('canvas');
// 通过判断getContext方法是否存在来判断浏览器的支持性
if(canvas.getContext) {
// 获取绘图上下文
var ctx = canvas.getContext('2d');
var img = new Image();
img.crossOrigin = 'anonymous';
img.src = 'https://img1.baidu.com/it/u=4141276181,3458238270&fm=253&fmt=auto&app=138&f=JPEG';
img.onload = function() {
ctx.drawImage(img, 0, 0, 450, 800);
img.style.display = 'none';
};
var hoveredColor = document.getElementById('hovered');
var selectedColor = document.getElementById('selected');
canvas.addEventListener('mousemove', function(event) {
pickColor('move', event, hoveredColor);
});
canvas.addEventListener('click', function(event) {
pickColor('click', event, selectedColor);
});
function pickColor(type, event, destination) {
var x = event.layerX;
var y = event.layerY;
var pixel = ctx.getImageData(x, y, 1, 1);
var data = pixel.data;
const rgba = `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${data[3] / 255})`;
destination.style.background = rgba;
if(type === 'move') {
destination.textContent = "划过的颜色为:" + rgba;
} else {
destination.textContent = "选中的颜色为:" + rgba;
}
return rgba;
}
}
</script>
</body>
</html>
效果如下:
应用四:签名
签名也是比较常见的案例,在各大银行的app上基本都有,还有一些桌面应用上也是比较常见的,但这个案例的实现难度确比较低,具体有多简单咱们直接看代码吧。
具体代码如下:
<!DOCTYPE html>
<html lang="en">
<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>canvas - 签名</title>
<style>
/* 给画布增加一个阴影和圆角的样式 */
canvas {
box-shadow: 0px 0px 5px #ccc;
border-radius: 8px;
}
div {
width: 450px;
box-shadow: 0px 0px 5px #ccc;
border-radius: 8px;
text-align: center;
}
</style>
</head>
<body>
<canvas id="canvas" width="450" height="300">
当前浏览器不支持canvas元素,请升级或更换浏览器!
</canvas>
<div id="clear">清空画布</div>
<script>
// 获取 canvas 元素
var canvas = document.getElementById('canvas');
var clear = document.getElementById('clear');
const ctx = canvas.getContext("2d");
canvas.addEventListener('mouseenter', () => {
canvas.addEventListener('mousedown', (e) => {
ctx.beginPath()
ctx.moveTo(e.offsetX, e.offsetY)
canvas.addEventListener('mousemove', draw)
})
canvas.addEventListener('mouseup', () => {
canvas.removeEventListener('mousemove', draw)
})
})
function draw(e) {
ctx.lineTo(e.offsetX, e.offsetY)
ctx.stroke()
}
clear.addEventListener('click', () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
})
</script>
</body>
</html>
效果如下:
应用五:刮刮奖
刮刮奖这个案例其实不是太常见,但确是一个经典的案例,比如我们要说的下一个案例:擦玻璃,就是这个案例的一个扩展。
所需方法:
不管是刮刮奖还是擦玻璃,我们主要应用到的方法是globalCompositeOperation
,该属性用于设置在绘制新形状时应用的合成操作的类型。
该属性有很多方法,下面咱们看一下都有哪些?
语法:globalCompositeOperation = type
,属性值 type 表示是要使用的合成或混合模式操作的类型。
type属性为不同值时,绘制显示将会不同:
上面是我在网上找的一个不同属性绘制成不同效果的图。
了解
具体代码如下:
<!DOCTYPE html>
<html lang="en">
<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>canvas - 刮刮奖</title>
<style>
/* 给画布增加一个阴影和圆角的样式 */
canvas {
background-color: #ccc;
box-shadow: 0px 0px 5px #ccc;
border-radius: 8px;
float: left;
}
</style>
</head>
<body>
<canvas id="canvas" width="1000" height="500">
当前浏览器不支持canvas元素,请升级或更换浏览器!
</canvas>
<script>
// 获取 canvas 元素
var canvas = document.getElementById('canvas');
// 通过判断getContext方法是否存在来判断浏览器的支持性
if(canvas.getContext) {
// 获取绘图上下文
var ctx = canvas.getContext('2d');
const imageUrl = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.mp.itc.cn%2Fupload%2F20160909%2Feca561d1ecce4fcab4f600a74f15b221_th.jpeg&refer=http%3A%2F%2Fimg.mp.itc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1672410563&t=65c34c7d49a899c2f2a3c0f99827312f";
// 设置画笔
ctx.lineCap = 'round'
ctx.lineJoin = 'round'
ctx.lineWidth = 50
// 为canvas添加鼠标按下事件
canvas.addEventListener("mousedown", mousedownFn, false)
let downX,downY
// 鼠标按下触发的方法
function mousedownFn(e) {
e.preventDefault()
downX = e.pageX
downY = e.pageY
drawLine({startX: downX, startY: downY})
// 为canvas添加鼠标移动和鼠标抬起事件
canvas.addEventListener("mousemove", mousemoveFn, false)
canvas.addEventListener("mouseup", mouseupFn, false)
}
// 鼠标移动触发
function mousemoveFn(e) {
e.preventDefault()
const moveX = e.pageX
const moveY = e.pageY
drawLine({endX: moveX, endY: moveY})
downX = moveX
downY = moveY
}
// 鼠标抬起触发
function mouseupFn() {
// 鼠标抬起以后移除事件
canvas.removeEventListener("mousemove", mousemoveFn, false)
canvas.removeEventListener("mouseup", mouseupFn, false)
}
// 画线
function drawLine(position) {
const { startX, startY, endX, endY } = position
ctx.beginPath()
ctx.moveTo(startX, startY)
ctx.lineTo(endX || startX, endY || startY)
ctx.stroke()
}
drawImage(imageUrl)
function drawImage(src) {
const img = new Image()
img.crossOrigin = ''
img.src = src
img.onload = () => {
const imageAspectRatio = img.width / img.height
const canvasAspectRatio = canvas.width / canvas.height
ctx.drawImage( img, 0, 0, canvas.width, canvas.height )
ctx.globalCompositeOperation = 'destination-out'
}
}
}
</script>
</body>
</html>
效果如下:
应用六:擦玻璃
擦玻璃和刮刮奖是差不多的实现,不同的是,刮刮奖的下面是中奖的文字,上面是一张图,而擦玻璃的实现下面是一张图,上面也是一张图,并且上下来那张图是同一张图,只是上面这张图需要做成模糊的效果。
这里我们需要考虑一个问题:如何让图片模糊??
其实这个也比较简单,高斯模糊
想来大家都知道,至于如何高斯模糊
我这边在网上找了一个方法,可以直接使用,具体代码为:
function gaussBlur(imgData) {
const pixes = imgData.data;
const width = imgData.width;
const height = imgData.height;
const gaussMatrix = [];
let gaussSum = 0;
let x;
let y;
let r;
let g;
let b;
let a;
let i;
let j;
let k;
let len;
const radius = 10;
const sigma = 5;
a = 1 / (Math.sqrt(2 * Math.PI) * sigma);
b = -1 / (2 * sigma * sigma);
// 生成高斯矩阵
for (i = 0, x = -radius; x <= radius; x++, i++) {
g = a * Math.exp(b * x * x);
gaussMatrix[i] = g;
gaussSum += g;
}
// 归一化, 保证高斯矩阵的值在[0,1]之间
for (i = 0, len = gaussMatrix.length; i < len; i++) {
gaussMatrix[i] /= gaussSum;
}
// x 方向一维高斯运算
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
r = g = b = a = 0;
gaussSum = 0;
for (j = -radius; j <= radius; j++) {
k = x + j;
if (k >= 0 && k < width) {
// 确保 k 没超出 x 的范围
// r,g,b,a 四个一组
i = (y * width + k) * 4;
r += pixes[i] * gaussMatrix[j + radius];
g += pixes[i + 1] * gaussMatrix[j + radius];
b += pixes[i + 2] * gaussMatrix[j + radius];
// a += pixes[i + 3] * gaussMatrix[j];
gaussSum += gaussMatrix[j + radius];
}
}
i = (y * width + x) * 4;
// 除以 gaussSum 是为了消除处于边缘的像素, 高斯运算不足的问题
// console.log(gaussSum)
pixes[i] = r / gaussSum;
pixes[i + 1] = g / gaussSum;
pixes[i + 2] = b / gaussSum;
// pixes[i + 3] = a ;
}
}
// y 方向一维高斯运算
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
r = g = b = a = 0;
gaussSum = 0;
for (j = -radius; j <= radius; j++) {
k = y + j;
if (k >= 0 && k < height) {
// 确保 k 没超出 y 的范围
i = (k * width + x) * 4;
r += pixes[i] * gaussMatrix[j + radius];
g += pixes[i + 1] * gaussMatrix[j + radius];
b += pixes[i + 2] * gaussMatrix[j + radius];
// a += pixes[i + 3] * gaussMatrix[j];
gaussSum += gaussMatrix[j + radius];
}
}
i = (y * width + x) * 4;
pixes[i] = r / gaussSum;
pixes[i + 1] = g / gaussSum;
pixes[i + 2] = b / gaussSum;
}
}
return imgData
}
此方法就是把像素数据传进去,然后经过模糊处理以后再返回出来,整体的代码如下:
<!DOCTYPE html>
<html lang="en">
<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>canvas - 刮刮奖</title>
<style>
/* 给画布增加一个阴影和圆角的样式 */
canvas {
background-image: url("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Ffile.moyublog.com%2Fd%2Ffile%2F2021-05-29%2Ff8b2a20556774afebed8fd91ccbe0497.jpg&refer=http%3A%2F%2Ffile.moyublog.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1672406341&t=a0b71fded87dd696982c1632cc015397");
background-size: cover;
background-position: center;
box-shadow: 0px 0px 5px #ccc;
border-radius: 8px;
float: left;
}
</style>
</head>
<body>
<canvas id="canvas" width="1000" height="500">
当前浏览器不支持canvas元素,请升级或更换浏览器!
</canvas>
<script>
// 获取 canvas 元素
var canvas = document.getElementById('canvas');
// 通过判断getContext方法是否存在来判断浏览器的支持性
if(canvas.getContext) {
// 获取绘图上下文
var ctx = canvas.getContext('2d');
const imageUrl = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Ffile.moyublog.com%2Fd%2Ffile%2F2021-05-29%2Ff8b2a20556774afebed8fd91ccbe0497.jpg&refer=http%3A%2F%2Ffile.moyublog.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1672406341&t=a0b71fded87dd696982c1632cc015397";
// 设置画笔
ctx.lineCap = 'round'
ctx.lineJoin = 'round'
ctx.lineWidth = 50
// 为canvas添加鼠标按下事件
canvas.addEventListener("mousedown", mousedownFn, false)
let downX,downY
// 鼠标按下触发的方法
function mousedownFn(e) {
e.preventDefault()
downX = e.pageX
downY = e.pageY
drawLine({startX: downX, startY: downY})
// 为canvas添加鼠标移动和鼠标抬起事件
canvas.addEventListener("mousemove", mousemoveFn, false)
canvas.addEventListener("mouseup", mouseupFn, false)
}
// 鼠标移动触发
function mousemoveFn(e) {
e.preventDefault()
const moveX = e.pageX
const moveY = e.pageY
drawLine({endX: moveX, endY: moveY})
downX = moveX
downY = moveY
}
// 鼠标抬起触发
function mouseupFn() {
// 鼠标抬起以后移除事件
canvas.removeEventListener("mousemove", mousemoveFn, false)
canvas.removeEventListener("mouseup", mouseupFn, false)
}
// 画线
function drawLine(position) {
const { startX, startY, endX, endY } = position
ctx.beginPath()
ctx.moveTo(startX, startY)
ctx.lineTo(endX || startX, endY || startY)
ctx.stroke()
}
drawImage(imageUrl)
function drawImage(src) {
const img = new Image()
img.crossOrigin = ''
img.src = src
img.onload = () => {
const imageAspectRatio = img.width / img.height
const canvasAspectRatio = canvas.width / canvas.height
ctx.drawImage( img, 0, 0, canvas.width, canvas.height )
// 把像素数据模糊化
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var tempData = gaussBlur(canvasData, 20);
ctx.putImageData(tempData,0,0);
// 设置绘制类型
ctx.globalCompositeOperation = 'destination-out'
}
}
function gaussBlur(imgData) {
const pixes = imgData.data;
const width = imgData.width;
const height = imgData.height;
const gaussMatrix = [];
let gaussSum = 0;
let x;
let y;
let r;
let g;
let b;
let a;
let i;
let j;
let k;
let len;
const radius = 10;
const sigma = 20;
a = 1 / (Math.sqrt(2 * Math.PI) * sigma);
b = -1 / (2 * sigma * sigma);
// 生成高斯矩阵
for (i = 0, x = -radius; x <= radius; x++, i++) {
g = a * Math.exp(b * x * x);
gaussMatrix[i] = g;
gaussSum += g;
}
// 归一化, 保证高斯矩阵的值在[0,1]之间
for (i = 0, len = gaussMatrix.length; i < len; i++) {
gaussMatrix[i] /= gaussSum;
}
// x 方向一维高斯运算
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
r = g = b = a = 0;
gaussSum = 0;
for (j = -radius; j <= radius; j++) {
k = x + j;
if (k >= 0 && k < width) {
// 确保 k 没超出 x 的范围
// r,g,b,a 四个一组
i = (y * width + k) * 4;
r += pixes[i] * gaussMatrix[j + radius];
g += pixes[i + 1] * gaussMatrix[j + radius];
b += pixes[i + 2] * gaussMatrix[j + radius];
// a += pixes[i + 3] * gaussMatrix[j];
gaussSum += gaussMatrix[j + radius];
}
}
i = (y * width + x) * 4;
// 除以 gaussSum 是为了消除处于边缘的像素, 高斯运算不足的问题
// console.log(gaussSum)
pixes[i] = r / gaussSum;
pixes[i + 1] = g / gaussSum;
pixes[i + 2] = b / gaussSum;
// pixes[i + 3] = a ;
}
}
// y 方向一维高斯运算
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
r = g = b = a = 0;
gaussSum = 0;
for (j = -radius; j <= radius; j++) {
k = y + j;
if (k >= 0 && k < height) {
// 确保 k 没超出 y 的范围
i = (k * width + x) * 4;
r += pixes[i] * gaussMatrix[j + radius];
g += pixes[i + 1] * gaussMatrix[j + radius];
b += pixes[i + 2] * gaussMatrix[j + radius];
// a += pixes[i + 3] * gaussMatrix[j];
gaussSum += gaussMatrix[j + radius];
}
}
i = (y * width + x) * 4;
pixes[i] = r / gaussSum;
pixes[i + 1] = g / gaussSum;
pixes[i + 2] = b / gaussSum;
}
}
return imgData
}
}
</script>
</body>
</html>
具体效果如下:
canvas
的应用其实还很多,这里就不做过多的介绍了,希望已有的案例能让大家有所了解,有所收获。
到此咱们的canvas
应用也就结束了。
下回见。
暂无评论内容