<div id="root"></div>
html, body {
min-height: 100%;
overflow: hidden;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
}
main {
padding: 0 2rem;
position: relative;
background-color: #eeeeee;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
background: radial-gradient(ellipse at 0% 100%, rgba(#ec38bc, 0.6), rgba(#ec38bc, 0.1) 75%),
radial-gradient(ellipse at 100% 0%, rgba(#1CD8D2, 0.7), rgba(#7303c0, 0.1) 75%),
linear-gradient(#0b8793, #0099F7);
}
.input {
position: absolute;
z-index: 1;
padding: 0.8rem 1rem;
top: 2rem;
left: 50%;
margin-left: -15%;
width: 30%;
font-size: 18px;
font-weight: 100;
color: #333;
border: none;
outline: none;
border-radius: 3px;
transition: color 0.3s, background 0.3s;
background: linear-gradient(90deg, rgba(white, 0.15), rgba(white, 0.1));
&::placeholder {
color: rgba(white, 0.6);
}
&:focus {
color: white;
background: linear-gradient(90deg, rgba(black, 0.25), rgba(black, 0.2));
&::placeholder {
color: rgba(black, 0.6);
}
}
}
$time: 0.9s;
$ease: cubic-bezier(0.23, 1, 0.32, 1);
.title {
font-family: 'futura-pt', serif;
font-weight: 400;
font-size: 14vmin;
text-rendering: optimizeLegibility;
color: white;
line-height: 1.15;
transition: opacity $time $ease;
opacity: 0;
> span {
position: relative;
padding: 0 0.05em; // prevents some clipping issues due to the font bounds
display: inline-block;
margin-right: 0.15em;
margin-top: -0.2em;
margin-bottom: -0.2em;
overflow: hidden;
span {
display: block;
transition: transform $time $ease;
transform: translateY(100%);
}
}
}
.animate {
opacity: 1;
> span {
span {
transform: translateY(0%);
}
}
}
View Compiled
/**
* -------------------------------------------------------
* Word by Word Unmasking Animation
* -------------------------------------------------------
*
* Text animation effect to slide up and unmask each word.
* Type in the input to see new copy and animation.
*
*/
function spanifyWithDelay(string) {
// so this just adds a bunch of spans with a
// delay necessary for the animation effect.
const delay = 0.04;
return string
.trim()
.split(' ')
.map((word, idx) => (
`<span>
<span style="transition-delay: ${idx * delay}s">
${word}
</span>
</span>`
))
.reduce((acc, val) => (acc + val), '');
}
class Title extends React.Component {
state = {
animate: false,
};
componentDidMount() {
// this just add the class to trigger the animation
// ideally this would be in a transition group
// or use some other way to trigger the class.
setTimeout(() => {
this.setState(() => ({
animate: true,
}));
}, 500)
}
render() {
const { title } = this.props;
const { animate } = this.state;
// NOTE: Class "animate" triggers the animation
// when the "animate" state it true.
return (
<h1
className={classNames('title', { animate })}
dangerouslySetInnerHTML={{ __html: spanifyWithDelay(title) }}
/>
);
}
}
class Input extends React.Component {
render() {
return (
<input
className="input"
type="text"
onChange={this.props.onChange}
placeholder="Type Here"
/>
);
}
}
class App extends React.Component {
state = {
value: 'Effect For Word By Word Unmasking Animation',
};
handleChange = ({currentTarget}) => {
this.setState({
value: currentTarget.value,
});
};
render() {
return (
<main>
<Input onChange={this.handleChange} />
<Title key={this.state.value} title={this.state.value} />
</main>
);
}
}
const run = () => {
const root = document.getElementById('root');
ReactDOM.render(
<App />,
root
);
}
run();
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
function classNames () {
var classes = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) continue;
var argType = typeof arg;
if (argType === 'string' || argType === 'number') {
classes.push(arg);
} else if (Array.isArray(arg) && arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwnProperty.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(' ');
}
View Compiled
This Pen doesn't use any external CSS resources.