├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
└── workflows
│ └── deploy-github-pages.yaml
├── .gitignore
├── .npmignore
├── .prettierrc
├── .storybook
├── main.ts
└── preview.ts
├── LICENSE
├── README.md
├── demo
└── demo.gif
├── package-lock.json
├── package.json
├── src
├── drifting.component.tsx
├── drifting.hook.ts
├── drifting.interface.ts
├── drifting.story.css
├── drifting.story.tsx
└── index.tsx
├── tsconfig.build.json
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
4 | "plugins": ["@typescript-eslint/eslint-plugin"],
5 | "rules": {
6 | "quotes": ["error", "single"],
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-github-pages.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - 'master'
5 |
6 | permissions:
7 | contents: read
8 | pages: write
9 | id-token: write
10 |
11 | jobs:
12 | deploy:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - id: build-publish
16 | uses: bitovi/github-actions-storybook-to-github-pages@v1.0.2
17 | with:
18 | path: storybook-static
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .vscode
4 | build
5 | storybook-static
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | demo
3 | storybook-static
4 | build/drifting.story.js
5 | build/tsconfig.build.tsbuildinfo
6 | .eslintrc
7 | .storybook
8 | .github
9 | .eslintignore
10 | .editorconfig
11 | .gitignore
12 | .prettierrc
13 | tsconfig.build.json
14 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "semi": true,
4 | "tabWidth": 2,
5 | "singleQuote": true,
6 | "trailingComma": "all",
7 | "printWidth": 120
8 | }
9 |
--------------------------------------------------------------------------------
/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import type { StorybookConfig } from '@storybook/react-vite';
2 |
3 | const config: StorybookConfig = {
4 | stories: ['../src/**/*.story.@(ts|tsx)'],
5 | addons: [
6 | '@storybook/addon-links',
7 | '@storybook/addon-essentials',
8 | '@storybook/addon-onboarding',
9 | '@storybook/addon-interactions',
10 | ],
11 | framework: {
12 | name: '@storybook/react-vite',
13 | options: {},
14 | },
15 | docs: {
16 | autodocs: 'tag',
17 | },
18 | };
19 |
20 | export default config;
21 |
--------------------------------------------------------------------------------
/.storybook/preview.ts:
--------------------------------------------------------------------------------
1 | import type { Preview } from '@storybook/react';
2 |
3 | const preview: Preview = {
4 | parameters: {
5 | actions: { argTypesRegex: '^on[A-Z].*' },
6 | controls: {
7 | matchers: {
8 | color: /(background|color)$/i,
9 | date: /Date$/i,
10 | },
11 | },
12 | },
13 | };
14 |
15 | export default preview;
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Evgeny Zaytsev
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/react-drifting-component)
2 |
3 | # react-drifting-component
4 |
5 | The drifting component which allows you to drift on the screen :) The component uses mousemove event for desktops and deviceorientation for mobile devices.
6 |
7 | ## Installation
8 |
9 | ```
10 | $ npm install react-drifting-component
11 | ```
12 |
13 | ## Demo
14 |
15 | [Try it out](https://z4o4z.github.io/react-drifting-component/)
16 |
17 | 
18 |
19 | ## Basic Usage
20 |
21 | ```js
22 | import { Drifting } from 'react-drifting-component';
23 |
24 | // Inside of a component's render() method:
25 | render() {
26 | return (
27 |
28 |
29 | {({ ref }) => }
30 |
31 |
32 |
33 | {
34 | ({ pos, onRef }) =>
35 |
36 | }
37 |
38 |
39 | );
40 | }
41 | ```
42 |
43 | ## Examples
44 |
45 | Please clone the repo and run `npm run storybook` or `yarn storybook` to show examples of usages.
46 |
47 | ## Usage (API)
48 |
49 | The `Drifting` component has a few properties, as described below.
50 |
51 | > NOTE: this component uses rAF(requestAnimationFrame) if you need to support old browsers ensure that you are using polyfill for rAF!
52 |
53 | | Property | Type | Defaut | Description |
54 | | --------------------- | ---------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
55 | | `reverse` | `boolean` | `false` | Reverse drifting |
56 | | `children` | `function` | `({ pos: { x, y } }, onRef: () => ref)` | A function that gets an object with `pos` and `onRef` keys as an argument. Pos contains `x: number` and `y: number`, this numbers should pass to your component style to allow drift. `onRef` it is function which should be passed to `ref` of your component |
57 | | `maxMouseRange` | `number` | `null` | Max mouse drift range. |
58 | | `maxOrientationRange` | `number` | `maxMouseRange` | Max orientation drift range. devices. |
59 |
60 | ## Contributing
61 |
62 | I welcome contributions! Please open an issue if you have any feature ideas
63 | or find any bugs. I also accept pull requests with open arms. I will
64 | go over the issues when I have time. :)
65 |
--------------------------------------------------------------------------------
/demo/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/z4o4z/react-drifting-component/829646a812d94e4d0bf55c6542ae868436328a0d/demo/demo.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-drifting-component",
3 | "version": "3.0.2",
4 | "description": "Drifting react component",
5 | "main": "build/index.js",
6 | "scripts": {
7 | "clear": "rm ./build",
8 | "lint": "eslint -c ./.eslintrc .",
9 | "format": "eslint --format",
10 | "build": "tsc --build tsconfig.build.json",
11 | "storybook": "storybook dev",
12 | "build-storybook": "storybook build"
13 | },
14 | "keywords": [
15 | "react",
16 | "reactjs",
17 | "javascript",
18 | "react-component",
19 | "drifting",
20 | "drifting-component",
21 | "mousemoove",
22 | "deviceorientation"
23 | ],
24 | "author": "Evgeny Zaytsev (http://github.com/z4o4z)",
25 | "license": "MIT",
26 | "repository": {
27 | "type": "git",
28 | "url": "git@github.com:z4o4z/react-drifting-component.git"
29 | },
30 | "devDependencies": {
31 | "@storybook/addon-essentials": "^7.6.17",
32 | "@storybook/addon-interactions": "^7.6.17",
33 | "@storybook/addon-links": "^7.6.17",
34 | "@storybook/addon-onboarding": "^1.0.11",
35 | "@storybook/blocks": "^7.6.17",
36 | "@storybook/react": "^7.6.17",
37 | "@storybook/react-vite": "^7.6.17",
38 | "@storybook/test": "^7.6.17",
39 | "@types/react": "^18.2.57",
40 | "@types/react-dom": "^18.2.19",
41 | "@typescript-eslint/eslint-plugin": "^7.0.2",
42 | "@typescript-eslint/parser": "^7.0.2",
43 | "eslint": "^8.56.0",
44 | "eslint-config-prettier": "^9.1.0",
45 | "eslint-plugin-prettier": "^5.1.3",
46 | "eslint-plugin-storybook": "^0.8.0",
47 | "prettier": "^3.2.5",
48 | "react": "^18.2.0",
49 | "react-dom": "^18.2.0",
50 | "storybook": "^7.6.17",
51 | "typescript": "^5.3.3"
52 | },
53 | "peerDependencies": {
54 | "react": "^16.0.0 | ^17.0.0 | ^18.0.0",
55 | "react-dom": "^16.0.0 | ^17.0.0 | ^18.0.0"
56 | },
57 | "eslintConfig": {
58 | "extends": [
59 | "plugin:storybook/recommended"
60 | ]
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/drifting.component.tsx:
--------------------------------------------------------------------------------
1 | import { useDrifting } from './drifting.hook';
2 | import { DriftingProps } from './drifting.interface';
3 |
4 | export const Drifting = ({ reverse, children, maxMouseRange, maxOrientationRange }: DriftingProps) => {
5 | const ref = useDrifting({
6 | reverse,
7 | maxMouseRange,
8 | maxOrientationRange,
9 | });
10 |
11 | return children({ ref });
12 | };
13 |
--------------------------------------------------------------------------------
/src/drifting.hook.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 | import { DriftingOptions } from './drifting.interface';
3 |
4 | export const useDrifting = ({ reverse, maxMouseRange, maxOrientationRange = maxMouseRange }: DriftingOptions) => {
5 | const ref = useRef(null);
6 |
7 | useEffect(() => {
8 | const getDegreeSin = (degree: number) => Math.sin((Math.PI * degree) / 180);
9 |
10 | let frame: number | null;
11 |
12 | const onDeviceOrientation = ({ beta, gamma }: DeviceOrientationEvent) => {
13 | if (beta === null || gamma === null) return;
14 |
15 | frame = requestAnimationFrame(() => {
16 | if (!ref.current) return;
17 |
18 | const maxRange = maxOrientationRange * (reverse ? -1 : 1);
19 |
20 | if (window.screen.orientation.type.includes('landscape')) {
21 | ref.current.style.transform = `translateX(${getDegreeSin(beta) * maxRange}px) translateY(${getDegreeSin(gamma * 2) * maxRange}px)`;
22 | } else {
23 | ref.current.style.transform = `translateX(${getDegreeSin(gamma * 2) * maxRange}px) translateY(${getDegreeSin(beta) * maxRange}px)`;
24 | }
25 | });
26 | };
27 |
28 | const onMouseMove = ({ clientX, clientY }: MouseEvent) => {
29 | frame = requestAnimationFrame(() => {
30 | if (!ref.current) return;
31 |
32 | const maxRange = maxMouseRange * (reverse ? -1 : 1);
33 |
34 | const factorX = maxRange / (window.innerWidth / 2);
35 | const factorY = maxRange / (window.innerHeight / 2);
36 |
37 | const { top, left, width, height } = ref.current.getBoundingClientRect();
38 |
39 | ref.current.style.transform = `translateX(${factorX * (clientX - left - width / 2)}px) translateY(${factorY * (clientY - top - height / 2)}px)`;
40 | });
41 | };
42 |
43 | window.addEventListener('mousemove', onMouseMove);
44 | window.addEventListener('deviceorientation', onDeviceOrientation);
45 |
46 | return () => {
47 | if (frame) {
48 | cancelAnimationFrame(frame);
49 | }
50 |
51 | window.removeEventListener('mousemove', onMouseMove);
52 | window.removeEventListener('deviceorientation', onDeviceOrientation);
53 | };
54 | }, [reverse, maxMouseRange, maxOrientationRange]);
55 |
56 | return ref;
57 | };
58 |
--------------------------------------------------------------------------------
/src/drifting.interface.ts:
--------------------------------------------------------------------------------
1 | export interface DriftingOptions {
2 | reverse?: boolean;
3 | maxMouseRange: number;
4 | maxOrientationRange?: number;
5 | }
6 |
7 | export interface DriftingRenderedProps {
8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
9 | ref: React.RefObject;
10 | }
11 |
12 | export interface DriftingProps extends DriftingOptions {
13 | children: (props: DriftingRenderedProps) => React.ReactNode;
14 | }
15 |
--------------------------------------------------------------------------------
/src/drifting.story.css:
--------------------------------------------------------------------------------
1 | .wrapper,
2 | .background,
3 | .text-wrapper {
4 | position: absolute;
5 | top: 0;
6 | left: 0;
7 | right: 0;
8 | bottom: 0;
9 | }
10 |
11 | .background {
12 | top: -40px;
13 | left: -40px;
14 | right: -40px;
15 | bottom: -40px;
16 | background-size: cover;
17 | background-image: url(https://verdict4u.files.wordpress.com/2016/09/google-now-wallpaper-1.png);
18 | background-position: center;
19 | }
20 |
21 | .title {
22 | margin: 0;
23 | padding: 0;
24 | position: absolute;
25 | top: 50%;
26 | left: 50%;
27 | color: #fff;
28 | text-shadow: 1px 1px 1px rgb(51, 51, 51);
29 | font-family: Helvetica, Arial, sans-serif;
30 | transform: translate(-50%, -50%);
31 | }
32 |
33 | .emoji-1 {
34 | margin: 0;
35 | padding: 0;
36 | position: absolute;
37 | top: 10%;
38 | left: 10%;
39 | color: #fff;
40 | font-size: 90px;
41 | text-shadow: 1px 1px 1px rgb(51, 51, 51);
42 | font-family: Helvetica, Arial, sans-serif;
43 | }
44 |
45 | .emoji-2 {
46 | margin: 0;
47 | padding: 0;
48 | position: absolute;
49 | top: 70%;
50 | left: 70%;
51 | color: #fff;
52 | font-size: 120px;
53 | text-shadow: 1px 1px 1px rgb(51, 51, 51);
54 | font-family: Helvetica, Arial, sans-serif;
55 | transform: scaleX(-1);
56 | }
57 |
58 | .emoji-3 {
59 | margin: 0;
60 | padding: 0;
61 | position: absolute;
62 | top: 50%;
63 | left: 0%;
64 | color: #fff;
65 | font-size: 120px;
66 | text-shadow: 1px 1px 1px rgb(51, 51, 51);
67 | font-family: Helvetica, Arial, sans-serif;
68 | transform: scaleX(-1);
69 | }
70 |
71 | .emoji-4 {
72 | margin: 0;
73 | padding: 0;
74 | position: absolute;
75 | top: 0%;
76 | left: 90%;
77 | color: #fff;
78 | font-size: 100px;
79 | text-shadow: 1px 1px 1px rgb(51, 51, 51);
80 | font-family: Helvetica, Arial, sans-serif;
81 | }
82 |
--------------------------------------------------------------------------------
/src/drifting.story.tsx:
--------------------------------------------------------------------------------
1 | import type { StoryObj } from '@storybook/react';
2 | import { DriftingRenderedProps } from './drifting.interface';
3 | import React, { PropsWithChildren, forwardRef } from 'react';
4 | import { Drifting } from './drifting.component';
5 |
6 | import './drifting.story.css';
7 |
8 | const Background = forwardRef((_, ref) => {
9 | return ;
10 | });
11 |
12 | const Text = forwardRef(
13 | ({ text, className }, ref) => {
14 | return (
15 |
16 |
{text}
17 |
18 | );
19 | },
20 | );
21 |
22 | const meta = {
23 | title: 'Drifting',
24 | component: ({ children }: PropsWithChildren) => {children}
,
25 | };
26 |
27 | export default meta;
28 |
29 | type Story = StoryObj;
30 |
31 | export const Simple: Story = {
32 | args: {
33 | children: {props => },
34 | },
35 | };
36 |
37 | export const WithText: Story = {
38 | args: {
39 | children: (
40 | <>
41 | {props => }
42 |
43 |
44 | {props => }
45 |
46 | >
47 | ),
48 | },
49 | };
50 |
51 | export const AllInOne: Story = {
52 | args: {
53 | children: (
54 | <>
55 |
56 | {props => }
57 |
58 |
59 |
60 | {props => }
61 |
62 |
63 |
64 | {props => }
65 |
66 |
67 |
68 | {props => }
69 |
70 |
71 |
72 | {props => }
73 |
74 |
75 |
76 | {props => }
77 |
78 | >
79 | ),
80 | },
81 | };
82 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | export { Drifting } from './drifting.component';
2 | export { useDrifting } from './drifting.hook';
3 | export type { DriftingRenderedProps, DriftingOptions, DriftingProps } from './drifting.interface';
4 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "esnext",
5 | "outDir": "build"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "target": "ES2020",
5 | "module": "esnext",
6 | "jsx": "react",
7 | "noEmit": false,
8 | "composite": false,
9 | "declaration": false,
10 | "declarationMap": false,
11 | "esModuleInterop": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "inlineSources": false,
14 | "isolatedModules": true,
15 | "moduleResolution": "node",
16 | "noImplicitAny": true,
17 | "preserveWatchOutput": true,
18 | "skipLibCheck": true,
19 | "strict": true,
20 | "allowJs": false,
21 | "resolveJsonModule": false,
22 | "incremental": true
23 | },
24 | "exclude": ["node_modules"]
25 | }
26 |
--------------------------------------------------------------------------------