├── frontend
├── global.d.ts
├── src
│ ├── pages
│ │ ├── index.tsx
│ │ ├── examples
│ │ │ └── 1.tsx
│ │ ├── 404.tsx
│ │ ├── _error.tsx
│ │ ├── _app.tsx
│ │ └── _document.tsx
│ ├── utils
│ │ ├── functions
│ │ │ ├── delay.ts
│ │ │ ├── clamp.ts
│ │ │ ├── mix.ts
│ │ │ ├── lerp.ts
│ │ │ ├── detectDevice.ts
│ │ │ ├── isTouchDevice.ts
│ │ │ ├── getRand.ts
│ │ │ ├── getScrollbarWidth.ts
│ │ │ ├── setCssVariables.ts
│ │ │ └── seedRandom.ts
│ │ ├── GlobalStyles.ts
│ │ ├── media.ts
│ │ ├── sharedTypes.ts
│ │ ├── sharedValues.ts
│ │ └── sharedStyled.ts
│ ├── styles
│ │ ├── components
│ │ │ ├── measure.scss
│ │ │ └── canvas.scss
│ │ ├── index.scss
│ │ └── base
│ │ │ ├── fonts.scss
│ │ │ ├── reset.scss
│ │ │ └── global.scss
│ ├── containers
│ │ ├── ErrorPage
│ │ │ └── ErrorPage.tsx
│ │ ├── IndexPage
│ │ │ ├── IndexPage.styles.ts
│ │ │ └── IndexPage.tsx
│ │ └── Examples
│ │ │ └── 1
│ │ │ ├── ExamplePage.tsx
│ │ │ ├── ExamplePage.styles.ts
│ │ │ └── ExamplePage.data.ts
│ ├── components
│ │ ├── Caption
│ │ │ ├── Caption.state.ts
│ │ │ ├── coverBackgroundClasses
│ │ │ │ ├── Components
│ │ │ │ │ └── BackgroundSketch.ts
│ │ │ │ └── App.ts
│ │ │ ├── backgroundClasses
│ │ │ │ ├── Components
│ │ │ │ │ └── BackgroundSketch.ts
│ │ │ │ └── App.ts
│ │ │ ├── Caption.styles.ts
│ │ │ ├── Caption.tsx
│ │ │ └── classes
│ │ │ │ ├── Components
│ │ │ │ └── TextSketch.ts
│ │ │ │ └── App.ts
│ │ ├── PreloadImage
│ │ │ ├── PreloadImage.tsx
│ │ │ └── PreloadImage.styles.ts
│ │ ├── Layout
│ │ │ ├── Layout.styles.ts
│ │ │ └── Layout.tsx
│ │ ├── CodeRenderer
│ │ │ ├── CodeRenderer.tsx
│ │ │ └── CodeRenderer.styles.ts
│ │ ├── LinkHandler
│ │ │ └── LinkHandler.tsx
│ │ └── GameTile
│ │ │ ├── GameTile.styles.ts
│ │ │ └── GameTile.tsx
│ ├── sections
│ │ ├── ShowOff
│ │ │ ├── images
│ │ │ │ ├── shape4.svg
│ │ │ │ ├── shape5.svg
│ │ │ │ ├── shape7.svg
│ │ │ │ ├── shape2.svg
│ │ │ │ ├── shape3.svg
│ │ │ │ ├── shape1.svg
│ │ │ │ ├── shape6.svg
│ │ │ │ ├── shape8.svg
│ │ │ │ └── shape9.svg
│ │ │ ├── ShowOff.styles.ts
│ │ │ └── ShowOff.tsx
│ │ ├── CopyInfo
│ │ │ ├── CopyInfo.tsx
│ │ │ ├── CopyInfo.styles.ts
│ │ │ └── images
│ │ │ │ └── logo.svg
│ │ └── DocsInfo
│ │ │ ├── DocsInfo.styles.ts
│ │ │ └── DocsInfo.tsx
│ ├── seo
│ │ ├── GoogleAnalytics
│ │ │ └── GoogleAnalytics.tsx
│ │ └── Head
│ │ │ └── Head.tsx
│ └── hooks
│ │ ├── useWindowSize.ts
│ │ ├── useMediaPreload.ts
│ │ └── useElementSize.ts
├── public
│ ├── favicon.ico
│ ├── fonts
│ │ ├── teko700.woff2
│ │ ├── openSans400.woff
│ │ └── openSans800.woff2
│ └── vercel.svg
├── .eslintignore
├── .prettierrc.js
├── next-env.d.ts
├── next.config.js
├── .gitignore
├── tsconfig.json
├── README.md
├── .eslintrc.js
├── LICENSE
└── package.json
├── react-just-parallax
├── .gitignore
├── src
│ ├── utils
│ │ ├── lerp.ts
│ │ ├── isTouchDevice.ts
│ │ ├── getScrollOffsets.ts
│ │ ├── EventDispatcher.js
│ │ ├── EventDispatcher.d.ts
│ │ └── MouseMove.ts
│ ├── index.ts
│ ├── hooks
│ │ └── useWindowSize.ts
│ └── components
│ │ ├── ScrollParallax
│ │ └── ScrollParallax.tsx
│ │ └── MouseParallax
│ │ └── MouseParallax.tsx
├── declarations.d.ts
├── tsconfig.json
├── rollup.config.js
├── package.json
└── README.md
├── cra-for-testing
├── src
│ ├── react-app-env.d.ts
│ ├── setupTests.ts
│ ├── App.test.tsx
│ ├── index.css
│ ├── reportWebVitals.ts
│ ├── index.tsx
│ ├── logo.svg
│ ├── App.css
│ └── App.tsx
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── .gitignore
├── tsconfig.json
├── package.json
└── README.md
├── .vscode
├── settings.json
└── mySnippets.code-snippets
└── README.md
/frontend/global.d.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/react-just-parallax/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/cra-for-testing/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/frontend/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | export { default } from 'containers/IndexPage/IndexPage';
2 |
--------------------------------------------------------------------------------
/cra-for-testing/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalzalobny/react-just-parallax/HEAD/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/src/utils/functions/delay.ts:
--------------------------------------------------------------------------------
1 | export const delay = (time: number) => new Promise(res => setTimeout(res, time));
2 |
--------------------------------------------------------------------------------
/cra-for-testing/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalzalobny/react-just-parallax/HEAD/cra-for-testing/public/favicon.ico
--------------------------------------------------------------------------------
/cra-for-testing/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalzalobny/react-just-parallax/HEAD/cra-for-testing/public/logo192.png
--------------------------------------------------------------------------------
/cra-for-testing/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalzalobny/react-just-parallax/HEAD/cra-for-testing/public/logo512.png
--------------------------------------------------------------------------------
/frontend/src/utils/functions/clamp.ts:
--------------------------------------------------------------------------------
1 | export const clamp = (value: number) => {
2 | return Math.min(1, Math.max(0, value));
3 | };
4 |
--------------------------------------------------------------------------------
/frontend/src/utils/functions/mix.ts:
--------------------------------------------------------------------------------
1 | export const mix = (a: number, b: number, t: number) => {
2 | return a * (1 - t) + t * b;
3 | };
4 |
--------------------------------------------------------------------------------
/frontend/public/fonts/teko700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalzalobny/react-just-parallax/HEAD/frontend/public/fonts/teko700.woff2
--------------------------------------------------------------------------------
/frontend/src/utils/functions/lerp.ts:
--------------------------------------------------------------------------------
1 | export const lerp = (p1: number, p2: number, t: number) => {
2 | return p1 + (p2 - p1) * t;
3 | };
4 |
--------------------------------------------------------------------------------
/react-just-parallax/src/utils/lerp.ts:
--------------------------------------------------------------------------------
1 | export const lerp = (p1: number, p2: number, t: number) => {
2 | return p1 + (p2 - p1) * t;
3 | };
4 |
--------------------------------------------------------------------------------
/frontend/public/fonts/openSans400.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalzalobny/react-just-parallax/HEAD/frontend/public/fonts/openSans400.woff
--------------------------------------------------------------------------------
/frontend/public/fonts/openSans800.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalzalobny/react-just-parallax/HEAD/frontend/public/fonts/openSans800.woff2
--------------------------------------------------------------------------------
/frontend/.eslintignore:
--------------------------------------------------------------------------------
1 | # don't ever lint node_modules
2 | node_modules
3 | # don't lint build output (make sure it's set to your correct build folder name)
4 | dist
5 | .next
--------------------------------------------------------------------------------
/frontend/src/pages/examples/1.tsx:
--------------------------------------------------------------------------------
1 | export { default } from 'containers/Examples/1/ExamplePage';
2 | export { getStaticProps } from 'containers/Examples/1/ExamplePage.data';
3 |
--------------------------------------------------------------------------------
/frontend/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ...require('./node_modules/gts/.prettierrc.json'),
3 | bracketSpacing: true,
4 | endOfLine: 'auto',
5 | printWidth: 100,
6 | };
7 |
--------------------------------------------------------------------------------
/frontend/src/styles/components/measure.scss:
--------------------------------------------------------------------------------
1 | .scrollbar-measure {
2 | width: 100px;
3 | height: 100px;
4 | overflow: scroll;
5 | position: absolute;
6 | top: -9999px;
7 | }
8 |
--------------------------------------------------------------------------------
/react-just-parallax/declarations.d.ts:
--------------------------------------------------------------------------------
1 | declare module "prefix" {
2 | export default function prefix(action: string): string;
3 | export default function prefix(action: "transform"): "transform";
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/src/utils/functions/detectDevice.ts:
--------------------------------------------------------------------------------
1 | export const isIosMobile = () => {
2 | const userAgent = window.navigator.userAgent;
3 | return userAgent.match(/iPad/i) || userAgent.match(/iPhone/i);
4 | };
5 |
--------------------------------------------------------------------------------
/frontend/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './base/fonts.scss';
2 | @import './base/reset.scss';
3 | @import './base/global.scss';
4 |
5 | @import './components/measure.scss';
6 | @import './components/canvas.scss';
7 |
--------------------------------------------------------------------------------
/frontend/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/frontend/src/styles/components/canvas.scss:
--------------------------------------------------------------------------------
1 | .canvas {
2 | &__wrapper {
3 | width: 100%;
4 | height: 100%;
5 | position: fixed;
6 | top: 0;
7 | left: 0;
8 | z-index: -1;
9 | user-select: none;
10 | pointer-events: none;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/cra-for-testing/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 |
--------------------------------------------------------------------------------
/frontend/src/utils/functions/isTouchDevice.ts:
--------------------------------------------------------------------------------
1 | export const isBrowser = () => typeof window !== 'undefined';
2 | export const isTouchDevice = () =>
3 | isBrowser() &&
4 | ('ontouchstart' in window ||
5 | 'ontouchstart' in document.documentElement ||
6 | navigator.maxTouchPoints > 0);
7 |
--------------------------------------------------------------------------------
/react-just-parallax/src/utils/isTouchDevice.ts:
--------------------------------------------------------------------------------
1 | export const isBrowser = () => typeof window !== "undefined";
2 | export const isTouchDevice = () =>
3 | isBrowser() &&
4 | ("ontouchstart" in window ||
5 | "ontouchstart" in document.documentElement ||
6 | navigator.maxTouchPoints > 0);
7 |
--------------------------------------------------------------------------------
/frontend/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useRouter } from 'next/router';
3 |
4 | export default function Error404() {
5 | const router = useRouter();
6 |
7 | useEffect(() => {
8 | void router.push('/');
9 | }, [router]);
10 |
11 | return null;
12 | }
13 |
--------------------------------------------------------------------------------
/cra-for-testing/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render( );
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/react-just-parallax/src/index.ts:
--------------------------------------------------------------------------------
1 | // Setup based on: https://dev.to/siddharthvenkatesh/component-library-setup-with-react-typescript-and-rollup-onj
2 |
3 | export { MouseParallax } from "./components/MouseParallax/MouseParallax";
4 | export {
5 | ScrollParallax,
6 | ScrollParallaxHandle,
7 | } from "./components/ScrollParallax/ScrollParallax";
8 |
--------------------------------------------------------------------------------
/frontend/src/utils/functions/getRand.ts:
--------------------------------------------------------------------------------
1 | export const getRandInt = (minimum: number, maximum: number): number => {
2 | return Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
3 | };
4 |
5 | export const getRandFloat = (minimum: number, maximum: number): number => {
6 | return Math.random() * (maximum - minimum + 1) + minimum;
7 | };
8 |
--------------------------------------------------------------------------------
/frontend/src/utils/GlobalStyles.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | import { media } from 'utils/media';
4 |
5 | export const GlobalStyles = createGlobalStyle`
6 | html {
7 | font-size: calc(100vw / 375 * 10);
8 |
9 | ${media.tablet}{
10 | font-size: calc(100vw / 1920 * 10);
11 | }
12 |
13 | ${media.desktop}{
14 | font-size: 62.5%;
15 | }
16 | }
17 | `;
18 |
--------------------------------------------------------------------------------
/cra-for-testing/.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 |
--------------------------------------------------------------------------------
/frontend/src/containers/ErrorPage/ErrorPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface ErrorPageProps {
4 | statusCode: number;
5 | }
6 |
7 | export default function ErrorPage(props: ErrorPageProps) {
8 | const { statusCode } = props;
9 | return (
10 | <>
11 |
12 |
Something went wrong {`| ${statusCode || 'undefined code'}`}
13 |
14 | >
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/src/utils/functions/getScrollbarWidth.ts:
--------------------------------------------------------------------------------
1 | export const getScrollbarWidth = () => {
2 | // Create the div
3 | const scrollDiv = document.createElement('div');
4 | scrollDiv.className = 'scrollbar-measure';
5 | document.body.appendChild(scrollDiv);
6 | // Get the scrollbar width
7 | const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
8 | // Delete the div
9 | document.body.removeChild(scrollDiv);
10 | return scrollbarWidth;
11 | };
12 |
--------------------------------------------------------------------------------
/frontend/src/utils/media.ts:
--------------------------------------------------------------------------------
1 | export const breakpoints = {
2 | tablet: 767,
3 | tabletLand: 992,
4 | desktop: 1920,
5 | };
6 |
7 | const customMediaQuery = (minWidth: number) =>
8 | `@media only screen and (min-width: ${minWidth / 16}em)`;
9 |
10 | export const media = {
11 | custom: customMediaQuery,
12 | tablet: customMediaQuery(breakpoints.tablet),
13 | tabletLand: customMediaQuery(breakpoints.tabletLand),
14 | desktop: customMediaQuery(breakpoints.desktop),
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/src/components/Caption/Caption.state.ts:
--------------------------------------------------------------------------------
1 | import { App } from './classes/App';
2 | import * as Background from './backgroundClasses/App';
3 | import * as CoverBackground from './coverBackgroundClasses/App';
4 |
5 | interface AppState {
6 | app: App | null;
7 | background: Background.App | null;
8 | coverBackground: CoverBackground.App | null;
9 | }
10 |
11 | export const appState: AppState = {
12 | app: null,
13 | background: null,
14 | coverBackground: null,
15 | };
16 |
--------------------------------------------------------------------------------
/cra-for-testing/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
15 | h2,
16 | h3 {
17 | margin: 0;
18 | }
19 |
--------------------------------------------------------------------------------
/cra-for-testing/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 |
--------------------------------------------------------------------------------
/frontend/src/utils/functions/setCssVariables.ts:
--------------------------------------------------------------------------------
1 | export const VARIABLES = [
2 | {
3 | name: '--transition-duration',
4 | value: '100' + 'ms',
5 | },
6 | ];
7 |
8 | interface SetCssVariables {
9 | variables: typeof VARIABLES;
10 | }
11 |
12 | export const setCssVariables = ({ variables }: SetCssVariables) => {
13 | const root = document.documentElement;
14 |
15 | for (let i = 0; i < variables.length; i++) {
16 | const el = variables[i];
17 | root.style.setProperty(el.name, el.value);
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/images/shape4.svg:
--------------------------------------------------------------------------------
1 | shape4
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/images/shape5.svg:
--------------------------------------------------------------------------------
1 | shape5
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/images/shape7.svg:
--------------------------------------------------------------------------------
1 | shape7
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/images/shape2.svg:
--------------------------------------------------------------------------------
1 | shape2
--------------------------------------------------------------------------------
/frontend/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 |
3 | const withPlugins = require('next-compose-plugins');
4 | const withOptimizedImages = require('next-optimized-images');
5 |
6 | const nextConfig = {
7 | reactStrictMode: true,
8 | i18n: {
9 | locales: ['en-US'],
10 | defaultLocale: 'en-US',
11 | },
12 | compiler: {
13 | // ssr and displayName are configured by default
14 | styledComponents: true,
15 | },
16 | webpack: config => {
17 | return config;
18 | },
19 | };
20 |
21 | module.exports = withPlugins([[withOptimizedImages]], nextConfig);
22 |
--------------------------------------------------------------------------------
/frontend/.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 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 |
--------------------------------------------------------------------------------
/frontend/src/styles/base/fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'teko';
3 | font-weight: 700;
4 | font-style: normal;
5 | font-display: swap;
6 | src: url('/fonts/teko700.woff2') format('woff2');
7 | }
8 |
9 | @font-face {
10 | font-family: 'opensans';
11 | font-style: normal;
12 | font-weight: normal;
13 | font-display: swap;
14 | src: url('/fonts/openSans400.woff') format('woff');
15 | }
16 |
17 | @font-face {
18 | font-family: 'opensans';
19 | font-weight: 800;
20 | font-style: normal;
21 | font-display: swap;
22 | src: url('/fonts/openSans800.woff2') format('woff2');
23 | }
24 |
--------------------------------------------------------------------------------
/react-just-parallax/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | "target": "es6",
6 | "module": "esnext",
7 | "jsx": "react",
8 | "declaration": true,
9 | "declarationDir": "types",
10 | "sourceMap": true,
11 | "outDir": "dist",
12 | "strict": true,
13 | "moduleResolution": "node",
14 | "allowSyntheticDefaultImports": true,
15 | "esModuleInterop": true,
16 | "skipLibCheck": true,
17 | "forceConsistentCasingInFileNames": true,
18 | "emitDeclarationOnly": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/cra-for-testing/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 |
--------------------------------------------------------------------------------
/frontend/src/pages/_error.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import type { NextApiResponse } from 'next';
3 |
4 | import ErrorPage from 'containers/ErrorPage/ErrorPage';
5 |
6 | interface ErrorProps {
7 | res?: NextApiResponse;
8 | err?: NextApiResponse;
9 | }
10 |
11 | interface Error {
12 | statusCode: number;
13 | }
14 |
15 | export default function ErrorServer({ statusCode }: Error) {
16 | return ;
17 | }
18 |
19 | ErrorServer.getInitialProps = ({ res, err }: ErrorProps) => {
20 | const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
21 | return { statusCode };
22 | };
23 |
--------------------------------------------------------------------------------
/cra-for-testing/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOM.createRoot(
8 | document.getElementById('root') as HTMLElement
9 | );
10 | root.render(
11 |
12 |
13 |
14 | );
15 |
16 | // If you want to start measuring performance in your app, pass a function
17 | // to log results (for example: reportWebVitals(console.log))
18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19 | reportWebVitals();
20 |
--------------------------------------------------------------------------------
/cra-for-testing/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 |
--------------------------------------------------------------------------------
/frontend/src/components/PreloadImage/PreloadImage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { useMediaPreload } from 'hooks/useMediaPreload';
4 |
5 | import * as S from './PreloadImage.styles';
6 |
7 | interface Props {
8 | imageSrc: string;
9 | alt: string;
10 | shouldContain?: boolean;
11 | }
12 |
13 | export const PreloadImage = (props: Props) => {
14 | const { shouldContain = false, alt, imageSrc } = props;
15 |
16 | const { isLoaded } = useMediaPreload({ isImage: true, mediaSrc: imageSrc });
17 |
18 | return (
19 | <>
20 |
21 | >
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.fixAll.eslint": true
4 | },
5 | "editor.formatOnSave": true,
6 | "eslint.alwaysShowStatus": true,
7 | "editor.fontSize": 20,
8 | "emmet.includeLanguages": {
9 | "javascript": "javascriptreact"
10 | },
11 | "html.format.wrapAttributes": "force-aligned",
12 | "editor.tabSize": 2,
13 | "workbench.editor.labelFormat": "short",
14 | "editor.defaultFormatter": "esbenp.prettier-vscode",
15 | "explorer.confirmDelete": false
16 | }
17 | // Installed extensions : "eslint, prettier ,bracketPairColorizer, GitLens, vscode-styled-components, indent-rainbow, path intellisense"
18 | // Installed git bash
19 |
--------------------------------------------------------------------------------
/frontend/src/components/Layout/Layout.styles.ts:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 |
3 | import { sharedValues } from 'utils/sharedValues';
4 |
5 | interface ReadyWrapperProps {
6 | isReady: boolean;
7 | }
8 |
9 | export const ReadyWrapper = styled.div`
10 | position: fixed;
11 | top: 0;
12 | left: 0;
13 | width: 100%;
14 | height: 100%;
15 | z-index: 100;
16 | opacity: 1;
17 | transition: opacity 0.5s ease-in-out;
18 | background-color: ${sharedValues.colors.trueWhite};
19 |
20 | ${props =>
21 | props.isReady &&
22 | css`
23 | opacity: 0;
24 | user-select: none;
25 | pointer-events: none;
26 | `}
27 | `;
28 |
--------------------------------------------------------------------------------
/frontend/src/components/PreloadImage/PreloadImage.styles.ts:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 |
3 | interface ImageProps {
4 | isLoaded: boolean;
5 | shouldContain: boolean;
6 | }
7 |
8 | export const Image = styled.img`
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | width: 100%;
13 | height: 100%;
14 | object-fit: cover;
15 | opacity: 0;
16 | transition: opacity 0.45s;
17 | user-select: none;
18 | pointer-events: none;
19 |
20 | ${props =>
21 | props.shouldContain &&
22 | css`
23 | object-fit: contain;
24 | `}
25 |
26 | ${props =>
27 | props.isLoaded &&
28 | css`
29 | opacity: 1;
30 | `}
31 | `;
32 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/gts/tsconfig-google.json",
3 | "compilerOptions": {
4 | "target": "es2020",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "strict": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "noEmit": true,
11 | "esModuleInterop": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "jsx": "preserve",
17 | "baseUrl": "src",
18 | "incremental": true
19 | },
20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
21 | "exclude": ["node_modules"]
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/seo/GoogleAnalytics/GoogleAnalytics.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const GoogleAnalytics = () => {
4 | const googleAnalyticsTag = process.env.NEXT_PUBLIC_GA_KEY as string;
5 | return (
6 | <>
7 |
8 |
18 | >
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/frontend/src/utils/functions/seedRandom.ts:
--------------------------------------------------------------------------------
1 | export const seedRandom = (str: string) => {
2 | let h1 = 1779033703,
3 | h2 = 3144134277,
4 | h3 = 1013904242,
5 | h4 = 2773480762;
6 | for (let i = 0, k; i < str.length; i++) {
7 | k = str.charCodeAt(i);
8 | h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
9 | h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
10 | h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
11 | h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
12 | }
13 | h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
14 | h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
15 | h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
16 | h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
17 | return [(h1 ^ h2 ^ h3 ^ h4) >>> 0, (h2 ^ h1) >>> 0, (h3 ^ h1) >>> 0, (h4 ^ h1) >>> 0];
18 | };
19 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | Code for React Just Parallax documentation page
2 |
3 | ## Getting Started
4 |
5 | Node version used : `16.13.1`
6 |
7 | First install dependencies with:
8 |
9 | ```bash
10 | npm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Then, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | yarn dev
21 | ```
22 |
23 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
24 |
25 | ## Styling
26 |
27 | 1. For styling use styled-components.
28 |
29 | 2. In order to set the custom global css styles, use `/utils/GlobalStyles.ts` file.
30 |
31 | 3. There is also `/styles` folder in the root location. It is only
32 | used to initially set default and reset styles for the application and should not be updated in general.
--------------------------------------------------------------------------------
/frontend/src/utils/sharedTypes.ts:
--------------------------------------------------------------------------------
1 | import { HeadProps } from 'seo/Head/Head';
2 |
3 | export interface DomRectSSR {
4 | bottom: number;
5 | height: number;
6 | left: number;
7 | right: number;
8 | top: number;
9 | width: number;
10 | x: number;
11 | y: number;
12 | }
13 |
14 | export interface Bounds {
15 | width: number;
16 | height: number;
17 | }
18 |
19 | interface Coords {
20 | x: number;
21 | y: number;
22 | }
23 |
24 | export interface Mouse {
25 | current: Coords;
26 | target: Coords;
27 | }
28 |
29 | export interface PageProps {
30 | head: HeadProps;
31 | repoHref?: string;
32 | }
33 |
34 | export interface UpdateInfo {
35 | slowDownFactor: number;
36 | delta: number;
37 | time: number;
38 | }
39 |
40 | export interface Bounds {
41 | width: number;
42 | height: number;
43 | }
44 |
--------------------------------------------------------------------------------
/frontend/src/components/Layout/Layout.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | import { CopyInfo } from 'sections/CopyInfo/CopyInfo';
4 |
5 | import * as S from './Layout.styles';
6 |
7 | interface Props {
8 | isReady: boolean;
9 | children: React.ReactChild;
10 | repoHref?: string;
11 | }
12 |
13 | export const Layout = (props: Props) => {
14 | const { repoHref, children, isReady } = props;
15 |
16 | useEffect(() => {
17 | if (isReady && !document.body.classList.contains('isReady')) {
18 | document.body.classList.add('isReady');
19 | }
20 | return () => {
21 | document.body.classList.remove('isReady');
22 | };
23 | }, [isReady]);
24 |
25 | return (
26 | <>
27 |
28 |
29 |
30 | {children}
31 | >
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/frontend/src/components/CodeRenderer/CodeRenderer.tsx:
--------------------------------------------------------------------------------
1 | import SyntaxHighlighter from 'react-syntax-highlighter';
2 | import { nord } from 'react-syntax-highlighter/dist/cjs/styles/hljs';
3 |
4 | import * as S from './CodeRenderer.styles';
5 |
6 | interface Props {
7 | codeText: string;
8 | }
9 |
10 | export const CodeRenderer = (props: Props) => {
11 | const { codeText } = props;
12 |
13 | return (
14 | <>
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {codeText}
23 |
24 |
25 | >
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/react-just-parallax/src/utils/getScrollOffsets.ts:
--------------------------------------------------------------------------------
1 | export interface ScrollValues {
2 | xOffset: number;
3 | yOffset: number;
4 | xMaxOffset: number;
5 | yMaxOffset: number;
6 | }
7 |
8 | export const defaultScrollValues: ScrollValues = {
9 | xMaxOffset: 1,
10 | xOffset: 1,
11 | yMaxOffset: 1,
12 | yOffset: 1,
13 | };
14 |
15 | export const getViewportScrollOffsets = () => {
16 | return {
17 | xOffset: window.pageXOffset,
18 | yOffset: window.pageYOffset,
19 | xMaxOffset: document.body.clientWidth - window.innerWidth,
20 | yMaxOffset: document.body.clientHeight - window.innerHeight,
21 | };
22 | };
23 |
24 | export const getElementScrollOffsets = (element: HTMLElement) => {
25 | return {
26 | xOffset: element.scrollLeft,
27 | yOffset: element.scrollTop,
28 | xMaxOffset: element.scrollWidth - element.offsetWidth,
29 | yMaxOffset: element.scrollHeight - element.offsetHeight,
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/frontend/src/components/LinkHandler/LinkHandler.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 |
4 | interface Props {
5 | elHref?: string;
6 | isExternal?: boolean;
7 | onClickFn?: () => void;
8 | children: React.ReactNode;
9 | }
10 |
11 | export const LinkHandler = (props: Props) => {
12 | const { elHref, children, isExternal, onClickFn } = props;
13 |
14 | return (
15 | <>
16 | {isExternal ? (
17 |
18 | {children}
19 |
20 | ) : onClickFn ? (
21 | onClickFn()}>
22 | {children}
23 |
24 | ) : (
25 | elHref && (
26 |
27 | {children}
28 |
29 | )
30 | )}
31 | >
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/frontend/src/utils/sharedValues.ts:
--------------------------------------------------------------------------------
1 | const DEFAULT_FPS = 60;
2 |
3 | export const sharedValues = {
4 | colors: {
5 | trueBlack: '#000000',
6 | trueWhite: '#ffffff',
7 | black: '#161616',
8 | lightGray: '#f5f5f5',
9 | lightPurple: '#F0F0FF',
10 | purple: '#755DB5',
11 | blue: '#3087ff',
12 | },
13 | spacing: {
14 | s1: '15px',
15 | s2: '30px',
16 | s3: '8px',
17 | },
18 | timings: {
19 | t1: 'cubic-bezier(0.64, 0.02, 0.16, 0.97)',
20 | },
21 | motion: {
22 | DEFAULT_FPS: DEFAULT_FPS,
23 | DT_FPS: 1000 / DEFAULT_FPS,
24 | LERP_EASE: 0.07,
25 | MOMENTUM_DAMPING: 0.8,
26 | MOMENTUM_CARRY: 0.6,
27 | tween2: {
28 | type: 'tween',
29 | ease: [0.64, 0.02, 0.16, 0.97],
30 | duration: 0.8,
31 | },
32 | springSlow: {
33 | type: 'spring',
34 | stiffness: 350,
35 | damping: 80,
36 | mass: 5,
37 | restDelta: 0.001,
38 | restSpeed: 0.001,
39 | },
40 | },
41 | ISR_TIMEOUT: 5,
42 | };
43 |
--------------------------------------------------------------------------------
/frontend/src/utils/sharedStyled.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components';
2 |
3 | import { media } from 'utils/media';
4 | import { sharedValues } from 'utils/sharedValues';
5 |
6 | export const s1 = css`
7 | font-size: 13px;
8 | line-height: 1.6;
9 |
10 | ${media.tablet} {
11 | font-size: 15px;
12 | }
13 | `;
14 |
15 | export const s2 = css`
16 | font-size: 12px;
17 | line-height: 1.6;
18 | `;
19 |
20 | export const m1 = css`
21 | font-size: 2.5rem;
22 | line-height: 1.4;
23 | `;
24 |
25 | export const m2 = css`
26 | font-size: 4.5rem;
27 | `;
28 |
29 | export const underline = css`
30 | &:before {
31 | content: '';
32 | position: absolute;
33 | top: 85%;
34 | width: 100%;
35 | height: 1px;
36 | background-color: currentColor;
37 | transform-origin: left;
38 | transform: scaleX(0);
39 | transition: transform 0.7s ${sharedValues.timings.t1};
40 | }
41 |
42 | &:hover {
43 | &:before {
44 | transform: scaleX(1);
45 | }
46 | }
47 | `;
48 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | //Partly based on https://typescript-eslint.io/docs/linting/ and https://typescript-eslint.io/docs/linting/type-linting/
2 |
3 | module.exports = {
4 | root: true,
5 | parser: '@typescript-eslint/parser',
6 | parserOptions: {
7 | project: ['./tsconfig.json'],
8 | tsconfigRootDir: __dirname,
9 | },
10 | extends: [
11 | 'eslint:recommended',
12 | 'plugin:@typescript-eslint/recommended',
13 | 'plugin:@typescript-eslint/recommended-requiring-type-checking',
14 | 'prettier',
15 | 'next/core-web-vitals',
16 | 'plugin:react-hooks/recommended',
17 | ],
18 | plugins: ['prettier', '@typescript-eslint'],
19 | rules: {
20 | 'prettier/prettier': 'error',
21 | 'react/react-in-jsx-scope': 'off',
22 | '@next/next/no-document-import-in-page': 'off',
23 | '@typescript-eslint/no-empty-interface': 'off',
24 | 'react/prop-types': 0,
25 | },
26 | settings: {
27 | react: {
28 | version: 'detect',
29 | },
30 | },
31 | ignorePatterns: '.eslintrc.js',
32 | };
33 |
--------------------------------------------------------------------------------
/frontend/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/frontend/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Michal Zalobny
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 |
--------------------------------------------------------------------------------
/frontend/src/components/CodeRenderer/CodeRenderer.styles.ts:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 |
3 | export const SyntaxWrapper = styled.div`
4 | overflow: hidden;
5 | border-bottom-left-radius: 10px;
6 | border-bottom-right-radius: 10px;
7 | font-size: 15px;
8 | line-height: 1.6;
9 | background: #2b2b2b;
10 | padding: 16px;
11 |
12 | pre {
13 | background: transparent !important;
14 | padding: 0 !important;
15 | font-family: 'opensans' !important;
16 | letter-spacing: 0.3px;
17 | }
18 | `;
19 |
20 | export const SyntaxTop = styled.div`
21 | position: relative;
22 | border-top-left-radius: 10px;
23 | border-top-right-radius: 10px;
24 | background: #2b2b2b;
25 | height: 32px;
26 | transform: translateY(1px);
27 | `;
28 |
29 | interface SyntaxDotProps {
30 | $bgColor: string;
31 | $offsetX: number;
32 | }
33 |
34 | export const SyntaxDot = styled.div`
35 | position: absolute;
36 | top: 16px;
37 | left: 16px;
38 | width: 13px;
39 | height: 13px;
40 | border-radius: 50%;
41 | background-color: ${props => props.$bgColor};
42 | ${props => css`
43 | transform: translateY(0) ${`translateX(${props.$offsetX}%)`};
44 | `}
45 | `;
46 |
--------------------------------------------------------------------------------
/cra-for-testing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cra-for-testing",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.4",
7 | "@testing-library/react": "^13.3.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "@types/jest": "^27.5.2",
10 | "@types/node": "^16.11.43",
11 | "@types/react": "^18.0.15",
12 | "@types/react-dom": "^18.0.6",
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-scripts": "5.0.1",
16 | "typescript": "^4.7.4",
17 | "web-vitals": "^2.1.4",
18 | "react-just-parallax": "../react-just-parallax"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/frontend/src/sections/CopyInfo/CopyInfo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 |
4 | import { PreloadImage } from 'components/PreloadImage/PreloadImage';
5 | import { LinkHandler } from 'components/LinkHandler/LinkHandler';
6 |
7 | import * as S from './CopyInfo.styles';
8 | import logoSrc from './images/logo.svg';
9 |
10 | interface Props {
11 | repoHref?: string;
12 | }
13 |
14 | export const CopyInfo = (props: Props) => {
15 | const { repoHref = 'https://www.npmjs.com/package/react-just-parallax' } = props;
16 |
17 | return (
18 | <>
19 |
20 |
21 | Official NPM page
22 |
23 |
24 |
25 | React Just Parallax - showcase by
26 |
27 | @michalzalobny
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | >
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/frontend/src/containers/IndexPage/IndexPage.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { media } from 'utils/media';
4 | import { underline, s1 } from 'utils/sharedStyled';
5 |
6 | export const ScrollContainer = styled.div`
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | width: 100%;
11 | height: 100%;
12 | overflow: auto;
13 | `;
14 |
15 | export const DocsWrapper = styled.div`
16 | margin-top: -40vh;
17 |
18 | ${media.tablet} {
19 | margin-top: -50vh;
20 | }
21 | `;
22 |
23 | export const CaptionWrapper = styled.div`
24 | height: 300vh;
25 |
26 | ${media.tablet} {
27 | height: 340vh;
28 | }
29 | `;
30 |
31 | export const Wrapper = styled.div`
32 | margin: 0 auto;
33 | width: 80%;
34 |
35 | ${media.tablet} {
36 | width: 100rem;
37 | }
38 | `;
39 |
40 | export const GithubWrapper = styled.div`
41 | display: initial;
42 | position: fixed;
43 | z-index: 20;
44 | top: 16px;
45 | right: 20px;
46 | mix-blend-mode: difference;
47 | color: white;
48 | display: none;
49 |
50 | ${media.tablet} {
51 | display: initial;
52 | transform: none;
53 | top: 30px;
54 | right: 50px;
55 | }
56 | `;
57 |
58 | export const GithubLink = styled.span`
59 | display: inline-block;
60 | position: relative;
61 | ${s1};
62 | ${underline};
63 | `;
64 |
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/images/shape3.svg:
--------------------------------------------------------------------------------
1 | shape3
--------------------------------------------------------------------------------
/react-just-parallax/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from '@rollup/plugin-node-resolve';
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import typescript from '@rollup/plugin-typescript';
4 | import { terser } from 'rollup-plugin-terser';
5 | import external from 'rollup-plugin-peer-deps-external';
6 | import postcss from 'rollup-plugin-postcss';
7 | import dts from 'rollup-plugin-dts';
8 |
9 | const packageJson = require('./package.json');
10 |
11 | export default [
12 | {
13 | input: 'src/index.ts',
14 | output: [
15 | {
16 | file: packageJson.main,
17 | format: 'cjs',
18 | sourcemap: true,
19 | name: 'react-ts-lib'
20 | },
21 | {
22 | file: packageJson.module,
23 | format: 'esm',
24 | sourcemap: true
25 | }
26 | ],
27 | plugins: [
28 | external(),
29 | resolve(),
30 | commonjs(),
31 | typescript({ tsconfig: './tsconfig.json' }),
32 | postcss(),
33 | terser()
34 | ],
35 | },
36 | {
37 | input: 'dist/esm/types/index.d.ts',
38 | output: [{ file: 'dist/index.d.ts', format: "esm" }],
39 | external: [/\.css$/],
40 | plugins: [dts()],
41 | },
42 | ]
43 |
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/images/shape1.svg:
--------------------------------------------------------------------------------
1 | shape1
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "1.0.2",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "focus-visible": "5.2.0",
13 | "fontfaceobserver": "2.1.0",
14 | "framer-motion": "6.5.1",
15 | "gsap": "3.10.4",
16 | "lodash.debounce": "4.0.8",
17 | "next": "12.1.5",
18 | "next-compose-plugins": "^2.2.1",
19 | "next-optimized-images": "^2.6.2",
20 | "react": "18.0.0",
21 | "react-dom": "18.0.0",
22 | "react-just-parallax": "^3.1.14",
23 | "react-syntax-highlighter": "^15.5.0",
24 | "split-type": "0.2.5",
25 | "styled-components": "5.3.3"
26 | },
27 | "devDependencies": {
28 | "@types/fontfaceobserver": "2.1.0",
29 | "@types/lodash.debounce": "4.0.6",
30 | "@types/node": "17.0.17",
31 | "@types/react": "17.0.39",
32 | "@types/react-syntax-highlighter": "^15.5.5",
33 | "@types/styled-components": "5.1.22",
34 | "@typescript-eslint/eslint-plugin": "5.11.0",
35 | "@typescript-eslint/parser": "5.11.0",
36 | "eslint": "7.32.0",
37 | "eslint-config-next": "12.0.10",
38 | "eslint-plugin-react-hooks": "4.3.0",
39 | "gts": "3.1.0",
40 | "prettier": "2.5.1",
41 | "sass": "1.49.7",
42 | "typescript": "4.5.5"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useWindowSize.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback, useRef } from 'react';
2 | import debounce from 'lodash.debounce';
3 |
4 | import { getScrollbarWidth } from 'utils/functions/getScrollbarWidth';
5 |
6 | interface WindowSize {
7 | windowWidth: number;
8 | windowHeight: number;
9 | scrollbarWidth: number;
10 | isReady: boolean;
11 | }
12 |
13 | const windowSizeSSR: WindowSize = {
14 | isReady: false,
15 | scrollbarWidth: 0,
16 | windowHeight: 0,
17 | windowWidth: 0,
18 | };
19 |
20 | export const useWindowSize = () => {
21 | const [windowSize, setWindowSize] = useState(windowSizeSSR);
22 | const windowSizeRef = useRef(windowSizeSSR);
23 |
24 | const onResize = useCallback(() => {
25 | const newWindowSize: WindowSize = {
26 | isReady: true,
27 | scrollbarWidth: getScrollbarWidth(),
28 | windowHeight: window.innerHeight,
29 | windowWidth: window.innerWidth,
30 | };
31 |
32 | setWindowSize(newWindowSize);
33 | windowSizeRef.current = newWindowSize;
34 | }, []);
35 |
36 | useEffect(() => {
37 | const onResizeDebounced = debounce(onResize, 100);
38 | window.addEventListener('resize', onResizeDebounced);
39 | onResize();
40 | return () => {
41 | window.removeEventListener('resize', onResizeDebounced);
42 | };
43 | }, [onResize]);
44 |
45 | return { windowSize, windowSizeRef };
46 | };
47 |
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/images/shape6.svg:
--------------------------------------------------------------------------------
1 | shape6
--------------------------------------------------------------------------------
/react-just-parallax/src/hooks/useWindowSize.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback, useRef } from "react";
2 | import debounce from "lodash.debounce";
3 |
4 | interface WindowSize {
5 | windowWidth: number;
6 | windowHeight: number;
7 | scrollbarWidth: number;
8 | isReady: boolean;
9 | }
10 |
11 | const windowSizeSSR: WindowSize = {
12 | isReady: false,
13 | scrollbarWidth: 0,
14 | windowHeight: 0,
15 | windowWidth: 0,
16 | };
17 |
18 | export const useWindowSize = () => {
19 | const [windowSize, setWindowSize] = useState(windowSizeSSR);
20 | const windowSizeRef = useRef(windowSizeSSR);
21 | const getScrollbarWidth = () => {
22 | return window.innerWidth - document.documentElement.clientWidth;
23 | };
24 |
25 | const onResize = useCallback(() => {
26 | const newWindowSize: WindowSize = {
27 | isReady: true,
28 | scrollbarWidth: getScrollbarWidth(),
29 | windowHeight: window.innerHeight,
30 | windowWidth: window.innerWidth,
31 | };
32 |
33 | setWindowSize(newWindowSize);
34 | windowSizeRef.current = newWindowSize;
35 | }, []);
36 |
37 | useEffect(() => {
38 | const onResizeDebounced = debounce(onResize, 100);
39 | window.addEventListener("resize", onResizeDebounced);
40 | onResize();
41 | return () => {
42 | window.removeEventListener("resize", onResizeDebounced);
43 | };
44 | }, [onResize]);
45 |
46 | return { windowSize, windowSizeRef };
47 | };
48 |
--------------------------------------------------------------------------------
/frontend/src/seo/Head/Head.tsx:
--------------------------------------------------------------------------------
1 | import NextHead from 'next/head';
2 | import React from 'react';
3 |
4 | import { GoogleAnalytics } from '../GoogleAnalytics/GoogleAnalytics';
5 |
6 | export interface HeadProps {
7 | title?: string;
8 | description?: string;
9 | ogImage?: string;
10 | }
11 |
12 | export const Head = (props: HeadProps) => {
13 | const {
14 | ogImage = 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1659183816/ogimage-100_jec2sx.jpg',
15 | title = 'React Just Parallax',
16 | description = 'React library for scroll and mousemove parallax effect, open source, production-ready',
17 | } = props;
18 |
19 | return (
20 |
21 | {title}
22 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/frontend/src/containers/Examples/1/ExamplePage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 |
3 | import { Head } from 'seo/Head/Head';
4 | import { GameTile } from 'components/GameTile/GameTile';
5 | import { LinkHandler } from 'components/LinkHandler/LinkHandler';
6 |
7 | import * as S from './ExamplePage.styles';
8 | import { GameAsset, LogoAsset } from './ExamplePage.data';
9 |
10 | interface Props {
11 | gameAssets: GameAsset[];
12 | logoAssets: LogoAsset[];
13 | }
14 |
15 | export default function ExamplePage(props: Props) {
16 | const { gameAssets, logoAssets } = props;
17 |
18 | const scrollContainerRef = useRef(null);
19 |
20 | return (
21 | <>
22 |
23 |
29 | This page‘s code
30 |
31 |
32 |
33 |
34 |
35 | {gameAssets.map((item, key) => (
36 |
45 | ))}
46 |
47 |
48 | >
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useMediaPreload.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | interface Props {
4 | mediaSrc: string;
5 | isImage: boolean;
6 | shouldLoad?: boolean;
7 | }
8 |
9 | export const useMediaPreload = (props: Props) => {
10 | const { shouldLoad = true, mediaSrc, isImage } = props;
11 | const [isLoaded, setIsLoaded] = useState(false);
12 |
13 | //Handle images
14 | useEffect(() => {
15 | if (!isImage || !shouldLoad || !mediaSrc) {
16 | return;
17 | }
18 | const onMediaLoad = () => {
19 | setIsLoaded(true);
20 | };
21 |
22 | const image = new Image();
23 | image.src = mediaSrc;
24 |
25 | if (image.complete) {
26 | return setIsLoaded(true);
27 | }
28 |
29 | const load = () => onMediaLoad();
30 |
31 | image.addEventListener('load', load);
32 | return () => {
33 | image.removeEventListener('load', load);
34 | };
35 | }, [isImage, mediaSrc, shouldLoad]);
36 |
37 | //Handle videos
38 | useEffect(() => {
39 | if (isImage || !shouldLoad || !mediaSrc) {
40 | return;
41 | }
42 |
43 | const onMediaLoad = () => {
44 | setIsLoaded(true);
45 | };
46 |
47 | const videoEl = document.createElement('video');
48 | videoEl.setAttribute('src', mediaSrc);
49 |
50 | videoEl.addEventListener('canplay', onMediaLoad);
51 | videoEl.addEventListener('loadedmetadata', onMediaLoad);
52 |
53 | return () => {
54 | videoEl.removeEventListener('canplay', onMediaLoad);
55 | videoEl.removeEventListener('loadedmetadata', onMediaLoad);
56 | };
57 | }, [isImage, mediaSrc, shouldLoad]);
58 |
59 | return { isLoaded };
60 | };
61 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useElementSize.ts:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 | import debounce from 'lodash.debounce';
3 |
4 | import { DomRectSSR } from 'utils/sharedTypes';
5 |
6 | type ElRef = React.RefObject;
7 |
8 | interface Size {
9 | clientRect: DomRectSSR;
10 | offsetTop: number;
11 | offsetLeft: number;
12 | isReady: boolean;
13 | }
14 |
15 | const EmptySSRRect: DomRectSSR = {
16 | bottom: 1,
17 | height: 1,
18 | left: 1,
19 | right: 1,
20 | top: 1,
21 | width: 1,
22 | x: 1,
23 | y: 1,
24 | };
25 |
26 | const emptySize: Size = {
27 | clientRect: EmptySSRRect,
28 | offsetTop: 1,
29 | offsetLeft: 1,
30 | isReady: false,
31 | };
32 |
33 | export const useElementSize = (elRef: ElRef) => {
34 | const [size, setSize] = useState(emptySize);
35 | const sizeRef = useRef(emptySize);
36 |
37 | useEffect(() => {
38 | const onResize = () => {
39 | if (!elRef.current) return;
40 | const rect = elRef.current.getBoundingClientRect();
41 |
42 | const size = {
43 | clientRect: rect,
44 | isReady: true,
45 | offsetTop: elRef.current.offsetTop, //Retruns offset to relative element (not to the whole page)
46 | offsetLeft: elRef.current.offsetLeft,
47 | };
48 |
49 | sizeRef.current = size;
50 | setSize(size);
51 | };
52 |
53 | const onResizeDebounced = debounce(onResize, 100);
54 |
55 | window.addEventListener('resize', onResizeDebounced);
56 | onResize();
57 |
58 | return () => {
59 | window.removeEventListener('resize', onResizeDebounced);
60 | };
61 | }, [elRef]);
62 |
63 | return {
64 | size,
65 | sizeRef,
66 | };
67 | };
68 |
--------------------------------------------------------------------------------
/frontend/src/sections/CopyInfo/CopyInfo.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { media } from 'utils/media';
4 | import { underline, s1 } from 'utils/sharedStyled';
5 |
6 | export const GithubWrapper = styled.div`
7 | display: initial;
8 | position: fixed;
9 | z-index: 20;
10 | bottom: 0px;
11 | left: 0px;
12 | mix-blend-mode: difference;
13 | color: white;
14 | transform-origin: bottom left;
15 | transform: rotate(-90deg) translateY(calc(100% + 16px)) translateX(15px);
16 | display: none;
17 |
18 | ${media.tablet} {
19 | display: initial;
20 | transform: none;
21 | bottom: 30px;
22 | left: 50px;
23 | }
24 | `;
25 |
26 | export const AuthorWrapper = styled.h1`
27 | position: fixed;
28 | z-index: 20;
29 | bottom: 10px;
30 | right: 20px;
31 | display: flex;
32 | align-items: center;
33 | mix-blend-mode: difference;
34 | color: white;
35 | ${s1};
36 |
37 | ${media.tablet} {
38 | bottom: 30px;
39 | right: 50px;
40 | }
41 | `;
42 |
43 | export const LogoWrapper = styled.a`
44 | cursor: pointer;
45 | position: fixed;
46 | z-index: 20;
47 | top: 16px;
48 | left: 20px;
49 | display: flex;
50 | width: 150px;
51 |
52 | &:before {
53 | content: '';
54 | display: block;
55 | padding-bottom: 33%;
56 | }
57 |
58 | ${media.tablet} {
59 | width: 190px;
60 | top: 30px;
61 | left: 50px;
62 | }
63 | `;
64 |
65 | export const GithubLink = styled.span`
66 | display: inline-block;
67 | position: relative;
68 | ${s1};
69 | ${underline};
70 | `;
71 |
72 | export const AuthorLink = styled.span`
73 | display: inline-block;
74 | font-weight: 800;
75 | position: relative;
76 | ${underline};
77 | margin-left: 5px;
78 | `;
79 |
--------------------------------------------------------------------------------
/frontend/src/containers/Examples/1/ExamplePage.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { motion } from 'framer-motion';
3 |
4 | import { underline, s1 } from 'utils/sharedStyled';
5 | import { media } from 'utils/media';
6 | import { sharedValues } from 'utils/sharedValues';
7 |
8 | export const ScrollContainer = styled.div`
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | width: 100%;
13 | height: 100%;
14 | overflow: auto;
15 | overflow-x: hidden;
16 | background: white;
17 | `;
18 |
19 | export const Wrapper = styled(motion.div)`
20 | margin: 120px auto;
21 |
22 | width: 85%;
23 | position: relative;
24 | user-select: none;
25 | pointer-events: none;
26 |
27 | ${media.tablet} {
28 | width: 60rem;
29 | margin: 100px auto;
30 | }
31 | `;
32 |
33 | Wrapper.defaultProps = {
34 | variants: {
35 | initial: {
36 | opacity: 0,
37 | y: '5vh',
38 | },
39 | animate: {
40 | opacity: 1,
41 | y: '0vh',
42 | transition: {
43 | delay: 0.3,
44 | ...sharedValues.motion.springSlow,
45 | },
46 | },
47 | },
48 | };
49 |
50 | export const GithubWrapper = styled.div`
51 | display: initial;
52 | position: fixed;
53 | z-index: 20;
54 | top: 0px;
55 | right: 0px;
56 | mix-blend-mode: difference;
57 | color: white;
58 | transform-origin: bottom left;
59 | transform: rotate(-90deg) translateY(calc(100% + 16px)) translateX(15px);
60 | display: none;
61 |
62 | ${media.tablet} {
63 | display: initial;
64 | transform: none;
65 | top: 30px;
66 | right: 50px;
67 | }
68 | `;
69 |
70 | export const GithubLink = styled.span`
71 | display: inline-block;
72 | position: relative;
73 | ${s1};
74 | ${underline};
75 | `;
76 |
--------------------------------------------------------------------------------
/frontend/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import type { AppProps } from 'next/app';
3 | import { useRouter } from 'next/router';
4 | import FontFaceObserver from 'fontfaceobserver';
5 | import 'focus-visible';
6 | import '../styles/index.scss';
7 |
8 | import { GlobalStyles } from 'utils/GlobalStyles';
9 | import { Layout } from 'components/Layout/Layout';
10 | import { PageProps } from 'utils/sharedTypes';
11 |
12 | export default function MyApp({ Component, pageProps }: AppProps) {
13 | const router = useRouter();
14 | const canvasAppRef = useRef(null);
15 | const [isReady, setIsReady] = useState(false);
16 |
17 | useEffect(() => {
18 | const fontA = new FontFaceObserver('opensans');
19 | const fontB = new FontFaceObserver('teko');
20 |
21 | Promise.all([fontA.load(null, 2500), fontB.load(null, 2500)])
22 | .then(
23 | () => {
24 | setIsReady(true);
25 | },
26 | () => {
27 | setIsReady(true);
28 | console.warn('Fonts were loading too long (over 1500ms)');
29 | }
30 | )
31 | .catch(err => {
32 | setIsReady(true);
33 | console.warn('Some critical font are not available:', err);
34 | });
35 | }, []);
36 |
37 | return (
38 | <>
39 |
40 |
41 | <>
42 |
43 |
48 | >
49 |
50 | >
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/frontend/src/styles/base/reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html,
7 | body,
8 | div,
9 | span,
10 | applet,
11 | object,
12 | iframe,
13 | h1,
14 | h2,
15 | h3,
16 | h4,
17 | h5,
18 | h6,
19 | p,
20 | blockquote,
21 | pre,
22 | a,
23 | abbr,
24 | acronym,
25 | address,
26 | big,
27 | cite,
28 | code,
29 | del,
30 | dfn,
31 | em,
32 | img,
33 | ins,
34 | kbd,
35 | q,
36 | s,
37 | samp,
38 | small,
39 | strike,
40 | strong,
41 | sub,
42 | sup,
43 | tt,
44 | var,
45 | b,
46 | u,
47 | i,
48 | center,
49 | dl,
50 | dt,
51 | dd,
52 | ol,
53 | ul,
54 | li,
55 | fieldset,
56 | form,
57 | label,
58 | legend,
59 | table,
60 | caption,
61 | tbody,
62 | tfoot,
63 | thead,
64 | tr,
65 | th,
66 | td,
67 | article,
68 | aside,
69 | canvas,
70 | details,
71 | embed,
72 | figure,
73 | figcaption,
74 | footer,
75 | header,
76 | hgroup,
77 | menu,
78 | nav,
79 | output,
80 | ruby,
81 | section,
82 | summary,
83 | time,
84 | mark,
85 | audio,
86 | video {
87 | margin: 0;
88 | padding: 0;
89 | border: 0;
90 | font-size: 100%;
91 | font: inherit;
92 | vertical-align: baseline;
93 | }
94 |
95 | article,
96 | aside,
97 | details,
98 | figcaption,
99 | figure,
100 | footer,
101 | header,
102 | hgroup,
103 | menu,
104 | nav,
105 | section {
106 | display: block;
107 | }
108 | body {
109 | line-height: 1;
110 | }
111 | ol,
112 | ul {
113 | list-style: none;
114 | }
115 | blockquote,
116 | q {
117 | quotes: none;
118 | }
119 | blockquote:before,
120 | blockquote:after,
121 | q:before,
122 | q:after {
123 | content: '';
124 | content: none;
125 | }
126 | table {
127 | border-collapse: collapse;
128 | border-spacing: 0;
129 | }
130 |
--------------------------------------------------------------------------------
/react-just-parallax/src/utils/EventDispatcher.js:
--------------------------------------------------------------------------------
1 | /**
2 | * https://github.com/mrdoob/eventdispatcher.js/
3 | */
4 |
5 | class EventDispatcher {
6 | addEventListener(type, listener) {
7 | if (this._listeners === undefined) this._listeners = {};
8 |
9 | const listeners = this._listeners;
10 |
11 | if (listeners[type] === undefined) {
12 | listeners[type] = [];
13 | }
14 |
15 | if (listeners[type].indexOf(listener) === -1) {
16 | listeners[type].push(listener);
17 | }
18 | }
19 |
20 | hasEventListener(type, listener) {
21 | if (this._listeners === undefined) return false;
22 |
23 | const listeners = this._listeners;
24 |
25 | return (
26 | listeners[type] !== undefined && listeners[type].indexOf(listener) !== -1
27 | );
28 | }
29 |
30 | removeEventListener(type, listener) {
31 | if (this._listeners === undefined) return;
32 |
33 | const listeners = this._listeners;
34 | const listenerArray = listeners[type];
35 |
36 | if (listenerArray !== undefined) {
37 | const index = listenerArray.indexOf(listener);
38 |
39 | if (index !== -1) {
40 | listenerArray.splice(index, 1);
41 | }
42 | }
43 | }
44 |
45 | dispatchEvent(event) {
46 | if (this._listeners === undefined) return;
47 |
48 | const listeners = this._listeners;
49 | const listenerArray = listeners[event.type];
50 |
51 | if (listenerArray !== undefined) {
52 | event.target = this;
53 |
54 | // Make a copy, in case listeners are removed while iterating.
55 | const array = listenerArray.slice(0);
56 |
57 | for (let i = 0, l = array.length; i < l; i++) {
58 | array[i].call(this, event);
59 | }
60 |
61 | event.target = null;
62 | }
63 | }
64 | }
65 |
66 | export { EventDispatcher };
67 |
--------------------------------------------------------------------------------
/react-just-parallax/src/utils/EventDispatcher.d.ts:
--------------------------------------------------------------------------------
1 | export interface BaseEvent {
2 | type: string;
3 | }
4 |
5 | /**
6 | * Event object.
7 | */
8 | export interface Event extends BaseEvent {
9 | target?: any;
10 | [attachment: string]: any;
11 | }
12 | export type EventListener = (
13 | event: E & { type: T } & { target: U }
14 | ) => void;
15 |
16 | /**
17 | * JavaScript events for custom objects
18 | *
19 | * @source src/core/EventDispatcher.js
20 | */
21 | export class EventDispatcher {
22 | /**
23 | * Creates eventDispatcher object. It needs to be call with '.call' to add the functionality to an object.
24 | */
25 | constructor();
26 |
27 | /**
28 | * Adds a listener to an event type.
29 | * @param type The type of event to listen to.
30 | * @param listener The function that gets called when the event is fired.
31 | */
32 | addEventListener(
33 | type: T,
34 | listener: EventListener
35 | ): void;
36 |
37 | /**
38 | * Checks if listener is added to an event type.
39 | * @param type The type of event to listen to.
40 | * @param listener The function that gets called when the event is fired.
41 | */
42 | hasEventListener(
43 | type: T,
44 | listener: EventListener
45 | ): boolean;
46 |
47 | /**
48 | * Removes a listener from an event type.
49 | * @param type The type of the listener that gets removed.
50 | * @param listener The listener function that gets removed.
51 | */
52 | removeEventListener(
53 | type: T,
54 | listener: EventListener
55 | ): void;
56 |
57 | /**
58 | * Fire an event type.
59 | * @param type The type of event that gets fired.
60 | */
61 | dispatchEvent(event: E): void;
62 | }
63 |
--------------------------------------------------------------------------------
/react-just-parallax/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-just-parallax",
3 | "version": "3.1.16",
4 | "description": "React library for scroll and mousemove parallax effect ✨ Open source, production-ready",
5 | "main": "dist/cjs/index.js",
6 | "module": "dist/esm/index.js",
7 | "files": [
8 | "dist"
9 | ],
10 | "types": "dist/index.d.ts",
11 | "scripts": {
12 | "test": "echo \"Error: no test specified\" && exit 1",
13 | "clean": "rimraf dist",
14 | "build": "yarn run clean && rollup -c",
15 | "develop": "rollup -c rollup.config.js -w"
16 | },
17 | "keywords": [
18 | "react animation",
19 | "react",
20 | "parallax",
21 | "animation",
22 | "gestures",
23 | "jsx",
24 | "mousemove",
25 | "scroll"
26 | ],
27 | "repository": "git://github.com/michalzalobny/react-just-parallax/",
28 | "bugs": {
29 | "url": "https://github.com/michalzalobny/react-just-parallax/issues"
30 | },
31 | "homepage": "https://react-just-parallax.michalzalobny.com/",
32 | "author": "Michal Zalobny <@michalzalobny> (https://twitter.com/michalzalobny)",
33 | "license": "MIT",
34 | "peerDependencies": {
35 | "react": ">=16.8 || ^17.0.0 || ^18.0.0"
36 | },
37 | "devDependencies": {
38 | "@rollup/plugin-commonjs": "22.0.1",
39 | "@rollup/plugin-node-resolve": "13.3.0",
40 | "@rollup/plugin-typescript": "8.3.3",
41 | "@types/lodash.debounce": "4.0.7",
42 | "@types/react": "18.0.15",
43 | "postcss": "8.4.14",
44 | "rimraf": "3.0.2",
45 | "rollup": "2.76.0",
46 | "rollup-plugin-dts": "4.2.2",
47 | "rollup-plugin-peer-deps-external": "2.2.4",
48 | "rollup-plugin-postcss": "4.0.2",
49 | "rollup-plugin-terser": "7.0.2",
50 | "tslib": "2.4.0",
51 | "typescript": "4.7.4"
52 | },
53 | "dependencies": {
54 | "framesync": "6.1.0",
55 | "lodash.debounce": "4.0.8",
56 | "prefix": "1.0.0"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/cra-for-testing/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/frontend/src/components/Caption/coverBackgroundClasses/Components/BackgroundSketch.ts:
--------------------------------------------------------------------------------
1 | import { Bounds, UpdateInfo } from 'utils/sharedTypes';
2 |
3 | interface Constructor {
4 | ctx: CanvasRenderingContext2D | null;
5 | }
6 |
7 | export class BackgroundSketch {
8 | _rendererBounds: Bounds = { width: 100, height: 0 };
9 | _translateOffset = { x: 0, y: 0 };
10 | _opacity = 1;
11 | _ctx: CanvasRenderingContext2D | null;
12 | _pixelRatio = 1;
13 | _scrollRatio = 0;
14 |
15 | constructor({ ctx }: Constructor) {
16 | this._ctx = ctx;
17 | }
18 |
19 | update(updateInfo: UpdateInfo) {
20 | if (!this._ctx) return;
21 | this._drawBackground();
22 | }
23 |
24 | _drawBackground() {
25 | if (!this._ctx) return;
26 |
27 | this._ctx.beginPath();
28 |
29 | const segments = 20;
30 |
31 | const widthSegments = Math.ceil(this._rendererBounds.width / segments);
32 | this._ctx.moveTo(this._rendererBounds.width, this._rendererBounds.height);
33 | this._ctx.lineTo(0, this._rendererBounds.height);
34 |
35 | const t = (1 - this._scrollRatio) * this._rendererBounds.height;
36 | const amplitude = this._rendererBounds.width * 0.1 * Math.sin(this._scrollRatio * Math.PI);
37 |
38 | this._ctx.lineTo(0, t);
39 |
40 | for (let index = 0; index <= widthSegments; index++) {
41 | const n = segments * index;
42 | const r = t - Math.sin((n / this._rendererBounds.width) * Math.PI) * amplitude;
43 |
44 | this._ctx.lineTo(n, r);
45 | }
46 |
47 | this._ctx.fillStyle = 'rgba(255,255,255,1)';
48 | this._ctx.fill();
49 | }
50 |
51 | setRendererBounds(bounds: Bounds) {
52 | this._rendererBounds = bounds;
53 | if (!this._ctx) return;
54 | }
55 |
56 | setPixelRatio(value: number) {
57 | this._pixelRatio = value;
58 | }
59 |
60 | setScrollRatio(value: number) {
61 | this._scrollRatio = value;
62 | }
63 |
64 | // eslint-disable-next-line @typescript-eslint/no-empty-function
65 | destroy() {}
66 | }
67 |
--------------------------------------------------------------------------------
/frontend/src/components/Caption/backgroundClasses/Components/BackgroundSketch.ts:
--------------------------------------------------------------------------------
1 | import { Bounds, UpdateInfo } from 'utils/sharedTypes';
2 |
3 | interface Constructor {
4 | ctx: CanvasRenderingContext2D | null;
5 | }
6 |
7 | export class BackgroundSketch {
8 | _rendererBounds: Bounds = { width: 100, height: 0 };
9 | _translateOffset = { x: 0, y: 0 };
10 | _opacity = 1;
11 | _ctx: CanvasRenderingContext2D | null;
12 | _pixelRatio = 1;
13 | _scrollRatioRest = 0;
14 |
15 | constructor({ ctx }: Constructor) {
16 | this._ctx = ctx;
17 | }
18 |
19 | update(updateInfo: UpdateInfo) {
20 | if (!this._ctx) return;
21 | this._drawBackground();
22 | }
23 |
24 | _drawBackground() {
25 | if (!this._ctx) return;
26 |
27 | this._ctx.beginPath();
28 |
29 | const segments = 20;
30 |
31 | const widthSegments = Math.ceil(this._rendererBounds.width / segments);
32 | this._ctx.moveTo(this._rendererBounds.width, this._rendererBounds.height);
33 | this._ctx.lineTo(0, this._rendererBounds.height);
34 |
35 | const t = (1 - this._scrollRatioRest) * this._rendererBounds.height;
36 | const amplitude = this._rendererBounds.width * 0.1 * Math.sin(this._scrollRatioRest * Math.PI);
37 |
38 | this._ctx.lineTo(0, t);
39 |
40 | for (let index = 0; index <= widthSegments; index++) {
41 | const n = segments * index;
42 | const r = t - Math.sin((n / this._rendererBounds.width) * Math.PI) * amplitude;
43 |
44 | this._ctx.lineTo(n, r);
45 | }
46 |
47 | this._ctx.fillStyle = '#9BDBD8';
48 | this._ctx.fill();
49 | }
50 |
51 | setRendererBounds(bounds: Bounds) {
52 | this._rendererBounds = bounds;
53 | if (!this._ctx) return;
54 | }
55 |
56 | setPixelRatio(value: number) {
57 | this._pixelRatio = value;
58 | }
59 |
60 | setScrollRatioRest(value: number) {
61 | this._scrollRatioRest = value;
62 | }
63 |
64 | // eslint-disable-next-line @typescript-eslint/no-empty-function
65 | destroy() {}
66 | }
67 |
--------------------------------------------------------------------------------
/frontend/src/containers/Examples/1/ExamplePage.data.ts:
--------------------------------------------------------------------------------
1 | import { GetStaticProps } from 'next';
2 |
3 | export interface GameAsset {
4 | src: string;
5 | name: string;
6 | }
7 |
8 | export interface LogoAsset {
9 | src: string;
10 | name: string;
11 | }
12 |
13 | export const getStaticProps: GetStaticProps = () => {
14 | const gameAssets: GameAsset[] = [
15 | {
16 | src: 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1662114954/Horizon_Forbidden_West_nvvpvs.jpg',
17 | name: 'Horizon Forbidden West',
18 | },
19 | {
20 | src: 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1662114954/cyberpunk_lsevxo.jpg',
21 | name: 'Cyberpunk 2077',
22 | },
23 | {
24 | src: 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1662114954/God-of-War-Ragnarok-Featured-image_wubfns.jpg',
25 | name: 'God of War Ragnarok',
26 | },
27 |
28 | {
29 | src: 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1662114954/spider_man_miles_morales_kx1klv.jpg',
30 | name: 'Spider-Man: Miles Morales',
31 | },
32 | {
33 | src: 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1662114956/fifa23_kkme4i.jpg',
34 | name: 'Fifa 23',
35 | },
36 | // {
37 | // src: 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1662114954/ghost-of-tsushima_tsjwzk.jpg',
38 | // name: 'Ghost of Tsushima',
39 | // },
40 | ];
41 |
42 | const logoAssets: LogoAsset[] = [
43 | {
44 | name: 'circle',
45 | src: 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1662115291/circle-nobg-min_rfxukx.png',
46 | },
47 | {
48 | name: 'square',
49 | src: 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1662115289/square-nobg-min_mfzkuk.png',
50 | },
51 | {
52 | name: 'cross',
53 | src: 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1662115289/cross-nobg-min_uxroih.png',
54 | },
55 | {
56 | name: 'triangle',
57 | src: 'https://res.cloudinary.com/dpv0ukspz/image/upload/v1662115289/triangle-nobg-min_mrwsbp.png',
58 | },
59 | ];
60 |
61 | return {
62 | props: {
63 | gameAssets,
64 | logoAssets,
65 | },
66 | };
67 | };
68 |
--------------------------------------------------------------------------------
/.vscode/mySnippets.code-snippets:
--------------------------------------------------------------------------------
1 | {
2 | "Typescript React Function Component": {
3 | "prefix": "rc",
4 | "body": [
5 | "import React from 'react'",
6 | "",
7 | "import * as S from './$TM_FILENAME_BASE.styles';",
8 | "",
9 | "interface Props {$1}",
10 | "",
11 | "export const $TM_FILENAME_BASE = (props: Props) => {",
12 | "const { ...rest } = props",
13 | "\treturn (",
14 | "\t\t<>",
15 | "\t\t\ttest ",
16 | "\t\t>",
17 | "\t)",
18 | "}"
19 | ],
20 | "description": "Typescript React Function Component with memo"
21 | },
22 | "Switch statement": {
23 | "prefix": "ss",
24 | "body": ["switch ($1){", " case '$2' :", " break", " default :", " break", "}"],
25 | "description": "Switch statement"
26 | },
27 | "Console.log": {
28 | "prefix": "clg",
29 | "body": ["console.log($1)"],
30 | "description": "Console log"
31 | },
32 | "create styled component": {
33 | "prefix": "sc",
34 | "body": ["export const Component = styled.div``"],
35 | "description": "Creates styled component"
36 | },
37 | "framer-motion variants": {
38 | "prefix": "dp",
39 | "body": [" variants: {", " initial: {},", " animate: {},", " exit: {},", " },"],
40 | "description": "Generate default variants"
41 | },
42 | "import animation transitions": {
43 | "prefix": "it",
44 | "body": "import { springElastic } from 'components/Animations/framerTransitions';"
45 | },
46 | "media import": {
47 | "prefix": "im",
48 | "body": ["import { media } from 'utils/media'"],
49 | "description": "Importing media"
50 | },
51 | "media sharedValues": {
52 | "prefix": "is",
53 | "body": ["import { sharedValues } from 'utils/sharedValues'"],
54 | "description": "Importing sharedValues"
55 | },
56 | "import styled components library": {
57 | "prefix": "isc",
58 | "body": ["import styled, { css } from 'styled-components'"],
59 | "description": "Import styled components library"
60 | },
61 | "insert breakpoint": {
62 | "prefix": "br",
63 | "body": ["${media.tablet} {", "", "}"],
64 | "description": "Insert breakpoint to the styled component"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/cra-for-testing/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/frontend/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import NextDocument, { Head, Main, NextScript, DocumentContext, Html } from 'next/document';
2 | import React from 'react';
3 | import { ServerStyleSheet } from 'styled-components';
4 |
5 | import { VARIABLES, setCssVariables } from 'utils/functions/setCssVariables';
6 |
7 | export default class Document extends NextDocument {
8 | static async getInitialProps(ctx: DocumentContext) {
9 | const sheet = new ServerStyleSheet();
10 | const originalRenderPage = ctx.renderPage;
11 |
12 | try {
13 | ctx.renderPage = () =>
14 | originalRenderPage({
15 | enhanceApp: App => props => sheet.collectStyles( ),
16 | });
17 |
18 | const initialProps = await NextDocument.getInitialProps(ctx);
19 | return {
20 | ...initialProps,
21 | styles: (
22 | <>
23 | {initialProps.styles}
24 | {sheet.getStyleElement()}
25 | >
26 | ),
27 | };
28 | } finally {
29 | sheet.seal();
30 | }
31 | }
32 |
33 | render() {
34 | return (
35 |
36 |
37 |
44 |
45 |
52 |
53 |
60 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/frontend/src/components/Caption/Caption.styles.ts:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | import { motion } from 'framer-motion';
3 |
4 | export const Wrapper = styled.div`
5 | position: fixed;
6 | z-index: -1;
7 | top: 0;
8 | left: 0;
9 | width: 100%;
10 | height: 100%;
11 | overflow: hidden;
12 | background-color: #b0a3e2;
13 | `;
14 |
15 | export const CanvasWrapper = styled.div`
16 | position: absolute;
17 | top: 0;
18 | left: 0;
19 | width: 100%;
20 | height: 100%;
21 | z-index: 1;
22 | user-select: none;
23 | pointer-events: none;
24 | `;
25 |
26 | export const BackgroundCanvasWrapper = styled.div`
27 | position: absolute;
28 | top: 0;
29 | left: 0;
30 | width: 100%;
31 | height: 100%;
32 | z-index: -1;
33 | user-select: none;
34 | pointer-events: none;
35 | `;
36 |
37 | export const CoverBackgroundCanvasWrapper = styled.div`
38 | position: absolute;
39 | top: 0;
40 | left: 0;
41 | width: 100%;
42 | height: 100%;
43 | z-index: 3;
44 | user-select: none;
45 | pointer-events: none;
46 | `;
47 |
48 | interface ReadyWrapperProps {
49 | shouldReveal: boolean;
50 | }
51 |
52 | export const ReadyWrapper = styled.div`
53 | position: fixed;
54 | top: 0;
55 | left: 0;
56 | width: 100%;
57 | height: 100%;
58 | z-index: 25;
59 | opacity: 1;
60 | transition: opacity 0.5s ease-in-out;
61 | background-color: white;
62 |
63 | ${props =>
64 | props.shouldReveal &&
65 | css`
66 | opacity: 0;
67 | user-select: none;
68 | pointer-events: none;
69 | `}
70 | `;
71 |
72 | export const MotionWrapper = styled(motion.div)`
73 | position: absolute;
74 | top: 0;
75 | left: 0;
76 | width: 100%;
77 | height: 100%;
78 | `;
79 |
80 | export const Text = styled(motion.p)`
81 | font-family: 'teko';
82 | font-weight: 800;
83 | font-size: 1.6vw;
84 | text-transform: uppercase;
85 | color: black; //#E2C4A3 #b0a3e2 #9BDBD8
86 | transition: 0;
87 | letter-spacing: 0.05vw;
88 | `;
89 |
90 | export const TextWrapper = styled.div`
91 | overflow: hidden;
92 |
93 | transform: translateY(-540%);
94 | `;
95 |
96 | export const TextContainer = styled(motion.div)`
97 | position: absolute;
98 | z-index: 3;
99 | left: 7%;
100 | top: 50%;
101 | `;
102 |
--------------------------------------------------------------------------------
/frontend/src/sections/DocsInfo/DocsInfo.styles.ts:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 |
3 | import { media } from 'utils/media';
4 | import { underline } from 'utils/sharedStyled';
5 |
6 | export const Container = styled.div`
7 | width: 100%;
8 | position: relative;
9 | background: white;
10 | border-radius: 10px;
11 | box-shadow: 0 0 50px rgba(0, 0, 0, 0.12);
12 | margin-bottom: 10rem;
13 | padding: 2.5rem;
14 |
15 | ${media.tablet} {
16 | padding: 7rem;
17 | }
18 | `;
19 |
20 | export const Title = styled.h2`
21 | text-align: left;
22 | font-weight: 800;
23 | font-size: 25px;
24 |
25 | ${media.tablet} {
26 | font-size: 30px;
27 | }
28 | `;
29 |
30 | export const Paragraph = styled.p`
31 | font-size: 15px;
32 | margin: 20px 0;
33 | line-height: 1.6;
34 |
35 | ${media.tablet} {
36 | margin: 30px 0;
37 | }
38 | `;
39 |
40 | export const InlineLink = styled.span`
41 | display: inline-block;
42 | font-weight: 800;
43 | position: relative;
44 | ${underline};
45 | `;
46 |
47 | interface ExampleWrapperProps {
48 | $bgColor: string;
49 | }
50 |
51 | export const ExampleWrapper = styled.div`
52 | width: 100%;
53 | background-color: ${props => props.$bgColor};
54 | border-radius: 10px;
55 | position: relative;
56 | display: flex;
57 |
58 | &:before {
59 | content: '';
60 | display: inline-block;
61 | padding-bottom: 50%;
62 |
63 | ${media.tablet} {
64 | padding-bottom: 35%;
65 | }
66 | }
67 | `;
68 |
69 | interface RingProps {
70 | $dim?: boolean;
71 | }
72 |
73 | export const Ring = styled.div`
74 | position: absolute;
75 | top: 50%;
76 | left: 50%;
77 | transform: translate(-50%, -50%);
78 | width: 17%;
79 | border-radius: 50%;
80 | border: 2px solid white;
81 | display: flex;
82 |
83 | ${media.tablet} {
84 | border: 3px solid white;
85 | width: 10%;
86 | }
87 |
88 | &:before {
89 | content: '';
90 | display: inline-block;
91 | padding-bottom: 100%;
92 | }
93 |
94 | ${props =>
95 | props.$dim &&
96 | css`
97 | opacity: 0.5;
98 | `}
99 | `;
100 |
101 | export const SectionSeparator = styled.span`
102 | width: 100%;
103 | height: 80px;
104 | display: inline-block;
105 |
106 | ${media.tablet} {
107 | height: 120px;
108 | }
109 | `;
110 |
--------------------------------------------------------------------------------
/cra-for-testing/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/images/shape8.svg:
--------------------------------------------------------------------------------
1 | shape8
--------------------------------------------------------------------------------
/cra-for-testing/src/App.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: calc(100vw / 375 * 10);
3 | }
4 |
5 | h1 {
6 | margin: 0;
7 | }
8 |
9 | body::before {
10 | content: "";
11 | position: fixed;
12 | top: 50%;
13 | left: 0;
14 | width: 100%;
15 | transform: translateY(-50%);
16 | background-color: pink;
17 | height: 2px;
18 | z-index: 200;
19 | }
20 |
21 | /* ::-webkit-scrollbar {
22 | width: 100px;
23 | } */
24 |
25 | body::after {
26 | content: "";
27 | position: fixed;
28 | top: 0;
29 | left: 50%;
30 | height: 100%;
31 | transform: translateX(-50%);
32 | background-color: pink;
33 | width: 2px;
34 | z-index: 200;
35 | }
36 |
37 | .wrapper {
38 | margin: 0 auto;
39 | width: 80%;
40 | margin-top: 20rem;
41 | margin-bottom: 20rem;
42 | margin-bottom: 140rem;
43 | margin-top: 140rem;
44 | }
45 |
46 | .container {
47 | width: 100%;
48 | margin-top: 15rem;
49 | position: relative;
50 | }
51 |
52 | .container::after {
53 | content: "";
54 | position: absolute;
55 | top: 50%;
56 | left: 0;
57 | width: 100%;
58 | transform: translateY(-50%);
59 | background-color: yellow;
60 | height: 2px;
61 | z-index: 200;
62 | }
63 |
64 | .container::before {
65 | content: "";
66 | position: absolute;
67 | top: 0;
68 | left: 50%;
69 | height: 100%;
70 | transform: translateX(-50%);
71 | background-color: yellow;
72 | width: 2px;
73 | z-index: 200;
74 | }
75 |
76 | .container:last-child {
77 | margin-bottom: 15rem;
78 | }
79 |
80 | .container:nth-child(1) {
81 | background-color: red;
82 | }
83 |
84 | .container:nth-child(2) {
85 | background-color: purple;
86 | }
87 |
88 | .container:nth-child(3) {
89 | background-color: green;
90 | }
91 |
92 | .container:nth-child(4) {
93 | background-color: rgb(30, 216, 129);
94 | }
95 |
96 | .container:nth-child(5) {
97 | background-color: rgb(58, 75, 177);
98 | }
99 |
100 | .container:nth-child(6) {
101 | background-color: rgb(138, 65, 138);
102 | }
103 |
104 | .box {
105 | width: 100px;
106 | position: relative;
107 | background: black;
108 | }
109 |
110 | .box::before {
111 | content: "";
112 | display: block;
113 | width: 100%;
114 | padding-bottom: 100%;
115 | }
116 |
117 | .box[data-box="floating"] {
118 | position: absolute;
119 | top: 50%;
120 | left: 30%;
121 | transform: translate(-50%, -50%);
122 | }
123 |
124 | @media only screen and (min-width: 767px) {
125 | html {
126 | font-size: calc(100vw / 1920 * 10);
127 | }
128 |
129 | .wrapper {
130 | width: 100rem;
131 | }
132 | }
133 |
134 | @media only screen and (min-width: 1920px) {
135 | html {
136 | font-size: 62.5%;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/frontend/src/sections/DocsInfo/DocsInfo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MouseParallax, ScrollParallax } from 'react-just-parallax';
3 |
4 | import { LinkHandler } from 'components/LinkHandler/LinkHandler';
5 | import { CodeRenderer } from 'components/CodeRenderer/CodeRenderer';
6 |
7 | import * as S from './DocsInfo.styles';
8 |
9 | interface Props {
10 | scrollContainerRef: React.MutableRefObject;
11 | }
12 |
13 | const scrollText = `import { ScrollParallax } from 'react-just-parallax';
14 |
15 |
16 |
17 |
18 |
19 |
20 | `;
21 |
22 | const mouseText = `import { MouseParallax } from 'react-just-parallax';
23 |
24 |
25 |
26 |
27 |
28 |
29 | `;
30 |
31 | export const DocsInfo = (props: Props) => {
32 | const { scrollContainerRef } = props;
33 |
34 | return (
35 | <>
36 |
37 | 📜 Scroll Parallax
38 |
39 | Check all the parameters and props for scroll parallax on{' '}
40 |
41 | official npm page
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Example code:
51 |
52 |
53 |
54 |
55 | 🖱️ Mouse / Touch Parallax
56 |
57 | Check all the parameters and props for mouse parallax on{' '}
58 |
59 | official npm page
60 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
72 | Example code:
73 |
74 |
75 | >
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/images/shape9.svg:
--------------------------------------------------------------------------------
1 | shape9
--------------------------------------------------------------------------------
/frontend/src/components/GameTile/GameTile.styles.ts:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 |
3 | import { media } from 'utils/media';
4 |
5 | export const ImageWrapper = styled.div`
6 | width: 100%;
7 | position: relative;
8 | transform: scale(1.1);
9 |
10 | &:before {
11 | content: '';
12 | padding-bottom: 115%;
13 | display: block;
14 | }
15 | `;
16 |
17 | export const ImageContainer = styled.div`
18 | overflow: hidden;
19 | position: relative;
20 | clip-path: inset(0% round 15px);
21 |
22 | &:before {
23 | content: '';
24 | position: absolute;
25 | z-index: 1;
26 | top: 0;
27 | left: 0;
28 | width: 100%;
29 | height: 100%;
30 | background: rgba(0, 0, 0, 0);
31 | background: linear-gradient(
32 | 0deg,
33 | rgba(0, 0, 0, 1) 0%,
34 | rgba(0, 0, 0, 0) 75%,
35 | rgba(0, 0, 0, 1) 100%
36 | );
37 | opacity: 0.35;
38 | }
39 | `;
40 |
41 | interface IconWrapperProps {
42 | yTranslate: number;
43 | positionRight?: boolean;
44 | positionBottom?: boolean;
45 | isSmaller?: boolean;
46 | }
47 |
48 | export const IconWrapper = styled.div`
49 | position: absolute;
50 | z-index: 10;
51 | left: 0;
52 | top: 0;
53 |
54 | transform: ${props =>
55 | `translateX(${props.positionRight ? '50' : '-50'}%) translateY(${props.yTranslate}%)`};
56 |
57 | &:before {
58 | content: '';
59 | display: block;
60 | padding-bottom: 100%;
61 | }
62 |
63 | ${props =>
64 | props.positionRight &&
65 | css`
66 | left: initial;
67 | right: 0;
68 | `}
69 |
70 | ${props =>
71 | props.positionBottom &&
72 | css`
73 | top: initial;
74 | bottom: 0;
75 | `}
76 |
77 | width:15rem;
78 |
79 | ${props =>
80 | props.isSmaller &&
81 | css`
82 | width: 8rem;
83 | `}
84 | ${media.tablet} {
85 | width: 24rem;
86 | ${props =>
87 | props.isSmaller &&
88 | css`
89 | width: 13rem;
90 | `}
91 | }
92 | `;
93 |
94 | export const TileContainer = styled.div`
95 | position: relative;
96 | filter: drop-shadow(0 15px 12px rgba(0, 0, 0, 0.45));
97 |
98 | &:not(:last-child) {
99 | margin-bottom: 50px;
100 | ${media.tablet} {
101 | margin-bottom: 80px;
102 | }
103 | }
104 | `;
105 |
106 | export const TitleWrapper = styled.div`
107 | position: absolute;
108 | z-index: 2;
109 | bottom: 0;
110 | left: 0;
111 | width: 100%;
112 | `;
113 |
114 | export const Title = styled.p`
115 | font-size: 20px;
116 | color: white;
117 |
118 | width: 100%;
119 | line-height: 1.2;
120 | padding: 8%;
121 | padding-bottom: 5%;
122 | `;
123 |
--------------------------------------------------------------------------------
/frontend/src/styles/base/global.scss:
--------------------------------------------------------------------------------
1 | *,
2 | *:before,
3 | *:after {
4 | box-sizing: inherit;
5 | margin: 0;
6 | padding: 0;
7 | -webkit-touch-callout: none;
8 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
9 | -webkit-tap-highlight-color: transparent;
10 | }
11 |
12 | [data-js-focus-visible] :focus:not([data-focus-visible-added]) {
13 | outline: none;
14 | }
15 |
16 | html {
17 | box-sizing: border-box;
18 | -moz-osx-font-smoothing: grayscale;
19 | -webkit-font-smoothing: antialiased;
20 |
21 | font-family: 'opensans', Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu,
22 | Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
23 | }
24 |
25 | // body::before {
26 | // content: '';
27 | // position: fixed;
28 | // top: 0;
29 | // left: 50%;
30 | // transform: translateX(-50%);
31 | // width: 1px;
32 | // height: 100%;
33 | // background-color: red;
34 | // z-index: 100;
35 | // }
36 |
37 | // body::after {
38 | // content: '';
39 | // position: fixed;
40 | // top: 50%;
41 | // left: 0;
42 | // transform: translateY(-50%);
43 | // height: 1px;
44 | // width: 100%;
45 | // background-color: red;
46 | // z-index: 100;
47 | // }
48 |
49 | //Moved to /utils/GlobalStyles.ts (to get access to breakpoints variables)
50 | // html {
51 | // font-size: calc(100vw / 375 * 10);
52 |
53 | // @media only screen and (min-width: 767px) {
54 | // font-size: calc(100vw / 1920 * 10);
55 | // }
56 |
57 | // @media only screen and (min-width: 1920px) {
58 | // font-size: 62.5%;
59 | // }
60 | // }
61 |
62 | body {
63 | overflow-y: scroll;
64 | }
65 |
66 | a {
67 | color: inherit;
68 | pointer-events: auto;
69 | text-decoration: none;
70 | display: inline-block;
71 | }
72 |
73 | a[href^='tel'] {
74 | color: inherit;
75 | text-decoration: none;
76 | }
77 |
78 | button {
79 | background: none;
80 | border: none;
81 | border-radius: none;
82 | color: inherit;
83 | font: inherit;
84 | pointer-events: auto;
85 | }
86 |
87 | input,
88 | textarea {
89 | appearance: none;
90 | background: none;
91 | border: none;
92 | border-radius: 0;
93 | pointer-events: auto;
94 | }
95 |
96 | img,
97 | svg {
98 | vertical-align: center;
99 | }
100 |
101 | //Creative
102 |
103 | body {
104 | position: fixed;
105 | top: 0;
106 | left: 0;
107 | width: 100%;
108 | height: 99.95%;
109 | overflow: hidden;
110 | overscroll-behavior: none;
111 | }
112 |
113 | html {
114 | position: fixed;
115 | top: 0;
116 | left: 0;
117 | width: 100%;
118 | height: 100%;
119 | overscroll-behavior: none;
120 | }
121 |
122 | img {
123 | user-select: none;
124 | pointer-events: none;
125 | }
126 |
127 | *,
128 | *:before,
129 | *:after {
130 | // user-select: none;
131 | }
132 |
--------------------------------------------------------------------------------
/cra-for-testing/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react";
2 |
3 | //@ts-ignore
4 | import {
5 | ScrollParallax,
6 | MouseParallax,
7 | ScrollParallaxHandle,
8 | } from "react-just-parallax";
9 |
10 | import "./App.css";
11 |
12 | function App() {
13 | const wrapperRef = useRef(null);
14 |
15 | const [display, setDisplay] = useState(false);
16 |
17 | const scrollParallaxRef = useRef(null);
18 |
19 | useEffect(() => {
20 | scrollParallaxRef.current?.updateValues();
21 | }, [display]);
22 |
23 | return (
24 |
25 | {display && (
26 |
27 |
1
28 |
29 |
30 |
31 |
32 | Mouseparallax content btn
33 |
34 |
35 |
36 |
37 |
1123
38 |
39 | )}
40 |
41 |
setDisplay((prev) => !prev)}
44 | >
45 | UPDATE DISPLAY
46 |
47 |
48 |
3
49 |
50 |
51 | odasd
52 |
53 |
54 |
55 |
56 | 3213
57 |
58 |
59 |
60 |
61 |
4
62 |
4
63 |
4
64 |
4
65 |
4
66 |
67 |
68 |
69 |
70 |
71 |
5
72 | {/* If you need parallax to absolutely positioned element, wrap it in div*/}
73 | {/*
*/}
82 |
83 | tres
84 |
85 |
86 | {/*
*/}
87 |
88 |
89 | );
90 | }
91 |
92 | export default App;
93 |
--------------------------------------------------------------------------------
/frontend/src/components/GameTile/GameTile.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useMemo, useRef } from 'react';
2 | import { ScrollParallax } from 'react-just-parallax';
3 |
4 | import { PreloadImage } from 'components/PreloadImage/PreloadImage';
5 | import { seedRandom } from 'utils/functions/seedRandom';
6 |
7 | import * as S from './GameTile.styles';
8 | import { LogoAsset } from 'containers/Examples/1/ExamplePage.data';
9 |
10 | interface Props {
11 | imageSrc: string;
12 | alt: string;
13 | title: string;
14 | scrollContainer: React.MutableRefObject;
15 | logoAssets: LogoAsset[];
16 | itemKey: number;
17 | }
18 |
19 | export const GameTile = (props: Props) => {
20 | const { itemKey, logoAssets, scrollContainer, title, alt, imageSrc } = props;
21 | const containerRef = useRef(null);
22 |
23 | const seedRandom1 = useMemo(() => {
24 | const t = seedRandom(title.substring(3, itemKey + 2));
25 | return t[itemKey % 4];
26 | }, [itemKey, title]);
27 |
28 | const getSeed = useCallback(
29 | (offset: number) => {
30 | return (seedRandom1 + offset) % logoAssets.length;
31 | },
32 | [logoAssets.length, seedRandom1]
33 | );
34 |
35 | return (
36 | <>
37 |
38 |
46 |
47 |
52 |
53 |
54 | {(getSeed(2) === 0 || getSeed(2) === 2) && (
55 |
56 |
61 |
62 | )}
63 |
64 |
65 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | {title}
81 |
82 |
83 |
84 | >
85 | );
86 | };
87 |
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/ShowOff.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | position: absolute;
5 | top: 0;
6 | left: 0;
7 | width: 100%;
8 | height: 100%;
9 | `;
10 |
11 | export const PicturesContainer = styled.div`
12 | position: absolute;
13 | top: 50%;
14 | right: 0%;
15 | transform: translate(-8%, -50%);
16 | width: 50%;
17 | &:before {
18 | content: '';
19 | display: block;
20 | padding-bottom: 100%;
21 | }
22 | `;
23 |
24 | export const ContoursWrapper = styled.div`
25 | position: absolute;
26 | top: 0;
27 | left: 0;
28 | width: 100%;
29 | height: 100%;
30 | `;
31 |
32 | export const Shape1Wrapper = styled.div`
33 | position: absolute;
34 | bottom: 0%;
35 | left: 0%;
36 | transform: translate(-25%, -62%);
37 | width: 33%;
38 |
39 | &:before {
40 | content: '';
41 | display: block;
42 | padding-bottom: 100%;
43 | }
44 | `;
45 |
46 | export const Shape2Wrapper = styled.div`
47 | position: absolute;
48 | bottom: 33%;
49 | left: 17%;
50 | width: 9%;
51 |
52 | &:before {
53 | content: '';
54 | display: block;
55 | padding-bottom: 100%;
56 | }
57 | `;
58 |
59 | export const Shape3Wrapper = styled.div`
60 | position: absolute;
61 | top: 17%;
62 | left: 34%;
63 | width: 30%;
64 |
65 | &:before {
66 | content: '';
67 | display: block;
68 | padding-bottom: 100%;
69 | }
70 | `;
71 |
72 | export const Shape4Wrapper = styled.div`
73 | position: absolute;
74 | top: 23%;
75 | left: 47%;
76 | width: 9.5%;
77 |
78 | &:before {
79 | content: '';
80 | display: block;
81 | padding-bottom: 100%;
82 | }
83 | `;
84 |
85 | export const Shape5Wrapper = styled.div`
86 | position: absolute;
87 | top: 40%;
88 | left: 81.5%;
89 | width: 9.5%;
90 |
91 | &:before {
92 | content: '';
93 | display: block;
94 | padding-bottom: 100%;
95 | }
96 | `;
97 |
98 | export const Shape6Wrapper = styled.div`
99 | position: absolute;
100 | top: 28.5%;
101 | left: 60%;
102 | width: 32%;
103 |
104 | &:before {
105 | content: '';
106 | display: block;
107 | padding-bottom: 100%;
108 | }
109 | `;
110 |
111 | export const Shape7Wrapper = styled.div`
112 | position: absolute;
113 | top: 50%;
114 | left: 53.5%;
115 | width: 9.5%;
116 |
117 | &:before {
118 | content: '';
119 | display: block;
120 | padding-bottom: 100%;
121 | }
122 | `;
123 |
124 | export const Shape8Wrapper = styled.div`
125 | position: absolute;
126 | top: 48.5%;
127 | left: 42%;
128 | width: 37%;
129 |
130 | &:before {
131 | content: '';
132 | display: block;
133 | padding-bottom: 100%;
134 | }
135 | `;
136 |
137 | export const Shape9Wrapper = styled.div`
138 | position: absolute;
139 | top: 48.5%;
140 | left: 31.5%;
141 | width: 37%;
142 |
143 | &:before {
144 | content: '';
145 | display: block;
146 | padding-bottom: 100%;
147 | }
148 | `;
149 |
--------------------------------------------------------------------------------
/frontend/src/containers/IndexPage/IndexPage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import { useSpring, useTransform, useScroll } from 'framer-motion';
3 |
4 | import { LinkHandler } from 'components/LinkHandler/LinkHandler';
5 | import { useElementSize } from 'hooks/useElementSize';
6 | import { DocsInfo } from 'sections/DocsInfo/DocsInfo';
7 | import { Caption } from 'components/Caption/Caption';
8 | import { useWindowSize } from 'hooks/useWindowSize';
9 | import { Head } from 'seo/Head/Head';
10 |
11 | import * as S from './IndexPage.styles';
12 |
13 | const quickerPoint = 0.2;
14 |
15 | const sat = (x: number) => {
16 | return Math.min(Math.max(x, 0), 1);
17 | };
18 |
19 | //if t = a returns 0, if t = b returns 1, if t is half way then return 0.5 etc.
20 | const remap01 = (a: number, b: number, t: number) => {
21 | return sat((t - a) / (b - a));
22 | };
23 |
24 | // if t = a returns c, if t = b returns d, if t half way through a and b returns half way between c and d etc.
25 | const remap = (a: number, b: number, c: number, d: number, t: number) => {
26 | return sat(remap01(a, b, t) * (d - c) + c);
27 | };
28 |
29 | const springSettings = {
30 | stiffness: 350,
31 | damping: 80,
32 | mass: 5,
33 | restDelta: 0.001,
34 | restSpeed: 0.001,
35 | };
36 |
37 | export default function IndexPage() {
38 | const scrollContainerRef = useRef(null);
39 | const captionWrapperRef = useRef(null);
40 | const { windowSizeRef } = useWindowSize();
41 | const { sizeRef: captionWrapperSizeRef } = useElementSize(captionWrapperRef);
42 | const { scrollY } = useScroll({ container: scrollContainerRef });
43 |
44 | const scrollYPadded = useTransform(scrollY, v =>
45 | sat(v / (captionWrapperSizeRef.current.clientRect.height - windowSizeRef.current.windowHeight))
46 | );
47 |
48 | const scrollYPaddedQuicker = useTransform(scrollYPadded, v => remap(0, quickerPoint, 0, 1, v));
49 | const scrollYPaddedRest = useTransform(scrollYPadded, v =>
50 | remap(quickerPoint * 2, quickerPoint * 3, 0, 1, v)
51 | );
52 | const scrollYPaddedClose = useTransform(scrollYPadded, v => remap(quickerPoint * 4, 1, 0, 1, v));
53 |
54 | const scrollRatio = useSpring(scrollYPadded, springSettings);
55 | const scrollRatioQuicker = useSpring(scrollYPaddedQuicker, springSettings);
56 | const scrollRatioRest = useSpring(scrollYPaddedRest, springSettings);
57 | const scrollRatioClose = useSpring(scrollYPaddedClose, springSettings);
58 |
59 | return (
60 | <>
61 |
62 |
63 |
64 |
71 |
72 |
73 |
74 |
75 | Demo 1
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | >
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/react-just-parallax/src/utils/MouseMove.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { EventDispatcher } from "./EventDispatcher";
4 |
5 | interface Mouse {
6 | x: number;
7 | y: number;
8 | }
9 |
10 | export class MouseMove extends EventDispatcher {
11 | _mouseLast: Mouse = { x: 0, y: 0 };
12 | _isTouching = false;
13 | mouse: Mouse = { x: 0, y: 0 };
14 | _targetEl: any | Window;
15 | _shouldUpdate = false;
16 |
17 | constructor() {
18 | super();
19 | }
20 |
21 | _onTouchDown = (event: TouchEvent | MouseEvent) => {
22 | if (!this._shouldUpdate) return;
23 | this._isTouching = true;
24 | this._mouseLast.x =
25 | "touches" in event ? event.touches[0].clientX : event.clientX;
26 | this._mouseLast.y =
27 | "touches" in event ? event.touches[0].clientY : event.clientY;
28 |
29 | this.mouse.x = this._mouseLast.x;
30 | this.mouse.y = this._mouseLast.y;
31 |
32 | this.dispatchEvent({ type: "down" });
33 | this.dispatchEvent({ type: "mousemove" });
34 | };
35 |
36 | _onTouchMove = (event: TouchEvent | MouseEvent) => {
37 | if (!this._shouldUpdate) return;
38 | const touchX =
39 | "touches" in event ? event.touches[0].clientX : event.clientX;
40 | const touchY =
41 | "touches" in event ? event.touches[0].clientY : event.clientY;
42 |
43 | const deltaX = touchX - this._mouseLast.x;
44 | const deltaY = touchY - this._mouseLast.y;
45 |
46 | this._mouseLast.x = touchX;
47 | this._mouseLast.y = touchY;
48 |
49 | this.mouse.x += deltaX;
50 | this.mouse.y += deltaY;
51 | this.dispatchEvent({ type: "mousemove" });
52 | };
53 |
54 | _onTouchUp = () => {
55 | if (!this._shouldUpdate) return;
56 | this._isTouching = false;
57 | this.dispatchEvent({ type: "up" });
58 | this.dispatchEvent({ type: "mousemove" });
59 | };
60 |
61 | _onMouseLeave = () => {
62 | this.dispatchEvent({ type: "left" });
63 | };
64 |
65 | _addEvents() {
66 | this._targetEl.addEventListener("mousedown", this._onTouchDown);
67 | this._targetEl.addEventListener("mousemove", this._onTouchMove, {
68 | passive: true,
69 | });
70 | this._targetEl.addEventListener("mouseup", this._onTouchUp);
71 |
72 | this._targetEl.addEventListener("touchstart", this._onTouchDown, {
73 | passive: true,
74 | });
75 | this._targetEl.addEventListener("touchmove", this._onTouchMove, {
76 | passive: true,
77 | });
78 | this._targetEl.addEventListener("touchend", this._onTouchUp);
79 |
80 | this._targetEl.addEventListener("mouseout", this._onMouseLeave);
81 | }
82 |
83 | _removeEvents() {
84 | this._targetEl.removeEventListener("mousedown", this._onTouchDown);
85 | this._targetEl.removeEventListener("mousemove", this._onTouchMove);
86 | this._targetEl.removeEventListener("mouseup", this._onTouchUp);
87 |
88 | this._targetEl.removeEventListener("touchstart", this._onTouchDown);
89 | this._targetEl.removeEventListener("touchmove", this._onTouchMove);
90 | this._targetEl.removeEventListener("touchend", this._onTouchUp);
91 |
92 | this._targetEl.removeEventListener("mouseout", this._onMouseLeave);
93 | }
94 |
95 | init(targetEl?: React.MutableRefObject | null) {
96 | this._targetEl = window;
97 | if (targetEl && targetEl.current) {
98 | this._targetEl = targetEl.current;
99 | }
100 |
101 | this._addEvents();
102 | }
103 |
104 | destroy() {
105 | this._removeEvents();
106 | }
107 |
108 | setShouldUpdate(value: boolean) {
109 | this._shouldUpdate = value;
110 | }
111 |
112 | update() {
113 | this._mouseLast.x = this.mouse.x;
114 | this._mouseLast.y = this.mouse.y;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/frontend/src/components/Caption/coverBackgroundClasses/App.ts:
--------------------------------------------------------------------------------
1 | import debounce from 'lodash.debounce';
2 | import { MotionValue } from 'framer-motion';
3 |
4 | import { sharedValues } from 'utils/sharedValues';
5 | import { Bounds } from 'utils/sharedTypes';
6 |
7 | import { BackgroundSketch } from './Components/BackgroundSketch';
8 |
9 | interface Constructor {
10 | rendererEl: HTMLDivElement | null;
11 | scrollRatio: MotionValue;
12 | }
13 |
14 | export class App {
15 | _rendererEl: HTMLDivElement;
16 | _rafId: number | null = null;
17 | _isResumed = true;
18 | _lastFrameTime: number | null = null;
19 | _canvas: HTMLCanvasElement;
20 | _pixelRatio = 1;
21 | _ctx: CanvasRenderingContext2D | null;
22 | _rendererBounds: Bounds = { width: 100, height: 100 };
23 | _backgroundSketch: BackgroundSketch;
24 |
25 | constructor({ scrollRatio, rendererEl }: Constructor) {
26 | this._rendererEl = rendererEl as HTMLDivElement;
27 | this._canvas = document.createElement('canvas');
28 | this._rendererEl.appendChild(this._canvas);
29 | this._ctx = this._canvas.getContext('2d');
30 | this._backgroundSketch = new BackgroundSketch({
31 | ctx: this._ctx,
32 | });
33 |
34 | this._onResize();
35 | this._addListeners();
36 | this._resumeAppFrame();
37 |
38 | scrollRatio.onChange(v => {
39 | this._backgroundSketch.setScrollRatio(v as number);
40 | });
41 | }
42 |
43 | _onResizeDebounced = debounce(() => this._onResize(), 300);
44 |
45 | _onResize() {
46 | const clientRect = this._rendererEl.getBoundingClientRect();
47 | this._rendererBounds.width = clientRect.width;
48 | this._rendererBounds.height = clientRect.height;
49 | this._pixelRatio = Math.min(window.devicePixelRatio, 2);
50 | this._setSizes();
51 | this._backgroundSketch.setRendererBounds(this._rendererBounds);
52 | this._backgroundSketch.setPixelRatio(this._pixelRatio);
53 | }
54 |
55 | _setSizes() {
56 | if (this._canvas && this._ctx) {
57 | //The render stays sharp on devices with higher devicePixelRatio (retina)
58 | const w = this._rendererBounds.width;
59 | const h = this._rendererBounds.height;
60 | const ratio = this._pixelRatio;
61 |
62 | this._canvas.width = w * ratio;
63 | this._canvas.height = h * ratio;
64 | this._canvas.style.width = w.toString() + 'px';
65 | this._canvas.style.height = h.toString() + 'px';
66 | this._ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
67 | }
68 | }
69 |
70 | _onLoaded() {
71 | this._backgroundSketch.setRendererBounds(this._rendererBounds);
72 | }
73 |
74 | _onVisibilityChange = () => {
75 | if (document.hidden) {
76 | this._stopAppFrame();
77 | } else {
78 | this._resumeAppFrame();
79 | }
80 | };
81 |
82 | _addListeners() {
83 | window.addEventListener('resize', this._onResizeDebounced);
84 | window.addEventListener('visibilitychange', this._onVisibilityChange);
85 | }
86 |
87 | _removeListeners() {
88 | window.removeEventListener('resize', this._onResizeDebounced);
89 | window.removeEventListener('visibilitychange', this._onVisibilityChange);
90 | }
91 |
92 | _clear() {
93 | if (!this._ctx) return;
94 | this._ctx.save();
95 | this._ctx.setTransform(1, 0, 0, 1, 0, 0);
96 | this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
97 | this._ctx.restore();
98 | }
99 |
100 | _resumeAppFrame() {
101 | this._isResumed = true;
102 | if (!this._rafId) {
103 | this._rafId = window.requestAnimationFrame(this._renderOnFrame);
104 | }
105 | }
106 |
107 | _renderOnFrame = (time: number) => {
108 | this._rafId = window.requestAnimationFrame(this._renderOnFrame);
109 |
110 | if (this._isResumed || !this._lastFrameTime) {
111 | this._lastFrameTime = window.performance.now();
112 | this._isResumed = false;
113 | return;
114 | }
115 |
116 | const delta = time - this._lastFrameTime;
117 | let slowDownFactor = delta / sharedValues.motion.DT_FPS;
118 |
119 | //Rounded slowDown factor to the nearest integer reduces physics lags
120 | const slowDownFactorRounded = Math.round(slowDownFactor);
121 |
122 | if (slowDownFactorRounded >= 1) {
123 | slowDownFactor = slowDownFactorRounded;
124 | }
125 | this._lastFrameTime = time;
126 |
127 | this._clear();
128 | this._backgroundSketch.update({ delta, slowDownFactor, time });
129 | };
130 |
131 | _stopAppFrame() {
132 | if (this._rafId) {
133 | window.cancelAnimationFrame(this._rafId);
134 | this._rafId = null;
135 | }
136 | }
137 |
138 | destroy() {
139 | if (this._canvas.parentNode) {
140 | this._canvas.parentNode.removeChild(this._canvas);
141 | }
142 | this._stopAppFrame();
143 | this._removeListeners();
144 |
145 | this._backgroundSketch.destroy();
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/frontend/src/components/Caption/backgroundClasses/App.ts:
--------------------------------------------------------------------------------
1 | import debounce from 'lodash.debounce';
2 | import { MotionValue } from 'framer-motion';
3 |
4 | import { sharedValues } from 'utils/sharedValues';
5 | import { Bounds } from 'utils/sharedTypes';
6 |
7 | import { BackgroundSketch } from './Components/BackgroundSketch';
8 |
9 | interface Constructor {
10 | rendererEl: HTMLDivElement | null;
11 | scrollRatioRest: MotionValue;
12 | }
13 |
14 | export class App {
15 | _rendererEl: HTMLDivElement;
16 | _rafId: number | null = null;
17 | _isResumed = true;
18 | _lastFrameTime: number | null = null;
19 | _canvas: HTMLCanvasElement;
20 | _pixelRatio = 1;
21 | _ctx: CanvasRenderingContext2D | null;
22 | _rendererBounds: Bounds = { width: 100, height: 100 };
23 | _backgroundSketch: BackgroundSketch;
24 |
25 | constructor({ scrollRatioRest, rendererEl }: Constructor) {
26 | this._rendererEl = rendererEl as HTMLDivElement;
27 | this._canvas = document.createElement('canvas');
28 | this._rendererEl.appendChild(this._canvas);
29 | this._ctx = this._canvas.getContext('2d');
30 | this._backgroundSketch = new BackgroundSketch({
31 | ctx: this._ctx,
32 | });
33 |
34 | this._onResize();
35 | this._addListeners();
36 | this._resumeAppFrame();
37 |
38 | scrollRatioRest.onChange(v => {
39 | this._backgroundSketch.setScrollRatioRest(v as number);
40 | });
41 | }
42 |
43 | _onResizeDebounced = debounce(() => this._onResize(), 300);
44 |
45 | _onResize() {
46 | const clientRect = this._rendererEl.getBoundingClientRect();
47 | this._rendererBounds.width = clientRect.width;
48 | this._rendererBounds.height = clientRect.height;
49 | this._pixelRatio = Math.min(window.devicePixelRatio, 2);
50 | this._setSizes();
51 | this._backgroundSketch.setRendererBounds(this._rendererBounds);
52 | this._backgroundSketch.setPixelRatio(this._pixelRatio);
53 | }
54 |
55 | _setSizes() {
56 | if (this._canvas && this._ctx) {
57 | //The render stays sharp on devices with higher devicePixelRatio (retina)
58 | const w = this._rendererBounds.width;
59 | const h = this._rendererBounds.height;
60 | const ratio = this._pixelRatio;
61 |
62 | this._canvas.width = w * ratio;
63 | this._canvas.height = h * ratio;
64 | this._canvas.style.width = w.toString() + 'px';
65 | this._canvas.style.height = h.toString() + 'px';
66 | this._ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
67 | }
68 | }
69 |
70 | _onLoaded() {
71 | this._backgroundSketch.setRendererBounds(this._rendererBounds);
72 | }
73 |
74 | _onVisibilityChange = () => {
75 | if (document.hidden) {
76 | this._stopAppFrame();
77 | } else {
78 | this._resumeAppFrame();
79 | }
80 | };
81 |
82 | _addListeners() {
83 | window.addEventListener('resize', this._onResizeDebounced);
84 | window.addEventListener('visibilitychange', this._onVisibilityChange);
85 | }
86 |
87 | _removeListeners() {
88 | window.removeEventListener('resize', this._onResizeDebounced);
89 | window.removeEventListener('visibilitychange', this._onVisibilityChange);
90 | }
91 |
92 | _clear() {
93 | if (!this._ctx) return;
94 | this._ctx.save();
95 | this._ctx.setTransform(1, 0, 0, 1, 0, 0);
96 | this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
97 | this._ctx.restore();
98 | }
99 |
100 | _resumeAppFrame() {
101 | this._isResumed = true;
102 | if (!this._rafId) {
103 | this._rafId = window.requestAnimationFrame(this._renderOnFrame);
104 | }
105 | }
106 |
107 | _renderOnFrame = (time: number) => {
108 | this._rafId = window.requestAnimationFrame(this._renderOnFrame);
109 |
110 | if (this._isResumed || !this._lastFrameTime) {
111 | this._lastFrameTime = window.performance.now();
112 | this._isResumed = false;
113 | return;
114 | }
115 |
116 | const delta = time - this._lastFrameTime;
117 | let slowDownFactor = delta / sharedValues.motion.DT_FPS;
118 |
119 | //Rounded slowDown factor to the nearest integer reduces physics lags
120 | const slowDownFactorRounded = Math.round(slowDownFactor);
121 |
122 | if (slowDownFactorRounded >= 1) {
123 | slowDownFactor = slowDownFactorRounded;
124 | }
125 | this._lastFrameTime = time;
126 |
127 | this._clear();
128 | this._backgroundSketch.update({ delta, slowDownFactor, time });
129 | };
130 |
131 | _stopAppFrame() {
132 | if (this._rafId) {
133 | window.cancelAnimationFrame(this._rafId);
134 | this._rafId = null;
135 | }
136 | }
137 |
138 | destroy() {
139 | if (this._canvas.parentNode) {
140 | this._canvas.parentNode.removeChild(this._canvas);
141 | }
142 | this._stopAppFrame();
143 | this._removeListeners();
144 |
145 | this._backgroundSketch.destroy();
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/frontend/src/components/Caption/Caption.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 | import { MotionValue, useTransform, motion } from 'framer-motion';
3 |
4 | import { useWindowSize } from 'hooks/useWindowSize';
5 | import { ShowOff } from 'sections/ShowOff/ShowOff';
6 |
7 | import { appState } from './Caption.state';
8 | import { App } from './classes/App';
9 | import * as Background from './backgroundClasses/App';
10 | import * as CoverBackground from './coverBackgroundClasses/App';
11 | import * as S from './Caption.styles';
12 |
13 | interface Props {
14 | scrollRatio: MotionValue;
15 | scrollRatioQuicker: MotionValue;
16 | scrollRatioRest: MotionValue;
17 | scrollRatioClose: MotionValue;
18 | scrollContainerRef: React.MutableRefObject;
19 | }
20 |
21 | export const Caption = (props: Props) => {
22 | const { scrollRatioClose, scrollRatioRest, scrollRatioQuicker, scrollRatio, scrollContainerRef } =
23 | props;
24 | const rendererEl = useRef(null);
25 | const backgroundRendererEl = useRef(null);
26 | const coverBackgroundRendererEl = useRef(null);
27 | const [shouldReveal, setShouldReveal] = useState(false);
28 | const { windowSizeRef } = useWindowSize();
29 |
30 | const scaleValue = useTransform(scrollRatioQuicker, v => v * 0.2 + 0.8);
31 | const translateXValue = useTransform(scrollRatioQuicker, v => {
32 | return v * windowSizeRef.current.windowWidth * -0.45;
33 | });
34 |
35 | const translateXValue2 = useTransform(
36 | scrollRatioRest,
37 | v => v * windowSizeRef.current.windowWidth * 0.45 + v * windowSizeRef.current.windowWidth * 0.5
38 | );
39 |
40 | const translateXValue3 = useTransform(
41 | scrollRatioRest,
42 | v => v * windowSizeRef.current.windowWidth * -0.5
43 | );
44 |
45 | const translateBadgeYValue = useTransform(scrollRatioRest, v => {
46 | return `${(1 - v) * -480}%`;
47 | });
48 |
49 | const translateBadgeXValue = useTransform(scrollRatioRest, v => {
50 | return `${(1 - v) * 101}%`;
51 | });
52 |
53 | useEffect(() => {
54 | if (!rendererEl.current) return;
55 | appState.app = new App({
56 | rendererEl: rendererEl.current,
57 | setShouldReveal,
58 | scrollRatio,
59 | scrollRatioQuicker,
60 | scrollRatioRest,
61 | });
62 |
63 | return () => {
64 | if (appState.app) {
65 | appState.app.destroy();
66 | appState.app = null;
67 | }
68 | };
69 | }, []);
70 |
71 | useEffect(() => {
72 | if (!rendererEl.current) return;
73 | appState.background = new Background.App({
74 | rendererEl: backgroundRendererEl.current,
75 | scrollRatioRest,
76 | });
77 |
78 | return () => {
79 | if (appState.background) {
80 | appState.background.destroy();
81 | appState.background = null;
82 | }
83 | };
84 | }, []);
85 |
86 | useEffect(() => {
87 | if (!rendererEl.current) return;
88 | appState.coverBackground = new CoverBackground.App({
89 | rendererEl: coverBackgroundRendererEl.current,
90 | scrollRatio: scrollRatioClose,
91 | });
92 |
93 | return () => {
94 | if (appState.coverBackground) {
95 | appState.coverBackground.destroy();
96 | appState.coverBackground = null;
97 | }
98 | };
99 | }, []);
100 |
101 | return (
102 | <>
103 |
104 |
105 | {/*
106 |
107 | react just
108 |
109 | */}
110 |
115 |
121 |
122 |
123 |
124 |
125 |
131 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | >
145 | );
146 | };
147 |
--------------------------------------------------------------------------------
/frontend/src/components/Caption/classes/Components/TextSketch.ts:
--------------------------------------------------------------------------------
1 | import { gsap } from 'gsap';
2 |
3 | import { Bounds, UpdateInfo } from 'utils/sharedTypes';
4 |
5 | import { App } from '../App';
6 |
7 | interface Constructor {
8 | text: string;
9 | ctx: CanvasRenderingContext2D | null;
10 | }
11 |
12 | export class TextSketch {
13 | static defaultEase = 'expo.inOut';
14 | static edgeSize = 0.07;
15 |
16 | _rendererBounds: Bounds = { width: 1, height: 1 };
17 | _translateOffset = { x: 0, y: 0 };
18 | _textValue: string;
19 | _ctx: CanvasRenderingContext2D | null;
20 | _pixelRatio = 1;
21 | _textMeasures = { width: 0, height: 0, fontSize: 0 };
22 | _transitionTl: gsap.core.Timeline | null = null;
23 | _scrollRatio = 0;
24 | _scrollRatioQuicker = 0;
25 | _scrollRatioRest = 0;
26 |
27 | constructor({ text, ctx }: Constructor) {
28 | this._ctx = ctx;
29 | this._textValue = text;
30 | }
31 |
32 | _drawBackground() {
33 | if (!this._ctx) return;
34 |
35 | this._ctx.beginPath();
36 |
37 | const segments = 20;
38 |
39 | const widthSegments = Math.ceil(this._rendererBounds.width / segments);
40 | this._ctx.moveTo(this._rendererBounds.width, this._rendererBounds.height);
41 | this._ctx.lineTo(0, this._rendererBounds.height);
42 |
43 | const t = (1 - this._scrollRatioRest) * this._rendererBounds.height;
44 | const amplitude = this._rendererBounds.width * 0.1 * Math.sin(this._scrollRatioRest * Math.PI);
45 |
46 | this._ctx.lineTo(0, t);
47 |
48 | for (let index = 0; index <= widthSegments; index++) {
49 | const n = segments * index;
50 | const r = t - Math.sin((n / this._rendererBounds.width) * Math.PI) * amplitude;
51 |
52 | this._ctx.lineTo(n, r);
53 | }
54 |
55 | this._ctx.fillStyle = 'rgb(244,244,244)';
56 | this._ctx.fill();
57 | }
58 |
59 | _clipRect() {
60 | if (!this._ctx) return;
61 | const edgeRounded = Math.round(this._rendererBounds.width * TextSketch.edgeSize);
62 | const leftX = edgeRounded;
63 |
64 | this._ctx.beginPath();
65 | this._ctx.rect(leftX, 0, this._rendererBounds.width - 2 * leftX, this._rendererBounds.height);
66 | this._ctx.clip();
67 | }
68 |
69 | _scaleContext() {
70 | if (!this._ctx) return;
71 |
72 | const desiredScale = 1 - 0.51 * this._scrollRatioRest;
73 | const scale = desiredScale * this._pixelRatio;
74 | const sFactor = (scale - 1 * this._pixelRatio) * 0.5;
75 | const tX = -this._rendererBounds.width * sFactor;
76 | const tY = -this._rendererBounds.height * sFactor;
77 | this._ctx.setTransform(scale, 0, 0, scale, tX, tY);
78 | }
79 |
80 | _drawText() {
81 | if (!this._ctx) return;
82 | this._ctx.fillText(
83 | this._textValue,
84 | this._textMeasures.width * this._scrollRatioRest * 0.53 * 0.5 +
85 | this._rendererBounds.width * TextSketch.edgeSize +
86 | this._translateOffset.x -
87 | this._textMeasures.width * this._scrollRatioQuicker * 0.52 - //defines when to stop offseting
88 | this._textMeasures.width * 0.01, //extra letter offset due to font settings
89 | this._rendererBounds.height / 2 +
90 | this._textMeasures.height / 2 +
91 | this._translateOffset.y -
92 | this._textMeasures.width * -this._scrollRatioRest * 0.05 * 0
93 | );
94 | }
95 |
96 | update(updateInfo: UpdateInfo) {
97 | if (!this._ctx) return;
98 |
99 | this._ctx.font = `bold ${this._textMeasures.fontSize}px teko`;
100 |
101 | if (this._ctx) this._ctx.globalCompositeOperation = 'source-over';
102 | this._ctx.fillStyle = App.backgroundColor;
103 | this._ctx.fillRect(0, 0, this._rendererBounds.width, this._rendererBounds.height);
104 |
105 | if (this._ctx) this._ctx.globalCompositeOperation = 'source-over';
106 | this._drawBackground();
107 | if (this._ctx) this._ctx.globalCompositeOperation = 'xor';
108 |
109 | this._ctx.save();
110 | this._clipRect();
111 | this._scaleContext();
112 |
113 | this._drawText();
114 | this._ctx.restore();
115 | }
116 |
117 | _animateOffsetY(destination: number, duration: number) {
118 | return gsap.to(this._translateOffset, {
119 | y: destination,
120 | duration,
121 | ease: TextSketch.defaultEase,
122 | });
123 | }
124 |
125 | _animateOffsetX(destination: number, duration: number) {
126 | return gsap.to(this._translateOffset, {
127 | x: destination,
128 | duration,
129 | ease: TextSketch.defaultEase,
130 | });
131 | }
132 |
133 | _updateFontSize() {
134 | if (!this._ctx) return;
135 | this._ctx.font = `bold ${this._textMeasures.fontSize}px teko`;
136 |
137 | const metrics = this._ctx.measureText(this._textValue);
138 | const actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
139 |
140 | this._textMeasures.height = actualHeight;
141 | this._textMeasures.width = metrics.width;
142 | }
143 |
144 | setRendererBounds(bounds: Bounds) {
145 | this._rendererBounds = bounds;
146 | if (!this._ctx) return;
147 |
148 | this._textMeasures.fontSize = this._rendererBounds.width * 0.417;
149 | this._updateFontSize();
150 | }
151 |
152 | setPixelRatio(value: number) {
153 | this._pixelRatio = value;
154 | }
155 |
156 | setScrollRatio(value: number) {
157 | this._scrollRatio = value;
158 | }
159 |
160 | setScrollRatioQuicker(value: number) {
161 | this._scrollRatioQuicker = value;
162 | }
163 |
164 | setScrollRatioRest(value: number) {
165 | this._scrollRatioRest = value;
166 | }
167 |
168 | animateIn() {
169 | this._transitionTl = gsap.timeline();
170 |
171 | // this._transitionTl.add(this._animateOffsetY(0, 1.5));
172 | // .add(this._animateOffsetX(0, 1.2))
173 | }
174 |
175 | destroy() {
176 | this._transitionTl && this._transitionTl.kill();
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/frontend/src/sections/ShowOff/ShowOff.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MouseParallax } from 'react-just-parallax';
3 |
4 | import { PreloadImage } from 'components/PreloadImage/PreloadImage';
5 |
6 | import frameSrc from './images/frame.svg';
7 | import shape1Src from './images/shape1.svg';
8 | import shape2Src from './images/shape2.svg';
9 | import shape3Src from './images/shape3.svg';
10 | import shape4Src from './images/shape4.svg';
11 | import shape5Src from './images/shape5.svg';
12 | import shape6Src from './images/shape6.svg';
13 | import shape7Src from './images/shape7.svg';
14 | import shape8Src from './images/shape8.svg';
15 | import shape9Src from './images/shape9.svg';
16 | import * as S from './ShowOff.styles';
17 |
18 | interface Props {
19 | scrollContainer: React.MutableRefObject;
20 | }
21 |
22 | const defStrength = 0.24;
23 | const mulC = 0.3;
24 | const mul1 = 0.84;
25 | const mul2 = 0.82;
26 | const mul3 = 0.7;
27 | const mul4 = 0.32;
28 | const mul5 = 0.21;
29 | const mul6 = 0.56;
30 | const mul7 = 0.46;
31 | const mul8 = 0.91;
32 | const mul9 = 0.91;
33 |
34 | export const ShowOff = ({ scrollContainer }: Props) => {
35 | return (
36 | <>
37 |
38 |
39 |
47 |
48 |
49 |
50 |
51 |
58 |
59 |
60 |
61 |
62 |
63 |
70 |
71 |
72 |
73 |
74 |
75 |
82 |
83 |
84 |
85 |
86 |
87 |
94 |
95 |
96 |
97 |
98 |
99 |
107 |
108 |
109 |
110 |
111 |
112 |
119 |
120 |
121 |
122 |
123 |
124 |
132 |
133 |
134 |
135 |
136 |
137 |
144 |
145 |
146 |
147 |
148 |
149 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | >
163 | );
164 | };
165 |
--------------------------------------------------------------------------------
/frontend/src/components/Caption/classes/App.ts:
--------------------------------------------------------------------------------
1 | import debounce from 'lodash.debounce';
2 | import FontFaceObserver from 'fontfaceobserver';
3 | import { MotionValue } from 'framer-motion';
4 |
5 | import { sharedValues } from 'utils/sharedValues';
6 | import { Bounds } from 'utils/sharedTypes';
7 |
8 | import { TextSketch } from './Components/TextSketch';
9 |
10 | interface Constructor {
11 | rendererEl: HTMLDivElement;
12 | setShouldReveal: React.Dispatch>;
13 | scrollRatio: MotionValue;
14 | scrollRatioQuicker: MotionValue;
15 | scrollRatioRest: MotionValue;
16 | }
17 |
18 | export class App {
19 | static backgroundColor = 'rgba(255,255,255,1)';
20 |
21 | _rendererEl: HTMLDivElement;
22 | _rafId: number | null = null;
23 | _isResumed = true;
24 | _lastFrameTime: number | null = null;
25 | _canvas: HTMLCanvasElement;
26 | _pixelRatio = 1;
27 | _ctx: CanvasRenderingContext2D | null;
28 | _setShouldRevealReact;
29 | _rendererBounds: Bounds = { width: 100, height: 100 };
30 | _textSketch: TextSketch;
31 | _scrollRatioRest = 0;
32 |
33 | constructor({
34 | scrollRatioRest,
35 | rendererEl,
36 | setShouldReveal,
37 | scrollRatio,
38 | scrollRatioQuicker,
39 | }: Constructor) {
40 | this._rendererEl = rendererEl;
41 | this._setShouldRevealReact = setShouldReveal;
42 | this._canvas = document.createElement('canvas');
43 | this._rendererEl.appendChild(this._canvas);
44 | this._ctx = this._canvas.getContext('2d');
45 | this._textSketch = new TextSketch({
46 | ctx: this._ctx,
47 | text: 'PARA LLAX',
48 | });
49 |
50 | this._preloadFont();
51 | this._onResize();
52 | this._addListeners();
53 | this._resumeAppFrame();
54 |
55 | scrollRatio.onChange(v => {
56 | this._textSketch.setScrollRatio(v as number);
57 | });
58 |
59 | scrollRatioQuicker.onChange(v => {
60 | this._textSketch.setScrollRatioQuicker(v as number);
61 | });
62 |
63 | scrollRatioRest.onChange(v => {
64 | this._textSketch.setScrollRatioRest(v as number);
65 | this._scrollRatioRest = v as number;
66 | });
67 | }
68 |
69 | _onResizeDebounced = debounce(() => this._onResize(), 300);
70 |
71 | _onResize() {
72 | const clientRect = this._rendererEl.getBoundingClientRect();
73 | this._rendererBounds.width = clientRect.width;
74 | this._rendererBounds.height = clientRect.height;
75 | this._pixelRatio = Math.min(window.devicePixelRatio, 2);
76 | this._setSizes();
77 | this._textSketch.setRendererBounds(this._rendererBounds);
78 | this._textSketch.setPixelRatio(this._pixelRatio);
79 | }
80 |
81 | _setSizes() {
82 | if (this._canvas && this._ctx) {
83 | //The render stays sharp on devices with higher devicePixelRatio (retina)
84 | const w = this._rendererBounds.width;
85 | const h = this._rendererBounds.height;
86 | const ratio = this._pixelRatio;
87 |
88 | this._canvas.width = w * ratio;
89 | this._canvas.height = h * ratio;
90 | this._canvas.style.width = w.toString() + 'px';
91 | this._canvas.style.height = h.toString() + 'px';
92 | this._ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
93 | }
94 | }
95 |
96 | _preloadFont() {
97 | const fontA = new FontFaceObserver('opensans');
98 | const fontB = new FontFaceObserver('teko');
99 |
100 | Promise.all([fontA.load(null, 2500), fontB.load(null, 2500)])
101 | .then(
102 | () => {
103 | this._onLoaded();
104 | },
105 | () => {
106 | this._onLoaded();
107 | console.warn('Fonts were loading too long (over 1500ms)');
108 | }
109 | )
110 | .catch(err => {
111 | this._onLoaded();
112 | console.warn('Some critical font are not available:', err);
113 | });
114 | }
115 |
116 | _onLoaded() {
117 | this._textSketch.setRendererBounds(this._rendererBounds);
118 | this._setShouldRevealReact(true);
119 | this._textSketch.animateIn();
120 | }
121 |
122 | _onVisibilityChange = () => {
123 | if (document.hidden) {
124 | this._stopAppFrame();
125 | } else {
126 | this._resumeAppFrame();
127 | }
128 | };
129 |
130 | _addListeners() {
131 | window.addEventListener('resize', this._onResizeDebounced);
132 | window.addEventListener('visibilitychange', this._onVisibilityChange);
133 | }
134 |
135 | _removeListeners() {
136 | window.removeEventListener('resize', this._onResizeDebounced);
137 | window.removeEventListener('visibilitychange', this._onVisibilityChange);
138 | }
139 |
140 | _clear() {
141 | if (!this._ctx) return;
142 | this._ctx.save();
143 | this._ctx.setTransform(1, 0, 0, 1, 0, 0);
144 | this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
145 | this._ctx.restore();
146 | }
147 |
148 | _resumeAppFrame() {
149 | this._isResumed = true;
150 | if (!this._rafId) {
151 | this._rafId = window.requestAnimationFrame(this._renderOnFrame);
152 | }
153 | }
154 |
155 | _renderOnFrame = (time: number) => {
156 | this._rafId = window.requestAnimationFrame(this._renderOnFrame);
157 |
158 | if (this._isResumed || !this._lastFrameTime) {
159 | this._lastFrameTime = window.performance.now();
160 | this._isResumed = false;
161 | return;
162 | }
163 |
164 | const delta = time - this._lastFrameTime;
165 | let slowDownFactor = delta / sharedValues.motion.DT_FPS;
166 |
167 | //Rounded slowDown factor to the nearest integer reduces physics lags
168 | const slowDownFactorRounded = Math.round(slowDownFactor);
169 |
170 | if (slowDownFactorRounded >= 1) {
171 | slowDownFactor = slowDownFactorRounded;
172 | }
173 | this._lastFrameTime = time;
174 |
175 | this._clear();
176 | this._textSketch.update({ delta, slowDownFactor, time });
177 | };
178 |
179 | _stopAppFrame() {
180 | if (this._rafId) {
181 | window.cancelAnimationFrame(this._rafId);
182 | this._rafId = null;
183 | }
184 | }
185 |
186 | destroy() {
187 | if (this._canvas.parentNode) {
188 | this._canvas.parentNode.removeChild(this._canvas);
189 | }
190 | this._stopAppFrame();
191 | this._removeListeners();
192 |
193 | this._textSketch.destroy();
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/react-just-parallax/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | React Just Parallax
5 |
6 | React library for scroll and mousemove parallax effect ✨ Open source, production-ready
7 |
8 |
9 |
10 |
11 | React Just Parallax is an open source, production-ready library that's designed for all creative developers and more.
12 |
13 | - Written in TypeScript
14 | - Super lightweight
15 | - Easy to use
16 | - blazing fast
17 |
18 | ### 📦 NPM Link
19 |
20 | - [Link to official NPM page](https://www.npmjs.com/package/react-just-parallax/).
21 |
22 | ### 📜 Examples
23 |
24 | - Check out [our examples and showcase page](https://react-just-parallax.michalzalobny.com/) for guides.
25 |
26 | ### ⚖️ License
27 |
28 | - React Just Parallax is MIT licensed.
29 |
30 | ### ✍🏻 Author
31 |
32 | - [@michalzalobny](https://twitter.com/michalzalobny)
33 |
34 | ### 🐇 Quick start
35 |
36 | ```
37 | npm install react-just-parallax
38 | ```
39 |
40 | ```jsx
41 | import { MouseParallax, ScrollParallax } from "react-just-parallax";
42 |
43 | export const MyComponent = () => (
44 | <>
45 |
46 | I'm reacting to mouse move
47 |
48 |
49 |
50 | I'm reacting to scroll
51 |
52 | >
53 | );
54 | ```
55 |
56 | ### Props for MouseParallax
57 |
58 | | Name | Type | Default | Description |
59 | | ---------------------- | ------------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
60 | | strength | number | 0.14 | Parallax offset multiplier. Moving mouse by 10 pixels will move element position by 10px \* strength |
61 | | lerpEase | number | 0.06 | Determines how quick the interpolation between offset values occurs (the higher the quicker) |
62 | | isAbsolutelyPositioned | boolean | false | If the element you want to use parallax on is positioned absolutely, set this prop to `true` |
63 | | zIndex | number \| null | null | Specify element's outer container z-index (useful while using `isAbsolutelyPositioned` prop) |
64 | | shouldPause | boolean | true | Stops element from reacting to scroll and interpolating offset if it is out of view |
65 | | enableOnTouchDevice | boolean | false | Turns on/off parallax effect on touch devices |
66 | | scrollContainerRef | React.MutableRefObject \| null | null | Use when element is situated in scrollable element other than window |
67 | | parallaxContainerRef | React.MutableRefObject \| null | null | By default, element reacts to mousemove on window. You can specify any other container using this prop to make element react only within given container |
68 | | shouldResetPosition | boolean | false | Resets element's position if cursor leaves window or leaves `parallaxContainerRef` |
69 |
70 | ### Props for ScrollParallax
71 |
72 | | Name | Type | Default | Description |
73 | | ---------------------- | ------------------------------ | ------- | ------------------------------------------------------------------------------------------------- |
74 | | strength | number | 0.14 | Parallax offset multiplier. Scrolling by 10 pixels will move element position by 10px \* strength |
75 | | lerpEase | number | 0.06 | Determines how quick the interpolation between offset values occurs (the higher the quicker) |
76 | | isAbsolutelyPositioned | boolean | false | If the element you want to use parallax on is positioned absolutely, set this prop to `true` |
77 | | zIndex | number \| null | null | Specify element's outer container z-index (useful while using `isAbsolutelyPositioned` prop) |
78 | | shouldPause | boolean | true | Stops element from reacting to scroll and interpolating offset if it is out of view |
79 | | enableOnTouchDevice | boolean | true | Turns on/off parallax effect on touch devices |
80 | | isHorizontal | boolean | false | Enable if using horizontal scrolling |
81 | | scrollContainerRef | React.MutableRefObject \| null | null | Use when element is situated in scrollable element other than window |
82 |
83 | ### Recalculating values on demand for ScrollParallax
84 |
85 | It's sometimes necessary to update values such as element's position or sizes on demand, for example if the DOM structure changes.
86 |
87 | Library can't know of this kind of changes so it is not able to handle it by itself, and that's when we need to use `ScrollParallaxHandle` to update them manually.
88 |
89 | ```jsx
90 | import { ScrollParallax, ScrollParallaxHandle } from "react-just-parallax";
91 |
92 | export const MyComponent = () => {
93 | const [display, setDisplay] = useState(true);
94 | const scrollParallaxRef = useRef(null);
95 |
96 | useEffect(() => {
97 | scrollParallaxRef.current?.updateValues();
98 | }, [display]);
99 |
100 | return (
101 | <>
102 |
103 | I'm reacting to scroll
104 |
105 |
106 | {display && (
107 | setDisplay((false)}>
108 | Disappear me
109 |
110 | )}
111 | >
112 | );
113 | };
114 | ```
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | React Just Parallax
5 |
6 | React library for scroll and mousemove parallax effect ✨ Open source, production-ready
7 |
8 |
9 |
10 |
11 | This repo contains the source code for [React Just Parallax](https://github.com/michalzalobny/react-just-parallax/tree/main/react-just-parallax), source code for [documentation page](https://github.com/michalzalobny/react-just-parallax/tree/main/frontend) and source code for [testing the package locally](https://github.com/michalzalobny/react-just-parallax/tree/main/cra-for-testing).
12 |
13 | - Written in TypeScript
14 | - Super lightweight
15 | - Easy to use
16 | - blazing fast
17 |
18 | ### 📦 NPM Link
19 |
20 | - [Link to official NPM page](https://www.npmjs.com/package/react-just-parallax/).
21 |
22 | ### 📜 Examples
23 |
24 | - Check out [our examples and showcase page](https://react-just-parallax.michalzalobny.com/) for guides.
25 |
26 | ### ⚖️ License
27 |
28 | - React Just Parallax is MIT licensed.
29 |
30 | ### ✍🏻 Author
31 |
32 | - [@michalzalobny](https://twitter.com/michalzalobny)
33 |
34 | ### 🐇 Quick start
35 |
36 | ```
37 | npm install react-just-parallax
38 | ```
39 |
40 | ```jsx
41 | import { MouseParallax, ScrollParallax } from "react-just-parallax";
42 |
43 | export const MyComponent = () => (
44 | <>
45 |
46 | I'm reacting to mouse move
47 |
48 |
49 |
50 | I'm reacting to scroll
51 |
52 | >
53 | );
54 | ```
55 |
56 | ### Props for MouseParallax
57 |
58 | | Name | Type | Default | Description |
59 | | ---------------------- | ------------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
60 | | strength | number | 0.14 | Parallax offset multiplier. Moving mouse by 10 pixels will move element position by 10px \* strength |
61 | | lerpEase | number | 0.06 | Determines how quick the interpolation between offset values occurs (the higher the quicker) |
62 | | isAbsolutelyPositioned | boolean | false | If the element you want to use parallax on is positioned absolutely, set this prop to `true` |
63 | | zIndex | number \| null | null | Specify element's outer container z-index (useful while using `isAbsolutelyPositioned` prop) |
64 | | shouldPause | boolean | true | Stops element from reacting to scroll and interpolating offset if it is out of view |
65 | | enableOnTouchDevice | boolean | false | Turns on/off parallax effect on touch devices |
66 | | scrollContainerRef | React.MutableRefObject \| null | null | Use when element is situated in scrollable element other than window |
67 | | parallaxContainerRef | React.MutableRefObject \| null | null | By default, element reacts to mousemove on window. You can specify any other container using this prop to make element react only within given container |
68 | | shouldResetPosition | boolean | false | Resets element's position if cursor leaves window or leaves `parallaxContainerRef` |
69 |
70 | ### Props for ScrollParallax
71 |
72 | | Name | Type | Default | Description |
73 | | ---------------------- | ------------------------------ | ------- | ------------------------------------------------------------------------------------------------- |
74 | | strength | number | 0.14 | Parallax offset multiplier. Scrolling by 10 pixels will move element position by 10px \* strength |
75 | | lerpEase | number | 0.06 | Determines how quick the interpolation between offset values occurs (the higher the quicker) |
76 | | isAbsolutelyPositioned | boolean | false | If the element you want to use parallax on is positioned absolutely, set this prop to `true` |
77 | | zIndex | number \| null | null | Specify element's outer container z-index (useful while using `isAbsolutelyPositioned` prop) |
78 | | shouldPause | boolean | true | Stops element from reacting to scroll and interpolating offset if it is out of view |
79 | | enableOnTouchDevice | boolean | true | Turns on/off parallax effect on touch devices |
80 | | isHorizontal | boolean | false | Enable if using horizontal scrolling |
81 | | scrollContainerRef | React.MutableRefObject \| null | null | Use when element is situated in scrollable element other than window |
82 |
83 | ### Recalculating values on demand for ScrollParallax
84 |
85 | It's sometimes necessary to update values such as element's position or sizes on demand, for example if the DOM structure changes.
86 |
87 | Library can't know of this kind of changes so it is not able to handle it by itself, and that's when we need to use `ScrollParallaxHandle` to update them manually.
88 |
89 | ```jsx
90 | import { ScrollParallax, ScrollParallaxHandle } from "react-just-parallax";
91 |
92 | export const MyComponent = () => {
93 | const [display, setDisplay] = useState(true);
94 | const scrollParallaxRef = useRef(null);
95 |
96 | useEffect(() => {
97 | scrollParallaxRef.current?.updateValues();
98 | }, [display]);
99 |
100 | return (
101 | <>
102 |
103 | I'm reacting to scroll
104 |
105 |
106 | {display && (
107 | setDisplay((false)}>
108 | Disappear me
109 |
110 | )}
111 | >
112 | );
113 | };
114 | ```
115 |
--------------------------------------------------------------------------------
/react-just-parallax/src/components/ScrollParallax/ScrollParallax.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | useEffect,
3 | useRef,
4 | forwardRef,
5 | useImperativeHandle,
6 | } from "react";
7 | import sync, { cancelSync, FrameData, Process } from "framesync";
8 | import debounce from "lodash.debounce";
9 | import Prefix from "prefix";
10 |
11 | import { lerp } from "../../utils/lerp";
12 | import { isTouchDevice } from "../../utils/isTouchDevice";
13 | import {
14 | defaultScrollValues,
15 | ScrollValues,
16 | getElementScrollOffsets,
17 | getViewportScrollOffsets,
18 | } from "../../utils/getScrollOffsets";
19 | import { useWindowSize } from "../../hooks/useWindowSize";
20 |
21 | export interface ScrollParallaxProps {
22 | strength?: number;
23 | children?: React.ReactNode;
24 | scrollContainerRef?: React.MutableRefObject | null;
25 | enableOnTouchDevice?: boolean;
26 | lerpEase?: number;
27 | isHorizontal?: boolean;
28 | isAbsolutelyPositioned?: boolean;
29 | zIndex?: number | null;
30 | shouldPause?: boolean;
31 | }
32 |
33 | export type ScrollParallaxHandle = {
34 | updateValues: () => void;
35 | };
36 |
37 | const DEFAULT_FPS = 60;
38 | const DT_FPS = 1000 / DEFAULT_FPS;
39 | const SHOULD_UPDATE_OFFSET = 1.5; // 1 means, that after element is out of 1 screen size (height or width) it stops updating,
40 |
41 | export const ScrollParallax = forwardRef<
42 | ScrollParallaxHandle,
43 | ScrollParallaxProps
44 | >((props, ref) => {
45 | useImperativeHandle(ref, () => ({
46 | updateValues,
47 | }));
48 | const {
49 | children,
50 | strength = 0.14,
51 | scrollContainerRef = null,
52 | enableOnTouchDevice = true,
53 | lerpEase = 0.06,
54 | isHorizontal = false,
55 | isAbsolutelyPositioned = false,
56 | zIndex = null,
57 | shouldPause = true,
58 | } = props;
59 | const parallaxSpanRef = useRef(null);
60 | const parentSpanRef = useRef(null);
61 | const currentX = useRef(1);
62 | const currentY = useRef(1);
63 | const targetX = useRef(1);
64 | const targetY = useRef(1);
65 | const syncRenderRef = useRef(null);
66 | const syncUpdateRef = useRef(null);
67 | const shouldUpdate = useRef(true);
68 | const scrollValues = useRef(defaultScrollValues);
69 | const parentOffsetX = useRef(1);
70 | const parentOffsetY = useRef(1);
71 | const { windowSizeRef } = useWindowSize();
72 | const transformPrefix = useRef(Prefix("transform"));
73 |
74 | const resumeAppFrame = () => {
75 | if (!parallaxSpanRef.current) return;
76 | parallaxSpanRef.current.style.willChange = "transform";
77 |
78 | syncRenderRef.current = sync.render(syncOnRender, true);
79 | syncUpdateRef.current = sync.update(syncOnUpdate, true);
80 | };
81 |
82 | const stopAppFrame = () => {
83 | if (!parallaxSpanRef.current) return;
84 |
85 | parallaxSpanRef.current.style.willChange = "auto"; //initial
86 |
87 | if (syncRenderRef.current) cancelSync.render(syncRenderRef.current);
88 | if (syncUpdateRef.current) cancelSync.update(syncUpdateRef.current);
89 | };
90 |
91 | const syncOnRender = () => {
92 | if (!shouldUpdate.current) return;
93 | if (!parallaxSpanRef.current) return;
94 |
95 | let isHorizontalValue = isHorizontal ? 1 : 0;
96 |
97 | parallaxSpanRef.current.style[transformPrefix.current] = `translate3d(${
98 | currentX.current * strength * isHorizontalValue
99 | }px, ${currentY.current * strength * (1 - isHorizontalValue)}px, 0px)`;
100 | };
101 | const syncOnUpdate = ({ delta }: FrameData) => {
102 | if (!shouldUpdate.current) return;
103 | const diffX = Math.abs(targetX.current - currentX.current);
104 | const diffY = Math.abs(targetY.current - currentY.current);
105 |
106 | //Don't update if difference is too low
107 | if (diffX < 0.001 && diffY < 0.001) return;
108 |
109 | let slowDownFactor = delta / DT_FPS;
110 |
111 | // Rounded slowDown factor to the nearest integer reduces physics lags
112 | const slowDownFactorRounded = Math.round(slowDownFactor);
113 |
114 | if (slowDownFactorRounded >= 1) {
115 | slowDownFactor = slowDownFactorRounded;
116 | }
117 |
118 | const newX = lerp(
119 | currentX.current,
120 | targetX.current,
121 | lerpEase * slowDownFactor
122 | );
123 | currentX.current = newX;
124 |
125 | const newY = lerp(
126 | currentY.current,
127 | targetY.current,
128 | lerpEase * slowDownFactor
129 | );
130 | currentY.current = newY;
131 | };
132 |
133 | const onVisibilityChange = () => {
134 | if (document.hidden) {
135 | stopAppFrame();
136 | } else {
137 | resumeAppFrame();
138 | }
139 | };
140 |
141 | const calculateParentSpanOffset = () => {
142 | if (!parentSpanRef || !parentSpanRef.current) return;
143 | updateScrollValues();
144 | const boundingBox = parentSpanRef.current.getBoundingClientRect();
145 | parentOffsetX.current =
146 | scrollValues.current.xOffset + boundingBox.x + boundingBox.width * 0.5;
147 | parentOffsetY.current =
148 | scrollValues.current.yOffset + boundingBox.y + boundingBox.height * 0.5;
149 | };
150 |
151 | const updateValues = () => {
152 | calculateParentSpanOffset();
153 | handleScroll();
154 | };
155 | const updateValuesDebounced = debounce(updateValues, 150);
156 |
157 | const updateScrollValues = () => {
158 | if (scrollContainerRef && scrollContainerRef.current) {
159 | scrollValues.current = getElementScrollOffsets(
160 | scrollContainerRef.current
161 | );
162 | } else {
163 | scrollValues.current = getViewportScrollOffsets();
164 | }
165 | };
166 |
167 | const handleScroll = () => {
168 | updateScrollValues();
169 | let offsetX = scrollValues.current.xOffset - parentOffsetX.current;
170 | let offsetY = scrollValues.current.yOffset - parentOffsetY.current;
171 |
172 | //Normalize offset to be 0 if element is in the middle of the screen
173 | offsetX = offsetX + 0.5 * windowSizeRef.current.windowWidth;
174 | offsetY = offsetY + 0.5 * windowSizeRef.current.windowHeight;
175 |
176 | targetX.current = offsetX;
177 | targetY.current = offsetY;
178 |
179 | if (
180 | Math.abs(targetY.current) >
181 | windowSizeRef.current.windowHeight * SHOULD_UPDATE_OFFSET ||
182 | Math.abs(targetX.current) >
183 | windowSizeRef.current.windowWidth * SHOULD_UPDATE_OFFSET
184 | ) {
185 | //dont check if is intersecting etc.
186 | if (!shouldPause) return;
187 | shouldUpdate.current = false;
188 | } else {
189 | shouldUpdate.current = true;
190 | }
191 | };
192 |
193 | useEffect(() => {
194 | if (!enableOnTouchDevice && isTouchDevice()) return;
195 |
196 | resumeAppFrame();
197 | let eventTarget = window;
198 | if (scrollContainerRef && scrollContainerRef.current) {
199 | eventTarget = scrollContainerRef.current;
200 | }
201 |
202 | eventTarget.addEventListener("scroll", handleScroll, { passive: true });
203 | window.addEventListener("visibilitychange", onVisibilityChange);
204 | window.addEventListener("resize", updateValuesDebounced);
205 |
206 | updateValues();
207 |
208 | return () => {
209 | stopAppFrame();
210 |
211 | eventTarget.removeEventListener("scroll", handleScroll);
212 | window.removeEventListener("visibilitychange", onVisibilityChange);
213 | window.removeEventListener("resize", updateValuesDebounced);
214 | };
215 | }, []);
216 |
217 | return (
218 | <>
219 |
233 |
245 | {children}
246 |
247 |
248 | >
249 | );
250 | });
251 |
--------------------------------------------------------------------------------
/frontend/src/sections/CopyInfo/images/logo.svg:
--------------------------------------------------------------------------------
1 | logo
--------------------------------------------------------------------------------
/react-just-parallax/src/components/MouseParallax/MouseParallax.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import sync, { cancelSync, FrameData, Process } from "framesync";
3 | import debounce from "lodash.debounce";
4 | import Prefix from "prefix";
5 |
6 | import { useWindowSize } from "../../hooks/useWindowSize";
7 | import { lerp } from "../../utils/lerp";
8 | import { MouseMove } from "../../utils/MouseMove";
9 | import { Event as DispatchEvent } from "../../utils/EventDispatcher";
10 | import { isTouchDevice } from "../../utils/isTouchDevice";
11 |
12 | export interface MouseParallaxProps {
13 | strength?: number;
14 | children?: React.ReactNode;
15 | parallaxContainerRef?: React.MutableRefObject | null;
16 | scrollContainerRef?: React.MutableRefObject | null; //Should be passed if parallaxed element is situated in other scrollable HTML element
17 | shouldResetPosition?: boolean;
18 | enableOnTouchDevice?: boolean;
19 | lerpEase?: number;
20 | isAbsolutelyPositioned?: boolean;
21 | zIndex?: number | null;
22 | shouldPause?: boolean;
23 | }
24 |
25 | const DEFAULT_FPS = 60;
26 | const DT_FPS = 1000 / DEFAULT_FPS;
27 |
28 | interface Rect {
29 | x: number;
30 | y: number;
31 | width: number;
32 | height: number;
33 | }
34 |
35 | const defaultRect: Rect = {
36 | height: 1,
37 | width: 1,
38 | x: 1,
39 | y: 1,
40 | };
41 |
42 | export const MouseParallax = (props: MouseParallaxProps) => {
43 | const {
44 | children,
45 | strength = 0.14,
46 | parallaxContainerRef = null,
47 | scrollContainerRef = null,
48 | shouldResetPosition = false,
49 | enableOnTouchDevice = false,
50 | isAbsolutelyPositioned = false,
51 | lerpEase = 0.06,
52 | zIndex = null,
53 | shouldPause = true,
54 | } = props;
55 | const { windowSizeRef } = useWindowSize();
56 | const parallaxSpanRef = useRef(null);
57 | const parentSpanRef = useRef(null);
58 | const currentX = useRef(0);
59 | const currentY = useRef(0);
60 | const targetX = useRef(0);
61 | const targetY = useRef(0);
62 | const syncRenderRef = useRef(null);
63 | const syncUpdateRef = useRef(null);
64 | const shouldUpdate = useRef(true);
65 | const parallaxContainerRefRect = useRef(defaultRect);
66 | const mouseMove = useRef(new MouseMove());
67 | const observer = useRef(null);
68 | const transformPrefix = useRef(Prefix("transform"));
69 |
70 | const resumeAppFrame = () => {
71 | if (!shouldUpdate.current) return;
72 | if (!parallaxSpanRef.current) return;
73 | parallaxSpanRef.current.style.willChange = "transform";
74 |
75 | syncRenderRef.current = sync.render(syncOnRender, true);
76 | syncUpdateRef.current = sync.update(syncOnUpdate, true);
77 | };
78 |
79 | const stopAppFrame = () => {
80 | if (!parallaxSpanRef.current) return;
81 | parallaxSpanRef.current.style.willChange = "auto"; //initial
82 |
83 | if (syncRenderRef.current) cancelSync.render(syncRenderRef.current);
84 | if (syncUpdateRef.current) cancelSync.update(syncUpdateRef.current);
85 | };
86 |
87 | const syncOnRender = () => {
88 | if (!parallaxSpanRef.current) return;
89 | let xMultiplier = windowSizeRef.current.windowWidth;
90 | let yMultiplier = windowSizeRef.current.windowHeight;
91 |
92 | if (parallaxContainerRef && parallaxContainerRef.current) {
93 | xMultiplier = parallaxContainerRefRect.current.width;
94 | yMultiplier = parallaxContainerRefRect.current.height;
95 | }
96 |
97 | //Maps movement to exact mouse position
98 | xMultiplier *= 0.5;
99 | yMultiplier *= 0.5;
100 |
101 | //Changes direction and strength
102 | xMultiplier *= strength;
103 | yMultiplier *= strength;
104 |
105 | parallaxSpanRef.current.style[transformPrefix.current] = `translate3d(${
106 | currentX.current * xMultiplier
107 | }px, ${currentY.current * yMultiplier}px, 0px)`;
108 | };
109 | const syncOnUpdate = ({ delta }: FrameData) => {
110 | const diffX = Math.abs(targetX.current - currentX.current);
111 | const diffY = Math.abs(targetY.current - currentY.current);
112 |
113 | //Don't update if difference is too low
114 | if (diffX < 0.001 && diffY < 0.001) return;
115 |
116 | let slowDownFactor = delta / DT_FPS;
117 |
118 | mouseMove.current.update();
119 |
120 | // Rounded slowDown factor to the nearest integer reduces physics lags
121 | const slowDownFactorRounded = Math.round(slowDownFactor);
122 |
123 | if (slowDownFactorRounded >= 1) {
124 | slowDownFactor = slowDownFactorRounded;
125 | }
126 |
127 | const newX = lerp(
128 | currentX.current,
129 | targetX.current,
130 | lerpEase * slowDownFactor
131 | );
132 | currentX.current = newX;
133 |
134 | const newY = lerp(
135 | currentY.current,
136 | targetY.current,
137 | lerpEase * slowDownFactor
138 | );
139 | currentY.current = newY;
140 | };
141 |
142 | const onVisibilityChange = () => {
143 | if (document.hidden) {
144 | stopAppFrame();
145 | } else {
146 | resumeAppFrame();
147 | }
148 | };
149 |
150 | const normalizeXY = (x: number, y: number) => {
151 | //might need to substract scrollbar width for perfect alignment (same for yComponent if scrolling horizontally)
152 | let xComponent = windowSizeRef.current.windowWidth;
153 | let yComponent = windowSizeRef.current.windowHeight;
154 | let relativeX = x;
155 | let relativeY = y;
156 |
157 | if (parallaxContainerRef && parallaxContainerRef.current) {
158 | xComponent = parallaxContainerRefRect.current.width;
159 | yComponent = parallaxContainerRefRect.current.height;
160 | relativeX = x - parallaxContainerRefRect.current.x;
161 | relativeY = y - parallaxContainerRefRect.current.y;
162 | }
163 |
164 | return {
165 | x: (relativeX / xComponent) * 2 - 1, // -1 to 1, left to right,
166 | y: (relativeY / yComponent) * 2 - 1, // -1 to 1, from top to bottom
167 | };
168 | };
169 |
170 | const onMouseMove = (e: DispatchEvent) => {
171 | const mouseX = (e.target as MouseMove).mouse.x;
172 | const mouseY = (e.target as MouseMove).mouse.y;
173 |
174 | const { x, y } = normalizeXY(mouseX, mouseY);
175 |
176 | targetX.current = x;
177 | targetY.current = y;
178 | };
179 |
180 | const handleMouseLeave = () => {
181 | if (shouldResetPosition) {
182 | targetX.current = 0;
183 | targetY.current = 0;
184 | }
185 | };
186 |
187 | const onTouchStart = (event: TouchEvent | MouseEvent) => {
188 | const mouseX =
189 | "touches" in event ? event.touches[0].clientX : event.clientX;
190 | const mouseY =
191 | "touches" in event ? event.touches[0].clientY : event.clientY;
192 |
193 | const { x, y } = normalizeXY(mouseX, mouseY);
194 |
195 | if (x <= -1 || x >= 1 || y >= 1 || y <= -1) {
196 | handleMouseLeave();
197 | }
198 | };
199 |
200 | const updateValues = () => {
201 | if (!parallaxContainerRef || !parallaxContainerRef.current) return;
202 | const boundingBox = parallaxContainerRef.current.getBoundingClientRect();
203 | parallaxContainerRefRect.current = {
204 | x: boundingBox.x,
205 | y: boundingBox.y,
206 | width: parallaxContainerRef.current.clientWidth,
207 | height: parallaxContainerRef.current.clientHeight,
208 | };
209 | };
210 |
211 | const updateValuesDebounced = debounce(updateValues, 150);
212 |
213 | const handleIntersection = (entries: IntersectionObserverEntry[]) => {
214 | const isIntersecting = entries[0].isIntersecting;
215 | if (isIntersecting) {
216 | shouldUpdate.current = true;
217 | resumeAppFrame();
218 | mouseMove.current.setShouldUpdate(true);
219 | } else {
220 | //dont check if is intersecting etc.
221 | if (!shouldPause) return;
222 | shouldUpdate.current = false;
223 | stopAppFrame();
224 | mouseMove.current.setShouldUpdate(false);
225 | }
226 | };
227 |
228 | useEffect(() => {
229 | if (!enableOnTouchDevice && isTouchDevice()) return;
230 | mouseMove.current.init(parallaxContainerRef);
231 | resumeAppFrame();
232 |
233 | let eventTarget = window;
234 | let scrollTarget = window;
235 |
236 | if (scrollContainerRef && scrollContainerRef.current) {
237 | scrollTarget = scrollContainerRef.current;
238 | }
239 |
240 | if (parallaxContainerRef && parallaxContainerRef.current) {
241 | updateValues();
242 | eventTarget = parallaxContainerRef.current;
243 | scrollTarget.addEventListener("scroll", updateValuesDebounced, {
244 | passive: true,
245 | });
246 | window.addEventListener("resize", updateValuesDebounced, {
247 | passive: true,
248 | });
249 | }
250 |
251 | mouseMove.current.addEventListener("mousemove", onMouseMove);
252 | window.addEventListener("visibilitychange", onVisibilityChange);
253 | window.addEventListener("touchstart", onTouchStart, { passive: true });
254 | eventTarget.addEventListener("mouseout", handleMouseLeave);
255 |
256 | observer.current = new IntersectionObserver(handleIntersection, {
257 | threshold: 0.5,
258 | });
259 | if (parentSpanRef.current) {
260 | observer.current.observe(parentSpanRef.current);
261 | }
262 |
263 | return () => {
264 | stopAppFrame();
265 |
266 | if (parallaxContainerRef && parallaxContainerRef.current) {
267 | scrollTarget.removeEventListener("scroll", updateValuesDebounced);
268 | window.removeEventListener("resize", updateValuesDebounced);
269 | }
270 |
271 | mouseMove.current.removeEventListener("mousemove", onMouseMove);
272 | window.removeEventListener("visibilitychange", onVisibilityChange);
273 | window.removeEventListener("touchstart", onTouchStart);
274 | eventTarget.removeEventListener("mouseout", handleMouseLeave);
275 |
276 | if (parentSpanRef.current && observer.current) {
277 | observer.current.unobserve(parentSpanRef.current);
278 | }
279 | mouseMove.current.destroy();
280 | };
281 | }, []);
282 |
283 | return (
284 | <>
285 |
299 |
311 | {children}
312 |
313 |
314 | >
315 | );
316 | };
317 |
--------------------------------------------------------------------------------