├── src
├── index.ts
└── FadeIn.tsx
├── .npmignore
├── .gitignore
├── .babelrc
├── example
├── example.gif
├── client.js
├── index.html
└── FadeInTest.js
├── tsconfig.json
├── webpack.config.js
├── LICENSE
├── CHANGELOG.md
├── package.json
└── README.md
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './FadeIn';
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | example
2 | webpack.config.js
3 | yarn.lock
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn.lock
3 |
4 | lib
5 | dist
6 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"]
3 | }
4 |
--------------------------------------------------------------------------------
/example/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gkaemmer/react-fade-in/HEAD/example/example.gif
--------------------------------------------------------------------------------
/example/client.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 | import React from 'react';
3 | import FadeInTest from './FadeInTest';
4 |
5 | ReactDOM.render(, document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "declaration": true,
6 | "outDir": "./lib",
7 | "jsx": "react",
8 | "strict": true,
9 | "esModuleInterop": true
10 | },
11 | "include": [
12 | "src/**/*"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Fade-In Example
8 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack");
2 | var HtmlWebpackPlugin = require("html-webpack-plugin");
3 | var path = require("path");
4 | var env = process.env.NODE_ENV;
5 | var isExample = process.env.EXAMPLE;
6 |
7 | var config = {
8 | entry: path.join(__dirname, "src", "index.js"),
9 | output: {
10 | path: path.join(__dirname, "dist"),
11 | filename: "[name].js"
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.js$/,
17 | exclude: /node_modules/,
18 | use: "babel-loader"
19 | }
20 | ]
21 | },
22 | plugins: [new webpack.EnvironmentPlugin(["NODE_ENV"])]
23 | };
24 |
25 | if (env === "production") {
26 | config.mode = "production";
27 | }
28 |
29 | if (isExample) {
30 | // Build example
31 | config.entry = path.join(__dirname, "example", "client.js");
32 | config.output.path = path.join(__dirname, "example", "public");
33 | config.plugins.push(
34 | new HtmlWebpackPlugin({
35 | template: "./example/index.html"
36 | })
37 | );
38 | }
39 |
40 | module.exports = config;
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Graham Kaemmer
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 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### v0.0.1
2 |
3 | - Initial version
4 |
5 | ### v0.1.5
6 |
7 | - Prevent `setState` on unmounted `Fade` component. (thanks to @Themandunord)
8 |
9 | ### v0.1.6
10 |
11 | - Added `delay` and `transitionDuration` props.
12 |
13 | ### v0.1.7
14 |
15 | - Pass `className` prop through to container element. (thanks to @Deveosys)
16 |
17 | ### v0.1.8
18 |
19 | - Add `childClassName` prop to allow styling for child divs. (thanks to @Deveosys)
20 |
21 | ### v1.0.0
22 |
23 | - Use `transform: translateY(...)` instead of `position: relative` and `top` to animate elements. This is a breaking change if you rely on `position: relative` on the child divs. Thanks to [@bogdansoare](https://github.com/gkaemmer/react-fade-in/issues/8) for the suggestion.
24 |
25 | ### v1.1.0
26 |
27 | - Add `wrapperTag` and `childTag` props. Thanks to [@domarp-j](https://github.com/domarp-j).
28 |
29 | ### v1.1.1
30 |
31 | - Transition to `transform: none` instead of `transform: translateY(0)`. Thanks to [@atnpcg](https://github.com/atnpcg).
32 |
33 | ### v2.0.0
34 |
35 | - Add `visible`, `onComplete` props. Rewrite component using typescript and react hooks. New children added after the initial render are now also animated.
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-fade-in",
3 | "version": "2.0.1",
4 | "description": "Super-easy fade-in animation for react children",
5 | "main": "lib/index.js",
6 | "types": "lib/index.d.ts",
7 | "repository": {
8 | "url": "git@github.com:gkaemmer/react-fade-in.git",
9 | "type": "git"
10 | },
11 | "scripts": {
12 | "build": "npm run build:clean && npm run build:lib",
13 | "build:clean": "rm -rf lib",
14 | "build:lib": "tsc",
15 | "prepublish": "NODE_ENV=production npm run build",
16 | "dev:server": "EXAMPLE=true webpack-dev-server --port 3124 --hot --inline --content-base example/public",
17 | "dev:compile": "tsc --watch",
18 | "dev": "npm run dev:server"
19 | },
20 | "author": "Graham Kaemmer",
21 | "license": "MIT",
22 | "dependencies": {},
23 | "devDependencies": {
24 | "@babel/cli": "^7.8.3",
25 | "@babel/core": "^7.8.3",
26 | "@babel/preset-env": "7.8.7",
27 | "@babel/preset-react": "^7.8.3",
28 | "@types/react": "^17.0.0",
29 | "babel-loader": "^8.0.6",
30 | "html-webpack-plugin": "^3.2.0",
31 | "react": "^16.12.0",
32 | "react-dom": "^16.12.0",
33 | "styled-components": "^1.1.3",
34 | "typescript": "^4.1.3",
35 | "webpack": "^4.41.5",
36 | "webpack-cli": "^3.3.10",
37 | "webpack-dev-server": "^3.10.1"
38 | },
39 | "peerDependencies": {
40 | "react": "^16.8 || 17"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-fade-in
2 |
3 | Dead-simple and opinionated component to fade in an element's children.
4 |
5 | 
6 |
7 | ## Installation
8 |
9 | `npm install react-fade-in`
10 |
11 | ## Usage
12 |
13 | `react-fade-in`
14 |
15 | ```
16 | import FadeIn from 'react-fade-in';
17 | // ...
18 |
19 | Element 1
20 | Element 2
21 | Element 3
22 | Element 4
23 | Element 5
24 | Element 6
25 |
26 | ```
27 |
28 | ## API
29 |
30 | ### `FadeIn`
31 |
32 | Animates its children, one by one.
33 |
34 | > **Note**: To have children animate separately, they must be first-level children of the `` component (i.e. members of its `props.children`).
35 |
36 | #### Props
37 |
38 | - `delay`: Default: 50. Delay between each child's animation in milliseconds.
39 | - `transitionDuration`: Default: 400. Duration of each child's animation in milliseconds.
40 | - `className`: No default. Adds a `className` prop to the container div.
41 | - `childClassName`: No default. Adds a `className` prop to each child div, allowing you to style the direct children of the `FadeIn` component.
42 | - `wrapperTag`: Default: `"div"`. Override the HTML element of the wrapping div.
43 | - `childTag`: Default: `"div"`. Override the HTML element wrapped around each child element.
44 | - `visible`: **New in 2.0.0**: If not `undefined`, the `visible` prop can be used to control when the fade in occurs. If set to `false` after the fade-in animation completes, the children will fade out one by one.
45 | - `onComplete`: **New in 2.0.0**: specifies a callback to be called when the animation completes.
46 |
47 | Happy fading.
48 |
--------------------------------------------------------------------------------
/src/FadeIn.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | JSXElementConstructor,
3 | PropsWithChildren,
4 | useEffect,
5 | useState,
6 | } from "react";
7 |
8 | interface Props {
9 | delay?: number;
10 | transitionDuration?: number;
11 | wrapperTag?: JSXElementConstructor;
12 | childTag?: JSXElementConstructor;
13 | className?: string;
14 | childClassName?: string;
15 | visible?: boolean;
16 | onComplete?: () => any;
17 | }
18 |
19 | export default function FadeIn(props: PropsWithChildren) {
20 | const [maxIsVisible, setMaxIsVisible] = useState(0);
21 | const transitionDuration = typeof props.transitionDuration === "number" ? props.transitionDuration : 400;
22 | const delay = typeof props.delay === "number" ? props.delay : 50;
23 | const WrapperTag = props.wrapperTag || "div";
24 | const ChildTag = props.childTag || "div";
25 | const visible = typeof props.visible === "undefined" ? true : props.visible;
26 |
27 | useEffect(() => {
28 | let count = React.Children.count(props.children);
29 | if (!visible) {
30 | // Animate all children out
31 | count = 0;
32 | }
33 |
34 | if (count == maxIsVisible) {
35 | // We're done updating maxVisible, notify when animation is done
36 | const timeout = setTimeout(() => {
37 | if (props.onComplete) props.onComplete();
38 | }, transitionDuration);
39 | return () => clearTimeout(timeout);
40 | }
41 |
42 | // Move maxIsVisible toward count
43 | const increment = count > maxIsVisible ? 1 : -1;
44 | const timeout = setTimeout(() => {
45 | setMaxIsVisible(maxIsVisible + increment);
46 | }, delay);
47 | return () => clearTimeout(timeout);
48 | }, [
49 | React.Children.count(props.children),
50 | delay,
51 | maxIsVisible,
52 | visible,
53 | transitionDuration,
54 | ]);
55 |
56 | return (
57 |
58 | {React.Children.map(props.children, (child, i) => {
59 | return (
60 | i ? "none" : "translateY(20px)",
65 | opacity: maxIsVisible > i ? 1 : 0,
66 | }}
67 | >
68 | {child}
69 |
70 | );
71 | })}
72 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/example/FadeInTest.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import FadeIn from "../lib";
3 | import styled from "styled-components";
4 |
5 | const Container = styled.div`
6 | text-align: center;
7 | padding: 20px;
8 | font-family: Helvetica, Arial, sans-serif;
9 | `;
10 |
11 | const Title = styled.h1`
12 | font-weight: normal;
13 | font-size: 2em;
14 | margin: 10px 0;
15 | `;
16 |
17 | const Element = styled.div`
18 | line-height: 1.5em;
19 | `;
20 |
21 | function DynamicChild({ start }) {
22 | const [count, setCount] = useState(start);
23 | useEffect(() => {
24 | const timeout = setTimeout(() => setCount(count + 1), 100);
25 | return () => clearTimeout(timeout);
26 | }, [count]);
27 | return Count: {count}
;
28 | }
29 |
30 | export default function FadeInTest() {
31 | const [childCount, setChildCount] = useState(6);
32 | const [visible, setVisible] = useState(true);
33 | return (
34 |
35 |
36 |
41 | React Fade-In
42 | console.log("onComplete")}>
43 | {[...Array(childCount).keys()].map((i) => (
44 | Element {i + 1}
45 | ))}
46 |
47 | With `visible` prop
48 |
51 | console.log("onComplete")}>
52 | {[...Array(childCount).keys()].map((i) => (
53 | Element {i + 1}
54 | ))}
55 |
56 | With dynamic child
57 |
58 | {[...Array(childCount).keys()].map((i) => (
59 |
60 | ))}
61 |
62 | With Delay
63 |
64 | {[...Array(childCount).keys()].map((i) => (
65 | Element {i + 1}
66 | ))}
67 |
68 | With Class names
69 |
70 | {[...Array(childCount).keys()].map((i) => (
71 | Element {i + 1}
72 | ))}
73 |
74 | With Custom Wrapper Tag (<section>)
75 |
76 | {[...Array(childCount).keys()].map((i) => (
77 | Element {i + 1}
78 | ))}
79 |
80 | With Custom Child Tag (<section>)
81 |
82 | {[...Array(childCount).keys()].map((i) => (
83 | Element {i + 1}
84 | ))}
85 |
86 |
94 |
95 | );
96 | }
97 |
--------------------------------------------------------------------------------