├── src ├── vite-env.d.ts ├── main.tsx ├── index.css ├── components │ └── Flame.tsx ├── App.tsx └── assets │ └── react.svg ├── postcss.config.js ├── README.md ├── vite.config.ts ├── tsconfig.node.json ├── tailwind.config.js ├── .gitignore ├── .eslintrc.cjs ├── tsconfig.json ├── index.html ├── package.json └── public └── vite.svg /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SVG Hover Animation 2 | 3 | This is the code that can be found within this tutorial 4 | 5 | [Watch the video](https://youtu.be/KKQQn_lDuVQ?si=44Eir01RVUmvWLx8) 6 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: { 6 | fontFamily: { 7 | poppins: ["Poppins", "sans-serif"], 8 | }, 9 | }, 10 | }, 11 | plugins: [], 12 | } 13 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | @apply bg-neutral-900; 7 | } 8 | 9 | .card { 10 | @apply w-[44rem] h-[26rem] bg-neutral-800 rounded-lg border border-neutral-600 flex flex-row p-8 absolute justify-between stroke-[0.1] hover:stroke-[0.15]; 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | Vite + React + TS 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svg-hover-effect", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@heroicons/react": "^2.1.1", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.2.64", 19 | "@types/react-dom": "^18.2.21", 20 | "@typescript-eslint/eslint-plugin": "^7.1.1", 21 | "@typescript-eslint/parser": "^7.1.1", 22 | "@vitejs/plugin-react": "^4.2.1", 23 | "autoprefixer": "^10.4.18", 24 | "eslint": "^8.57.0", 25 | "eslint-plugin-react-hooks": "^4.6.0", 26 | "eslint-plugin-react-refresh": "^0.4.5", 27 | "postcss": "^8.4.36", 28 | "tailwindcss": "^3.4.1", 29 | "typescript": "^5.2.2", 30 | "vite": "^5.1.6" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Flame.tsx: -------------------------------------------------------------------------------- 1 | import { useState, type RefObject, useEffect } from "react" 2 | 3 | interface Props { 4 | cursor: { x: number; y: number } 5 | cardRef: RefObject 6 | mouseOnCard: boolean 7 | } 8 | 9 | const Flame = ({ cursor, cardRef, mouseOnCard }: Props) => { 10 | const [gradientCenter, setGradientCenter] = useState({ cx: "50%", cy: "50%" }) 11 | 12 | useEffect(() => { 13 | if (cardRef.current && cursor.x !== null && cursor.y !== null) { 14 | const cardRect = cardRef.current.getBoundingClientRect() 15 | const cxPercentage = (cursor.x / cardRect.width) * 100 - 24 16 | const cyPercentage = (cursor.y / cardRect.height) * 100 17 | setGradientCenter({ 18 | cx: `${cxPercentage}%`, 19 | cy: `${cyPercentage}%`, 20 | }) 21 | } 22 | }, [cursor, cardRef]) 23 | 24 | return ( 25 | 30 | 31 | 38 | {mouseOnCard && } 39 | 40 | 41 | 42 | 49 | 56 | 57 | ) 58 | } 59 | 60 | export default Flame 61 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { CheckIcon, CircleStackIcon } from "@heroicons/react/24/outline" 2 | import Flame from "./components/Flame" 3 | import { useRef, useState } from "react" 4 | 5 | const App = () => { 6 | const cardsRef = useRef(null) 7 | const [cursor, setCursor] = useState({ x: 0, y: 0 }) 8 | const [mouseOnCard, setMouseOnCard] = useState(false) 9 | 10 | const handleMouseMove = ( 11 | event: React.MouseEvent 12 | ) => { 13 | if (cardsRef.current !== null) { 14 | const rect = cardsRef.current.getBoundingClientRect() 15 | const x = event.clientX - rect.left 16 | const y = event.clientY - rect.top 17 | setCursor({ x: x, y: y }) 18 | } 19 | } 20 | 21 | return ( 22 |
23 |
setMouseOnCard(true)} 27 | onMouseLeave={() => setMouseOnCard(false)} 28 | onMouseMove={(event) => handleMouseMove(event)} 29 | > 30 |
31 |
32 | 33 |

34 | Database 35 |

36 |

37 | Every project is a full Postgres database, the world's most 38 | trusted relational database. 39 |

40 |
41 |
42 | 43 | 44 |

100% portable

45 |
46 | 47 | 48 |

Built-in Auth with RLS

49 |
50 | 51 | 52 |

Easy to extend

53 |
54 |
55 |
56 |
57 | {/* SVG Here */} 58 | 59 |
60 |
61 |
62 | ) 63 | } 64 | 65 | export default App 66 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------