Skip to content

Instantly share code, notes, and snippets.

@ilteris
Created November 23, 2020 03:19
Show Gist options
  • Save ilteris/b4ce0b640765a323ca1074976b4e2b81 to your computer and use it in GitHub Desktop.
Save ilteris/b4ce0b640765a323ca1074976b4e2b81 to your computer and use it in GitHub Desktop.
Rubber Mesh Swipe Transition
<div id='root'></div>
<div id='message'>PULL & RELEASE IMAGE UP OR DOWN</div>
const { useState, Component } = React;
const { useSpring } = ReactSpring;
const { useDrag } = ReactUseGesture;
function lerp(v0, v1, t) {
return v0*(1-t)+v1*t
}
const frag = `
uniform float time;
uniform float progress;
uniform float width;
uniform float scaleX;
uniform float scaleY;
uniform float transition;
uniform float radius;
uniform float intensity;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D displacement;
uniform vec4 resolution;
varying vec2 vUv;
void main() {
vec2 newUV = (vUv - vec2(0.5))*resolution.zw + vec2(0.5);
vec4 d1 = texture2D(texture1, newUV);
vec4 d2 = texture2D(texture2, newUV);
float displace1 = (d1.r + d1.g + d1.b)*0.33;
float displace2 = (d2.r + d2.g + d2.b)*0.33;
vec4 t1 = texture2D(texture1, vec2(newUV.x, newUV.y + progress * (displace2 * intensity)));
vec4 t2 = texture2D(texture2, vec2(newUV.x, newUV.y + (1.0 - progress) * (displace1 * intensity)));
gl_FragColor = mix(t1, t2, progress);
}
`;
const vert = `
uniform float scale;
uniform float shift;
uniform float shiftx;
varying vec2 vUv;
void main() {
vec3 pos = position;
pos.y = pos.y + ((sin(uv.x * 3.1415926535897932384626433832795) * shift * 5.0) * 0.125);
pos.x = pos.x + ((sin(uv.y * 3.1415926535897932384626433832795) * shiftx * 5.0) * 0.125);
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos,1.);
}
`;
class Noise extends Component {
uniforms = {
time: { type: "f", value: 0 },
progress: { type: "f", value: 0 },
border: { type: "f", value: 0 },
intensity: { type: "f", value: 20 },
scaleX: { type: "f", value: 40 },
scaleY: { type: "f", value: 40 },
transition: { type: "f", value: 40 },
swipe: { type: "f", value: 0 },
width: { type: "f", value: 400 },
radius: { type: "f", value: 0 },
scale: { value: 0 },
shift: { value: 0 },
shiftx: { value: 0 },
intensity: { value: 0.3, type: "f" },
resolution: { type: "v4", value: new THREE.Vector4() },
};
time = 0;
images = [
"https://images.unsplash.com/photo-1514423140760-4479e12ca0cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2550&q=80",
"https://images.unsplash.com/photo-1470217407524-b1e77afc6ec5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2546&q=80",
"https://images.unsplash.com/photo-1505598872760-6090aa9ed603?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1650&q=80",
];
textures = [];
isRunning = false;
duration = 2;
easing = "easeInOut";
current = 0;
componentDidMount() {
this.initRender();
this.setTextures(() => {
this.addLighting();
this.addPlane();
this.handleResize();
this.animateRender();
});
window.addEventListener("resize", this.handleResize, false);
}
componentWillUnmount() {
window.removeEventListener("resize", this.handleResize, false);
}
componentDidUpdate(prevProps) {
if (this.props.active !== prevProps.active) {
setTimeout(() => {
this.updateTexture();
}, 400);
}
}
initRender() {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(
75,
400 / window.innerHeight,
0.1,
1000
);
this.camera.position.z = 5;
this.vertex = `varying vec2 vUv;void main() {vUv = uv;gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );}`;
this.renderer = new THREE.WebGLRenderer({ alpha: true });
this.renderer.shadowMap.enabled = true;
this.renderer.antialias = true;
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(this.width, window.innerHeight);
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.renderer.interpolateneMapping = THREE.ACESFilmicToneMapping;
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.renderer.setClearAlpha(0);
document.getElementById("world").appendChild(this.renderer.domElement);
this.renderer.render(this.scene, this.camera);
}
addLighting = () => {
const hemislight = new THREE.HemisphereLight();
hemislight.intensity = 0.2;
this.scene.add(hemislight);
const pointlight = new THREE.PointLight();
pointlight.distance = 1000;
pointlight.intensity = 0.7;
pointlight.position.set(30, 70, 20);
this.scene.add(pointlight);
};
addPlane = () => {
this.geometry = new THREE.PlaneGeometry(2, 3, 10, 10);
this.material = new THREE.ShaderMaterial({
vertexShader: vert,
fragmentShader: frag,
uniforms: {
...this.uniforms,
texture1: { type: "f", value: this.textures[0] },
texture2: { type: "f", value: this.textures[1] },
displacement: {
type: "f",
value: new THREE.TextureLoader().load(
"https://images.unsplash.com/photo-1470217407524-b1e77afc6ec5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2546&q=80"
),
},
},
});
this.material.uniforms.resolution.value.x = this.width;
this.material.uniforms.resolution.value.y = this.height;
this.plane = new THREE.Mesh(this.geometry, this.material);
this.scene.add(this.plane);
};
handleResize = () => {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.camera.aspect = this.width / this.height;
const dist = this.camera.position.z;
const height = 1;
let a2;
this.camera.fov = 2 * (180 / Math.PI) * Math.atan(height / (2 * dist));
this.imageAspect =
this.textures[this.current].image.height /
this.textures[this.current].image.width;
if (this.width < 700) {
if (this.height / this.width > this.imageAspect) {
a2 = 1;
} else {
a2 = this.height / 7000 / (this.width / 1800) / this.imageAspect;
}
this.plane.scale.x = this.width / 1800;
this.plane.scale.y = window.innerHeight / 7000;
} else if (this.height < 600) {
if (this.height / this.width > this.imageAspect) {
a2 = 1;
} else {
a2 = this.height / this.width / this.imageAspect;
}
this.plane.scale.x = this.width / 2000;
this.plane.scale.y = window.innerHeight / 2500;
} else {
if (this.height / this.width > this.imageAspect) {
a2 = 1;
} else {
a2 = this.height / this.width / this.imageAspect;
}
this.plane.scale.x = this.width / 3000;
this.plane.scale.y = window.innerHeight / 6000;
}
this.material.uniforms.resolution.value.x = this.width;
this.material.uniforms.resolution.value.y = this.height;
this.material.uniforms.resolution.value.z = a2;
this.material.uniforms.resolution.value.w = a2;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.width, this.height);
};
setTextures = (cb) => {
const promises = [];
let that = this;
this.images.forEach((url, i) => {
let promise = new Promise((resolve) => {
that.textures[i] = new THREE.TextureLoader().load(url, resolve);
});
promises.push(promise);
});
Promise.all(promises).then(() => {
cb();
});
};
updateTexture = () => {
let next;
if (this.current === this.images.length - 1) {
this.current = 0;
next = 1;
} else {
this.current += 1;
next = this.current + 1;
if (next === this.images.length) {
next = 0;
}
}
if (this.props.active) {
this.material.uniforms.texture1.value = this.textures[next];
} else {
this.material.uniforms.texture2.value = this.textures[next];
}
};
animateRender() {
this.renderer.render(this.scene, this.camera);
this.time += 0.05;
this.material.uniforms.time.value = this.time;
this.plane.geometry.vertices.forEach((element) => {
this.material.uniforms.shift.value = lerp(
this.material.uniforms.shift.value,
-this.props.Y / 100,
0.01
);
this.material.uniforms.shiftx.value = lerp(
this.material.uniforms.shiftx.value,
this.props.X / 1000,
0.01
);
});
this.material.uniforms.progress.value = this.props.P;
this._frameId = window.requestAnimationFrame(this.animateRender.bind(this));
}
render() {
return <div id="world" className="app"></div>;
}
}
const Parent = () => {
const [active, setActive] = useState(false);
const [current, setCurrent] = useState(0);
const [{ X, Y, P }, set] = useState({
X: 0,
Y: 0,
P: 0,
});
const [{ yPos, xPos, progress }, setPos] = useSpring(() => ({
yPos: 0,
xPos: 0,
progress: 0,
config: {
mass: 2,
tension: 670,
},
onFrame: ({ yPos, xPos, progress }) => {
set({
Y: yPos,
X: xPos,
P: progress,
});
},
}));
const bind = useDrag(
({ down, movement: [x, y], distance, direction: [xDir], velocity }) => {
if (down) {
if (!active) {
setPos({ yPos: y, xPos: x, progress: Math.abs(y / 500) });
} else {
setPos({ yPos: y, xPos: x, progress: 1 - Math.abs(y / 500) });
}
} else {
if (Math.abs(Y) > 150) {
if (!active) {
setPos({ yPos: 0, xPos: 0, progress: 1 });
setActive(true);
} else {
setPos({ yPos: 0, xPos: 0, progress: 0 });
setActive(false);
}
if (current === 2) {
setCurrent(0);
} else {
setCurrent(current + 1);
}
} else {
if (!active) {
setPos({ yPos: 0, xPos: 0, progress: 0 });
} else {
setPos({ yPos: 0, xPos: 0, progress: 1 });
}
}
}
}
);
return (
<div id="container" {...bind()}>
<Noise X={X} Y={Y} P={P} active={active} />
<div id="textCont">
<div
id="text"
style={{
opacity: current === 0 ? 1 : 0,
transform: current === 0 ? `translateY(0px)` : `translateY(20px)`,
}}
>
ICELAND
</div>
<div
id="text"
style={{
opacity: current === 1 ? 1 : 0,
transform: current === 1 ? `translateY(0px)` : `translateY(20px)`,
}}
>
MALDIVES
</div>
<div
id="text"
style={{
opacity: current === 2 ? 1 : 0,
transform: current === 2 ? `translateY(0px)` : `translateY(20px)`,
}}
>
NAMIBIA
</div>
</div>
</div>
);
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r118/three.min.js"></script>
<script src="https://odintsov.me/static/libs/react-spring.js"></script>
<script src="https://odintsov.me/static/libs/react-use-gesture.js"></script>
* {
overflow: hidden;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
user-select: none;
background-color: black;
overscroll-behavior-y: contain;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
#root {
align-items: center;
justify-content: center;
}
#container {
position: absolute;
width: 100%;
height: 100vh;
}
#textCont {
position: absolute;
align-items: center;
display: flex;
justify-content: center;
width: 100%;
top: 0;
height: 100vh;
}
#text {
color: rgb(255, 255, 255);
font-weight: 900;
font-size: 7vw;
position: absolute;
margin-left: 27%;
margin-top: 29vh;
transition: 0.4s;
}
@media only screen and (max-width: 700px) {
#container {
height: 90vh;
}
#text {
font-size: 13vw;
margin-top: 33vh;
}
}
#message {
position: absolute;
bottom: 4vh;
width: 100%;
color: #242424;
text-align: center;
font-weight: 900;
z-index: 1000;
font-size: 26px;
}
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;900&amp;display=swap" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment