使用canvas实现将图片粒子化处理。
HTML5 标签元素本身并没有绘制能力,它仅仅是图形的容器,需要使用getContext()
方法可返回一个对象,然后利用getContext("2d")
对象属性和方法,在画布上绘制文本、线条、矩形、圆形等。
要实现的效果:
完成思路:
- 创建
getContext("2d")
对象; - 画布添加图片(或 画布添加文字);
- 根据
getImageData
方法获取画布指定矩形像素; - 降低像素,舍弃部分像素点,跳跃性地获取获取画布像素数据,并存储当前选取像素点的位置信息;
- 循环储存的像素信息,并根据其位置信息在画布上绘制圆点。
首先,创建对象;
const canvas = document.getElementById('particleImg');
if (!canvas.getContext) return;
const ctx = canvas.getContext('2d');
绘制图片。canvas上可以直接绘制图片,创建图片的方法:
var img = new Image(); // 创建一个<img>元素
img.src = 'myImage.png'; // 设置图片源地址
脚本执行后图片开始装载。
绘制img:
// 参数 1:要绘制的 img
// 参数 2、3:绘制的 img 在 canvas 中的坐标
ctx.drawImage(img,0,0);
注意:考虑到图片是从网络加载,如果 drawImage 的时候图片还没有完全加载完成,则什么都不做,个别浏览器会抛异常。所以我们应该保证在 img 绘制完成之后再 drawImage。
var img = new Image(); // 创建img元素
img.onload = function(){
ctx.drawImage(img, 0, 0)
}
img.src = 'myImage.png'; // 设置图片源地址
使用 getImageData() 获取指定矩形像素信息。
关于 getImageData() 方法:
getImageData() 方法返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据。
对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:
R - 红色 (0-255)
G - 绿色 (0-255)
B - 蓝色 (0-255)
A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)
color/alpha 以数组形式存在,并存储于 ImageData 对象的 data 属性中。
故绘制图片并拿到画布信息代码:
const img = document.createElement('img');
img.src = require('../../assets/laiang.jpg');
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height); // 参数 2、3:img 在 canvas 中的坐标
const imgData = ctx.getImageData(0, 0, img.width, img.height); //复制画布上指定矩形的像素数据,RGBA的一维数组数据
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
降低像素:在画布的水平和垂直方向都每隔4个(根据实际计算)像素获取一次像素信息,并将此像素的“水平”、“垂直”以及“像素信息”记录。
const dots = [];
function getImgData(imgData) {
for (var x = 0; x < imgData.width; x += 4) {
for (var y = 0; y < imgData.height; y += 4) {
var i = (y * imgData.width + x) * 4; // y为行,x为列 每个像素点由RGBA四个数据组成
if (!(imgData.data[i] >= 200 && imgData.data[i + 1] >= 200 && imgData.data[i + 2] >= 200) && imgData.data[i + 3] >= 128) { // 有颜色值就渲染
var dot = new Dot(x - 3, y - 3, 2, `rgba(${imgData.data[i]},${imgData.data[i + 1]},${imgData.data[i + 2]},${imgData.data[i + 3]})`);
dots.push(dot);
}
}
}
}
创建构造函数Dot的代码为:
const Dot = function (centerX, centerY, radius, fillStyle) {
this.x = centerX;
this.y = centerY;
this.radius = radius;
this.fillStyle = fillStyle;
}
Dot.prototype = {
paint: function () {
ctx.save();
ctx.beginPath();
// arc(x, y, r, startAngle, endAngle, anticlockwise) 以x,y为圆心,以r为半径,从startAngle弧度开始到endAngle弧度结束,anticlosewise是布尔值,true表示逆时针,false表示顺时针,默认是顺时针
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.fillStyle;
ctx.fill()
ctx.restore();
}
}
最后,循环dots并绘制:
function drawData() {
dots.forEach(function (item) {
item.paint();
})
}
当然,如果不是根据图片绘制粒子,而是根据文字绘制粒子,则画布应该添加文字信息,故第二步,创建及绘制图片处应改为:
function createText() {
ctx.save();
ctx.font = "140px 微软雅黑 bold"
ctx.fillStyle = "rgba(168,168,168,1)";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// fillText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的
ctx.fillText(textStr, canvas.width / 2, canvas.height / 2);
ctx.restore();
}
关于 canvas 的状态保存 save 及恢复 restore :
save()
:Canvas状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存;
一个绘画状态包括:
- 当前应用的变形(即移动,旋转和缩放);
- strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值;
- 当前的裁切路径(clipping path)。restore()
:每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复(类似数组的 pop())
完整代码:
<canvas id='particleImg'></canvas>
const dots = [];
const canvas = document.getElementById('particleImg');
const ctx = canvas.getContext('2d');
const img = document.createElement('img');
img.src = require('../../assets/laiang.jpg');
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height); // 参数 2、3:img 在 canvas 中的坐标
const imgData = ctx.getImageData(0, 0, img.width, img.height); //复制画布上指定矩形的像素数据,RGBA的一维数组数据
ctx.clearRect(0, 0, canvas.width, canvas.height);
getImgData(imgData);
drawData();
}
// 圆点构造函数
const Dot = function (centerX, centerY, radius, fillStyle) {
this.x = centerX;
this.y = centerY;
this.radius = radius;
this.fillStyle = fillStyle;
}
Dot.prototype = {
paint: function () {
ctx.save();
ctx.beginPath();
// arc(x, y, r, startAngle, endAngle, anticlockwise) 以x,y为圆心,以r为半径,从startAngle弧度开始到endAngle弧度结束,anticlosewise是布尔值,true表示逆时针,false表示顺时针,默认是顺时针
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.fillStyle;
ctx.fill()
ctx.restore();
}
}
function getImgData(imgData) {
for (var x = 0; x < imgData.width; x += 4) {
for (var y = 0; y < imgData.height; y += 4) {
var i = (y * imgData.width + x) * 4; // y为行,x为列 每个像素点由RGBA四个数据组成
if (!(imgData.data[i] >= 200 && imgData.data[i + 1] >= 200 && imgData.data[i + 2] >= 200) && imgData.data[i + 3] >= 128) { // 有颜色值就渲染
var dot = new Dot(x - 3, y - 3, 2, `rgba(${imgData.data[i]},${imgData.data[i + 1]},${imgData.data[i + 2]},${imgData.data[i + 3]})`);
dots.push(dot);
}
}
}
}
function drawData() {
dots.forEach(function (item) {
item.paint();
})
}
参考文章: