前言
在这之前我写过这样一篇文章教你用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的介绍是这样的:
CSSPaint API
容许开发者应用 JavaScript 创立对款式与大小变动自适应的 CSS 图像。这种形式创立的图像,能够通过调用paint()
函数,利用在诸如background-image
、border-image
和mask-image
等属性上。
有了这个API,咱们在CSS中能够绘制任意本人想要的图案了,是不是泰裤辣!
应用
应用流程具体能够依照这张图来即可:
- 创立一个 js 文件,应用
registerPaint()
办法注册一个 PaintWorklet - 调用
addModule()
办法增加此模块 - 在 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.jsregisterPaint( "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.jsregisterPaint( "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.jsregisterPaint( "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.jsregisterPaint( "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;}
最初的成果:
欢送大家关注公众号 「前端南玖」
我是南玖,咱们下期见!!!