使用Curtains.js在动画完成时运行一个函数



我想在抖动效果结束时调用一个函数。

也就是说,在阻尼效应结束时(当摆动停止时(,我想执行一个GSAP时间轴函数。我认为这种类型的";onComplete";函数将需要在Curtains的onReady()内部调用,并可能通过跟踪阻尼效应来调用。我只熟悉GSAP的onComplete函数,但不知道如何在这里实现它。也许有什么东西可以检查deltas.applied是否小于0.001,然后调用该函数?

下面是代码片段(不包含片段着色器和顶点着色器(。此处提供完整的工作代码:CodePen

class Img {
constructor() {
const curtain = new Curtains({
container: "canvas",
watchScroll: false,
});

const params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
}
}
}
const planeElements = document.getElementsByClassName("plane")[0];  

this.plane = curtain.addPlane(planeElements, params);
if (this.plane) {
this.plane
.onReady(() => {
this.introAnim();
})
.onRender(() => {
this.plane.uniforms.time.value++;
deltas.applied += (deltas.max - deltas.applied) * 0.05;
deltas.max += (0 - deltas.max) * 0.07;
this.plane.uniforms.prog.value = deltas.applied 
})
}
// error handling
curtain.onError(function() {
document.body.classList.add("no-curtains");
});
}

introAnim() {
deltas.max = 6;
//console.log("complete") <-- need an onComplete type function~!
}
}
window.onload = function() {
const img = new Img();
}

您可以使用一些代数:(

首先,您应该简化deltas.max函数,如下所示:

deltas.max += (0 - deltas.max) * 0.07;
// Simplifies to
deltas.max -= deltas.max * 0.07;
// Rewrite to
deltas.max = deltas.max - deltas.max * 0.07;
// Rewrite to
deltas.max = deltas.max * (1 - 0.07); 
// Simplifies to
deltas.max *= 0.93; // Much nicer :)

这实际上非常重要,因为它使我们计算时间变量的最终值和动画的持续时间的工作变得更加容易

// Given deltas.max *= 0.93, need to calculate end time value
// endVal = startVal * reductionFactor^n
// Rewrite as 
// n = ln(endVal / startVal) / ln(reductionFactor) // for more see https://www.purplemath.com/modules/solvexpo2.htm
// n = ln(0.001 / 8) / ln(0.93)
const n = 123.84;

// Assuming 60fps normally: n / 60
const dur = 2.064;

一旦我们有了这些值,我们所要做的就是创建一个线性的tween,用这个持续时间将我们的时间动画化到这个值,并更新onUpdate:中的最大值和prog值

gsap.to(this.plane.uniforms.time, {
value: n,
duration: dur,
ease: "none",
onUpdate: () => {
this.deltas.applied += (this.deltas.max - this.deltas.applied) * 0.05;
this.deltas.max *= 0.93;
this.plane.uniforms.prog.value = this.deltas.applied;
},
onComplete: () => console.log("complete!")
});

然后你得到";完成"动画结束时!

为了确保您的Curtains动画即使在具有高刷新率的显示器上也能以正确的速率运行(即使是那些没有直接使用GSAP制作动画的显示器(,最好关闭Curtain的autoRendering并使用GSAP的自动收报机:

const curtains = new Curtains({ container: "canvas", autoRender: false });
// Use a single rAF for both GSAP and Curtains
function renderScene() {
curtains.render();
}
gsap.ticker.add(renderScene);

总之,你得到了这个演示。

这不是最好的答案,但你可以从中获得一些想法和见解。

打开控制台,查看动画完成后只会激发一次。


//Fire an onComplete event and listen for that
const event = new Event('onComplete');
class Img {
constructor() {
// Added a instance variable for encapsulation
this.animComplete = {anim1: false}
//Changed code above
const curtain = new Curtains({
container: "canvas",
watchScroll: false,
});
const params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
}
}
}
const planeElements = document.getElementsByClassName("plane")[0];  

this.plane = curtain.addPlane(planeElements, params);
if (this.plane) {
this.plane
.onReady(() => {
this.introAnim();

document.addEventListener('onComplete', ()=>{
//Do damping effects here
console.log('complete')
})
})
.onRender(() => {
this.plane.uniforms.time.value++;
deltas.applied += (deltas.max - deltas.applied) * 0.05;
deltas.max += (0 - deltas.max) * 0.07;
this.plane.uniforms.prog.value = deltas.applied 
if(deltas.applied<0.001 && !this.animComplete.anim1){
document.dispatchEvent(event)
this.animComplete.anim1 = true
}
})
}
// error handling
curtain.onError(function() {
document.body.classList.add("no-curtains");
});
}

introAnim() {
deltas.max = 6;
}
}
window.onload = function() {
const img = new Img();
}

我找到了一个在阻尼(摆动(效果结束时调用函数的解决方案,它不使用GSAP,而是使用Curtains onRender方法。由于uTime值无限上升,uProg值接近0,因此通过在Curtains onRender方法中跟踪uTime和uProg的值,我们可以找到阻尼效果基本完成的点(2个阈值(。不确定这是否是最有效的方法,但它似乎有效。

.onRender(() => {
if (this.plane.uniforms.prog.value < 0.008 && this.plane.uniforms.time.value > 50) { console.log("complete")}
})

多亏了Curtains文档的异步纹理,我能够更好地控制抖动效果的时间,每次都能获得所需的结果。也就是说,在FPS较低的计算机上,整个阻尼效果会顺利发生,最后会调用onComplete函数,在帧速率较高的comp上也是如此。

尽管如前所述,对效果长度的控制较少,因为我们没有使用GSAP来控制Utime值。谢谢@Zach!然而,使用";阈值检查";在窗帘内部以这种方式渲染,意味着阻尼摆动效果永远不会受到影响,如果我们在完成调用时禁用绘图。

通过在加载图像的同时启用绘图,我们可以避免任何不稳定的行为。以下内容现在也适用于硬刷新。

export default class Img {
constructor() {
this.deltas = {
max: 0,
applied: 0,
};
this.curtain = new Curtains({
container: "canvas",
watchScroll: false,
pixelRatio: Math.min(1.5, window.devicePixelRatio),
});
this.params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
},
},
};
this.planeElements = document.getElementsByClassName("plane")[0];
this.curtain.onError(() => document.body.classList.add("no-curtains"));
this.curtain.disableDrawing(); // disable drawing to begin with to prevent erratic timing issues
this.init();
}
init() {
this.plane = new Plane(this.curtain, this.planeElements, this.params);
this.playWobble();
}
loaded() {
return new Promise((resolve) => {
// load image and enable drawing as soon as it's ready
const asyncImgElements = document
.getElementById("async-textures-wrapper")
.getElementsByTagName("img");
// track image loading
let imagesLoaded = 0;
const imagesToLoad = asyncImgElements.length;
// load the images
this.plane.loadImages(asyncImgElements, {
// textures options
// improve texture rendering on small screens with LINEAR_MIPMAP_NEAREST minFilter
minFilter: this.curtain.gl.LINEAR_MIPMAP_NEAREST,
});
this.plane.onLoading(() => {
imagesLoaded++;
if (imagesLoaded === imagesToLoad) {
console.log("loaded");
// everything is ready, we need to render at least one frame
this.curtain.needRender();
// if window has been resized between plane creation and image loading, we need to trigger a resize
this.plane.resize();
// show our plane now
this.plane.visible = true;
this.curtain.enableDrawing();
resolve();
}
});
});
}

playWobble() {
if (this.plane) {
this.plane
.onReady(() => {
this.deltas.max = 7; // 7
})
.onRender(() => {
this.plane.uniforms.time.value++;
this.deltas.applied += (this.deltas.max - this.deltas.applied) * 0.05;
this.deltas.max += (0 - this.deltas.max) * 0.07;
this.plane.uniforms.prog.value = this.deltas.applied;
console.log(this.plane.uniforms.prog.value);
// ----  "on complete" working!! ( even on hard refresh) -----//
if (
this.plane.uniforms.prog.value < 0.001 &&
this.plane.uniforms.time.value > 50
) {
console.log("complete");
this.curtain.disableDrawing();
}
});
}
}
destroy() {
if (this.plane) {
this.curtain.disableDrawing();
this.curtain.dispose();
this.plane.remove();
}
}
}
const img = new Img();
Promise.all([img.loaded()]).then(() => {
console.log("animation started");
});

最新更新