├── 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 | ![React Fade In](/example/example.gif) 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 | --------------------------------------------------------------------------------