pwl*_*rry 2 html javascript canvas particles particle-system
只是想知道是否有人可以为我指出一个好的方向,让我可以用粒子成行填充不规则形状,然后将其设置为动画。
这是我能找到的最接近的例子 - http://www.wkams.com/#!/work/detail/coca-cola-music-vis
我认为可行的两种方法是计算出我想要的密度,绘制出每行需要多少个粒子,并相应地定位。这种方式看起来很及时,但也不是很稳健。
第二种方法,我似乎不知道如何做到这一点,是在画布中绘制形状,然后用粒子生成填充形状,将它们保持在形状的约束中。
任何关于如何做到这一点的一般概念将不胜感激。
如果没有意义请告诉我。
干杯
Ana*_*Ana 11
我们首先在画布上绘制我们想要的形状。
这个形状可以是任何形状。它可能是文本,也可能是图像中的形状 - 具有透明度的 .png 的非透明部分、.jpg 图像的非黑色或黑色部分 - 这并不重要,所有这些工作。
让我们从形状是文本的非常简单的情况开始。
我们将有一些常量(画布、上下文、RGBA 通道数、一个以我们想要打点的字符串开头的文本框对象、我们定义点半径的点网格和同一行/列上两个连续点之间的距离):
const _C = document.getElementById('c'),
CT = _C.getContext('2d'),
TEXT_BOX = { str: 'HELLO!' },
DOT_GRID = { gap: 6 },
NUM_CH = 'RGBA'.length;
Run Code Online (Sandbox Code Playgroud)
我们设置画布尺寸,然后计算一些有关文本的信息,以便它很好地适合画布的中间。并不是所有的字母都需要一个方形的盒子,有些字母(比如“I”)要窄得多,但是我们可以从这样的假设开始来获取文本框的高度,我们也将其存储在文本框对象中并设置作为字体大小:
TEXT_BOX.height = Math.min(.7*_C.height, _C.width/TEXT_BOX.str.length);
CT.font = `600 ${TEXT_BOX.height}px arial black, sans serif`;
CT.letterSpacing = '8px';
Run Code Online (Sandbox Code Playgroud)
我们还稍微间隔了字母。
然后,我们使用该字体大小和字母间距测量文本,以获得实际的文本框宽度。我们还计算左上角的坐标(我们将在点化文本时使用这些坐标)。
TEXT_BOX.width = CT.measureText(TEXT_BOX.str).width;
TEXT_BOX.x = .5*(_C.width - TEXT_BOX.width);
TEXT_BOX.y = .5*(_C.height - TEXT_BOX.height);
Run Code Online (Sandbox Code Playgroud)
我们为文本提供自定义填充样式(这是完全可选的,只有当您计划让文本在点网格下方可见时才真正需要),并将其沿其自己的垂直轴和水平轴居中对齐。
CT.fillStyle = 'purple';
CT.textAlign = 'center';
CT.textBaseline = 'middle';
Run Code Online (Sandbox Code Playgroud)
现在我们实际上可以在画布上绘制文本:
CT.fillText(TEXT_BOX.str, .5*_C.width, .5*_C.height);
Run Code Online (Sandbox Code Playgroud)

现在到了有趣的部分 - 我们对文本进行点化!
我们首先获取文本框矩形的画布图像数据。
let data = CT.getImageData(TEXT_BOX.x, TEXT_BOX.y,
TEXT_BOX.width, TEXT_BOX.height).data;
Run Code Online (Sandbox Code Playgroud)
这为我们提供了一个非常长的一维数组,其中包含文本框矩形内所有像素的 RGBA 值(逐行、逐列)。
/* 1st row, 1st column: */
R0, G0, B0, A0,
/* 1st row, 2nd column: */
R1, G1, B1, A1,
...
/* last row, last column: */
RN, GN, BN, AN
Run Code Online (Sandbox Code Playgroud)
然后,我们将此数组转换为像素对象数组,每个像素对象包含x,y每个像素的坐标和 RGBA 通道值作为数组。然后,我们过滤掉 alpha 所在的所有像素0(在文本形状之外),并且这些像素不是具有给定间隙的点网格的网格节点。这基本上给了我们想要在文本形状内绘制的点数组。
DOT_GRID.arr =
data.reduce((a, c, i, o) => {
if(i%NUM_CH === 0)
a.push({
x: (i/NUM_CH)%TEXT_BOX.width + TEXT_BOX.x,
y: Math.floor(i/NUM_CH/TEXT_BOX.width) + TEXT_BOX.y,
rgba: o.slice(i, i + NUM_CH)
});
return a
}, []).filter(c => c.rgba[NUM_CH - 1] &&
!(Math.ceil(c.x - .5*DOT_GRID.gap)%DOT_GRID.gap) &&
!(Math.ceil(c.y - .5*DOT_GRID.gap)%DOT_GRID.gap));
Run Code Online (Sandbox Code Playgroud)
如果愿意,我们可以删除点下方的文本。
CT.clearRect(TEXT_BOX.x, TEXT_BOX.y, TEXT_BOX.width, TEXT_BOX.height);
Run Code Online (Sandbox Code Playgroud)
然后我们画点,比如说填充gold。
CT.fillStyle = 'gold'
CT.beginPath();
DOT_GRID.arr.forEach(c => {
CT.moveTo(c.x, c.y);
CT.arc(c.x, c.y, DOT_GRID.rad, 0, 2*Math.PI)
});
CT.closePath();
CT.fill();
Run Code Online (Sandbox Code Playgroud)
我想在这里指出的一件事是,填充的计算成本很高,因此在这种情况下,所有点都具有相同的填充样式,我们将保留在循环fill()之外forEach,因此它在最后只被调用一次。
一般来说,如果某些东西不需要依赖于循环变量或在每次迭代中随机生成,请将其保留在循环之外!
这是点化结果与下面写的原始文本的样子:

...并且没有:

这就是非常基本的点化文本情况。
这是上面解释的非常基本的案例的工作片段。
const _C = document.getElementById('c'),
CT = _C.getContext('2d'),
TEXT_BOX = { str: 'HELLO!' },
DOT_GRID = { gap: 6 },
NUM_CH = 'RGBA'.length;
Run Code Online (Sandbox Code Playgroud)
TEXT_BOX.height = Math.min(.7*_C.height, _C.width/TEXT_BOX.str.length);
CT.font = `600 ${TEXT_BOX.height}px arial black, sans serif`;
CT.letterSpacing = '8px';
Run Code Online (Sandbox Code Playgroud)
TEXT_BOX.width = CT.measureText(TEXT_BOX.str).width;
TEXT_BOX.x = .5*(_C.width - TEXT_BOX.width);
TEXT_BOX.y = .5*(_C.height - TEXT_BOX.height);
Run Code Online (Sandbox Code Playgroud)
这是一个得到大量评论的版本,它也可以很好地处理页面大小调整。
当然,我们也可以给文本进行渐变填充,然后将渐变生成的 RGB 像素值用于网格上的点。它们也可以有不同的半径、位置的随机分量以及取决于指针的运动,如本例所示(请注意,在这种情况下,我清除了画布上绘制的原始文本)。

它对于图像的工作方式非常相似。我们在画布上绘制图像,读取图像数据,决定要排除哪些像素(也许是透明的,也许是黑色的,也许是白色的……没关系),然后只保留未排除的网格节点像素前一步。
假设我们有这张猫图像(具有透明度的.png)。我们排除透明像素,然后排除所有不是网格节点的像素。

我们可以使用 Base64 图像源来避免 CORS 问题。有很多网站可以进行转换(例如这个)。
我们复制它并将其设置为BASE64_SRC常量。
常量几乎相同,只是TEXT_BOX被替换为IMG_RECT:
const IMG_RECT = { img: new Image() }
Run Code Online (Sandbox Code Playgroud)
设置画布尺寸后,我们不会在画布上写入文本,而是继续绘制图像。
我们将图像源设置为 Base64 图像源。
IMG_RECT.img.src = BASE64_SRC;
Run Code Online (Sandbox Code Playgroud)
加载完成后,我们将继续根据其尺寸获取其纵横比。然后我们获取绘制图像的框的尺寸和左上角,使其适合画布。然后我们实际上在这个矩形内绘制图像。
IMG_RECT.img.onload = function() {
IMG_RECT.ratio = IMG_RECT.img.width/IMG_RECT.img.height;
IMG_RECT.width =
Math.min(IMG_RECT.img.width, _C.width, _C.height*IMG_RECT.ratio);
IMG_RECT.height =
Math.min(IMG_RECT.img.height, _C.height, _C.width/IMG_RECT.ratio);
IMG_RECT.x = .5*(_C.width - IMG_RECT.width);
IMG_RECT.y = .5*(_C.height - IMG_RECT.height);
CT.drawImage(IMG_RECT.img, IMG_RECT.x, IMG_RECT.y,
IMG_RECT.width, IMG_RECT.height);
}
Run Code Online (Sandbox Code Playgroud)
点化部分与之前完全相同,我们只是将所有出现的 替换TEXT_BOX为IMG_RECT。这样我们就有了点状的猫:

就像文本的情况一样,我们可以从点下方删除原始形状:

这是一个广受好评的演示,展示了这一点的实际效果。
我们实际上不必对我们的图像进行 Base64 处理。我们可以这样做:
const IMG_RECT = {
img: new Image(),
src: 'https://i.stack.imgur.com/KleBk.png'
}
Run Code Online (Sandbox Code Playgroud)
进而...
IMG_RECT.img.crossOrigin = 'anonymous';
IMG_RECT.img.src = IMG_RECT.src;
Run Code Online (Sandbox Code Playgroud)
这是使用此 CORS 设置的现场演示。
我们也不一定需要具有透明度的图像。我们还可以使用像这样的图像,其中芭蕾舞演员的深色形状与背景形成强烈对比。
在这种情况下,我们需要更改第一个过滤条件。我们不需要 alpha(第四个)通道非零,而是所有其他通道(RGB,前三个)都为相当低的值(我想在这种特殊情况下对它们求和也能达到目的):
Math.max(...c.rgba.slice(0, 3)) < 36
Run Code Online (Sandbox Code Playgroud)
这几乎可以做到(现场演示):

对于此棕榈树图像的工作方式相同:
点化版本(现场演示):

我们也可以采取另一种方式,对图像中不暗的部分进行点化:
特别是在这种情况下,我们可以只关注蓝色(第三个)通道并使用以下过滤条件:
c.rgba[2] > 200
Run Code Online (Sandbox Code Playgroud)
现场演示。

就像渐变文本的情况一样,我们也可以将原始图像中的 RGB 值用于点化版本。
假设我们从这张图片开始:

点化它以保留 RGB 通道给我们这个(现场演示):

这个演示(请注意,它已经有近十年的历史了,JS 变得更好,希望我也如此......无论如何,该代码可以改进)使用类似的技术将图像转换为瓷砖网格,然后折叠。
| 归档时间: |
|
| 查看次数: |
2357 次 |
| 最近记录: |