I simply want to implement the pixel distortion effect on multiple images the way it is on zajno.com. I used the pixelation effect from the codrops example I specified, but the effect ends up on working partially on ONLY the last image.
MY CODE:
Simply uncomment the this.generateGrid accordingly.
import * as THREE from "three";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
import vertex from "./shaders/vertexShader.glsl";
import fragment from "./shaders/fragmentShader.glsl";
const canvas = document.querySelector(".effect__canvas");
function clamp(number, min, max) {
return Math.max(min, Math.min(number, max));
}
class Effect {
constructor() {
this.scroll = 0;
this.threejsCanvas = document.querySelector(".effect__canvas");
this.width = this.threejsCanvas.offsetWidth;
this.height = this.threejsCanvas.offsetHeight;
// Test Effect
this.mouse = {
x: 0,
y: 0,
prevX: 0,
prevY: 0,
vX: 0,
vY: 0,
};
this.settings = {
grid: 50,
mouse: 0.05,
strength: 0.11,
relaxation: 0.9,
};
let st = ScrollTrigger.create({
trigger: ".container",
start: "top top",
end: "bottom bottom",
});
document.addEventListener("scroll", () => {
this.scroll = st.scroll();
});
this.setup();
this.addTextureImages();
this.onResize();
this.addEventListeners();
// this.onMouseMove();
this.animate();
}
addTextureImages() {
// this.regenerateGrid();
this.images = [
...document.querySelectorAll(".image__wrapper .work__detail__image"),
];
const geometry = new THREE.PlaneGeometry(1, 1);
const textureLoader = new THREE.TextureLoader();
this.imageData = this.images.map((image) => {
const bounds = image.getBoundingClientRect();
const imageTexture = textureLoader.load(image.getAttribute("src"));
const material = new THREE.ShaderMaterial({
uniforms: {
uHoverState: { value: 0 },
uTexture: { value: imageTexture },
},
vertexShader: vertex,
fragmentShader: fragment,
});
const imageMesh = new THREE.Mesh(geometry, material);
imageMesh.scale.set(bounds.width, bounds.height);
this.scene.add(imageMesh);
image.addEventListener("mouseenter", () => {
gsap.to(material.uniforms.uHoverState, {
duration: 1,
value: 1,
ease: "power3.out",
});
});
image.addEventListener("mouseout", () => {
gsap.to(material.uniforms.uHoverState, {
duration: 1,
value: 0,
ease: "power3.out",
});
});
return {
image,
imageMesh,
width: bounds.width,
height: bounds.height,
top: bounds.top,
left: bounds.left,
};
});
}
setup() {
const cameraFOV = 2 * Math.atan(this.height / 2 / 10) * (180 / Math.PI);
const cameraASPECT_RATIO = this.width / this.height;
const cameraNEAR = 0.01;
const cameraFAR = 1000;
// Camera
this.camera = new THREE.PerspectiveCamera(
cameraFOV,
cameraASPECT_RATIO,
cameraNEAR,
cameraFAR
);
this.camera.position.set(0, 0, 10);
// Scene
this.scene = new THREE.Scene();
// Renderer
this.renderer = new THREE.WebGLRenderer({
canvas: this.threejsCanvas,
antialias: true,
alpha: true,
});
this.renderer.setSize(this.width, this.height);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
}
updatePosition() {
this.imageData.forEach((image) => {
image.imageMesh.position.x =
image.left - this.width / 2 + image.width / 2;
image.imageMesh.position.y =
this.scroll - image.top + this.height / 2 - image.height / 2;
});
}
addEventListeners() {
window.addEventListener("resize", this.onResize.bind(this));
}
regenerateGrid() {
this.size = this.settings.grid;
const width = this.size;
const height = this.size;
const size = width * height;
const data = new Float32Array(4 * size);
const color = new THREE.Color(0xffffff);
const r = Math.floor(color.r * 255);
const g = Math.floor(color.g * 255);
const b = Math.floor(color.b * 255);
for (let i = 0; i < size; i++) {
let r = Math.random() * 255 - 125;
let r1 = Math.random() * 255 - 125;
const stride = 1;
data[stride] = r;
data[stride + 1] = r1;
data[stride + 2] = r;
data[stride + 3] = 1;
}
// used the buffer to create a DataTexture
this.dataTexture = new THREE.DataTexture(
data,
width,
height,
THREE.RGBAFormat,
THREE.FloatType
);
this.dataTexture.magFilter = this.dataTexture.minFilter =
THREE.NearestFilter;
if (this.material) {
this.material.uniforms.uDataTexture.value = this.dataTexture;
this.material.uniforms.uDataTexture.value.needsUpdate = true;
}
}
updateDataTexture() {
let data = this.dataTexture.image.data;
let gridMouseX = this.size * this.mouse.x;
let gridMouseY = this.size * (1 - this.mouse.y);
let maxDist = this.size * this.settings.mouse;
let aspect = this.height / this.width;
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.size; j++) {
let distance = (gridMouseX - i) ** 2 / aspect + (gridMouseY - j) ** 2;
let maxDistSq = maxDist ** 2;
if (distance < maxDistSq) {
let index = 4 * (i + this.size * j);
let power = maxDist / Math.sqrt(distance);
power = clamp(power, 0, 10);
data[index] += this.settings.strength * 100 * this.mouse.vX * power;
data[index + 1] -=
this.settings.strength * 100 * this.mouse.vY * power;
}
}
}
for (let i = 0; i < data.length; i += 4) {
data[i] *= this.settings.relaxation;
data[i + 1] *= this.settings.relaxation;
}
this.mouse.vX *= 0.9;
this.mouse.vY *= 0.9;
this.dataTexture.needsUpdate = true;
}
onResize() {
this.width = this.threejsCanvas.offsetWidth;
this.height = this.threejsCanvas.offsetHeight;
this.renderer.setSize(this.width, this.height);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.camera.aspect = this.width / this.height;
this.camera.updateProjectionMatrix();
if (this.imageData) {
for (let i = 0; i < this.imageData.length; i++) {
this.imageData[i].top = this.images[i].getBoundingClientRect().top;
this.imageData[i].height =
this.images[i].getBoundingClientRect().height;
this.imageData[i].left = this.images[i].getBoundingClientRect().left;
this.imageData[i].width = this.images[i].getBoundingClientRect().width;
this.imageData[i].imageMesh.scale.set(
this.images[i].getBoundingClientRect().width,
this.images[i].getBoundingClientRect().height
);
}
}
// this.regenerateGrid();
}
animate() {
// this.updateDataTexture();
this.updatePosition();
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.animate.bind(this));
}
}
new Effect();
// image cover
// let imageAspect = imageHeight / imageWidth;
// let a1;
// let a2;
// if (bounds.height / bounds.width > imageAspect) {
// a1 = (bounds.width / bounds.height) * imageAspect;
// a2 = 1;
// } else {
// a1 = 1;
// a2 = bounds.height / bounds.width / imageAspect;
// }
// this.material.uniforms.resolution.value.x = bounds.width;
// this.material.uniforms.resolution.value.y = bounds.height;
// this.material.uniforms.resolution.value.z = a1;
// this.material.uniforms.resolution.value.w = a2;