├── .eslintrc.json
├── public
├── images
│ ├── 1.jpg
│ ├── 2.jpg
│ ├── 3.jpg
│ ├── 4.jpg
│ ├── 5.jpg
│ ├── 6.jpg
│ └── 7.jpg
├── PPGatwick-Regular.otf
├── vercel.svg
└── next.svg
├── src
├── app
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.js
│ └── page.js
└── components
│ ├── Scene.jsx
│ ├── useMouse.js
│ ├── useDimension.js
│ ├── data.js
│ ├── Projects.jsx
│ ├── Shader.js
│ └── Model.jsx
├── jsconfig.json
├── next.config.mjs
├── postcss.config.mjs
├── .gitignore
├── tailwind.config.js
├── package.json
└── README.md
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/public/images/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olivierlarose/mouse-image-distortion/HEAD/public/images/1.jpg
--------------------------------------------------------------------------------
/public/images/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olivierlarose/mouse-image-distortion/HEAD/public/images/2.jpg
--------------------------------------------------------------------------------
/public/images/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olivierlarose/mouse-image-distortion/HEAD/public/images/3.jpg
--------------------------------------------------------------------------------
/public/images/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olivierlarose/mouse-image-distortion/HEAD/public/images/4.jpg
--------------------------------------------------------------------------------
/public/images/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olivierlarose/mouse-image-distortion/HEAD/public/images/5.jpg
--------------------------------------------------------------------------------
/public/images/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olivierlarose/mouse-image-distortion/HEAD/public/images/6.jpg
--------------------------------------------------------------------------------
/public/images/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olivierlarose/mouse-image-distortion/HEAD/public/images/7.jpg
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olivierlarose/mouse-image-distortion/HEAD/src/app/favicon.ico
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./src/*"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/public/PPGatwick-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olivierlarose/mouse-image-distortion/HEAD/public/PPGatwick-Regular.otf
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @font-face {
6 | font-family: Regular;
7 | src: url('../../public/PPGatwick-Regular.otf');
8 | }
9 |
10 | body{
11 | font-family: Regular;
12 | background-color: white;
13 | }
--------------------------------------------------------------------------------
/src/app/layout.js:
--------------------------------------------------------------------------------
1 | import "./globals.css";
2 |
3 | export const metadata = {
4 | title: "Create Next App",
5 | description: "Generated by create next app",
6 | };
7 |
8 | export default function RootLayout({ children }) {
9 | return (
10 |
11 |
{children}
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Scene.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Canvas } from '@react-three/fiber'
3 | import React from 'react'
4 | import Model from './Model'
5 |
6 | export default function Scene({activeMenu}) {
7 | return (
8 |
9 |
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/useMouse.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 |
3 | export default function useMouse() {
4 | const [mouse, setMouse] = useState({x: 0, y: 0});
5 |
6 | const mouseMove = (e) => {
7 | const { clientX, clientY } = e;
8 | setMouse({
9 | x: clientX,
10 | y: clientY
11 | })
12 | }
13 |
14 | useEffect( () => {
15 | window.addEventListener("mousemove", mouseMove)
16 | return () => window.removeEventListener("mousemove", mouseMove)
17 | }, [])
18 |
19 | return mouse;
20 | }
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
5 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
7 | ],
8 | theme: {
9 | extend: {
10 | backgroundImage: {
11 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
12 | "gradient-conic":
13 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
14 | },
15 | },
16 | },
17 | plugins: [],
18 | };
19 |
--------------------------------------------------------------------------------
/src/components/useDimension.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 |
3 | export default function useDimension() {
4 | const [dimension, setDimension] = useState({width: 0, height: 0});
5 |
6 | const resize = () => {
7 | const { innerWidth, innerHeight } = window;
8 | setDimension({
9 | width: innerWidth,
10 | height: innerHeight
11 | })
12 | }
13 |
14 | useEffect( () => {
15 | resize();
16 | window.addEventListener("resize", resize)
17 | return () => window.removeEventListener("resize", resize)
18 | }, [])
19 |
20 | return dimension;
21 | }
22 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/data.js:
--------------------------------------------------------------------------------
1 | export const projects = [
2 | {
3 | title: "Richard Gaston",
4 | src: "/images/5.jpg"
5 | },
6 | {
7 | title: "KangHee Kim",
8 | src: "/images/6.jpg"
9 | },
10 | {
11 | title: "Inka and Niclas",
12 | src: "/images/7.jpg"
13 | },
14 | {
15 | title: "Arch McLeish",
16 | src: "/images/2.jpg"
17 | },
18 | {
19 | title: "Nadir Bucan",
20 | src: "/images/1.jpg"
21 | },
22 | {
23 | title: "Chandler Bondurant",
24 | src: "/images/3.jpg"
25 | },
26 | {
27 | title: "Arianna Lago",
28 | src: "/images/4.jpg"
29 | }
30 | ]
--------------------------------------------------------------------------------
/src/components/Projects.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { projects } from './data';
3 |
4 | export default function Projects({setActiveMenu}) {
5 | return (
6 |
7 |
{setActiveMenu(null)}} className='border-b'>
8 | {
9 | projects.map( (project, i) => {
10 | return (
11 | - {setActiveMenu(i)}} key={project.title} className='text-[4vw] p-5 border-t'>
12 |
{project.title}
13 |
14 | )
15 | })
16 | }
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mouse-image-distortion",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@react-three/drei": "^9.106.0",
13 | "@react-three/fiber": "^8.16.7",
14 | "framer-motion": "^11.2.10",
15 | "framer-motion-3d": "^11.2.10",
16 | "lenis": "^1.1.1",
17 | "leva": "^0.9.35",
18 | "next": "14.2.3",
19 | "react": "^18",
20 | "react-dom": "^18"
21 | },
22 | "devDependencies": {
23 | "eslint": "^8",
24 | "eslint-config-next": "14.2.3",
25 | "postcss": "^8",
26 | "tailwindcss": "^3.4.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/Shader.js:
--------------------------------------------------------------------------------
1 | export const vertex = `
2 | varying vec2 vUv;
3 | uniform vec2 uDelta;
4 | uniform float uAmplitude;
5 | float PI = 3.141592653589793238;
6 |
7 | void main() {
8 | vUv = uv;
9 | vec3 newPosition = position;
10 | newPosition.x += sin(uv.y * PI) * uDelta.x * uAmplitude;
11 | newPosition.y += sin(uv.x * PI) * uDelta.y * uAmplitude;
12 | gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
13 | }
14 | `
15 |
16 | export const fragment = `
17 | varying vec2 vUv;
18 | uniform sampler2D uTexture;
19 | uniform float uAlpha;
20 | void main() {
21 | vec3 texture = texture2D(uTexture, vUv).rgb;
22 | gl_FragColor = vec4(texture, uAlpha);
23 | // gl_FragColor = vec4(1., 0., 0., 1.);
24 | }
25 | `
--------------------------------------------------------------------------------
/src/app/page.js:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useEffect, useState } from "react";
3 | import Scene from "@/components/Scene";
4 | import Projects from "@/components/Projects";
5 | import Lenis from 'lenis'
6 | export default function Home() {
7 |
8 | const [activeMenu, setActiveMenu] = useState(null)
9 | useEffect( () => {
10 | const lenis = new Lenis()
11 |
12 | function raf(time) {
13 | lenis.raf(time)
14 | requestAnimationFrame(raf)
15 | }
16 |
17 | requestAnimationFrame(raf)
18 | }, [])
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/src/components/Model.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react'
2 | import { useFrame, useThree } from '@react-three/fiber'
3 | import { motion } from "framer-motion-3d"
4 | import { animate, useMotionValue, useTransform } from 'framer-motion'
5 | import { vertex, fragment } from './Shader'
6 | import { useTexture, useAspect } from '@react-three/drei';
7 | import useMouse from './useMouse';
8 | import useDimension from './useDimension';
9 | import { projects } from './data';
10 |
11 | export default function Model({activeMenu}) {
12 |
13 | const plane = useRef();
14 | const { viewport } = useThree();
15 | const dimension = useDimension();
16 | const mouse = useMouse();
17 | const opacity = useMotionValue(0);
18 | const textures = projects.map(project => useTexture(project.src))
19 | const { width, height } = textures[0].image;
20 | const lerp = (x, y, a) => x * (1 - a) + y * a
21 |
22 | const scale = useAspect(
23 | width,
24 | height,
25 | 0.225
26 | )
27 | const smoothMouse = {
28 | x: useMotionValue(0),
29 | y: useMotionValue(0)
30 | }
31 |
32 | useEffect( () => {
33 | if(activeMenu != null){
34 | plane.current.material.uniforms.uTexture.value = textures[activeMenu]
35 | animate(opacity, 1, {duration: 0.2, onUpdate: latest => plane.current.material.uniforms.uAlpha.value = latest})
36 | }
37 | else {
38 | animate(opacity, 0, {duration: 0.2, onUpdate: latest => plane.current.material.uniforms.uAlpha.value = latest})
39 | }
40 | }, [activeMenu])
41 |
42 | const uniforms = useRef({
43 | uDelta: { value: { x: 0, y: 0 } },
44 | uAmplitude: { value: 0.0005 },
45 | uTexture: { value: textures[0] },
46 | uAlpha: { value: 0 }
47 | })
48 |
49 | useFrame(() => {
50 | const { x, y } = mouse
51 | const smoothX = smoothMouse.x.get();
52 | const smoothY = smoothMouse.y.get();
53 |
54 | if(Math.abs(x - smoothX) > 1){
55 | smoothMouse.x.set(lerp(smoothX, x, 0.1))
56 | smoothMouse.y.set(lerp(smoothY, y, 0.1))
57 | plane.current.material.uniforms.uDelta.value = {
58 | x: x - smoothX,
59 | y: -1 * (y - smoothY)
60 | }
61 | }
62 |
63 | })
64 |
65 | const x = useTransform(smoothMouse.x, [0, dimension.width], [-1 * viewport.width / 2, viewport.width / 2])
66 | const y = useTransform(smoothMouse.y, [0, dimension.height], [viewport.height / 2, -1 * viewport.height / 2])
67 |
68 | return (
69 |
70 |
71 | {/* */}
72 |
79 |
80 | )
81 | }
82 |
--------------------------------------------------------------------------------