├── .github
└── workflows
│ └── deploy.yml
├── .gitignore
├── .prettierrc
├── .vscode
├── extensions.json
└── launch.json
├── README.md
├── astro.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.cjs
├── public
├── favicon.svg
├── fonts
│ └── Poppins-Light.ttf
└── images
│ ├── 0p0lVOLdVmg.jpg
│ ├── 1MHWTwXL62w.jpg
│ ├── L6_MQVHz3Eg.jpg
│ ├── aeNg4YA41P8.jpg
│ └── bg8o3ZWwY3E.jpg
├── src
├── components
│ ├── Canvas.astro
│ ├── Card.astro
│ ├── Cards.astro
│ ├── Heading.astro
│ └── Link.astro
├── env.d.ts
├── layouts
│ └── Layout.astro
├── pages
│ └── index.astro
├── scripts
│ └── webgl
│ │ ├── TCanvas.ts
│ │ ├── core
│ │ └── WebGL.ts
│ │ ├── glsl
│ │ └── noise.glsl
│ │ ├── shaders
│ │ ├── fragment.glsl
│ │ └── vertex.glsl
│ │ └── utils
│ │ └── coveredTexture.ts
├── styles
│ ├── global.scss
│ └── mixins
│ │ ├── fonts.scss
│ │ └── medias.scss
└── types
│ └── glsl.d.ts
└── tsconfig.json
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 |
3 | on:
4 | # Trigger the workflow every time you push to the `main` branch
5 | # Using a different branch name? Replace `main` with your branch’s name
6 | push:
7 | branches: [ main ]
8 | paths-ignore:
9 | - '**/README.md'
10 | # Allows you to run this workflow manually from the Actions tab on GitHub.
11 | workflow_dispatch:
12 |
13 | # Allow this job to clone the repo and create a page deployment
14 | permissions:
15 | contents: read
16 | pages: write
17 | id-token: write
18 |
19 | jobs:
20 | build:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Checkout your repository using git
24 | uses: actions/checkout@v3
25 | - name: Install, build, and upload your site
26 | uses: withastro/action@v0
27 | # with:
28 | # path: . # The root location of your Astro project inside the repository. (optional)
29 | # node-version: 16 # The specific version of Node that should be used to build your site. Defaults to 16. (optional)
30 | # package-manager: yarn # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)
31 |
32 | deploy:
33 | needs: build
34 | runs-on: ubuntu-latest
35 | environment:
36 | name: github-pages
37 | url: ${{ steps.deployment.outputs.page_url }}
38 | steps:
39 | - name: Deploy to GitHub Pages
40 | id: deployment
41 | uses: actions/deploy-pages@v1
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 |
4 | # generated types
5 | .astro/
6 |
7 | # dependencies
8 | node_modules/
9 |
10 | # logs
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # environment variables
17 | .env
18 | .env.production
19 |
20 | # macOS-specific files
21 | .DS_Store
22 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "semi": false,
5 | "trailingComma": "all",
6 | "singleQuote": true,
7 | "printWidth": 120
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["astro-build.astro-vscode"],
3 | "unwantedRecommendations": []
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "command": "./node_modules/.bin/astro dev",
6 | "name": "Development server",
7 | "request": "launch",
8 | "type": "node-terminal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About
2 | The hover animation of the image is inspired by [L. K.](https://leonidkostetskyi.com/) .
3 |
4 | https://nemutas.github.io/lk-effect/
5 |
6 |
7 |
--------------------------------------------------------------------------------
/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'astro/config'
2 | import glsl from 'vite-plugin-glsl'
3 |
4 | // https://astro.build/config
5 | export default defineConfig({
6 | site: 'https://nemutas.github.io',
7 | base: '/lk-effect',
8 | vite: {
9 | plugins: [glsl()],
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/basics",
3 | "type": "module",
4 | "version": "0.0.1",
5 | "private": true,
6 | "scripts": {
7 | "dev": "astro dev",
8 | "start": "astro dev",
9 | "build": "astro build",
10 | "preview": "astro preview",
11 | "astro": "astro",
12 | "host": "astro dev --host 192.168.0.8"
13 | },
14 | "dependencies": {
15 | "astro": "^2.0.6",
16 | "gsap": "^3.11.4",
17 | "lil-gui": "^0.17.0",
18 | "three": "^0.149.0"
19 | },
20 | "devDependencies": {
21 | "@types/three": "^0.149.0",
22 | "autoprefixer": "^10.4.13",
23 | "jsdom": "^21.1.0",
24 | "octokit": "^2.0.14",
25 | "prettier": "^2.8.3",
26 | "ress": "^5.0.2",
27 | "sass": "^1.58.0",
28 | "vite-plugin-glsl": "^1.1.2"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require('autoprefixer')],
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/public/fonts/Poppins-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemutas/lk-effect/70cacf519259b738315faf819ec1181d13c99abe/public/fonts/Poppins-Light.ttf
--------------------------------------------------------------------------------
/public/images/0p0lVOLdVmg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemutas/lk-effect/70cacf519259b738315faf819ec1181d13c99abe/public/images/0p0lVOLdVmg.jpg
--------------------------------------------------------------------------------
/public/images/1MHWTwXL62w.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemutas/lk-effect/70cacf519259b738315faf819ec1181d13c99abe/public/images/1MHWTwXL62w.jpg
--------------------------------------------------------------------------------
/public/images/L6_MQVHz3Eg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemutas/lk-effect/70cacf519259b738315faf819ec1181d13c99abe/public/images/L6_MQVHz3Eg.jpg
--------------------------------------------------------------------------------
/public/images/aeNg4YA41P8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemutas/lk-effect/70cacf519259b738315faf819ec1181d13c99abe/public/images/aeNg4YA41P8.jpg
--------------------------------------------------------------------------------
/public/images/bg8o3ZWwY3E.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemutas/lk-effect/70cacf519259b738315faf819ec1181d13c99abe/public/images/bg8o3ZWwY3E.jpg
--------------------------------------------------------------------------------
/src/components/Canvas.astro:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
24 |
--------------------------------------------------------------------------------
/src/components/Card.astro:
--------------------------------------------------------------------------------
1 | ---
2 | interface Props {
3 | src: string
4 | href: string
5 | title: string
6 | }
7 |
8 | const { src, href, title } = Astro.props
9 | ---
10 |
11 |
12 |
13 |
14 |
15 | {title}
16 |
17 |
18 |
19 |
38 |
39 |
58 |
--------------------------------------------------------------------------------
/src/components/Cards.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { HTMLAttributes } from 'astro/types'
3 | import Card from './Card.astro'
4 |
5 | interface Props extends HTMLAttributes<'ul'> {}
6 | const { ...attrs } = Astro.props
7 |
8 | const imageIds = ['bg8o3ZWwY3E', 'aeNg4YA41P8', 'L6_MQVHz3Eg', '0p0lVOLdVmg', '1MHWTwXL62w']
9 | ---
10 |
11 |
12 | {imageIds.map((id) => )}
13 |
14 |
15 |
23 |
--------------------------------------------------------------------------------
/src/components/Heading.astro:
--------------------------------------------------------------------------------
1 |
2 |
3 | Using
4 | Astro
5 | Three.js
6 | Gsap
7 |
8 |
9 |
10 |
29 |
30 |
50 |
--------------------------------------------------------------------------------
/src/components/Link.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { HTMLAttributes } from 'astro/types';
3 |
4 | interface Props extends HTMLAttributes<'a'> {
5 | children: any
6 | }
7 |
8 | const { ...attrs } = Astro.props;
9 | ---
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
--------------------------------------------------------------------------------
/src/layouts/Layout.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import '../styles/global.scss';
3 |
4 | export interface Props {
5 | title: string;
6 | }
7 |
8 | const { title } = Astro.props;
9 | ---
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {title}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
36 |
--------------------------------------------------------------------------------
/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from '../layouts/Layout.astro'
3 | import Link from '../components/Link.astro'
4 | import Cards from '../components/Cards.astro'
5 | import Canvas from '../components/Canvas.astro'
6 | import Heading from '../components/Heading.astro'
7 | ---
8 |
9 |
10 |
11 |
12 | Github
13 |
14 |
15 |
16 |
17 |
18 |
33 |
--------------------------------------------------------------------------------
/src/scripts/webgl/TCanvas.ts:
--------------------------------------------------------------------------------
1 | import gsap from 'gsap'
2 | import * as THREE from 'three'
3 | import { gl } from './core/WebGL'
4 | import fragmentShader from './shaders/fragment.glsl'
5 | import vertexShader from './shaders/vertex.glsl'
6 | import { calcCoveredTextureScale } from './utils/coveredTexture'
7 |
8 | export class TCanvas {
9 | private images = new THREE.Group()
10 | private raycaster = new THREE.Raycaster()
11 | private pointer = new THREE.Vector2(99999, 99999)
12 | private currentScrollPos = 0
13 |
14 | constructor(private container: HTMLElement) {
15 | this.init()
16 | this.createObjects().then(() => {
17 | window.addEventListener('mousemove', this.handleMousemove)
18 | window.addEventListener('mouseout', this.handleMouseout)
19 | gl.requestAnimationFrame(this.anime)
20 | })
21 | }
22 |
23 | private init() {
24 | gl.setup(this.container)
25 | gl.camera.position.z = 5
26 | }
27 |
28 | private async createObjects() {
29 | const loader = new THREE.TextureLoader()
30 | const cardImages = document.querySelectorAll('.cards .img')
31 |
32 | const imageDatas = await Promise.all(
33 | [...cardImages].map(async (card) => ({ texture: await loader.loadAsync(card.dataset.imgSrc), element: card })),
34 | )
35 |
36 | const material = new THREE.ShaderMaterial({
37 | uniforms: {
38 | tImage: { value: null },
39 | uCoveredScale: { value: new THREE.Vector2() },
40 | uProgress: { value: 0 },
41 | uTime: { value: 0 },
42 | uAspect: { value: 1 },
43 | uSpeed: { value: 0 },
44 | },
45 | vertexShader,
46 | fragmentShader,
47 | transparent: true,
48 | })
49 |
50 | imageDatas.forEach((data) => {
51 | const rect = data.element.getBoundingClientRect()
52 | const coveredScale = calcCoveredTextureScale(data.texture, rect.width / rect.height)
53 | const aspect = data.texture.image.width / data.texture.image.height
54 |
55 | const geometry = new THREE.PlaneGeometry(1, 1, 50, 50)
56 | const mat = material.clone()
57 | mat.uniforms.tImage.value = data.texture
58 | mat.uniforms.uCoveredScale.value.set(coveredScale[0], coveredScale[1])
59 | mat.uniforms.uAspect.value = aspect
60 |
61 | const mesh = new THREE.Mesh(geometry, mat)
62 | mesh.userData.element = data.element
63 | this.images.add(mesh)
64 | })
65 |
66 | gl.scene.add(this.images)
67 |
68 | this.syncImages()
69 | }
70 |
71 | private updateImagesUniforms(callback: (uniforms: { [uniform: string]: THREE.IUniform }) => void) {
72 | this.images.children.forEach((child) => {
73 | callback(((child as THREE.Mesh).material as THREE.ShaderMaterial).uniforms)
74 | })
75 | }
76 |
77 | private syncImages() {
78 | this.images.children.forEach((child) => {
79 | const mesh = child as THREE.Mesh
80 | const element = mesh.userData.element as HTMLElement
81 | const rect = element.getBoundingClientRect()
82 | const width = (rect.width / gl.size.width) * 2
83 | const height = (rect.height / gl.size.height) * 2
84 | const x = ((rect.width / 2 + rect.left - gl.size.width / 2) * 2) / gl.size.width
85 | const y = ((gl.size.height / 2 - (rect.height / 2 + rect.top)) * 2) / gl.size.height
86 | mesh.scale.set(width, height, 1)
87 | mesh.position.set(x, y, 0)
88 | })
89 | }
90 |
91 | // ----------------------------------
92 | // intersection
93 | private handleMousemove = (e: MouseEvent) => {
94 | this.pointer.x = (e.clientX / window.innerWidth) * 2 - 1
95 | this.pointer.y = -(e.clientY / window.innerHeight) * 2 + 1
96 | }
97 |
98 | private handleMouseout = () => {
99 | this.pointer.set(99999, 99999)
100 | }
101 |
102 | private intersects() {
103 | this.raycaster.setFromCamera(this.pointer, gl.camera)
104 | const intersects = this.raycaster.intersectObjects([this.images])
105 |
106 | let intersectImage: THREE.Mesh
107 |
108 | if (0 < intersects.length) {
109 | intersectImage = intersects[0].object as THREE.Mesh
110 | }
111 |
112 | this.images.children.forEach((child) => {
113 | const uniforms = ((child as THREE.Mesh).material as THREE.ShaderMaterial).uniforms
114 | if (child === intersectImage) {
115 | gsap.to(uniforms.uProgress, { value: 1, duration: 2, ease: 'power1.out' })
116 | } else {
117 | gsap.to(uniforms.uProgress, { value: 0, duration: 0.8, ease: 'power1.out' })
118 | }
119 | })
120 | }
121 |
122 | // ----------------------------------
123 | // animation
124 | private anime = () => {
125 | this.currentScrollPos = THREE.MathUtils.lerp(this.currentScrollPos, window.scrollX, 0.1)
126 | const scrollSpeed = window.scrollX - this.currentScrollPos
127 |
128 | this.syncImages()
129 | this.intersects()
130 | this.updateImagesUniforms((uniforms) => {
131 | uniforms.uTime.value += gl.time.delta
132 | uniforms.uSpeed.value = scrollSpeed
133 | })
134 | gl.render()
135 | }
136 |
137 | // ----------------------------------
138 | // dispose
139 | dispose() {
140 | gl.dispose()
141 | window.removeEventListener('mousemove', this.handleMousemove)
142 | window.removeEventListener('mouseout', this.handleMouseout)
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/scripts/webgl/core/WebGL.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 |
3 | class WebGL {
4 | public renderer: THREE.WebGLRenderer
5 | public scene: THREE.Scene
6 | public camera: THREE.OrthographicCamera
7 | public time = { delta: 0, elapsed: 0 }
8 |
9 | private clock = new THREE.Clock()
10 | private resizeCallback?: () => void
11 |
12 | constructor() {
13 | const { width, height } = this.size
14 |
15 | this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
16 | this.renderer.setPixelRatio(window.devicePixelRatio)
17 | this.renderer.setSize(width, height)
18 | this.renderer.shadowMap.enabled = true
19 | this.renderer.outputEncoding = THREE.sRGBEncoding
20 |
21 | this.scene = new THREE.Scene()
22 | this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.01, 10)
23 |
24 | window.addEventListener('resize', this.handleResize)
25 | }
26 |
27 | private handleResize = () => {
28 | this.resizeCallback && this.resizeCallback()
29 |
30 | const { width, height } = this.size
31 | this.camera.updateProjectionMatrix()
32 | this.renderer.setSize(width, height)
33 | }
34 |
35 | get size() {
36 | const { innerWidth: width, innerHeight: height } = window
37 | return { width, height, aspect: width / height }
38 | }
39 |
40 | setup(container: HTMLElement) {
41 | container.appendChild(this.renderer.domElement)
42 | }
43 |
44 | setResizeCallback(callback: () => void) {
45 | this.resizeCallback = callback
46 | }
47 |
48 | getMesh(name: string) {
49 | return this.scene.getObjectByName(name) as THREE.Mesh
50 | }
51 |
52 | render() {
53 | this.renderer.render(this.scene, this.camera)
54 | }
55 |
56 | requestAnimationFrame(callback: () => void) {
57 | gl.renderer.setAnimationLoop(() => {
58 | this.time.delta = this.clock.getDelta()
59 | this.time.elapsed = this.clock.getElapsedTime()
60 | callback()
61 | })
62 | }
63 |
64 | cancelAnimationFrame() {
65 | gl.renderer.setAnimationLoop(null)
66 | }
67 |
68 | dispose() {
69 | this.cancelAnimationFrame()
70 | gl.scene?.clear()
71 | }
72 | }
73 |
74 | export const gl = new WebGL()
75 |
--------------------------------------------------------------------------------
/src/scripts/webgl/glsl/noise.glsl:
--------------------------------------------------------------------------------
1 | // Classic Perlin 3D Noise
2 | // by Stefan Gustavson
3 | //
4 | vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
5 | vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
6 | vec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}
7 |
8 | float cnoise(vec3 P){
9 | vec3 Pi0 = floor(P); // Integer part for indexing
10 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1
11 | Pi0 = mod(Pi0, 289.0);
12 | Pi1 = mod(Pi1, 289.0);
13 | vec3 Pf0 = fract(P); // Fractional part for interpolation
14 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
15 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
16 | vec4 iy = vec4(Pi0.yy, Pi1.yy);
17 | vec4 iz0 = Pi0.zzzz;
18 | vec4 iz1 = Pi1.zzzz;
19 |
20 | vec4 ixy = permute(permute(ix) + iy);
21 | vec4 ixy0 = permute(ixy + iz0);
22 | vec4 ixy1 = permute(ixy + iz1);
23 |
24 | vec4 gx0 = ixy0 / 7.0;
25 | vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;
26 | gx0 = fract(gx0);
27 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
28 | vec4 sz0 = step(gz0, vec4(0.0));
29 | gx0 -= sz0 * (step(0.0, gx0) - 0.5);
30 | gy0 -= sz0 * (step(0.0, gy0) - 0.5);
31 |
32 | vec4 gx1 = ixy1 / 7.0;
33 | vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;
34 | gx1 = fract(gx1);
35 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
36 | vec4 sz1 = step(gz1, vec4(0.0));
37 | gx1 -= sz1 * (step(0.0, gx1) - 0.5);
38 | gy1 -= sz1 * (step(0.0, gy1) - 0.5);
39 |
40 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
41 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
42 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
43 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
44 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
45 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
46 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
47 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
48 |
49 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
50 | g000 *= norm0.x;
51 | g010 *= norm0.y;
52 | g100 *= norm0.z;
53 | g110 *= norm0.w;
54 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
55 | g001 *= norm1.x;
56 | g011 *= norm1.y;
57 | g101 *= norm1.z;
58 | g111 *= norm1.w;
59 |
60 | float n000 = dot(g000, Pf0);
61 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
62 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
63 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
64 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
65 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
66 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
67 | float n111 = dot(g111, Pf1);
68 |
69 | vec3 fade_xyz = fade(Pf0);
70 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
71 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
72 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
73 | return 2.2 * n_xyz;
74 | }
--------------------------------------------------------------------------------
/src/scripts/webgl/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | uniform sampler2D tImage;
2 | uniform vec2 uCoveredScale;
3 | uniform float uTime;
4 | uniform float uProgress;
5 | uniform float uAspect;
6 | varying vec2 vUv;
7 | varying float vShift;
8 |
9 | float luma(vec3 color) {
10 | return dot(color, vec3(0.299, 0.587, 0.114));
11 | }
12 |
13 | float parabola(float x, float k) {
14 | return pow(4. * x * (1. - x), k);
15 | }
16 |
17 | #include '../glsl/noise.glsl'
18 |
19 | void main() {
20 | vec2 uv = (vUv - 0.5) * uCoveredScale + 0.5;
21 |
22 | float n = cnoise(vec3(uv * 5.0, uTime));
23 |
24 | vec2 aspect = vec2(uAspect, 1.0);
25 | float dist = distance(uv * aspect, vec2(0.5) * aspect) + n * 0.05;
26 | // 範囲
27 | float progress = uProgress * 1.2;
28 | float d = smoothstep(progress - 0.5, progress, dist);
29 | float range = parabola(d, 1.5);
30 | // 向き
31 | vec2 dir = normalize(uv - vec2(0.5));
32 | dir *= smoothstep(uProgress - 0.1, uProgress - 0.2, dist) * 2.0 - 1.0;
33 | // dir *= smoothstep(uProgress - 0.1, uProgress - 0.2, dist) * 3.0 - 2.0;
34 | vec2 distortion = dir * range * dist * 0.5;
35 | // 波
36 | float w = sin(dist * 30.0 - uTime * 10.0);
37 | float wd = smoothstep(0.0, 0.1, dist);
38 | vec2 wave = dir * w * wd * uProgress * 0.01;
39 | // texture参照
40 | // vec4 tex = texture2D(tImage, uv - distortion - wave);
41 | float tR = texture2D(tImage, uv - distortion - wave + vShift * 0.0).r;
42 | float tG = texture2D(tImage, uv - distortion - wave + vShift * 0.05).g;
43 | float tB = texture2D(tImage, uv - distortion - wave + vShift * 0.10).b;
44 | vec3 color = vec3(tR, tG, tB);
45 | // 輝度
46 | float l = luma(color) * parabola(uProgress, 3.0);
47 | color *= (1.0 + l * 5.0);
48 |
49 | float gray = luma(color);
50 | color = mix(color, vec3(gray), d - abs(vShift) * 5.0);
51 |
52 | gl_FragColor = vec4(color, 1.0);
53 | }
--------------------------------------------------------------------------------
/src/scripts/webgl/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | uniform float uSpeed;
2 | varying vec2 vUv;
3 | varying float vShift;
4 |
5 | #include '../glsl/noise.glsl'
6 |
7 | void main() {
8 | vec3 pos = position;
9 | vec3 worldPos = (modelMatrix * vec4(pos, 1.0)).xyz;
10 | float dist = 1.0 - smoothstep(0.0, 0.7, abs(pos.y));
11 | float n = cnoise(worldPos * 2.0);
12 | float shift = uSpeed * (dist + n) * 0.001;
13 | pos.x -= shift;
14 |
15 | vUv = uv;
16 | vShift = shift;
17 |
18 | gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );
19 | }
--------------------------------------------------------------------------------
/src/scripts/webgl/utils/coveredTexture.ts:
--------------------------------------------------------------------------------
1 | export function calcCoveredTextureScale(texture: THREE.Texture, aspect: number, target?: THREE.Vector2) {
2 | const imageAspect = texture.image.width / texture.image.height
3 |
4 | let result: [number, number] = [1, 1]
5 | if (aspect < imageAspect) result = [aspect / imageAspect, 1]
6 | else result = [1, imageAspect / aspect]
7 |
8 | target?.set(result[0], result[1])
9 |
10 | return result
11 | }
12 |
13 | export function coveredTexture(texture: THREE.Texture, screenAspect: number) {
14 | texture.matrixAutoUpdate = false
15 | const scale = calcCoveredTextureScale(texture, screenAspect)
16 | texture.matrix.setUvTransform(0, 0, scale[0], scale[1], 0, 0.5, 0.5)
17 |
18 | return texture
19 | }
20 |
--------------------------------------------------------------------------------
/src/styles/global.scss:
--------------------------------------------------------------------------------
1 | @use 'ress';
2 | @use './mixins/medias.scss' as *;
3 | @use './mixins/fonts.scss';
4 |
5 | @font-face {
6 | font-family: 'poppins';
7 | src: url('/lk-effect/fonts/Poppins-Light.ttf') format('truetype');
8 | font-style: normal;
9 | font-display: swap;
10 | }
11 |
12 | html {
13 | width: 100%;
14 | height: 100%;
15 | // font-size: calc(1000vw / 750);
16 | // @include pc {
17 | // font-size: calc(1000vw / 1500);
18 | // }
19 | font-size: calc(1000vw / 1500);
20 | @include fonts.poppins;
21 | color: #530;
22 | background-color: #f8f5ee;
23 | overflow: overlay;
24 | }
25 |
26 | body {
27 | position: relative;
28 | width: 100%;
29 | height: 100%;
30 | }
31 |
32 | a {
33 | color: inherit;
34 | text-decoration: none;
35 | }
36 |
37 | ul {
38 | list-style: none;
39 | }
40 |
41 | h1,
42 | h2,
43 | h3,
44 | h4,
45 | h5,
46 | h6 {
47 | font-size: inherit;
48 | font-weight: inherit;
49 | }
50 |
51 | ::-webkit-scrollbar {
52 | width: 5px;
53 | height: 5px;
54 | }
55 | ::-webkit-scrollbar-track {
56 | background: transparent;
57 | }
58 | ::-webkit-scrollbar-thumb {
59 | background: #0005;
60 | }
61 |
--------------------------------------------------------------------------------
/src/styles/mixins/fonts.scss:
--------------------------------------------------------------------------------
1 | @mixin poppins($weight: 300) {
2 | font-family: 'poppins';
3 | font-weight: $weight;
4 | }
5 |
--------------------------------------------------------------------------------
/src/styles/mixins/medias.scss:
--------------------------------------------------------------------------------
1 | $--pc-width: 751px;
2 | $--sp-width: 750px;
3 |
4 | @mixin pc {
5 | @media screen and (min-width: $--pc-width) {
6 | @content;
7 | }
8 | }
9 |
10 | @mixin sp {
11 | @media screen and (max-width: $--sp-width) {
12 | @content;
13 | }
14 | }
15 |
16 | @mixin hoverable {
17 | @media (hover: hover) and (pointer: fine) {
18 | @content;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/types/glsl.d.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/UstymUkhman/vite-plugin-glsl/blob/main/test/shaders.d.ts
2 |
3 | declare module '*.vs' {
4 | const value: string
5 | export default value
6 | }
7 |
8 | declare module '*.fs' {
9 | const value: string
10 | export default value
11 | }
12 |
13 | declare module '*.vert' {
14 | const value: string
15 | export default value
16 | }
17 |
18 | declare module '*.frag' {
19 | const value: string
20 | export default value
21 | }
22 |
23 | declare module '*.glsl' {
24 | const value: string
25 | export default value
26 | }
27 |
28 | declare module '*.wgsl' {
29 | const value: string
30 | export default value
31 | }
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/base"
3 | }
--------------------------------------------------------------------------------