├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── assets
│ ├── icons
│ │ └── github.svg
│ └── textures
│ │ └── lense.png
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── components
│ ├── App.tsx
│ ├── LinkIconButton.tsx
│ └── three
│ │ ├── Background.tsx
│ │ ├── Lense.tsx
│ │ ├── TCanvas.tsx
│ │ ├── TextPlane.tsx
│ │ └── drawer.ts
├── index.css
├── index.tsx
├── modules
│ └── glsl
│ │ ├── noise.ts
│ │ └── shader.ts
├── react-app-env.d.ts
└── reportWebVitals.ts
└── tsconfig.json
/.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About
2 | This application reproduces the effect of [Monopo London](https://monopo.london/).
3 |
4 | https://nemutas.github.io/r3f-monopo-london/
5 |
6 | 
7 |
8 | # Technology
9 |
10 | - TypeScript
11 | - React(Create React App)
12 | - React Three Fiber(Three.js)
13 | - Canvas Texture
14 | - Shader
15 |
16 | # License
17 |
18 | This source code is not MIT License.
19 |
20 | ❌ Commercial use is prohibited.
21 | ❌ Redistribution is prohibited.
22 | ❌ Diversion is prohibited.(Incorporate all of the code into the project, etc.)
23 | ✅ You can look at the application and reproduce the representation.
24 | ✅ You can use parts of the code.
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "r3f-monopo-london",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "https://nemutas.github.io/r3f-monopo-london/",
6 | "dependencies": {
7 | "@emotion/css": "^11.9.0",
8 | "@react-three/drei": "^8.20.2",
9 | "@react-three/fiber": "^7.0.27",
10 | "@testing-library/jest-dom": "^5.16.4",
11 | "@testing-library/react": "^12.1.4",
12 | "@testing-library/user-event": "^13.5.0",
13 | "@types/jest": "^27.4.1",
14 | "@types/node": "^16.11.26",
15 | "@types/react": "^17.0.44",
16 | "@types/react-dom": "^17.0.14",
17 | "gsap": "^3.10.3",
18 | "lil-gui": "^0.16.1",
19 | "react": "^18.0.0",
20 | "react-dom": "^18.0.0",
21 | "react-scripts": "5.0.0",
22 | "three": "^0.139.2",
23 | "typescript": "^4.6.3",
24 | "valtio": "^1.5.2",
25 | "web-vitals": "^2.1.4"
26 | },
27 | "scripts": {
28 | "start": "react-scripts start",
29 | "build": "react-scripts build",
30 | "deploy": "npm run build && gh-pages -d build"
31 | },
32 | "eslintConfig": {
33 | "extends": [
34 | "react-app",
35 | "react-app/jest"
36 | ]
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | },
50 | "devDependencies": {
51 | "@types/three": "^0.139.0",
52 | "gh-pages": "^3.2.3"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/public/assets/icons/github.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/assets/textures/lense.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemutas/r3f-monopo-london/b6c34d114531654c4ef806e0c9e3b251293de88e/public/assets/textures/lense.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemutas/r3f-monopo-london/b6c34d114531654c4ef806e0c9e3b251293de88e/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemutas/r3f-monopo-london/b6c34d114531654c4ef806e0c9e3b251293de88e/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemutas/r3f-monopo-london/b6c34d114531654c4ef806e0c9e3b251293de88e/public/logo512.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { VFC } from 'react';
2 | import { css } from '@emotion/css';
3 | import { LinkIconButton } from './LinkIconButton';
4 | import { TCanvas } from './three/TCanvas';
5 |
6 | export const App: VFC = () => {
7 | return (
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
15 | const styles = {
16 | container: css`
17 | position: relative;
18 | width: 100vw;
19 | height: 100vh;
20 | `
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/LinkIconButton.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, VFC } from 'react';
2 |
3 | type LinkIconButtonProps = {
4 | /**
5 | * Resource path directly under the public folder.
6 | * @example '/assets/icons/github.svg'
7 | */
8 | imagePath: string
9 | /**
10 | * @example 'https://github.com'
11 | */
12 | linkPath: string
13 | /**
14 | * @default 'bottom-right'
15 | */
16 | position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
17 | /**
18 | * @default [50, 50] - width:50px, height:50px
19 | */
20 | size?: [number, number]
21 | }
22 |
23 | export const LinkIconButton: VFC = props => {
24 | const { imagePath, linkPath, position = 'bottom-right', size = [50, 50] } = props
25 | const [hover, setHover] = useState(false)
26 |
27 | const publicImagePath = process.env.PUBLIC_URL + imagePath
28 |
29 | let positionStyle
30 | switch (position) {
31 | case 'top-left':
32 | positionStyle = styles.topLeft
33 | break
34 | case 'top-right':
35 | positionStyle = styles.topRight
36 | break
37 | case 'bottom-left':
38 | positionStyle = styles.bottomLeft
39 | break
40 | default:
41 | positionStyle = styles.bottomRight
42 | }
43 |
44 | return (
45 | setHover(true)}
51 | onMouseLeave={() => setHover(false)}>
52 |
53 |
54 | )
55 | }
56 |
57 | // ========================================================
58 | // styles
59 |
60 | type Styles = { [key in string]: React.CSSProperties }
61 |
62 | const temp: Styles = {
63 | container: {
64 | position: 'fixed',
65 | bottom: '0',
66 | right: '0',
67 | fontSize: '0'
68 | }
69 | }
70 |
71 | const styles: Styles = {
72 | topLeft: {
73 | ...temp.container,
74 | top: '10px',
75 | left: '10px'
76 | },
77 | topRight: {
78 | ...temp.container,
79 | top: '10px',
80 | right: '10px'
81 | },
82 | bottomLeft: {
83 | ...temp.container,
84 | bottom: '10px',
85 | left: '10px'
86 | },
87 | bottomRight: {
88 | ...temp.container,
89 | bottom: '10px',
90 | right: '10px'
91 | },
92 | img: {
93 | objectFit: 'cover',
94 | opacity: '0.5',
95 | transform: 'rotate(0deg)',
96 | transition: 'all 0.3s'
97 | }
98 | }
99 |
100 | const hoverStyles: Styles = {
101 | img: {
102 | ...styles.img,
103 | opacity: '1',
104 | transform: 'rotate(360deg)'
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/components/three/Background.tsx:
--------------------------------------------------------------------------------
1 | import { VFC } from 'react';
2 | import * as THREE from 'three';
3 | import { Plane } from '@react-three/drei';
4 | import { useFrame } from '@react-three/fiber';
5 | import { cnoise21 } from '../../modules/glsl/noise';
6 |
7 | export const Background: VFC = () => {
8 | const shader: THREE.Shader = {
9 | uniforms: {
10 | u_time: { value: 0 },
11 | u_mouse: { value: new THREE.Vector2() }
12 | },
13 | vertexShader,
14 | fragmentShader
15 | }
16 |
17 | const target = new THREE.Vector2()
18 | useFrame(({ mouse }) => {
19 | shader.uniforms.u_time.value += 0.005
20 | target.set((mouse.x + 1) * 0.5, (mouse.y + 1) * 0.5)
21 | shader.uniforms.u_mouse.value.lerp(target, 0.2)
22 | })
23 |
24 | return (
25 |
26 |
27 |
28 | )
29 | }
30 |
31 | const vertexShader = `
32 | varying vec2 v_uv;
33 |
34 | void main() {
35 | v_uv = uv;
36 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
37 | }
38 | `
39 |
40 | const fragmentShader = `
41 | uniform float u_time;
42 | uniform vec2 u_mouse;
43 | varying vec2 v_uv;
44 |
45 | ${cnoise21}
46 |
47 | float random(vec2 p) {
48 | vec2 k1 = vec2(
49 | 23.14069263277926, // e^pi (Gelfond's constant)
50 | 2.665144142690225 // 2^sqrt(2) (Gelfond–Schneider constant)
51 | );
52 | return fract(
53 | cos(dot(p, k1)) * 12345.6789
54 | );
55 | }
56 |
57 | const vec3 black = vec3(0.0);
58 | const vec3 color1 = vec3(0.89, 0.34, 0.11);
59 | const vec3 color2 = vec3(0.56, 0.64, 0.64);
60 | const vec3 color3 = vec3(0.16, 0.26, 0.47);
61 |
62 | void main() {
63 | vec2 seed = v_uv * 1.5 * (u_mouse + 0.3 * (length(u_mouse) + 0.5));
64 | float n = cnoise21(seed) + length(u_mouse) * 0.9;
65 |
66 | float ml = pow(length(u_mouse), 2.5) * 0.15;
67 | float n1 = smoothstep(0.0, 0.0 + 0.2, n);
68 | vec3 color = mix(black, color1, n1);
69 |
70 | float n2 = smoothstep(0.1 + ml, 0.1 + ml + 0.2, n);
71 | color = mix(color, color2, n2);
72 |
73 | float n3 = smoothstep(0.2 + ml, 0.2 + ml + 0.2, n);
74 | color = mix(color, color3, n3);
75 |
76 | float n4 = smoothstep(0.3 + ml, 0.3 + ml + 0.2, n);
77 | color = mix(color, black, n4);
78 |
79 | vec2 uvrandom = v_uv;
80 | uvrandom.y *= random(vec2(uvrandom.y, 0.4));
81 | color.rgb += random(uvrandom) * 0.05;
82 |
83 | gl_FragColor = vec4(color, 1.0);
84 | }
85 | `
86 |
--------------------------------------------------------------------------------
/src/components/three/Lense.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, VFC } from 'react';
2 | import * as THREE from 'three';
3 | import { Circle, useTexture } from '@react-three/drei';
4 | import { useFrame, useThree } from '@react-three/fiber';
5 |
6 | export const Lense: VFC = () => {
7 | const ref = useRef(null)
8 | const texture = useTexture(process.env.PUBLIC_URL + '/assets/textures/lense.png')
9 | const { aspect } = useThree(({ viewport }) => viewport)
10 |
11 | const target = new THREE.Vector3()
12 | useFrame(({ mouse }) => {
13 | target.set(mouse.x, mouse.y, 0.01)
14 | ref.current!.position.lerp(target, 0.1)
15 | })
16 |
17 | return (
18 |
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/three/TCanvas.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense, VFC } from 'react';
2 | import * as THREE from 'three';
3 | import { Canvas } from '@react-three/fiber';
4 | import {
5 | enFragmentShader, enVertexShader, jpFragmentShader, jpVertexShader
6 | } from '../../modules/glsl/shader';
7 | import { Background } from './Background';
8 | import { Lense } from './Lense';
9 | import { TextPlane } from './TextPlane';
10 |
11 | export const TCanvas: VFC = () => {
12 | const OrthographicCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, -10, 10)
13 |
14 | return (
15 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/three/TextPlane.tsx:
--------------------------------------------------------------------------------
1 | import { VFC } from 'react';
2 | import * as THREE from 'three';
3 | import { Plane } from '@react-three/drei';
4 | import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
5 | import { Drawer } from './drawer';
6 |
7 | type TextPlaneProps = {
8 | text: [string, string]
9 | vertexShader: string
10 | fragmentShader: string
11 | }
12 |
13 | export const TextPlane: VFC = props => {
14 | const { text, vertexShader, fragmentShader } = props
15 |
16 | const drawer = new Drawer(text[0], text[1])
17 | drawer.draw()
18 |
19 | const { aspect } = useThree(({ viewport }) => viewport)
20 |
21 | const shader: THREE.Shader = {
22 | uniforms: {
23 | u_texture: { value: drawer.texture },
24 | u_mouse: { value: new THREE.Vector2() },
25 | u_aspect: { value: drawer.aspect },
26 | u_enable: { value: false }
27 | },
28 | vertexShader,
29 | fragmentShader
30 | }
31 |
32 | const target = new THREE.Vector2()
33 | useFrame(() => {
34 | shader.uniforms.u_mouse.value.lerp(target, 0.1)
35 | })
36 |
37 | const handlePointerMove = (e: ThreeEvent) => {
38 | target.copy(e.uv!)
39 | }
40 |
41 | const handlePointerEnter = (e: ThreeEvent) => {
42 | shader.uniforms.u_mouse.value.copy(e.uv!)
43 | shader.uniforms.u_enable.value = true
44 | }
45 |
46 | const handlePointerLeave = () => {
47 | shader.uniforms.u_enable.value = false
48 | }
49 |
50 | return (
51 |
57 |
58 |
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/three/drawer.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | export class Drawer {
4 | public texture
5 | public aspect
6 |
7 | private _ctx
8 | private readonly _margin = 130
9 |
10 | constructor(private _text1: string, private _text2: string) {
11 | const canvas = document.createElement('canvas')
12 | canvas.width = 1024
13 | canvas.height = canvas.width / 2.2
14 | this._ctx = canvas.getContext('2d')!
15 | this.aspect = canvas.width / canvas.height
16 | this.texture = new THREE.CanvasTexture(canvas)
17 | }
18 |
19 | draw = () => {
20 | const ctx = this._ctx
21 | const { width, height } = this._ctx.canvas
22 |
23 | ctx.clearRect(0, 0, width, height)
24 |
25 | const fontSize = 85
26 |
27 | ctx.textAlign = 'left'
28 | ctx.textBaseline = 'hanging'
29 |
30 | ctx.font = `bold ${fontSize}px 'Poppins'`
31 | ctx.fillStyle = '#fff'
32 |
33 | const text2Metrics = ctx.measureText(this._text2)
34 |
35 | ctx.fillText(this._text1, this._margin, this._margin)
36 | ctx.fillText(this._text2, width - text2Metrics.width - this._margin, height - (fontSize + this._margin))
37 |
38 | // ctx.lineWidth = 3
39 | // ctx.strokeStyle = '#f00'
40 | // ctx.strokeRect(0, 0, width, height)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300&display=swap');
2 |
3 | body {
4 | margin: 0;
5 | padding: 0;
6 | font-family: 'Poppins', sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | font-size: 62.5%;
10 | }
11 |
12 | code {
13 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
14 | }
15 |
16 | * {
17 | box-sizing: border-box;
18 | overflow: hidden;
19 | }
20 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.css';
2 | import React from 'react';
3 | import * as ReactDOMClient from 'react-dom/client';
4 | import { App } from './components/App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOMClient.createRoot(document.getElementById('root')!)
8 | root.render(
9 |
10 |
11 |
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/modules/glsl/noise.ts:
--------------------------------------------------------------------------------
1 | export const cnoise21 = `
2 | // Classic Perlin 2D Noise
3 | // by Stefan Gustavson
4 | //
5 | vec2 fade(vec2 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}
6 | vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
7 |
8 | float cnoise21(vec2 P){
9 | vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
10 | vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
11 | Pi = mod(Pi, 289.0); // To avoid truncation effects in permutation
12 | vec4 ix = Pi.xzxz;
13 | vec4 iy = Pi.yyww;
14 | vec4 fx = Pf.xzxz;
15 | vec4 fy = Pf.yyww;
16 | vec4 i = permute(permute(ix) + iy);
17 | vec4 gx = 2.0 * fract(i * 0.0243902439) - 1.0; // 1/41 = 0.024...
18 | vec4 gy = abs(gx) - 0.5;
19 | vec4 tx = floor(gx + 0.5);
20 | gx = gx - tx;
21 | vec2 g00 = vec2(gx.x,gy.x);
22 | vec2 g10 = vec2(gx.y,gy.y);
23 | vec2 g01 = vec2(gx.z,gy.z);
24 | vec2 g11 = vec2(gx.w,gy.w);
25 | vec4 norm = 1.79284291400159 - 0.85373472095314 * vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11));
26 | g00 *= norm.x;
27 | g01 *= norm.y;
28 | g10 *= norm.z;
29 | g11 *= norm.w;
30 | float n00 = dot(g00, vec2(fx.x, fy.x));
31 | float n10 = dot(g10, vec2(fx.y, fy.y));
32 | float n01 = dot(g01, vec2(fx.z, fy.z));
33 | float n11 = dot(g11, vec2(fx.w, fy.w));
34 | vec2 fade_xy = fade(Pf.xy);
35 | vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
36 | float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
37 | return 2.3 * n_xy;
38 | }
39 | `
40 |
--------------------------------------------------------------------------------
/src/modules/glsl/shader.ts:
--------------------------------------------------------------------------------
1 | export const enVertexShader = `
2 | varying vec2 v_uv;
3 |
4 | void main() {
5 | v_uv = uv;
6 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
7 | }
8 | `
9 |
10 | export const enFragmentShader = `
11 | uniform sampler2D u_texture;
12 | uniform vec2 u_mouse;
13 | uniform float u_aspect;
14 | uniform bool u_enable;
15 | varying vec2 v_uv;
16 |
17 | void main() {
18 | vec4 tex = texture2D(u_texture, v_uv);
19 |
20 | vec2 aspect = vec2(u_aspect, 1.0);
21 | float radius = 0.19;
22 | float dist = distance(u_mouse * aspect, v_uv * aspect);
23 | float d = 1.0 - smoothstep(radius, radius + 0.005, dist);
24 |
25 | if (u_enable) {
26 | tex.a = mix(tex.a, 0.0, d);
27 | }
28 |
29 | gl_FragColor = tex;
30 | }
31 | `
32 |
33 | // ========================================================
34 | export const jpVertexShader = `
35 | varying vec2 v_uv;
36 |
37 | void main() {
38 | v_uv = uv;
39 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
40 | }
41 | `
42 |
43 | export const jpFragmentShader = `
44 | uniform sampler2D u_texture;
45 | uniform vec2 u_mouse;
46 | uniform float u_aspect;
47 | uniform bool u_enable;
48 | varying vec2 v_uv;
49 |
50 | void main() {
51 | vec2 aspect = vec2(u_aspect, 1.0);
52 | float radius = 0.19;
53 | float dist = distance(u_mouse * aspect, v_uv * aspect);
54 | float d = smoothstep(radius, radius + 0.005, dist);
55 |
56 | vec2 sub = u_mouse - v_uv;
57 | sub *= aspect;
58 |
59 | vec2 uv = v_uv - sub * pow(dist * 0.7, 0.7);
60 | vec4 tex_r = texture2D(u_texture, uv);
61 | vec4 tex_g = texture2D(u_texture, uv + sub * 0.03);
62 | vec4 tex_b = texture2D(u_texture, uv + sub * 0.01);
63 | float a = max(max(tex_r.a, tex_g.a), tex_b.a);
64 | vec4 tex = vec4(tex_r.r, tex_g.g, tex_b.b, a);
65 |
66 | tex.a = mix(tex.a, 0.0, d);
67 |
68 | if (!u_enable) {
69 | tex.a = 0.0;
70 | }
71 |
72 | gl_FragColor = tex;
73 | }
74 | `
75 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------