react-text-swap-animation 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -43
- package/dist/components/TextSwap.js +73 -64
- package/dist/components/constants.js +18 -16
- package/dist/components/index.css +34 -32
- package/dist/components/index.css.map +1 -0
- package/dist/components/index.js +16 -21
- package/dist/components/index.scss +1 -2
- package/dist/components/useFonts.js +29 -0
- package/dist/index.js +2 -3
- package/dist/utils.js +1 -1
- package/package.json +29 -26
- package/.eslintrc.js +0 -74
- package/.github/workflows/greetings.yml +0 -16
- package/.github/workflows/node.js.yml +0 -46
- package/babel.config.json +0 -18
- package/dist/components/Anagram.js +0 -156
- package/public/index.html +0 -19
- package/src/App.css +0 -12
- package/src/index.js +0 -31
- package/src/lib/components/TextSwap.jsx +0 -200
- package/src/lib/components/constants.js +0 -21
- package/src/lib/components/index.css +0 -32
- package/src/lib/components/index.jsx +0 -38
- package/src/lib/components/index.scss +0 -39
- package/src/lib/index.js +0 -3
- package/src/lib/utils.js +0 -17
package/.eslintrc.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
extends: [
|
|
3
|
-
'react-app',
|
|
4
|
-
'react-app/jest',
|
|
5
|
-
],
|
|
6
|
-
rules: {
|
|
7
|
-
indent: ['warn', 4],
|
|
8
|
-
'react/jsx-indent': ['warn', 4],
|
|
9
|
-
'react/jsx-indent-props': ['warn', 4],
|
|
10
|
-
quotes: [0, 'double', { avoidEscape: true }],
|
|
11
|
-
'quote-props': ['error', 'as-needed'],
|
|
12
|
-
'jsx-quotes': [2, 'prefer-double'],
|
|
13
|
-
'no-undef': 2,
|
|
14
|
-
'id-length': 0,
|
|
15
|
-
'max-len': 0,
|
|
16
|
-
'brace-style': [
|
|
17
|
-
1,
|
|
18
|
-
'stroustrup',
|
|
19
|
-
{ allowSingleLine: true },
|
|
20
|
-
],
|
|
21
|
-
curly: 2,
|
|
22
|
-
'no-use-before-define': [
|
|
23
|
-
1,
|
|
24
|
-
'nofunc',
|
|
25
|
-
],
|
|
26
|
-
'no-unused-vars': [
|
|
27
|
-
1,
|
|
28
|
-
{
|
|
29
|
-
args: 'none',
|
|
30
|
-
ignoreRestSiblings: true,
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
'arrow-body-style': 0,
|
|
34
|
-
'no-unused-expressions': [
|
|
35
|
-
2,
|
|
36
|
-
{ allowShortCircuit: true },
|
|
37
|
-
],
|
|
38
|
-
'object-curly-spacing': ['warn', 'always', { objectsInObjects: true }],
|
|
39
|
-
'object-curly-newline': ['warn', { multiline: true }],
|
|
40
|
-
'prefer-const': 'warn',
|
|
41
|
-
'no-restricted-syntax': [
|
|
42
|
-
1,
|
|
43
|
-
'WithStatement',
|
|
44
|
-
'DebuggerStatement',
|
|
45
|
-
],
|
|
46
|
-
'no-underscore-dangle': 0,
|
|
47
|
-
'react/jsx-boolean-value': 0,
|
|
48
|
-
'react/jsx-first-prop-new-line': 0,
|
|
49
|
-
'react/jsx-no-bind': 0,
|
|
50
|
-
'react/no-did-mount-set-state': 0,
|
|
51
|
-
'react/prefer-stateless-function': 0,
|
|
52
|
-
'react/jsx-one-expression-per-line': [0],
|
|
53
|
-
// Allow jsx tags inside .js files.
|
|
54
|
-
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],
|
|
55
|
-
// Disable props spreading (<App {...props} />) warning.
|
|
56
|
-
'react/jsx-props-no-spreading': 0,
|
|
57
|
-
// Throw warning instead of error when using array index as a key.
|
|
58
|
-
'react/no-array-index-key': 1,
|
|
59
|
-
// Allow using (props) => <Component /> and ({propName}) => <Component /> syntax.
|
|
60
|
-
'react/destructuring-assignment': 'off',
|
|
61
|
-
// Disable <Fragment> => <> replacement. Feel free to change
|
|
62
|
-
'react/jsx-fragments': 'off',
|
|
63
|
-
// Allow modules with named exports only.
|
|
64
|
-
'import/prefer-default-export': 0,
|
|
65
|
-
// Throw warning when <a href="#"> or <a href="javascript:void(0)"> are used. Use <button> instead.
|
|
66
|
-
'jsx-a11y/anchor-is-valid': ['off', { aspects: ['invalidHref'] }],
|
|
67
|
-
},
|
|
68
|
-
// DeprecationWarning: The 'ecmaFeatures' config file property is deprecated and has no effect. (found in ".eslintrc.js")
|
|
69
|
-
// ecmaFeatures: {
|
|
70
|
-
// jsx: true,
|
|
71
|
-
// modules: true,
|
|
72
|
-
// },
|
|
73
|
-
parser: 'babel-eslint',
|
|
74
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
name: Greetings
|
|
2
|
-
|
|
3
|
-
on: [pull_request, issues]
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
greeting:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
permissions:
|
|
9
|
-
issues: write
|
|
10
|
-
pull-requests: write
|
|
11
|
-
steps:
|
|
12
|
-
- uses: actions/first-interaction@v1
|
|
13
|
-
with:
|
|
14
|
-
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
15
|
-
issue-message: 'Hey, thanks for raising this issue!'
|
|
16
|
-
pr-message: 'You are so AWESOME for chipping in and helping out. Thanks! Keep up the GOOD WORK!'
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# This is a basic workflow to help you get started with Actions
|
|
2
|
-
|
|
3
|
-
name: CI
|
|
4
|
-
|
|
5
|
-
# Controls when the workflow will run
|
|
6
|
-
on:
|
|
7
|
-
# Triggers the workflow on push or pull request events but only for the main branch
|
|
8
|
-
push:
|
|
9
|
-
branches: [ main ]
|
|
10
|
-
pull_request:
|
|
11
|
-
branches: [ main ]
|
|
12
|
-
|
|
13
|
-
# Allows you to run this workflow manually from the Actions tab
|
|
14
|
-
workflow_dispatch:
|
|
15
|
-
|
|
16
|
-
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
|
17
|
-
jobs:
|
|
18
|
-
# This workflow contains a single job called "build"
|
|
19
|
-
build:
|
|
20
|
-
# The type of runner that the job will run on
|
|
21
|
-
runs-on: ubuntu-latest
|
|
22
|
-
|
|
23
|
-
strategy:
|
|
24
|
-
matrix:
|
|
25
|
-
node-version: [ 14.x, 16.x, 18.x ]
|
|
26
|
-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
27
|
-
|
|
28
|
-
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
29
|
-
steps:
|
|
30
|
-
- uses: actions/checkout@v3
|
|
31
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
32
|
-
uses: actions/setup-node@v3
|
|
33
|
-
with:
|
|
34
|
-
node-version: ${{ matrix.node-version }}
|
|
35
|
-
cache: 'npm'
|
|
36
|
-
# Runs a single command using the runners shell
|
|
37
|
-
- name: Run a one-line script
|
|
38
|
-
run: echo Building now...
|
|
39
|
-
|
|
40
|
-
# - name: Install python2
|
|
41
|
-
# run: apt update && DEBIAN_FRONTEND=noninteractive apt install -y python2 make g++
|
|
42
|
-
# - name: Test python2
|
|
43
|
-
# run: python2 --version
|
|
44
|
-
# - run: npm install sass node-sass@4.14.1
|
|
45
|
-
- run: npm i
|
|
46
|
-
- run: npm run build-fresh --if-present
|
package/babel.config.json
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = Anagram;
|
|
7
|
-
|
|
8
|
-
require("core-js/modules/web.dom-collections.iterator.js");
|
|
9
|
-
|
|
10
|
-
var _react = _interopRequireWildcard(require("react"));
|
|
11
|
-
|
|
12
|
-
var _utils = require("../utils");
|
|
13
|
-
|
|
14
|
-
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
15
|
-
|
|
16
|
-
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
17
|
-
|
|
18
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
|
|
19
|
-
|
|
20
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
21
|
-
|
|
22
|
-
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Render and animate from one word to another word and back again.
|
|
26
|
-
*
|
|
27
|
-
* @param {[{string}]} words The 2 words to animate between.
|
|
28
|
-
* @param {AnimationOptions} animationOptions Timing options for when to start, how fast forward/backwards, and when to loop.
|
|
29
|
-
* @returns {JSX.Element}
|
|
30
|
-
*/
|
|
31
|
-
function Anagram(_ref) {
|
|
32
|
-
let {
|
|
33
|
-
words,
|
|
34
|
-
animationOptions
|
|
35
|
-
} = _ref;
|
|
36
|
-
const lettersRefs1 = (0, _react.useRef)([...words[0]].map(() => /*#__PURE__*/(0, _react.createRef)()));
|
|
37
|
-
const lettersRefs2 = (0, _react.useRef)([...words[1]].map(() => /*#__PURE__*/(0, _react.createRef)()));
|
|
38
|
-
const [swapAnimations, setSwapAnimations] = (0, _react.useState)({});
|
|
39
|
-
const playAnimation = (0, _react.useCallback)(function (i) {
|
|
40
|
-
let playing = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
41
|
-
setSwapAnimations(prevState => {
|
|
42
|
-
return _objectSpread(_objectSpread({}, prevState), {}, {
|
|
43
|
-
[i]: _objectSpread(_objectSpread({}, prevState[i]), {}, {
|
|
44
|
-
playing
|
|
45
|
-
})
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
}, [setSwapAnimations]);
|
|
49
|
-
const {
|
|
50
|
-
randomStartMin,
|
|
51
|
-
randomStartMax,
|
|
52
|
-
randomReverseMin,
|
|
53
|
-
randomReverseMax,
|
|
54
|
-
loopAnimation,
|
|
55
|
-
waitToStart
|
|
56
|
-
} = animationOptions;
|
|
57
|
-
(0, _react.useEffect)(() => {
|
|
58
|
-
const swaps = [];
|
|
59
|
-
const destLettersPairedByIndex = [];
|
|
60
|
-
[...words[0]].forEach((letter, i) => {
|
|
61
|
-
// Find a matching dest character to execute the swap with
|
|
62
|
-
const destLetterIndex = [...words[1]].findIndex((destLetter, srcIndex) => {
|
|
63
|
-
return destLetter.toLowerCase() === letter.toLowerCase() && destLettersPairedByIndex[srcIndex] !== true;
|
|
64
|
-
});
|
|
65
|
-
destLettersPairedByIndex[destLetterIndex] = true; // mark this source paired/used
|
|
66
|
-
|
|
67
|
-
if (destLetterIndex === -1) {
|
|
68
|
-
throw new Error("Not sure how to animate since all source letters were paired already, disappear maybe?");
|
|
69
|
-
} // If the text wraps then the offset left isn't correct.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const swap = {
|
|
73
|
-
src: {
|
|
74
|
-
letter,
|
|
75
|
-
element: lettersRefs1.current[i].current,
|
|
76
|
-
offsetLeft: lettersRefs1.current[i].current.offsetLeft,
|
|
77
|
-
offsetTop: lettersRefs1.current[i].current.offsetTop // rect: lettersRefs1.current[i].current.getBoundingClientRect(),
|
|
78
|
-
|
|
79
|
-
},
|
|
80
|
-
dest: {
|
|
81
|
-
letter: words[1][destLetterIndex],
|
|
82
|
-
element: lettersRefs2.current[destLetterIndex].current,
|
|
83
|
-
offsetLeft: lettersRefs2.current[destLetterIndex].current.offsetLeft,
|
|
84
|
-
offsetTop: lettersRefs2.current[destLetterIndex].current.offsetTop // rect: lettersRefs2.current[destLetterIndex].current.getBoundingClientRect(),
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
swaps.push(swap);
|
|
89
|
-
});
|
|
90
|
-
setSwapAnimations(swaps);
|
|
91
|
-
|
|
92
|
-
const animateFunc = () => {
|
|
93
|
-
swaps.forEach((swap, i) => {
|
|
94
|
-
// Animate each character towards the destination
|
|
95
|
-
setTimeout(() => {
|
|
96
|
-
playAnimation(i);
|
|
97
|
-
}, (0, _utils.randomMinMax)(randomStartMin, randomStartMax)); // Animate each character back to their original location
|
|
98
|
-
|
|
99
|
-
setTimeout(() => {
|
|
100
|
-
playAnimation(i, false);
|
|
101
|
-
}, (0, _utils.randomMinMax)(randomReverseMin, randomReverseMax));
|
|
102
|
-
}); // Repeat forever
|
|
103
|
-
|
|
104
|
-
setTimeout(() => {
|
|
105
|
-
animateFunc();
|
|
106
|
-
}, loopAnimation);
|
|
107
|
-
}; // Start the process
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
setTimeout(() => {
|
|
111
|
-
animateFunc();
|
|
112
|
-
}, waitToStart);
|
|
113
|
-
}, [lettersRefs1, lettersRefs2, loopAnimation, playAnimation, randomReverseMax, randomReverseMin, randomStartMax, randomStartMin, waitToStart, words]);
|
|
114
|
-
return /*#__PURE__*/_react.default.createElement("div", {
|
|
115
|
-
className: "anagram-swap"
|
|
116
|
-
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
117
|
-
className: "word word-1 hidden"
|
|
118
|
-
}, [...words[0]].map((letter, i) => {
|
|
119
|
-
return /*#__PURE__*/_react.default.createElement("span", {
|
|
120
|
-
ref: lettersRefs1.current[i],
|
|
121
|
-
className: "letter",
|
|
122
|
-
key: "".concat(i).concat(letter)
|
|
123
|
-
}, letter);
|
|
124
|
-
})), /*#__PURE__*/_react.default.createElement("div", {
|
|
125
|
-
className: "word word-2 hidden"
|
|
126
|
-
}, [...words[1]].map((letter, i) => {
|
|
127
|
-
return /*#__PURE__*/_react.default.createElement("span", {
|
|
128
|
-
ref: lettersRefs2.current[i],
|
|
129
|
-
className: "letter",
|
|
130
|
-
key: "".concat(i).concat(letter)
|
|
131
|
-
}, letter);
|
|
132
|
-
})), /*#__PURE__*/_react.default.createElement("div", {
|
|
133
|
-
className: "word word-animation"
|
|
134
|
-
}, [...words[0]].map((letter, i) => {
|
|
135
|
-
let letterStyles = {};
|
|
136
|
-
const swap = swapAnimations[i];
|
|
137
|
-
|
|
138
|
-
if (swap && swap.playing) {
|
|
139
|
-
const left = "".concat(swap.dest.offsetLeft - swap.src.offsetLeft, "px");
|
|
140
|
-
const top = "".concat(swap.dest.offsetTop - swap.src.offsetTop, "px"); // Trying to fix issue with wrapped text
|
|
141
|
-
// const left = `${swap.dest.rect.x - swap.src.rect.x}px`;
|
|
142
|
-
// const top = `${swap.dest.rect.y - swap.src.rect.y}px`;
|
|
143
|
-
|
|
144
|
-
letterStyles = {
|
|
145
|
-
left,
|
|
146
|
-
top
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return /*#__PURE__*/_react.default.createElement("span", {
|
|
151
|
-
key: "".concat(i).concat(letter),
|
|
152
|
-
className: "letter",
|
|
153
|
-
style: letterStyles
|
|
154
|
-
}, letter);
|
|
155
|
-
})));
|
|
156
|
-
}
|
package/public/index.html
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
-
<meta name="theme-color" content="#000000" />
|
|
8
|
-
<meta name="description"
|
|
9
|
-
content="A React component to use CSS animations to swap letters in 2 words. The text is animated in position after calculating initial and final positions of each letter. Words which are anagrams will animate well, but you can use any words or phrases." />
|
|
10
|
-
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
|
11
|
-
<title>React Text Swap Animation</title>
|
|
12
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
13
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
14
|
-
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
|
15
|
-
</head>
|
|
16
|
-
<body>
|
|
17
|
-
<div id="root"></div>
|
|
18
|
-
</body>
|
|
19
|
-
</html>
|
package/src/App.css
DELETED
package/src/index.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import ReactDOM from 'react-dom';
|
|
3
|
-
import TextSwap from './lib/components';
|
|
4
|
-
import './App.css';
|
|
5
|
-
|
|
6
|
-
ReactDOM.render(
|
|
7
|
-
<React.StrictMode>
|
|
8
|
-
<div>
|
|
9
|
-
<h1>React Text Swap Animation</h1>
|
|
10
|
-
<h2>Demo</h2>
|
|
11
|
-
|
|
12
|
-
<TextSwap fontToObserve={{ family: 'Open Sans' }} />
|
|
13
|
-
|
|
14
|
-
<br />
|
|
15
|
-
<br />
|
|
16
|
-
|
|
17
|
-
<TextSwap fontToObserve={{ family: 'Open Sans' }} words={['a witty saying', 'proves nothing']} />
|
|
18
|
-
|
|
19
|
-
<br />
|
|
20
|
-
<br />
|
|
21
|
-
|
|
22
|
-
<TextSwap fontToObserve={{ family: 'Open Sans' }} words={['don\'t be sad it\'s over', 'be happy that it happened']} />
|
|
23
|
-
|
|
24
|
-
<br />
|
|
25
|
-
<br />
|
|
26
|
-
|
|
27
|
-
<TextSwap fontToObserve={{ family: 'Open Sans' }} words={['debit card', 'bad credit']} />
|
|
28
|
-
</div>
|
|
29
|
-
</React.StrictMode>,
|
|
30
|
-
document.getElementById('root'),
|
|
31
|
-
);
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import React, { createRef, useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { randomMinMax, uuidv4 } from '../utils';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Render and animate from one word to another word and back again.
|
|
6
|
-
*
|
|
7
|
-
* @param {[{string}]} words The 2 words to animate between.
|
|
8
|
-
* @param {AnimationOptions} animationOptions Timing options for when to start, how fast forward/backwards, and when to loop.
|
|
9
|
-
* @returns {JSX.Element}
|
|
10
|
-
*/
|
|
11
|
-
export default function TextSwap({ words, animationOptions }) {
|
|
12
|
-
const [swapAnimations, setAnimations] = useState([]);
|
|
13
|
-
const lettersRefs1 = useRef([...words[0]].map(() => createRef()));
|
|
14
|
-
const lettersRefs2 = useRef([...words[1]].map(() => createRef()));
|
|
15
|
-
const updateAnimation = useCallback((i, update = {}) => {
|
|
16
|
-
setAnimations((prevState) => {
|
|
17
|
-
const newState = [
|
|
18
|
-
...prevState,
|
|
19
|
-
];
|
|
20
|
-
newState[i] = {
|
|
21
|
-
...prevState[i],
|
|
22
|
-
...update,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
return newState;
|
|
26
|
-
});
|
|
27
|
-
}, [setAnimations]);
|
|
28
|
-
|
|
29
|
-
const {
|
|
30
|
-
randomStartMin,
|
|
31
|
-
randomStartMax,
|
|
32
|
-
randomReverseMin,
|
|
33
|
-
randomReverseMax,
|
|
34
|
-
loopAnimation,
|
|
35
|
-
waitToStart,
|
|
36
|
-
} = animationOptions;
|
|
37
|
-
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
const swaps = [];
|
|
40
|
-
const destLettersPairedByIndex = [];
|
|
41
|
-
|
|
42
|
-
[...words[0]].forEach((letter, i) => {
|
|
43
|
-
// Find a matching destination character to execute the swap with
|
|
44
|
-
let destLetterIndex = [...words[1]].findIndex((destLetter, srcIndex) => {
|
|
45
|
-
return destLetter.toLowerCase() === letter.toLowerCase()
|
|
46
|
-
&& destLettersPairedByIndex[srcIndex] !== true;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// If no matching character
|
|
50
|
-
if (destLetterIndex === -1) {
|
|
51
|
-
// Find a space to swap with
|
|
52
|
-
destLetterIndex = [...words[1]].findIndex((destLetter, srcIndex) => {
|
|
53
|
-
return destLetter === ' ' && destLettersPairedByIndex[srcIndex] !== true;
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// If there was no space, find first non-used space
|
|
57
|
-
if (destLetterIndex === -1) {
|
|
58
|
-
destLetterIndex = [...words[1]].findIndex((destLetter, srcIndex) => {
|
|
59
|
-
return destLettersPairedByIndex[srcIndex] !== true;
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (destLetterIndex === -1) {
|
|
65
|
-
throw new Error(`Not sure how to animate since all source letters were paired already, disappear maybe?`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
destLettersPairedByIndex[destLetterIndex] = true; // mark this source paired/used
|
|
69
|
-
|
|
70
|
-
// If the text wraps then the offset left isn't correct.
|
|
71
|
-
const swap = {
|
|
72
|
-
id: uuidv4(), // for a unique key
|
|
73
|
-
letter, // the displayed letter
|
|
74
|
-
playing: false, // if this letter is animating to the destination
|
|
75
|
-
disappear: false, // if this letter should disappear (temporarily)
|
|
76
|
-
// the source location, starting place and letter
|
|
77
|
-
src: {
|
|
78
|
-
letter: words[0][i],
|
|
79
|
-
element: lettersRefs1.current[i].current,
|
|
80
|
-
offsetLeft: lettersRefs1.current[i].current.offsetLeft,
|
|
81
|
-
offsetTop: lettersRefs1.current[i].current.offsetTop,
|
|
82
|
-
rect: lettersRefs1.current[i].current.getBoundingClientRect(),
|
|
83
|
-
},
|
|
84
|
-
// the destination location and letter
|
|
85
|
-
dest: {
|
|
86
|
-
letter: words[1][destLetterIndex],
|
|
87
|
-
element: lettersRefs2.current[destLetterIndex].current,
|
|
88
|
-
offsetLeft: lettersRefs2.current[destLetterIndex].current.offsetLeft,
|
|
89
|
-
offsetTop: lettersRefs2.current[destLetterIndex].current.offsetTop,
|
|
90
|
-
rect: lettersRefs2.current[destLetterIndex].current.getBoundingClientRect(),
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
swaps.push(swap);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
setAnimations(swaps);
|
|
97
|
-
|
|
98
|
-
const animateFunc = () => {
|
|
99
|
-
swaps.forEach((swap, i) => {
|
|
100
|
-
// Animate each character towards the destination
|
|
101
|
-
const forwardStartTime = randomMinMax(randomStartMin, randomStartMax);
|
|
102
|
-
setTimeout(() => {
|
|
103
|
-
updateAnimation(i, { playing: true, disappear: false });
|
|
104
|
-
if (swap.src.letter !== ' ' && swap.dest.letter === ' ') {
|
|
105
|
-
updateAnimation(i, { disappear: true });
|
|
106
|
-
}
|
|
107
|
-
}, forwardStartTime);
|
|
108
|
-
|
|
109
|
-
// Halfway towards the destination, switch to the destination letter
|
|
110
|
-
setTimeout(() => {
|
|
111
|
-
if (swap.src.letter !== ' ' && swap.dest.letter === ' ') {
|
|
112
|
-
// do nothing
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
updateAnimation(i, { letter: swap.dest.letter });
|
|
116
|
-
}
|
|
117
|
-
}, forwardStartTime + 500);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// Animate each character back to their original location
|
|
121
|
-
const reverseStartTime = randomMinMax(randomReverseMin, randomReverseMax);
|
|
122
|
-
setTimeout(() => {
|
|
123
|
-
updateAnimation(i, { playing: false, disappear: false });
|
|
124
|
-
if (swap.dest.letter !== ' ' && swap.src.letter === ' ') {
|
|
125
|
-
updateAnimation(i, { disappear: true });
|
|
126
|
-
}
|
|
127
|
-
}, reverseStartTime);
|
|
128
|
-
|
|
129
|
-
// Half way back to the initial location, switch to the initial letter
|
|
130
|
-
setTimeout(() => {
|
|
131
|
-
if (swap.dest.letter !== ' ' && swap.src.letter === ' ') {
|
|
132
|
-
// do nothing
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
updateAnimation(i, { letter: swap.src.letter });
|
|
136
|
-
}
|
|
137
|
-
}, reverseStartTime + 500);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Repeat forever
|
|
141
|
-
setTimeout(() => {
|
|
142
|
-
animateFunc();
|
|
143
|
-
}, loopAnimation);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
// Start the process
|
|
147
|
-
setTimeout(() => {
|
|
148
|
-
animateFunc();
|
|
149
|
-
}, waitToStart);
|
|
150
|
-
|
|
151
|
-
}, [lettersRefs1, lettersRefs2, loopAnimation, updateAnimation, randomReverseMax, randomReverseMin, randomStartMax, randomStartMin, waitToStart, words]);
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<div className="text-swap">
|
|
155
|
-
<div className="word word-1 hidden">
|
|
156
|
-
{
|
|
157
|
-
[...words[0]].map((letter, i) => {
|
|
158
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
159
|
-
return <span ref={lettersRefs1.current[i]} className="letter" key={`${i}${letter}`}>{letter}</span>;
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
</div>
|
|
163
|
-
<div className="word word-2 hidden">
|
|
164
|
-
{
|
|
165
|
-
[...words[1]].map((letter, i) => {
|
|
166
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
167
|
-
return <span ref={lettersRefs2.current[i]} className="letter" key={`${i}${letter}`}>{letter}</span>;
|
|
168
|
-
})
|
|
169
|
-
}
|
|
170
|
-
</div>
|
|
171
|
-
<div className="word word-animation">
|
|
172
|
-
{
|
|
173
|
-
swapAnimations.map((renderedLetter) => {
|
|
174
|
-
const { id, letter, playing, disappear, src, dest } = renderedLetter;
|
|
175
|
-
|
|
176
|
-
let letterStyles;
|
|
177
|
-
if (playing) {
|
|
178
|
-
const left = `${dest.rect.x}px`;
|
|
179
|
-
letterStyles = { left };
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
const left = `${src.rect.x}px`;
|
|
183
|
-
letterStyles = { left };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (disappear) {
|
|
187
|
-
letterStyles.opacity = 0;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return (
|
|
191
|
-
<span key={id} className="letter" style={letterStyles}>
|
|
192
|
-
{letter}
|
|
193
|
-
</span>
|
|
194
|
-
);
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
</div>
|
|
198
|
-
</div>
|
|
199
|
-
);
|
|
200
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export const DEFAULT_WORDS = ['Text Swap Animation', 'Antitoxin Swamp Tea'];
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @typedef AnimationOptions Timing options for when to start, how fast to animate forwards, backwards, and when to loop.
|
|
5
|
-
* @property {number} randomStartMin The minimum amount of time to randomly wait before starting to animate each letter
|
|
6
|
-
* @property {number} randomStartMax The maximum amount of time to randomly wait before starting to animate each letter
|
|
7
|
-
* @property {number} randomReverseMin The minimum amount of time to randomly wait before starting to animate each letter in reverse
|
|
8
|
-
* @property {number} randomReverseMax The maximum amount of time to randomly wait before starting to animate each letter in reverse
|
|
9
|
-
* @property {number} loopAnimation The amount of time for each full loop of the animation
|
|
10
|
-
* @property {number} waitToStart The amount of time to wait before beginning the animation on start up
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/** @type AnimationOptions */
|
|
14
|
-
export const DEFAULT_ANIMATION_OPTIONS = {
|
|
15
|
-
randomStartMin: 500,
|
|
16
|
-
randomStartMax: 500,
|
|
17
|
-
randomReverseMin: 8000,
|
|
18
|
-
randomReverseMax: 8000,
|
|
19
|
-
loopAnimation: 12000,
|
|
20
|
-
waitToStart: 0,
|
|
21
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
.text-swap {
|
|
2
|
-
color: #fff;
|
|
3
|
-
flex: 0 0 auto;
|
|
4
|
-
align-self: center;
|
|
5
|
-
width: 100%;
|
|
6
|
-
height: 100%;
|
|
7
|
-
margin: 0 auto;
|
|
8
|
-
text-align: left;
|
|
9
|
-
text-transform: uppercase;
|
|
10
|
-
display: flex;
|
|
11
|
-
flex-direction: column;
|
|
12
|
-
padding: 0;
|
|
13
|
-
position: relative; }
|
|
14
|
-
|
|
15
|
-
.text-swap .word {
|
|
16
|
-
position: relative; }
|
|
17
|
-
|
|
18
|
-
.text-swap .word.hidden {
|
|
19
|
-
position: absolute;
|
|
20
|
-
visibility: hidden;
|
|
21
|
-
z-index: -9000; }
|
|
22
|
-
|
|
23
|
-
.text-swap .word .letter {
|
|
24
|
-
white-space: pre;
|
|
25
|
-
z-index: 10;
|
|
26
|
-
display: inline-block; }
|
|
27
|
-
|
|
28
|
-
.text-swap .word-animation .letter {
|
|
29
|
-
z-index: 10;
|
|
30
|
-
position: absolute;
|
|
31
|
-
/*transition: all, 2s, cubic-bezier(0.1, 0.7, 1.0, 0.1), 2s;*/
|
|
32
|
-
transition: all, 2s, ease-in-out, 2s; }
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import useFontFaceObserver from 'use-font-face-observer';
|
|
3
|
-
import TextSwap from './TextSwap';
|
|
4
|
-
import { DEFAULT_ANIMATION_OPTIONS, DEFAULT_WORDS } from './constants';
|
|
5
|
-
|
|
6
|
-
import './index.css';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef FontToObserve A description of an embedded font to observe and wait until loaded.
|
|
10
|
-
* @property {string} [family] The font-family: Roboto, Inter, Open Sans, etc
|
|
11
|
-
* @property {string|number} [weight] The font-weight: normal, bold, 800, etc
|
|
12
|
-
* @property {string} [style] The font-style: normal, italic, oblique
|
|
13
|
-
* @property {string} [stretch] The font stretch: normal, condensed, expanded, etc
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Render and animate from one word to another word and back again.
|
|
18
|
-
* @param {[string]} [words] The 2 words to animate between.
|
|
19
|
-
* @param {AnimationOptions} [animationOptions] Timing options for when to start, how fast to animate forwards, backwards, and when to loop.
|
|
20
|
-
* @param {FontToObserve} [fontToObserve] A description of an embedded font to observe and wait until loaded.
|
|
21
|
-
* @returns {JSX.Element|null}
|
|
22
|
-
*/
|
|
23
|
-
export default function Loader({ words = DEFAULT_WORDS, animationOptions = {}, fontToObserve }) {
|
|
24
|
-
const animOptions = {
|
|
25
|
-
...DEFAULT_ANIMATION_OPTIONS,
|
|
26
|
-
...animationOptions,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const isFontLoaded = useFontFaceObserver(fontToObserve ? [fontToObserve] : []);
|
|
30
|
-
|
|
31
|
-
let word1 = words[0];
|
|
32
|
-
let word2 = words[1];
|
|
33
|
-
const maxLength = Math.max(word1.length, word2.length);
|
|
34
|
-
word1 = word1.padEnd(maxLength, ' ');
|
|
35
|
-
word2 = word2.padEnd(maxLength, ' ');
|
|
36
|
-
|
|
37
|
-
return isFontLoaded ? <TextSwap words={[word1, word2]} animationOptions={animOptions} /> : null;
|
|
38
|
-
}
|