效果图
PIXI版本:v8.2.2
官网:pixijs.com/
素材图
背景图(fantasy.jpg)
涟漪 = 置换精灵图(texture.jpg)
在线置换精灵图: www.kkkk1000.com/images/lear…
HTML代码
<!DOCTYPE html>
<html lang="zn">
<head>
<meta charset="UTF-8" />
<title>Pixi JS 置换滤镜效果</title>
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<!-- <div class="btnBox"> -->
<div class="btnBox" style="display: none">
<button class="start-btn">开始</button>
<button class="stop-btn">停止</button>
</div>
<!-- pixi操作的对象 -->
<div id="px-render"></div>
<script src="./js/pixi.min.js"></script>
<script type="module" src="./js/base.js"></script>
</body>
</html>
CSS
body {
display: flex;
flex-wrap: wrap;
flex-direction: column;
width: 100vw;
height: 100vh;
margin: 0px;
padding: 0px;
position: relative;
top: 0;
left: 0;
}
#px-render {
width: 100%;
flex: 1;
overflow: hidden;
display: flex;
align-items: center;
justify-items: center;
}
JS代码
首先在html导入pixi.js
涟漪类(核心)
/**
* PIXIJS涟漪动画
* @description 生成一个图片的涟漪动画
* @since 2024-07-10 12:42:37
* @author Ayo
*
* @parentDom {Node} 父容器
* @bgUrl {String} 图片地址
* @velocity {Number} 涟漪速度
* @filterUrl {String} 置换图地址
*
* @returns {rippleView}
*
* @example
* const example = new rippleView(playground, bgUrl, 1);
* example.start();
*/
export default class rippleView {
constructor(parentDom, bgUrl, velocity, filterUrl) {
//验证容器是否存在且是否为dom
if (!parentDom || !parentDom.nodeType) {
throw new Error('父容器不符合规范(不存在或不是dom)');
}
//验证bgUrl是否为图片
if (!bgUrl || !/(\.jpg|\.jpeg|\.png|\.gif)$/i.test(bgUrl)) {
throw new Error('图片对象不存在');
}
//父容器
this.parentDom = parentDom;
//背景图
this.bgUrl = bgUrl || '';
//水波图
this.filterUrl = filterUrl || './img/texture.jpg';
//是否在进行水波渲染
this.isPlay = false;
// 创建一个 Pixi应用
this.app = new PIXI.Application();
//canvas对象
this.canvasDom;
// 图片精灵
this.preview;
// 置换图精灵
this.displacementSprite;
// 滤镜
this.displacementFilter;
// 舞台(一个容器),这里面包括了图片精灵、置换图精灵
this.stage;
//配置
this.option = {
width: parentDom.clientWidth,
height: parentDom.clientHeight,
transparent: true,
backgroundColor: 0x000000,
};
// 置换图精灵的移动速度
this.velocity = velocity || 1;
/* 执行的动画函数 => 暴露出来方便移除 */
this.tickerFunction;
//防止animate方法丢失上下文指向
this.animate = this.animate.bind(this);
//计时器对象
this.timer = null;
//监听器对象
this.observe = null;
}
/* 初始化 */
async start() {
await this.app.init(this.option);
this.canvasDom = this.app.canvas;
//加载图片资源
PIXI.Assets.addBundle('resource', {
bg: this.bgUrl,
filter: this.filterUrl,
});
// const assets = await Assets.loadBundle('animals');
await PIXI.Assets.loadBundle('resource');
this.setScene();
//容器尺寸监听
this.resize();
}
/* 开始执行 */
setScene() {
// 把 Pixi 创建的 canvas 添加到页面上
this.parentDom.appendChild(this.canvasDom);
// 创建一个容器
this.stage = new PIXI.Container();
/* 背景图加载 设置 */
const texture = PIXI.Sprite.from(this.bgUrl);
this.preview = new PIXI.Sprite(texture);
this.preview.x = 0;
this.preview.y = 0;
/* 设置容器缩放 => 让背景图等比缩放 */
this.setScale();
/* 处理纹理 */
this.displacementSprite = PIXI.Sprite.from(this.filterUrl);
// 设置置换图精灵为平铺模式
this.displacementSprite.texture.baseTexture.wrapMode = PIXI.WRAP_MODES.MIRRORED_REPEAT;
// 创建一个置换滤镜
this.displacementFilter = new PIXI.DisplacementFilter(this.displacementSprite);
// 添加 图片精灵 到舞台
this.stage.addChild(this.preview);
// 添加 置换图精灵 到舞台
this.stage.addChild(this.displacementSprite);
// 把 stage 添加到根容器上
this.app.stage.addChild(this.stage);
/* 播放 */
this.playRipple();
}
/* 设置容器缩放 => 让背景图等比缩放 */
setScale(resizeApp = false) {
//是否要充值canvas尺寸
if (resizeApp) {
this.app.renderer.resize(this.parentDom.clientWidth, this.parentDom.clientHeight);
}
const scale = Math.max(
this.app.view.width / this.preview.width,
this.app.view.height / this.preview.height
);
this.stage.scale = scale;
}
/* 监听窗口尺寸变化 */
resize() {
let that = this;
// 配置观察器
const config = { attributes: true, childList: true, subtree: true };
// 启动观察器
this.observe = new ResizeObserver((mutation, ob) => {
that.timer && clearTimeout(that.timer) && (that.timer = null);
that.timer = setTimeout(() => {
that.setScale(true);
that.timer = null;
}, 300);
});
// 启动观察器
this.observe.observe(this.parentDom, config);
}
/* 执行的动画函数 */
animate() {
let that = this;
this.tickerFunction = requestAnimationFrame(that.animate);
// 改变置换图精灵的位置
this.displacementSprite.x += this.velocity;
this.displacementSprite.y += this.velocity;
}
//播放涟漪
playRipple() {
if (!this.isPlay) {
this.isPlay = true;
// 设置舞台的滤镜
this.stage.filters = [this.displacementFilter];
// 开始动画
this.animate();
}
}
//停止涟漪
stopRipple() {
this.isPlay = false;
this.displacementSprite.x = 0;
this.displacementSprite.y = 0;
// 取消滤镜
this.stage.filters = [];
// 停止动画
cancelAnimationFrame(this.tickerFunction);
}
destroyed() {
//停止涟漪
this.stopRipple();
//
this.stage.destroy();
this.app.destroy();
//移除canvas容器
this.parentDom.removeChild(this.canvasDom);
//移除尺寸监听
this.observe.unobserve();
this.observe = null;
}
}
调用涟漪类
import rippleView from './rippleView.js';
const bgUrl = './img/fantasy.jpg';
const filterUrl = './img/texture.jpg';
const playground = document.getElementById('px-render');
let example;
function init() {
example = new rippleView(playground, bgUrl, 1, filterUrl);
window.example = example;
example.start();
}
init();
//绑定点击事件
let start = document.querySelector('.start-btn');
let stop = document.querySelector('.stop-btn');
start.onclick = () => {
example.playRipple();
};
stop.onclick = () => {
example.stopRipple();
};