探索CSS绘画API——图像碎片化的效果

161 阅读9分钟

我之前的文章中,我使用CSS遮罩和自定义属性创建了一个碎片化的效果。这是一个整洁的效果,但它有一个缺点:它使用了大量的CSS代码(用Sass生成)。这一次,我打算重新做同样的效果,但要依靠新的Paint API。这大大减少了CSS的数量,并完全消除了对Sass的需求。

下面是我们要做的东西。和上一篇文章一样,目前只有Chrome和Edge支持这个。

CodePen嵌入回退

看到了吗?没有超过五个CSS声明,但我们得到了一个相当酷的悬停动画。

什么是Paint API?

Paint API是Houdini项目的一部分。是的,"Houdini "这个奇怪的术语,大家都在谈论。很多文章已经涵盖了它的理论方面,所以我就不多费口舌了。如果要我用几个词来概括它,我只想说:它是CSS的未来。Paint API(以及属于Houdini伞下的其他API)允许我们用我们自己的功能来扩展CSS。我们不再需要等待新功能的发布,因为我们可以自己来做!"。

规范上看。

一个允许Web开发者用javascript定义一个自定义CSS的API。 <image>与javascript[原文如此],它将对样式和尺寸的变化做出反应。

以及来自解释者

开发CSS绘画API是为了提高CSS的可扩展性。具体来说,它允许开发者编写一个绘画函数,使我们能够直接在一个元素[原文如此]的背景、边框或内容中绘画。

我认为这个想法很清楚。我们可以画出我们想要的东西。让我们从一个非常基本的背景着色的演示开始。

CodePen嵌入回退

  1. 我们使用CSS.paintWorklet.addModule('your_js_file') 添加绘画工作小程序。
  2. 我们注册一个新的绘画方法,叫做draw
  3. 在这里面,我们创建了一个paint() 函数,在这里我们做所有的工作。你猜怎么着?一切就像使用<canvas> 。那个ctx 是二维上下文,我简单地使用了一些众所周知的函数来画一个覆盖整个区域的红色矩形。

这乍一看可能不直观,但请注意,主要结构总是相同的:上面的三个步骤是 "复制/粘贴 "部分,你在每个项目中都要重复。真正的工作是我们在paint() 函数里面写的代码。

让我们添加一个变量。

CodePen Embed Fallback

正如你所看到的,逻辑是非常简单的。我们用我们的变量定义getterinputProperties ,作为一个数组。我们将properties 作为第三个参数添加到paint() ,随后我们使用properties.get() 来获取我们的变量。

这就是了!现在我们有了建立我们复杂的碎片化效果所需要的一切。

构建遮罩

你可能想知道为什么要用paint API来创建一个碎片化效果。我们说过它是一个绘制图像的工具,那么它是如何让我们对图像进行分片的呢?

在上一篇文章中,我使用了不同的遮罩层来做这个效果,每个遮罩层都是一个用梯度定义的正方形(记住,梯度是一种图像),所以我们得到了一种矩阵,诀窍是单独调整每个通道的alpha。

这一次,我们将不再使用许多渐变,而是只为我们的遮罩定义一个自定义图像,这个自定义图像将由我们的绘画API处理。

请举一个例子!

CodePen 嵌入回退

在上面,我创建了一个图像,左边是不透明的颜色,右边是半透明的颜色。应用这个图像作为掩码,给了我们一个半透明的图像的逻辑结果。

现在我们需要做的是将我们的图像分割成更多的部分。让我们定义两个变量并更新我们的代码。

CodePen嵌入回退

代码的相关部分如下。

const n = properties.get('--f-n');
const m = properties.get('--f-m');

const w = size.width/n;
const h = size.height/m;

for(var i=0;i<n;i++) {
  for(var j=0;j<m;j++) {
    ctx.fillStyle = 'rgba(0,0,0,'+(Math.random())+')';    
    ctx.fillRect(i*w, j*h, w, h);
}
}

NM 定义我们的矩形矩阵的尺寸。WH 是每个矩形的尺寸。然后我们有一个基本的FOR 循环,为每个矩形填充一个随机的透明颜色。

通过一点JavaScript,我们可以得到一个自定义的遮罩,我们可以通过调整CSS变量来轻松控制。

CodePen嵌入回退

现在,我们需要控制alpha通道,以创造每个矩形的渐变效果,并建立碎片化的效果。

让我们引入第三个变量,我们用于alpha通道,在悬停时也会改变。

CodePen嵌入回退

我们定义了一个CSS自定义属性作为<number> ,我们从1过渡到0,同样的属性被用来定义我们矩形的alpha通道。悬停时不会发生什么花哨的事情,因为所有的矩形都会以同样的方式褪色。

我们需要一个技巧来防止所有的矩形同时褪色,而是在它们之间创造一个延迟。这里有一个插图来解释我将要使用的想法。

上面显示的是两个矩形的alpha动画。首先,我们定义一个变量L,它应该大于或等于1,然后对于我们矩阵中的每个矩形(即每个alpha通道),我们在XY 之间进行过渡,其中X - Y = L ,所以我们对所有alpha通道有相同的整体持续时间。X应该大于或等于1,Y应该小于或等于0。

等等,alpha值不应该在[1 0] ,对吗?

是的,应该是这样的!而我们正在研究的所有技巧都依赖于此。上面,阿尔法值从8到-2的动画,意味着我们在[8 1] 范围内有一个不透明的颜色,在[0 -2] 范围内有一个透明的颜色,在[1 0]内有一个动画。换句话说,任何大于1的值都会产生与1相同的效果,而任何小于0的值都会产生与0相同的效果。

[1 0] 内的动画将不会同时发生在我们的两个矩形上。矩形2将在矩形1之前达到[1 0] 。我们将此应用于所有的alpha通道,以获得我们的延迟动画。

在我们的代码中,我们将更新这个。

rgba(0,0,0,'+(o)+')

...到这里。

rgba(0,0,0,'+((Math.random()*(l-1) + 1) - (1-o)*l)+')

L 是之前说明的变量,而O 是我们的CSS变量的值,从1过渡到0。

O=1 ,我们有(Math.random()*(l-1) + 1) 。考虑到random() 函数给我们一个在[0 1] 范围内的值,最终的值将在[L 1]范围内。

O=0 时,我们有(Math.random()*(l-1) + 1 - l) 和一个在[0 1-L] 范围内的值。

L 是我们用来控制延迟的变量。

让我们看看这个动作。

CodePen嵌入回退

我们越来越接近了。我们有一个很酷的碎片化效果,但不是我们在文章开头看到的那个。这个没有那么平滑。

这个问题与random() 功能有关。我们说过,每个alpha通道需要在XY 之间产生动画,所以从逻辑上讲,这些值需要保持不变。但是,在转换过程中,paint() 函数被多次调用,所以每次,random() 函数为每个alpha通道提供不同的XY 值;因此,我们得到的是 "随机 "效果。

为了解决这个问题,我们需要找到一种方法来存储生成的值,使它们在每次调用paint() 函数时总是相同的。让我们考虑一个伪随机函数,一个总是生成相同数值序列的函数。换句话说,我们想控制种子

不幸的是,我们无法用JavaScript内置的random() 函数做到这一点,所以像所有好的开发者一样,让我们从Stack Overflow上挑选一个

const mask = 0xffffffff;
const seed = 30; /* update this to change the generated sequence */
let m_w  = (123456789 + seed) & mask;
let m_z  = (987654321 - seed) & mask;

let random =  function() {
  m_z = (36969 * (m_z & 65535) + (m_z >>> 16)) & mask;
  m_w = (18000 * (m_w & 65535) + (m_w >>> 16)) & mask;
  var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
  result /= 4294967296;
  return result;
}

而结果就变成了。

CodePen嵌入回退

我们没有复杂的代码就有了我们的碎片化效果。

  • 一个基本的嵌套循环来创建NxM的矩形
  • 一个巧妙的通道alpha公式来创建过渡延迟
  • 一个来自网络的现成的random() 函数

这就是了!你所要做的就是应用你所要做的就是将mask 属性应用于任何元素,并调整CSS变量。

与空隙作斗争!

如果你看了上面的演示,你会注意到,在某些特殊情况下,矩形之间有奇怪的缝隙

为了避免这种情况,我们可以用一个小的偏移来扩展每个矩形的面积。

我们更新这个。

ctx.fillRect(i*w, j*h, w, h);

...用这个。

ctx.fillRect(i*w-.5, j*h-.5, w+.5, h+.5);

它在矩形之间创造了一个小的重叠,弥补了它们之间的间隙。我使用的数值0.5 ,没有什么特别的逻辑。你可以根据你的使用情况,做得更大或更小。

CodePen 嵌入回退

想要更多的形状?

上面的内容可以扩展到考虑更多的矩形形状吗?当然可以!别忘了,我们可以用 Canvas 绘制任何一种形状 -- 不像纯 CSS 形状,我们有时需要一些黑客代码。让我们试着建立那种三角形的碎裂效果。

在网上搜索后,我发现了一个叫Delaunay triangulation的东西。我不会去研究它背后的深层理论,但它是一种为一组点绘制具有特定属性的连接三角形的算法。有很多现成的实现方法,但我们将使用Delaunator,因为它应该是最快的一种。

我们首先定义一组点(这里我们将使用random() ),然后运行Delauntor为我们生成三角形。在这种情况下,我们只需要一个变量来定义点的数量。

const n = properties.get('--f-n');
const o = properties.get('--f-o');
const w = size.width;
const h = size.height;
const l = 7; 

var dots = [[0,0],[0,w],[h,0],[w,h]]; /* we always include the corners */
/* we generate N random points within the area of the element */
for (var i = 0; i < n; i++) {
  dots.push([random() * w, random() * h]);
}
/**/
/* We call Delaunator to generate the triangles*/
var delaunay = Delaunator.from(dots);
var triangles = delaunay.triangles;
/**/
for (var i = 0; i < triangles.length; i += 3) { /* we loop the triangles points */
  /* we draw the path of the triangles */
  ctx.beginPath();
  ctx.moveTo(dots[triangles[i]][0]    , dots[triangles[i]][1]);
  ctx.lineTo(dots[triangles[i + 1]][0], dots[triangles[i + 1]][1]);
  ctx.lineTo(dots[triangles[i + 2]][0], dots[triangles[i + 2]][1]);  
  ctx.closePath();
  /**/
  var alpha = (random()*(l-1) + 1) - (1-o)*l; /* the alpha value */
  /* we fill the area of triangle with the semi-transparent color */
  ctx.fillStyle = 'rgba(0,0,0,'+alpha+')';
  /* we consider stroke to fight the gaps */
  ctx.strokeStyle = 'rgba(0,0,0,'+alpha+')';
  ctx.stroke();
  ctx.fill();
} 

对于上述代码中的注释,我没有更多的东西可以补充。我只是简单地使用了一些基本的JavaScript和Canvas的东西,但我们却有一个相当酷的效果。

CodePen 嵌入回退

我们可以做出更多的形状!我们要做的就是为它找到一种算法。

我不能在不做六边形的情况下继续前进!

CodePen 嵌入回退

我从Izan Pérez Cosano写的这篇文章中提取了代码。我们的变量现在是R ,它将定义一个六角形的尺寸。

接下来是什么?

现在我们已经建立了我们的碎片化效果,让我们把重点放在CSS上。注意,这个效果很简单,就是在一个元素的悬停状态下改变它的opacity (或者你正在使用的任何属性的值)。

不透明度动画

img {
  opacity:1;
  transition:opacity 1s;
}

img:hover {
  opacity:0;
}

碎片化的效果

img {
  -webkit-mask: paint(fragmentation);
  --f-o:1;
  transition:--f-o 1s;
}

img:hover {
  --f-o:0;
}

这意味着我们可以很容易地整合这种效果来创造更复杂的动画。这里有一堆的想法!

响应式图像滑块

CodePen 嵌入回退

同一滑块的另一个版本。

CodePen Embed Fallback

噪音效果

CodePen 嵌入回退

加载屏幕

CodePen 嵌入回退

卡片悬停效果

CodePen 嵌入回退

这就是一个总结

而这一切只是使用Paint API可以实现的冰山一角。我将以两个重要的观点结束。

  • Paint API的90%是<canvas> ,所以你对<canvas> ,你能做的花哨事情就越多。Canvas被广泛使用,这意味着有很多关于它的文档和文章可以让你快速掌握。嘿,这里就有一个CSS-Tricks的文档
  • Paint API消除了CSS方面的所有复杂性。不需要处理复杂的黑客代码来绘制很酷的东西。这使得CSS代码更容易维护,更不用说出错的几率了。

The postExploring the CSS Paint API:图像分割效果》首次出现在CSS-Tricks上。你可以通过成为MVP支持者来支持CSS-Tricks。