乐趣区

关于前端:使用CSS-Paint-API实现有趣的图像碎片效果

前言

在这之前我写过这样一篇文章教你用 canvas 打造一个炫酷的碎片切图成果,这个成果是用 Canvas 来实现的,当初想着尝试应用 CSS Paint API 来实现一个相似的碎片成果。

如果这篇文章有帮忙到你,❤️关注 + 点赞❤️激励一下作者,文章公众号首发,关注 前端南玖 第一工夫获取最新文章~

简略介绍下 CSS Paint API

CSS Paint API 是 Houdini 我的项目的一部分。它容许咱们应用本人的性能扩大 CSS,所以咱们能够不再须要期待新性能的公布,齐全能够本人实现想要的新性能,能够说 CSS Paint API 就是 CSS 的将来。Houdini 是一组底层 API,它裸露一部分 CSS 引擎给开发者染指浏览器渲染与布局的能力,从而可能扩大 CSS。

W3C 上对于 CSS Paint API 的介绍是这样的:

CSS Paint API 容许开发者应用 JavaScript 创立对款式与大小变动自适应的 CSS 图像。这种形式创立的图像,能够通过调用 paint() 函数,利用在诸如 background-imageborder-imagemask-image 等属性上。

有了这个 API,咱们在 CSS 中能够绘制任意本人想要的图案了,是不是泰裤辣!

应用

应用流程具体能够依照这张图来即可:

  1. 创立一个 js 文件,应用 registerPaint() 办法注册一个 PaintWorklet
  2. 调用 addModule() 办法增加此模块
  3. 在 CSS 中应用 paint() 函数调用

更具体的介绍能够在 MDN 上查看,理解完大略的应用流程咱们就能够开始尝试实现咱们的图像碎片成果了

开始制作

初步绘制

首先咱们应用 Paint API 将咱们的图片绘制成为一半不通明一半透明的成果:

<template>
  <div :class="$style.paint_container">
    <img src="/public/3.jpg" :class="$style.paint_img1" />
  </div>
</template>

<script lang="ts" setup>
import {onMounted} from "vue";
// import paintSuipian from "../../utils/paintSuipian";

const paint = () => {if (CSS.paintWorklet) {CSS.paintWorklet.addModule("/src/utils/paintSuipian1.js");
  }
};

onMounted(() => {paint();
});
</script>
<style lang="scss" module>
.paint_img1 {
  width: 400px;
  height: auto;
  --m: 12;
  --n: 12;
  -webkit-mask: paint(suipian1);
}
</style>
// paintSuipian1.js
registerPaint(
  "suipian1",
  class {paint(ctx, size) {console.log("ctx:", ctx, "size:", size);
      /* left */
      ctx.fillStyle = "rgba(0,0,0,1)";
      ctx.fillRect(0, 0, size.width / 2, size.height);
      /* right */
      ctx.fillStyle = "rgba(0,0,0,0.5)";
      ctx.fillRect(size.width / 2, 0, size.width / 2, size.height);
    }
  }
);

从这里来看,paint 外部的一些操作 API 其实与咱们相熟的 Canvas 有点相似,ctx 就是以后的绘制上下文,而 size 则是利用这个 paint 元素的宽高大小。

所以从这里看上去了解并不难,与 Canvas 差不多,通过 fillRect 绘制矩形,而后通过 fillStyle 填充染色,咱们就能看到上面这个成果:

看到这个成果咱们是不是就可能设想到之前的那种碎片成果应该怎么去实现呢?

这下面其实就是绘制了两个矩形,别离填充了不同的透明度,那么咱们如果绘制多一点的矩形也让它填充不同的透明度会是什么样子呢,大家能够设想一下🤔…

略微加工

为了更加实用,咱们能够通过定义 CSS 变量来达到管制碎片密度的成果。

// paintSuipian1.js
registerPaint(
  "suipian1",
  class {static get inputProperties() {return ["--m", "--n"];
    }
    paint(ctx, size, properties) {console.log("ctx:", ctx, "size:", size);
      const m = properties.get("--m");
      const n = properties.get("--n");

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

      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);
        }
      }
    }
  }
);

此时咱们的 js 改的略微简单了一点,其实也还好,只是定义了一下矩形矩阵的维度,而后计算了没个碎片的宽高,再通过 for 循环进行绘制。

这里咱们能够通过 get inputProperties 来获取咱们 CSS 中定义的变量,须要留神的是这里的获取的变量也是有作用域的,它只能获取到本人身定义的变量或者是它继承而来的 CSS 变量

.paint_img1 {
  width: 400px;
  height: auto;
  --m: 12;
  --n: 12;
  -webkit-mask: paint(suipian1);
}

当初这张图片会变成什么样呢,咱们来看一下:

这样一看是不是有点感觉了

咱们能够通过管制 CSS 变量来实现不同的成果,比方:

.paint_img1 {
  width: 400px;
  height: auto;
  --m: 22;
  --n: 22;
  -webkit-mask: paint(suipian1);
}

增加动画

到这了,整个碎片的轮廓大略是有了,当初须要做的是怎么为这些碎片加上动画?

这里有个技巧是咱们能够通过应用 @property 来自定义一个属性,这个属性其实是一个 Number 值,而这个值又恰好是咱们每个碎片计算随机透明度须要用的值,而后咱们再应用 transition 再对这个自定义属性进行过渡。

OK,咱们来尝试一下:

// paintSuipian1.js
registerPaint(
  "suipian1",
  class {static get inputProperties() {return ["--m", "--n", "--f"];
    }
    paint(ctx, size, properties) {console.log("ctx:", ctx, "size:", size);
      const m = properties.get("--m");
      const n = properties.get("--n");
      const f = properties.get("--f");

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

      for (var i = 0; i < n; i++) {for (var j = 0; j < m; j++) {ctx.fillStyle = "rgba(0,0,0," + f + ")";
          ctx.fillRect(i * w - 0.5, j * h - 0.5, w + 0.5, h + 0.5);
        }
      }
    }
  }
);
.paint_img1 {
  width: 400px;
  height: auto;
  --m: 10;
  --n: 10;
  --f: 1;
  -webkit-mask: paint(suipian1);
  transition: --f 1s;
}
.paint_img1:hover {--f: 0;}

看看成果:

这里看着有点怪怪的,因为所有的碎片都会同时暗藏呈现,并没有达到咱们想要的成果,所以这里咱们还得想方法避免所有的碎片同时显示暗藏🤔

最初优化

这里其实须要通过一种算法来让每个碎片的不透明度达到一个随机的成果。

// paintSuipian.js
registerPaint(
  "suipian",
  class {static get inputProperties() {return ["--color", "--m", "--n", "--f"];
    }
    paint(ctx, size, properties) {console.log("ctx:", ctx, "size:", size, properties.get("--color"));
      const m = properties.get("--m");
      const n = properties.get("--n");
      const f = properties.get("--f");
      const w = size.width / m;
      const h = size.height / n;
      const l = 10;

      const mask = 0xffffffff;
      const seed = 30;
      let m_w = (123456789 + seed) & mask;
      let m_z = (987654321 - seed) & mask;
            // 随机算法
      const random = function () {m_z = (36969 * (m_z & 65535) + (m_z >>> 16)) & mask;
        m_w = (18000 * (m_w & 65535) + (m_w >>> 16)) & mask;
        let result = ((m_z << 16) + (m_w & 65535)) >>> 0;
        result /= 4294967296;
        return result;
      };

      for (let i = 0; i < n; i++) {for (let j = 0; j < m; j++) {
          ctx.fillStyle =
            "rgba(0,0,0," + (random() * (l - 1) + 1 - (1 - f) * l) + ")";
          ctx.fillRect(i * w - 1, j * h - 1, w + 1, h + 1);
        }
      }
    }
  }
);
.paint_img {
  width: 400px;
  height: auto;
  --color: rgba(255, 255, 255, 1);
  --m: 10;
  --n: 10;
  --f: 1;
  -webkit-mask: paint(suipian);
  transition: --f 1s;
}
.paint_img:hover {--f: 0;}

最初的成果:

欢送大家关注公众号 「前端南玖」

我是南玖,咱们下期见!!!

退出移动版