├── src
├── react-app-env.d.ts
├── setupTests.ts
├── app
│ ├── hooks
│ │ ├── useCanvas.ts
│ │ ├── useResponsiveSize.ts
│ │ └── useColor.ts
│ ├── index.tsx
│ ├── components
│ │ ├── canvas.tsx
│ │ └── wave.tsx
│ └── entity
│ │ └── WaveEntity.ts
├── reportWebVitals.ts
├── index.tsx
└── index.css
├── public
├── favicon.ico
├── robots.txt
├── manifest.json
└── index.html
├── .github
├── FUNDING.yml
└── workflows
│ └── gh-pages-deployment.yml
├── .gitignore
├── tsconfig.json
├── README.md
├── LICENSE
└── package.json
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ashiishme/react-sine-wave/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [ashiishme]
4 | custom: ['https://www.buymeacoffee.com/ashiishme']
5 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom'
6 |
--------------------------------------------------------------------------------
/src/app/hooks/useCanvas.ts:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from 'react'
2 |
3 | export const CanvasContext = createContext<{
4 | context: CanvasRenderingContext2D | undefined
5 | }>({
6 | context: undefined,
7 | })
8 |
9 | export const useCanvasContext = () => {
10 | return useContext(CanvasContext)
11 | }
12 |
--------------------------------------------------------------------------------
/.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 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals'
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry)
7 | getFID(onPerfEntry)
8 | getFCP(onPerfEntry)
9 | getLCP(onPerfEntry)
10 | getTTFB(onPerfEntry)
11 | })
12 | }
13 | }
14 |
15 | export default reportWebVitals
16 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import App from 'app'
5 | import reportWebVitals from './reportWebVitals'
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | )
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals()
18 |
--------------------------------------------------------------------------------
/src/app/hooks/useResponsiveSize.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from 'react'
2 |
3 | const useResponsiveSize = () => {
4 | const [width, setWidth] = useState(0)
5 | const [height, setHeight] = useState(0)
6 |
7 | const setSizes = useCallback(() => {
8 | setWidth(window.innerWidth)
9 | setHeight(window.innerHeight)
10 | }, [setWidth, setHeight])
11 |
12 | useEffect(() => {
13 | window.addEventListener('resize', setSizes)
14 | setSizes()
15 | }, [setSizes])
16 |
17 | return { width, height }
18 | }
19 |
20 | export default useResponsiveSize
21 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/index.tsx:
--------------------------------------------------------------------------------
1 | import Canvas from 'app/components/canvas'
2 |
3 | function App() {
4 | return (
5 |
21 | )
22 | }
23 |
24 | export default App
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "baseUrl": "src"
19 | },
20 | "include": ["src"]
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/hooks/useColor.ts:
--------------------------------------------------------------------------------
1 | const useColor = () => {
2 | const randomInteger = () => Math.floor(Math.random() * 180)
3 |
4 | const generateColor = () => {
5 | const red = randomInteger()
6 | const green = randomInteger()
7 | const blue = randomInteger()
8 | const primary = `rgba(${red}, ${green}, ${blue}, 0.88)`
9 | const secondary = `rgba(${red}, ${green}, ${blue}, 0.48)`
10 | const bodyBackgroundColor = `rgba(${red}, ${green}, ${blue}, 0.28)`
11 | // TODO: remove it from color generator
12 | document.body.style.backgroundColor = bodyBackgroundColor
13 | return {
14 | frontWave: primary,
15 | backWave: secondary,
16 | backgroundColor: bodyBackgroundColor,
17 | }
18 | }
19 |
20 | return {
21 | generateColor,
22 | }
23 | }
24 |
25 | export default useColor
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
React Sine Wave
4 |
Sine wave animation with random colors using React & Typescript with Canvas API :heart_eyes:.
5 |
6 |
7 | # Demo
8 |
9 | Deployed URL:
10 |
11 |
12 |
13 | # Installation
14 |
15 | ```bash
16 | git clone https://github.com/ashiishme/react-sine-wave.git
17 | cd react-sine-wave
18 | yarn install
19 | ```
20 |
21 | # Start the server
22 |
23 | ```bash
24 | yarn start
25 | ```
26 |
27 | Enjoy the wave animation at:
28 |
--------------------------------------------------------------------------------
/src/app/components/canvas.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, FC, useEffect, useState } from 'react'
2 |
3 | import { CanvasContext } from 'app/hooks/useCanvas'
4 | import useResponsiveSize from 'app/hooks/useResponsiveSize'
5 | import Wave from './wave'
6 |
7 | const Canvas: FC = () => {
8 | const canvasRef = useRef(null)
9 | const { width, height } = useResponsiveSize()
10 |
11 | const [context, setContext] = useState()
12 |
13 | useEffect(() => {
14 | const ctx = canvasRef?.current?.getContext('2d')
15 | if (ctx) setContext(ctx)
16 | }, [])
17 |
18 | return (
19 | <>
20 |
21 |
27 |
28 |
29 | >
30 | )
31 | }
32 |
33 | export default Canvas
34 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | html,
7 | body {
8 | height: 100%;
9 | width: 100%;
10 | }
11 |
12 | body {
13 | margin: 0;
14 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
15 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
16 | sans-serif;
17 | -webkit-font-smoothing: antialiased;
18 | -moz-osx-font-smoothing: grayscale;
19 | background: #ffffff;
20 | position: relative;
21 | }
22 |
23 | code {
24 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
25 | monospace;
26 | }
27 |
28 | canvas {
29 | position: absolute;
30 | width: 100%;
31 | height: 100%;
32 | }
33 |
34 | .header-info {
35 | position: absolute;
36 | top: 10%;
37 | left: 50%;
38 | transform: translateX(-50%);
39 | text-align: center;
40 | z-index: 1;
41 | }
42 |
43 | .app-title {
44 | font-size: 2rem;
45 | color: #212121;
46 | }
47 |
48 | .project-source {
49 | margin-top: 0.5rem;
50 | }
51 |
52 | .project-source a {
53 | color: #212121;
54 | text-decoration: none;
55 | }
56 |
--------------------------------------------------------------------------------
/src/app/entity/WaveEntity.ts:
--------------------------------------------------------------------------------
1 | class WaveEntity {
2 | private waveLength: number[]
3 | private color: string
4 |
5 | constructor(waveLength: number[], color: string) {
6 | this.waveLength = waveLength
7 | this.color = color
8 | }
9 |
10 | public set waveColor(color: string) {
11 | this.color = color
12 | }
13 |
14 | public draw = (
15 | context: CanvasRenderingContext2D,
16 | width: number,
17 | height: number,
18 | frequency: number
19 | ): void => {
20 | context.beginPath()
21 | context.moveTo(0, height)
22 | if (this.waveLength.length < 3) {
23 | return
24 | }
25 | for (let i = 0; i < width; i++) {
26 | let wave1 = Math.sin(i * this.waveLength[0] - frequency)
27 | let wave2 = Math.sin(i * this.waveLength[1] - frequency)
28 | let wave3 = Math.sin(i * this.waveLength[2] - frequency)
29 |
30 | context.lineTo(i * 2.5, height - 400 + wave1 * wave2 * wave3 * 200)
31 | }
32 | context.lineTo(width, height)
33 | context.fillStyle = this.color
34 | context.fill()
35 | context.closePath()
36 | }
37 | }
38 |
39 | export default WaveEntity
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Ashish Yadav
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/app/components/wave.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useCanvasContext } from 'app/hooks/useCanvas'
3 | import useResponsiveSize from 'app/hooks/useResponsiveSize'
4 | import WaveEntity from 'app/entity/WaveEntity'
5 | import useColor from 'app/hooks/useColor'
6 |
7 | const Wave: FC = () => {
8 | const { context } = useCanvasContext()
9 | const { width, height } = useResponsiveSize()
10 | const { generateColor } = useColor()
11 |
12 | let frequency = 0.013
13 | let colors: { [key: string]: string } = generateColor()
14 | let timer = 1
15 | const waves = {
16 | frontWave: new WaveEntity([0.0211, 0.028, 0.015], 'rgba(255,179,0,0.88)'),
17 | backWave: new WaveEntity([0.0122, 0.018, 0.005], 'rgba(255,179,0,0.48)'),
18 | }
19 |
20 | const render = () => {
21 | context?.clearRect(0, 0, width, height)
22 | Object.entries(waves).forEach(([waveName, wave]) => {
23 | wave.waveColor = colors[waveName]
24 | wave.draw(context!, width, height, frequency)
25 | })
26 | if (timer === 500) {
27 | colors = generateColor()
28 | timer = 1
29 | }
30 | timer++
31 | frequency += 0.013
32 | requestAnimationFrame(render)
33 | }
34 | if (context) render()
35 | return null
36 | }
37 |
38 | export default Wave
39 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages-deployment.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Deploy React Sine Wave
4 |
5 | # Controls when the action will run.
6 | on:
7 | # Triggers the workflow on push or pull request events but only for the main branch
8 | push:
9 | branches: [ main ]
10 |
11 | # Allows you to run this workflow manually from the Actions tab
12 | workflow_dispatch:
13 |
14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
15 | jobs:
16 | # This workflow contains a single job called "build"
17 | build:
18 | # The type of runner that the job will run on
19 | runs-on: ubuntu-latest
20 |
21 | # Steps represent a sequence of tasks that will be executed as part of the job
22 | steps:
23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
24 | - uses: actions/checkout@v2
25 |
26 | # Runs a single command using the runners shell
27 | - name: Install and Build
28 | run: |
29 | yarn install
30 | yarn build
31 |
32 | # Runs a set of commands using the runners shell
33 | - name: Deploy
34 | uses: JamesIves/github-pages-deploy-action@4.1.3
35 | with:
36 |
37 | branch: gh-pages
38 | folder: build
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-sine-wave",
3 | "homepage": "https://ashiishme.github.io/react-sine-wave/",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^5.11.4",
8 | "@testing-library/react": "^11.1.0",
9 | "@testing-library/user-event": "^12.1.10",
10 | "@types/jest": "^26.0.15",
11 | "@types/node": "^12.0.0",
12 | "@types/react": "^17.0.0",
13 | "@types/react-dom": "^17.0.0",
14 | "react": "^17.0.2",
15 | "react-dom": "^17.0.2",
16 | "react-scripts": "4.0.3",
17 | "typescript": "^4.1.2",
18 | "web-vitals": "^1.0.1"
19 | },
20 | "scripts": {
21 | "predeploy": "yarn build",
22 | "deploy": "gh-pages -d build",
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test",
26 | "eject": "react-scripts eject"
27 | },
28 | "eslintConfig": {
29 | "extends": [
30 | "react-app",
31 | "react-app/jest"
32 | ]
33 | },
34 | "browserslist": {
35 | "production": [
36 | ">0.2%",
37 | "not dead",
38 | "not op_mini all"
39 | ],
40 | "development": [
41 | "last 1 chrome version",
42 | "last 1 firefox version",
43 | "last 1 safari version"
44 | ]
45 | },
46 | "devDependencies": {
47 | "gh-pages": "^3.1.0"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React Sine Wave
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------