├── .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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 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 | 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 | } --------------------------------------------------------------------------------