目前尝试着做一些有意思的页面特效而不仅限于这静态页面,该特效实现了视频的播放和动画的专场。
该功能分为两个部分,分别是视频播放和视频专场
- 视频播放基于video标签实现动画的播放。
- 视频的转场效果是通过使用
PlaneGeometry创建的平面几何体完成的动画。
该特效的处理逻辑为:
- 展示起始图像
- 播放视频
- 视频播放完毕后隐藏video
- 将几何体粒子展开
- 展开过程中替换下一张图片的开头图片
- 粒子还原
- 重复2-6
准备资源
图片资源分别对应视频的起始和结尾,这样我们可以在视频播放完成时将视频替换为平面几何体,然后实现过渡。
组件编写
<div className={style.box}>
<canvas ref={canvas} style={{ width: "100%", height: "100%" }}></canvas>
<video onClick={handlePlay} className={style.video} ref={video}></video>
</div>
视频播放地址
每个视频资源对应两张开始和结束图像。control是我们创建的threejs控制器,用于控制播放几何体和切换图像,同时在theejs控制器播放完成时设置下一个播放视频的索引。
const videos = [
"/transparent/video/video-01.mp4",
"/transparent/video/video-02.mp4",
"/transparent/video/video-03.mp4"
];
const canvas = useRef<HTMLCanvasElement | null>(null);
const video = useRef<HTMLVideoElement | null>(null);
let control = Experience();
control.animateControl.on("end", () => {
if (video.current && index <= 2) {
gsap.to(video.current, {
opacity: 1,
duration: 0.1,
});
video.current.src = videos[index];
index++;
} else {
// 播放完成重置 索引
control.animateControl.shaderImgIndex = -1;
index = 0;
}
});
video.current.onended = () => {
gsap.to(video.current, {
opacity: 0,
duration: 0.1,
});
control.animateControl.first();
};
video.current.onloadstart = () => {
console.log("loaded and prestart", video.current?.src);
if (video.current) video.current.play();
};
动画播放控制器
这里我们定义了 first动画和end动画,我们在执行动画时,将贴图传入着色器中,并通过gsap实现动画并在延时动画完成后将图像索引指向下一帧,并执行end动画,end动画组执行完毕后触发 外部end事件,触发视频播放操作。
export default class AnimateControl extends EventEmitter {
shaderImgFirst = [
new THREE.TextureLoader().load("/transparent/img/video-01-first.jpg"),
new THREE.TextureLoader().load("/transparent/img/video-02-first.jpg"),
new THREE.TextureLoader().load("/transparent/img/video-03-first.jpg")
];
shaderImgEnd = [
new THREE.TextureLoader().load("/transparent/img/video-01-end.jpg"),
new THREE.TextureLoader().load("/transparent/img/video-02-end.jpg"),
new THREE.TextureLoader().load("/transparent/img/video-03-end.jpg")
];
shaderImgIndex = 0;
exprience: Exprience;
model: ShaderBox;
isAdd = true;
max = 15;
min = 0;
postProcess: PostProcessing;
constructor() {
super();
this.exprience = new Exprience();
this.postProcess = this.exprience.postProcessing;
this.model = this.exprience.resource.shaderBox;
}
// 扩散动画
first() {
if (this.model.box.material instanceof THREE.ShaderMaterial) {
this.model.box.material.uniforms.u_texture.value =
this.shaderImgEnd[this.shaderImgIndex];
gsap.to(this.model.box.material.uniforms.u_scale, {
value: 10,
duration: 5,
});
gsap.to(this.postProcess.unealBlommPass, {
threshold: 0,
duration: 5,
onComplete: () => {
this.shaderImgIndex++;
console.log(this.shaderImgIndex);
this.end();
},
});
}
}
// 收敛动画
end() {
if (this.model.box.material instanceof THREE.ShaderMaterial) {
this.model.box.material.uniforms.u_texture.value =
this.shaderImgFirst[this.shaderImgIndex];
gsap.to(this.model.box.material.uniforms.u_scale, {
value: 0,
duration: 5,
});
gsap.to(this.postProcess.unealBlommPass, {
threshold: 1,
duration: 5,
onComplete: () => {
if (this.shaderImgIndex === 2) {
this.shaderImgIndex = 0;
}
this.emit("end");
}
})
}
}
next() {}
}
平面几何体
我们创建一个平面几何体,并使用ShaderMaterial着色器矩阵来渲染几何体。
const geometry2 = new BufferGeometry();
const biLi = 480 / 820;
const geometry = new THREE.PlaneGeometry(
11.3 * biLi,11.3,
240 * 6,480 * 6
);
geometry2.setAttribute(
"position",
new THREE.BufferAttribute(
//@ts-ignore
geometry.attributes.position.array,
3
)
);
const material = new THREE.ShaderMaterial({
uniforms: {
u_time: { value: 0 },
u_scale: { value: 0 },
u_texture: {
value: new THREE.TextureLoader().load("/transparent/img/video-01-end.jpg")
}
},
fragmentShader: fs,
vertexShader: vs,
});
console.log(material);
const mesh = new THREE.Points(geometry, material);
return mesh;
片元着色器
将贴图传入片元着色器,并通过texture2D获取rgba颜色。
// 纹理
uniform sampler2D u_texture;
void main() {
vec3 color = texture2D(u_texture, v_uv).xyz;
gl_FragColor = vec4(vec3(color), 1.0);
}