Skip to content

Commit

Permalink
Merge pull request #54 from binhtran432k/feature/hover-effect
Browse files Browse the repository at this point in the history
feat: enhance hover effect
  • Loading branch information
binhtran432k authored Aug 29, 2024
2 parents 53f0ac6 + 386d4bd commit 8b2098a
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 105 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
},
"dependencies": {
"clsx": "^2.1.0",
"gsap": "^3.12.5",
"swiper": "^11.1.9",
"three": "^0.167.1"
}
Expand Down
18 changes: 12 additions & 6 deletions src/components/landing/landing.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
import backgroundImage from "~/assets/background.jpg";
import bgSrc from "~/assets/background.jpg";
import { coolBackground } from "~/utils/cool-background";
import Header from "../header/header";
import Intro from "../intro/intro";
import ScrollDown from "../scroll-down/scroll-down";
import Social from "../social/social";

export default component$(() => {
const imageRef = useSignal<Element>();
const containerRef = useSignal<HTMLDivElement>();
const bgRef = useSignal<Element>();

// eslint-disable-next-line qwik/no-use-visible-task
useVisibleTask$(() => {
if (imageRef.value) {
coolBackground(imageRef.value, backgroundImage);
if (containerRef.value && bgRef.value) {
coolBackground(
containerRef.value,
bgRef.value,
bgSrc,
"https://binhtran432k.com/binhtran432k/assets/avatar-lite.webp",
);
}
});

return (
<div class="relative flex min-h-screen flex-col">
<div class="relative flex min-h-screen flex-col" ref={containerRef}>
<div
class="absolute left-0 top-0 z-[-1] h-full w-full overflow-hidden"
ref={imageRef}
ref={bgRef}
/>
<Header />
<div class="flex grow flex-col flex-wrap justify-between">
Expand Down
22 changes: 22 additions & 0 deletions src/shaders/hover-fragment.shader
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
uniform sampler2D uTexture;
uniform float uAlpha;
uniform vec2 uOffset;

varying vec2 vUv;

// zoom on texture
vec2 scaleUV(vec2 uv, float scale) {
float center = 0.5;
return ((uv - center) * scale) + center;
}

vec3 rgbShift(sampler2D txe, vec2 uv, vec2 offset) {
float r = texture2D(txe, scaleUV(uv, 0.8) + offset).r;
vec2 gb = texture2D(txe, scaleUV(uv, 0.8)).gb;
return vec3(r, gb);
}

void main() {
vec3 color = rgbShift(uTexture, vUv, uOffset);
gl_FragColor = vec4(color, uAlpha);
}
17 changes: 17 additions & 0 deletions src/shaders/hover-vertex.shader
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
uniform vec2 uOffset;
varying vec2 vUv;

#define M_PI 3.1415926535897932384626433832795

vec3 deformationCurve(vec3 position, vec2 uv, vec2 offset) {
position.x = position.x + (sin(uv.y * M_PI) * offset.x);
position.y = position.y + (sin(uv.x * M_PI) * offset.y);
return position;
}

void main() {
vUv = uv + (uOffset * 2.);
vec3 newPosition = position;
newPosition = deformationCurve(position, uv, uOffset);
gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );
}
67 changes: 67 additions & 0 deletions src/utils/background-effect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as THREE from "three";
import type { ThreeState } from "./three-util";

export namespace BackgroundEffect {
export function setup(bgTexture: THREE.Texture, state: ThreeState) {
const geometry = new THREE.PlaneGeometry(12, 6, 10, 6);
const material = new THREE.MeshBasicMaterial({ map: bgTexture });

const mesh = new THREE.Mesh(geometry, material);

state.scene.add(mesh);

state.camera.position.z = 5;

const count = geometry.attributes.position.count;

calculateWaveEffects(geometry, count);
animate({ count, geometry, state });
}
}

function animate(opts: {
state: ThreeState;
geometry: THREE.PlaneGeometry;
count: number;
}) {
const time = opts.state.clock.getElapsedTime();
animateWaveEffect(opts.geometry, time, opts.count);
requestAnimationFrame(animate.bind(null, opts));
opts.state.renderer.render(opts.state.scene, opts.state.camera);
}

const MAX_FRAME = 60;
const TIME_FACTOR = MAX_FRAME / 4;
const waveEffects: number[][] = [];
function calculateWaveEffects(geometry: THREE.PlaneGeometry, count: number) {
for (let frame = 0; frame < MAX_FRAME; frame++) {
waveEffects[frame] = [];
const time = (2 * Math.PI * frame) / MAX_FRAME;
for (let i = 0; i < count; i++) {
const x = geometry.attributes.position.getX(i);
const y = geometry.attributes.position.getY(i);

const anim1 = 0.75 * Math.sin(x * 2 + time);
const anim2 = 0.25 * Math.sin(x + time);
const anim3 = 0.1 * Math.sin(y * 15 + time);
waveEffects[frame][i] = anim1 + anim2 + anim3;
}
}
}
let oldFrame = -1;
function animateWaveEffect(
geometry: THREE.PlaneGeometry,
time: number,
count: number,
) {
const frame = Math.round(time * TIME_FACTOR) % MAX_FRAME;
if (oldFrame === frame) {
return;
}
for (let i = 0; i < count; i++) {
geometry.attributes.position.setZ(i, waveEffects[frame][i]);
geometry.computeVertexNormals();
geometry.attributes.position.needsUpdate = true;
}
oldFrame = frame;
}
113 changes: 14 additions & 99 deletions src/utils/cool-background.ts
Original file line number Diff line number Diff line change
@@ -1,102 +1,17 @@
import * as THREE from "three";

export function coolBackground(container: Element, image: string) {
const scene = new THREE.Scene();
const loader = new THREE.TextureLoader();
const camera = new THREE.PerspectiveCamera(70, getCameraAspect(), 0.1, 1000);

const renderer = new THREE.WebGLRenderer({
antialias: false,
});

handleSetRendererSize(renderer);
container.appendChild(renderer.domElement);

window.addEventListener("resize", handleResize.bind(null, camera, renderer));

const texture = loader.load(image);
texture.colorSpace = THREE.SRGBColorSpace;

const geometry = new THREE.PlaneGeometry(16, 8, 15, 9);
const material = new THREE.MeshBasicMaterial({ map: texture });

const mesh = new THREE.Mesh(geometry, material);

scene.add(mesh);

camera.position.z = 5;

const count = geometry.attributes.position.count;
const clock = new THREE.Clock();

calculateWaveEffects(geometry, count);

animate({ scene, camera, renderer, count, geometry, clock });
}

function animate(opts: {
scene: THREE.Scene;
camera: THREE.Camera;
renderer: THREE.Renderer;
geometry: THREE.PlaneGeometry;
clock: THREE.Clock;
count: number;
}) {
const time = opts.clock.getElapsedTime();
animateWaveEffect(opts.geometry, time, opts.count);
requestAnimationFrame(animate.bind(null, opts));
opts.renderer.render(opts.scene, opts.camera);
}

const MAX_FRAME = 60;
const TIME_FACTOR = MAX_FRAME / 4;
const waveEffects: number[][] = [];
function calculateWaveEffects(geometry: THREE.PlaneGeometry, count: number) {
for (let frame = 0; frame < MAX_FRAME; frame++) {
waveEffects[frame] = [];
const time = (2 * Math.PI * frame) / MAX_FRAME;
for (let i = 0; i < count; i++) {
const x = geometry.attributes.position.getX(i);
const y = geometry.attributes.position.getY(i);

const anim1 = 0.75 * Math.sin(x * 2 + time);
const anim2 = 0.25 * Math.sin(x + time);
const anim3 = 0.1 * Math.sin(y * 15 + time);
waveEffects[frame][i] = anim1 + anim2 + anim3;
}
}
}
let oldFrame = -1;
function animateWaveEffect(
geometry: THREE.PlaneGeometry,
time: number,
count: number,
import { ThreeUtil } from "./three-util";
import { HoverEffect } from "./hover-effect";
import { BackgroundEffect } from "./background-effect";

export function coolBackground(
container: HTMLDivElement,
background: Element,
bgSrc: string,
hoverSrc: string,
) {
const frame = Math.round(time * TIME_FACTOR) % MAX_FRAME;
if (oldFrame === frame) {
return;
}
for (let i = 0; i < count; i++) {
geometry.attributes.position.setZ(i, waveEffects[frame][i]);
geometry.computeVertexNormals();
geometry.attributes.position.needsUpdate = true;
}
oldFrame = frame;
}

function getCameraAspect() {
return window.innerWidth / window.innerHeight;
}

function handleSetRendererSize(renderer: THREE.Renderer) {
renderer.setSize(window.innerWidth, window.innerHeight);
}

function handleResize(
camera: THREE.PerspectiveCamera,
renderer: THREE.Renderer,
) {
camera.aspect = getCameraAspect();
camera.updateProjectionMatrix();
handleSetRendererSize(renderer);
const state = ThreeUtil.createState(background);
const texture = state.loader.load(bgSrc);
texture.colorSpace = THREE.SRGBColorSpace;
BackgroundEffect.setup(texture, state);
HoverEffect.setup(state, container, hoverSrc);
}
Loading

0 comments on commit 8b2098a

Please sign in to comment.