├── .gitignore
├── src
├── vite-env.d.ts
├── react-app-env.d.ts
├── examples
│ ├── without-hook.tsx
│ ├── custom-characters.tsx
│ ├── examples.tsx
│ ├── initial-value.tsx
│ └── loop.tsx
├── dencrypt.test.ts
├── index.ts
├── index.test.ts
└── dencrypt.ts
├── docs
├── dencrypt.gif
└── example1.gif
├── .editorconfig
├── tsconfig.node.json
├── index.html
├── tsconfig.json
├── vite.config.ts
├── rollup.config.js
├── LICENSE
├── package.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/docs/dencrypt.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazko/use-dencrypt-effect/HEAD/docs/dencrypt.gif
--------------------------------------------------------------------------------
/docs/example1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazko/use-dencrypt-effect/HEAD/docs/example1.gif
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | indent_style = space
6 | indent_size = 2
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true,
7 | "resolveJsonModule": true
8 | },
9 | "include": ["vite.config.ts", "*.json"]
10 | }
11 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Examples
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { resolve } from "node:path";
4 | import { defineConfig } from "vite";
5 | import react from "@vitejs/plugin-react";
6 | import dts from "vite-plugin-dts";
7 |
8 | import packageJson from "./package.json";
9 |
10 | export default defineConfig({
11 | plugins: [react(), dts()],
12 |
13 | build: {
14 | lib: {
15 | entry: resolve("src", "index.ts"),
16 | name: packageJson.name,
17 | fileName: `index`,
18 | },
19 | rollupOptions: {
20 | external: [...Object.keys(packageJson.peerDependencies)],
21 | },
22 | },
23 |
24 | test: {
25 | environment: "jsdom",
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/examples/without-hook.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { dencrypt } from "..";
3 |
4 | export const WithoutHook = () => {
5 | const element = React.useRef(null);
6 |
7 | const setValue = dencrypt({
8 | callback: (value) => {
9 | element.current!.textContent = value;
10 | },
11 | });
12 |
13 | return (
14 | <>
15 |
16 | Even though this example is using React, dencrypt(){" "}
17 | function can be used without it.
18 |
19 |
20 | :{" "}
21 | value
22 |
23 | >
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/examples/custom-characters.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDencrypt } from "..";
3 |
4 | const value = "lorem ipsum";
5 |
6 | export const CustomCharacters = () => {
7 | const [result, setResult] = useDencrypt(value, { chars: "\\/" });
8 |
9 | React.useEffect(() => {
10 | let run = true;
11 |
12 | const loop = async () => {
13 | while (run) {
14 | await new Promise((resolve) => setTimeout(resolve, 500));
15 | await setResult(value);
16 | }
17 | };
18 |
19 | loop();
20 |
21 | return () => {
22 | run = false;
23 | };
24 | }, [setResult]);
25 |
26 | return (
27 | {result}
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/src/examples/examples.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 |
4 | import { InitialValue } from "./initial-value";
5 | import { Loop } from "./loop";
6 | import { WithoutHook } from "./without-hook";
7 | import { CustomCharacters } from "./custom-characters";
8 |
9 | const App = () => (
10 |
11 | Examples
12 | Initial Value
13 |
14 |
15 | Loop Through Values
16 |
17 |
18 | Without Hook
19 |
20 |
21 | Custom Characters
22 |
23 |
24 | );
25 |
26 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
27 |
28 |
29 |
30 | );
31 |
--------------------------------------------------------------------------------
/src/examples/initial-value.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDencrypt } from "..";
3 |
4 | type LinkProps = {
5 | children: string;
6 | };
7 |
8 | const Link = React.memo(({ children }: LinkProps) => {
9 | const [value, setValue] = useDencrypt(children);
10 |
11 | return (
12 | setValue(children)}>
13 | {value}
14 |
15 | );
16 | });
17 |
18 | export const InitialValue = () => {
19 | return (
20 |
21 | -
22 | Home
23 |
24 | -
25 | About
26 |
27 | -
28 | Blog
29 |
30 | -
31 | Projects
32 |
33 | -
34 | Contact
35 |
36 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from 'rollup-plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from 'rollup-plugin-node-resolve'
5 | import url from 'rollup-plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.tsx',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url({ exclude: ['**/*.svg'] }),
28 | resolve(),
29 | typescript({
30 | rollupCommonJSResolveHack: true,
31 | clean: true
32 | }),
33 | commonjs()
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/src/examples/loop.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDencrypt } from "..";
3 |
4 | const values = ["useDencrypt", "Customizable", "React Hook", "Text Effect", ""];
5 |
6 | export const Loop = () => {
7 | const [result, setResult] = useDencrypt();
8 |
9 | React.useEffect(() => {
10 | let i = 0;
11 | let run = true;
12 |
13 | const loop = async () => {
14 | while (run) {
15 | await new Promise((resolve) => setTimeout(resolve, 1000));
16 | await setResult(values[i]);
17 |
18 | i = i === values.length - 1 ? 0 : i + 1;
19 | }
20 | };
21 |
22 | loop();
23 |
24 | return () => {
25 | run = false;
26 | };
27 | }, [setResult]);
28 |
29 | return (
30 |
33 | {result}
34 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Roman Veselý
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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-dencrypt-effect",
3 | "version": "2.0.0",
4 | "type": "module",
5 | "description": "A custom React hook generating crypting text effect.",
6 | "author": "crazko",
7 | "license": "MIT",
8 | "repository": "crazko/use-dencrypt-effect",
9 | "main": "dist/index.umd.cjs",
10 | "module": "dist/index.js",
11 | "types": "dist/index.d.ts",
12 | "files": [
13 | "dist"
14 | ],
15 | "exports": {
16 | ".": {
17 | "import": "./dist/index.js",
18 | "require": "./dist/index.umd.cjs"
19 | }
20 | },
21 | "engines": {
22 | "node": ">=8",
23 | "npm": ">=5"
24 | },
25 | "scripts": {
26 | "test": "vitest",
27 | "build": "tsc && vite build",
28 | "start": "vite",
29 | "prepare": "npm run build"
30 | },
31 | "peerDependencies": {
32 | "react": "^16.8 || >= 17.x"
33 | },
34 | "devDependencies": {
35 | "@testing-library/dom": "^9.0.1",
36 | "@testing-library/react": "^14.0.0",
37 | "@types/react": "^18.0.27",
38 | "@types/react-dom": "^18.0.10",
39 | "@vitejs/plugin-react": "^3.1.0",
40 | "jsdom": "^21.1.0",
41 | "react": "^18.2.0",
42 | "react-dom": "^18.2.0",
43 | "typescript": "^4.9.3",
44 | "vite": "^4.1.0",
45 | "vite-plugin-dts": "^2.1.0",
46 | "vitest": "^0.29.2"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/dencrypt.test.ts:
--------------------------------------------------------------------------------
1 | import { waitFor } from "@testing-library/dom";
2 | import { expect, test } from "vitest";
3 |
4 | import { dencrypt } from "./";
5 |
6 | test("accepts initial value", () => {
7 | const initialValue = "value";
8 | let result = "";
9 |
10 | dencrypt({
11 | initialValue,
12 | callback: (value) => {
13 | result = value;
14 | },
15 | });
16 |
17 | expect(result).toBe(initialValue);
18 | });
19 |
20 | test("changes value with text effect", async () => {
21 | let result = "";
22 |
23 | const setValue = dencrypt({
24 | callback: (value) => {
25 | result = value;
26 | },
27 | chars: ".",
28 | });
29 |
30 | expect(result).toBe("");
31 |
32 | setValue("foo");
33 |
34 | await waitFor(() => expect(result).toBe("."));
35 | await waitFor(() => expect(result).toBe(".."));
36 | await waitFor(() => expect(result).toBe("..."));
37 | await waitFor(() => expect(result).toBe("f.."));
38 | await waitFor(() => expect(result).toBe("fo."));
39 | await waitFor(() => expect(result).toBe("foo"));
40 |
41 | setValue("hi");
42 |
43 | await waitFor(() => expect(result).toBe(".oo"));
44 | await waitFor(() => expect(result).toBe("..o"));
45 | await waitFor(() => expect(result).toBe("..."));
46 | await waitFor(() => expect(result).toBe("h.."));
47 | await waitFor(() => expect(result).toBe("hi."));
48 | await waitFor(() => expect(result).toBe("hi"));
49 | });
50 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | dencrypt,
4 | DencryptInitialOptions,
5 | DencryptDefaultOptions,
6 | } from "./dencrypt";
7 |
8 | type DencryptReturnType = ReturnType;
9 | type UseDencryptReturnType = [string, DencryptReturnType];
10 |
11 | export function useDencrypt(): UseDencryptReturnType;
12 | export function useDencrypt(
13 | initialValue: Required
14 | ): UseDencryptReturnType;
15 | export function useDencrypt(
16 | options: DencryptDefaultOptions
17 | ): UseDencryptReturnType;
18 | export function useDencrypt(
19 | initialValue: Required,
20 | options: DencryptDefaultOptions
21 | ): UseDencryptReturnType;
22 | export function useDencrypt(
23 | v?: string | DencryptDefaultOptions,
24 | o?: DencryptDefaultOptions
25 | ) {
26 | let initialValue = "";
27 | let options: DencryptDefaultOptions = {};
28 |
29 | if (typeof v === "object") {
30 | options = v;
31 | } else if (typeof v === "string") {
32 | initialValue = v;
33 | options = o ? o : {};
34 | }
35 |
36 | const [result, setResult] = React.useState();
37 | const [setValue, setSetValue] = React.useState(() =>
38 | dencrypt({
39 | ...options,
40 | initialValue,
41 | callback: setResult,
42 | })
43 | );
44 |
45 | return [result, setValue];
46 | }
47 |
48 | export { dencrypt };
49 |
--------------------------------------------------------------------------------
/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook, act, waitFor } from "@testing-library/react";
2 | import { expect, test } from "vitest";
3 |
4 | import { useDencrypt } from "./";
5 |
6 | test("accepts initial value", () => {
7 | const initialValue = "value";
8 |
9 | const { result } = renderHook(() => useDencrypt(initialValue));
10 |
11 | expect(result.current[0]).toBe(initialValue);
12 | });
13 |
14 | test("changes value with text effect", async () => {
15 | const { result } = renderHook(() =>
16 | useDencrypt({
17 | chars: ".",
18 | })
19 | );
20 |
21 | expect(result.current[0]).toBe(undefined);
22 |
23 | act(() => {
24 | result.current[1]("foo");
25 | });
26 |
27 | await waitFor(() => expect(result.current[0]).toBe(""));
28 | await waitFor(() => expect(result.current[0]).toBe("."));
29 | await waitFor(() => expect(result.current[0]).toBe(".."));
30 | await waitFor(() => expect(result.current[0]).toBe("..."));
31 | await waitFor(() => expect(result.current[0]).toBe("f.."));
32 | await waitFor(() => expect(result.current[0]).toBe("fo."));
33 | await waitFor(() => expect(result.current[0]).toBe("foo"));
34 |
35 | act(() => {
36 | result.current[1]("hi");
37 | });
38 |
39 | await waitFor(() => expect(result.current[0]).toBe(".oo"));
40 | await waitFor(() => expect(result.current[0]).toBe("..o"));
41 | await waitFor(() => expect(result.current[0]).toBe("..."));
42 | await waitFor(() => expect(result.current[0]).toBe("h.."));
43 | await waitFor(() => expect(result.current[0]).toBe("hi."));
44 | await waitFor(() => expect(result.current[0]).toBe("hi"));
45 | });
46 |
--------------------------------------------------------------------------------
/src/dencrypt.ts:
--------------------------------------------------------------------------------
1 | export type DencryptInitialOptions = {
2 | initialValue?: string;
3 | callback: (value: string) => void;
4 | };
5 | export type DencryptDefaultOptions = Partial;
6 |
7 | const defaultOptions = {
8 | chars: "-./^*!}<~$012345abcdef",
9 | interval: 50,
10 | };
11 |
12 | const getRandomChar = (chars: string) =>
13 | chars[Math.floor(Math.random() * chars.length)];
14 |
15 | const getChar = (
16 | i: number,
17 | j: number,
18 | maxLength: number,
19 | oldValue: string,
20 | newValue: string,
21 | chars: string
22 | ) => {
23 | if (j > i) {
24 | return oldValue[j];
25 | }
26 |
27 | if (i >= maxLength && j < i - maxLength) {
28 | return newValue[j];
29 | }
30 |
31 | return getRandomChar(chars);
32 | };
33 |
34 | export const dencrypt = (
35 | options: DencryptInitialOptions & DencryptDefaultOptions
36 | ) => {
37 | const { chars, interval, callback, initialValue } = {
38 | ...defaultOptions,
39 | ...options,
40 | };
41 |
42 | let lastValue: string;
43 | let isCrypting: ReturnType;
44 |
45 | if (initialValue) {
46 | lastValue = initialValue;
47 | callback(lastValue);
48 | }
49 |
50 | function* calculateValues(nextValue: string, prevValue = "") {
51 | const nextLength = nextValue.length;
52 | const prevLength = prevValue.length;
53 | const maxLength = Math.max(nextLength, prevLength);
54 | const iterations = 2 * maxLength;
55 |
56 | let i = 0;
57 |
58 | yield prevValue;
59 |
60 | while (i < iterations) {
61 | yield [...new Array(maxLength)]
62 | .map((_, j) => getChar(i, j, maxLength, prevValue, nextValue, chars))
63 | .join("");
64 |
65 | i++;
66 | }
67 |
68 | yield nextValue;
69 | }
70 |
71 | const setValue = (value: string) => {
72 | clearInterval(isCrypting);
73 | const values = calculateValues(value, lastValue);
74 |
75 | return new Promise((resolve) => {
76 | isCrypting = setInterval(() => {
77 | var next = values.next();
78 |
79 | if (next.done) {
80 | clearInterval(isCrypting);
81 | resolve(lastValue);
82 | } else {
83 | lastValue = next.value;
84 | callback(lastValue);
85 | }
86 | }, interval);
87 | });
88 | };
89 |
90 | return setValue;
91 | };
92 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
4 |
5 |
6 |
7 | # use-dencrypt-effect
8 |
9 | [](https://www.npmjs.com/package/use-dencrypt-effect)
10 |
11 |
12 | A custom [React hook](https://reactjs.org/docs/hooks-intro.html) generating crypting text effect.
13 |
14 | **Live demo**: https://codesandbox.io/s/use-dencrypt-effect-7td0f.
15 |
16 | ## Install
17 |
18 | ```bash
19 | npm install --save use-dencrypt-effect
20 | ```
21 |
22 | ## Usage
23 |
24 | ```tsx
25 | import * as React from "react";
26 |
27 | import { useDencrypt } from "use-dencrypt-effect";
28 |
29 | const Example = () => {
30 | const [value, setValue] = useDencrypt("initialValue");
31 |
32 | return setValue("newValue")}>{value}
;
33 | };
34 | ```
35 |
36 | ## API
37 |
38 | ### useDencrypt(initialValue?, options?)
39 |
40 | Returns a tuple `[value, setValue]` consisting of an actual value and a method to set a new value. Just like `useState()` hook.
41 |
42 | #### value
43 |
44 | Type: `string`
45 |
46 | Result of the animation.
47 |
48 | #### setValue(newValue)
49 |
50 | Sets a value and starts new animation.
51 |
52 | Returns a promise which is resolved when animation for `newValue` ends.
53 |
54 | ##### newValue
55 |
56 | Type: `string`
57 |
58 | A value used for next animation.
59 |
60 | #### initialValue
61 |
62 | Type: `string`
63 |
64 | Optional value that is returned immediately.
65 |
66 | #### options
67 |
68 | Type: `Object`
69 |
70 | All parameters are optional.
71 |
72 | ##### chars
73 |
74 | Type: `string`\
75 | Default: `-./^*!}<~$012345abcdef`
76 |
77 | Characters used for the effect. Picked by random.
78 |
79 | ##### interval
80 |
81 | Type: `number`\
82 | Default: `50`
83 |
84 | Number of miliseconds it takes for every animation step (one character).
85 |
86 | ## Examples
87 |
88 | See [`./src/examples`](./src/examples) directory.
89 |
90 | - [Custom Characters](./src/examples/custom-characters.tsx)
91 | - [Initial Value](./src/examples/initial-value.tsx)
92 | - [Loop Through Values](./src/examples/loop.tsx)
93 | - [Use without React hook](./src/examples/without-hook.tsx)
94 |
95 | ### One character
96 |
97 | 
98 |
99 | ```js
100 | const Example = () => {
101 | const [value, setValue] = useDencrypt({ chars: "_" });
102 |
103 | // ...
104 | ```
105 |
106 | ### Run effect on hover
107 |
108 | [Live Example](https://vojdivon.sk/) | [Source Code](https://github.com/ParalelnaPolisKE/vojdivon.sk/blob/54fcbf5c573de485b5d6ed2051d515da7f0bf252/src/index.jsx#L43)
109 |
--------------------------------------------------------------------------------