React sexy transition: Animate new text on keypress - css

I'm looking to scroll through a story, but let the user press (space) for the next phrase. I think it could be a beautiful optional intro on a website.
Already done: I've managed to scroll through an array of strings as the user presses (space):
CodePen: https://codepen.io/anon/pen/PbRLdp
React / JS:
var strings = ["Hi","it's not easy finding a freelancer, is it?", "referrals don't always come", "you need to know it'll get done", "I get it.", "perhaps we should connect"];
var i = 0;
var hitElement = document.querySelector( '.storylines' );
document.body.onkeyup = function(e) {
if( e.keyCode == 32 ) {
addHit();
}
}
var addHit = function() {
if ( i+2 <= strings.length) {
i++
renderStories();
}
}
var renderStories = function() {
hitElement.innerHTML = strings[i];
}
HTML:
<span class="storylines">press (spacebar)</span>
My question: How can I use React to create a transition between phrases? I'm thinking of translating / fading the current span downwards, and fading the new span in (without translating it).

I tried to change your question as little as possible since I'm not that familiar with reactJS.
I would do it only by css. This will allow you to change what each transition focuses on without too much code added (opacity vs. opacity and placement).
var strings = ["Hi", "it's not easy finding a freelancer, is it?", "referrals don't always come", "you need to know it'll get done", "I get it.", "perhaps we should connect"];
var i = 0;
var hitElement = document.querySelector('.storylines');
document.body.onkeyup = function(e) {
if (e.keyCode == 32) {
addHit();
}
}
var addHit = function() {
if (i + 2 <= strings.length) {
i++
renderStories();
}
}
var renderStories = function() {
hitElement.classList.remove('enter');
hitElement.classList.add('hide');
setTimeout(function() {
hitElement.innerHTML = strings[i];
hitElement.classList.remove('hide');
hitElement.classList.add('enter');
}, 250);
}
.storylines {
display: inline-block;
transition: opacity 250ms linear 150ms;
opacity: 0;
}
.storylines.hide {
transition: all 250ms linear;
transform: translateY(15px);
opacity: 0;
}
.storylines.enter {
opacity: 1;
}
<span class="storylines enter">press (spacebar)</span>
NOTICE: click on Expand snippet to not have the spacebar scroll the stackoverflow page :-)
This is the logic flow:
The page starts with the storylines class and the enter class.
This is to have the 1st element already shown.
When the change occurs, remove the enter class, and add the hide class to make the class leave.
hide will make sure the element disappears and moves down. enter is removed so it won't override the opacity of the element.
Also, hide contains a different transition that will animate all changes. This allows the transform to also move the element on exit.
After a timeout, add the enter class and remove the hide class
Here, since we remove the hide class, the transition changes to animate only opacity. So, the element appears in place instead of moving up or down.
NOTICE that the enter animation as a delay value added, so it won't happen in the same time as the hide animation.
transition: opacity 250ms linear 150ms;
Also, you can change the animations using css only, which I think is a good separation of roles.

Try this on your last function:
var renderStories = function() {
hitElement.style.opacity = 0;
setTimeout(function(){
hitElement.innerHTML = strings[i];
hitElement.style.opacity = 1;
}, 600);
}
An this in your CSS:
.storylines {
transition: all 0.6s;
opacity: 1;
}
The JS timeout should be exactly the transition time for your CSS element.
EDIT: With translate
var renderStories = function() {
hitElement.style.opacity = 0;
hitElement.style.transform = "translateY(-100%)";
setTimeout(function(){
hitElement.innerHTML = strings[i];
hitElement.style.opacity = 1;
hitElement.style.transform = "translateY(0%)";
}, 600);
}
CSS
.storylines {
transition: all 0.6s;
opacity: 1;
display: block; /* the element needs to be seen as solid */
}
EDIT 2: Changed the location so the new line appears in the same place:
var renderStories = function() {
hitElement.style.opacity = 0;
hitElement.style.transform = "translateY(-100%)";
setTimeout(function(){
hitElement.style.trasition = "opacity 0.3s, transform 0s"
hitElement.style.transform = "translateY(0%)";
}, 300);
setTimeout(function(){
hitElement.innerHTML = strings[i];
hitElement.style.opacity = 1;
hitElement.style.trasition = "opacity 0.3s, transform 0.3s"
}, 600);
}
CSS:
.storylines {
transition: opacity 0.3s, transform 0.3s;
opacity: 1;
display: block; /* the element needs to be seen as solid */
}
If you want it slower make sure that the 600 is the double of 0.3 or 300 with whatever value you're changing.

Related

React JS: How to animate conditionally rendered components?

Example is a functional component in which I am rendering a div conditionally. I want this div to fade-in when rendered conditionally and fade-out vice versa.
For that, I have maintained two local state variables: render and fadeIn which are computed based on show prop passed down to the Example component.
What I've done is:
When show prop it true, I set render as true, so the div renders conditionally and after a timeout of 10ms I set fadeIn as true which will set CSS classname for my div as show.
When show prop it false, I set fadeIn as false, which will set CSS classname for my div as hide and after a timeout of 200ms (transition time in CSS) I set render as false so the div is hidden conditionally.
Code:
interface Props {
show: boolean;
}
const Example: React.FC<Props> = ({ show, }) => {
const [render, setRender] = useState(false);
const [fadeIn, setFadeIn] = useState(false);
useEffect(() => {
if (show) {
// render component conditionally
setRender(show);
// change state to for conditional CSS classname which will
// animate opacity, I had to give a timeout of 10ms else the
// component shows up abruptly
setTimeout(() => {
setFadeIn(show);
}, 10);
} else {
// change state to change component classname for opacity animation
setFadeIn(false);
// hide component conditionally after 200 ms
// because that's the transition time in CSS
setTimeout(() => {
setRender(false);
}, 200);
}
}, [
show,
]);
return (
<div>
{render && (
<div className={`container ${fadeIn ? 'show' : 'hide'}`} />
)}
</div>
);
};
Stylesheet:
.container {
width: 100px;
height: 100px;
background-color: black;
transition: opacity 0.2s ease;
}
.show {
opacity: 1;
}
.hide {
opacity: 0;
}
I believe this is not a good coding practice to achieve the functionality and should maintain only one local state in my component. I need your suggestions on how I can solve this in a better way without using any 3rd Party Library.
Thanks :)
const [render, setRender] = useState(false);
useEffect(() => {
if(show) {
setTimeout(() => {
setRender(true);
}, 2000);
} else {
setRender(false);
}
}, [show]);
<div className={cs(s.render, render ? 'show' : undefined)}>
<p>{content}</p>
</div>
Css:
.render {
...,
visibility: hidden;
opacity: 0;
transition: all 0.6s ease;
}
.show {
visibility: visible;
opacity: 1;
}
Hope be helpful.

How to create a smooth background image transition in React?

I have a header, whose className changes depending on State. Each class has a different background image, specified in the CSS. Everything works fine, but the transitions are quite abrupt without a fade-in effect.
I wrote:
.jumbotron-img-1{
background-image: url("/images/myImg1.jpg");
transition: all 1s ease-in-out;
It works, but it's ugly. There is a zoom, and a distortion of the image before it shows up in its final form. I've watched some tutorials on Google, but nothing was simple and to the point for background-image transition in pure CSS or React.
Any help would be greatly appreciated, thanks!
background-image is not an animatable property. I feel what best serves your purpose is to render multiple headers with all the classnames available stacked over each other with position: absolute; relative to common parent and make only one of them visible using opacity property based on which classname is active in your state and use transition on opacity
Sample working code:
render() {
const {imgClassList} = this.props;
const {activeimgClass} = this.state;
return (
<div className="header-container">
{imgClassList.map(imgClass => {
return (
<div
className={`header ${imgClass} ${(imgClass === activeimgClass)? 'active' : ''}`}
/>)
})}
</div>
)
}
And css be something like:
.header-container {
position: relative;
}
.header{
position: absolute;
top: 0;
left: 0;
opacity: 0
transition: opacity 1s ease-in-out;
}
.header.active {
opacity: 1
}
.img-1 {
background:url('images/img-1')
}
.img-2 {
background: url('images/img-2')
} ... and so on
There's no good way to transition a background image using CSS because it's not an animatable property, per the CSS spec. One way to do this is to just have multiple images on top of one another, each containing a different one of the images you'd like to display, and then cycle through them by transitioning them to opacity: 0 and changing their z-index order.
I made a quick demo showing how you can achieve smooth changes by manipulating opacity and z-index. In pure Javascript, this is done by simply adjusting the styles with DOM manipulation and using setTimeout().
Of course in React you don't want to be doing DOM manipulation, so you can experiment with multiple classes with different opacity levels and transitions to accomplish this. There also seems to be a React component that enables all types of transitions: https://reactcommunity.org/react-transition-group/css-transition
Check out the Javascript solution demo to see how changing the opacity can get a crossfade effect on images:
function backgroundScheduler_1() {
setTimeout(() => {
document.querySelector(".img1").style.opacity = 0;
document.querySelector(".img2").style.opacity = 1;
document.querySelector(".img3").style.opacity = 1;
order(["-3", "-1", "-2"], () => { backgroundScheduler_2() }, 1000);
}, 3000);
}
function backgroundScheduler_2() {
setTimeout(() => {
document.querySelector(".img1").style.opacity = 1;
document.querySelector(".img2").style.opacity = 0;
document.querySelector(".img3").style.opacity = 1;
order(["-2", "-3", "-1"], () => { backgroundScheduler_3() }, 1000);
}, 3000);
}
function backgroundScheduler_3() {
setTimeout(() => {
document.querySelector(".img1").style.opacity = 1;
document.querySelector(".img2").style.opacity = 1;
document.querySelector(".img3").style.opacity = 0;
order(["-1", "-2", "-3"], () => { backgroundScheduler_1() }, 1000);
}, 3000);
}
function order(array, callback, time) {
setTimeout(() => {
document.querySelector(".img1").style.zIndex = array[0];
document.querySelector(".img2").style.zIndex = array[1];
document.querySelector(".img3").style.zIndex = array[2];
callback();
}, time);
}
backgroundScheduler_1();
.background-image {
position: absolute;
top: 0;
left: 0;
opacity: 1;
transition: 1s;
}
.img1 {
z-index: -1;
}
.img2 {
z-index: -2;
}
.img3 {
z-index: -3;
}
<div class="background-container">
<img class="background-image img1" src="https://placeimg.com/640/640/nature"></img>
<img class="background-image img2" src="https://placeimg.com/640/640/animals"></img>
<img class="background-image img3" src="https://placeimg.com/640/640/tech"></img>
<h2 style="color: white;">WOW!</h2>
</div>
I checked NPM momentarily and didn't see anything that promises this exact functionality. Hope this helps!

How to finish an animation even if its rule is removed?

I'm writing code that makes it so that when an element is given a class, it flashes briefly. To do this, I've created an animation from its "highlighted" appearance to its "unhighlighted" appearance, which is applied when the element is given the .highlight class.
The trouble is that the .highlight class is usually only applied for a very short moment - it's removed well before the animation finishes. The result of this is that the element will use its "unhighlighted" appearance immediately once the class is removed. But my goal is that it will finish the animation, gradually transitioning to the unhighlighted appearance, even though the class that applies that animation was removed.
Below is some code that represents the situation I'm dealing with. Try clicking the button once, then click it again before the animation has finished; note that the animation is cancelled and the "unhighlighted" appearance is immediately used.
#foo {
background: blue;
color: white;
}
#keyframes unhighlight {
from {
background: red;
}
to {
background: blue;
}
}
#foo.highlight {
animation-duration: 5s;
animation-name: unhighlight;
}
<p id="foo">
Hello!
</p>
<button onclick="document.getElementById('foo').classList.toggle('highlight')">
Click
</button>
Since in practice I'm writing in the context of React, I'd prefer to avoid involving JavaScript in the solution here (e.g. only removing the .highlight class once it's detected that the animation has finished) - it would be difficult to incorporate into my existing code (really).
You can remove .highlight class using timer. I understand you have not added JavaScript tag but you are already using JavaScript to add and remove class.
See the Snippet below:
var timer = 0;
var stopAnimation = false;
var animationTimer = 5;
function playStopAnimation(){
console.log(document.getElementById("foo").classList.contains("highlight"));
if(document.getElementById("foo").classList.contains("highlight")){
if(timer != animationTimer){
stopAnimation = true;
}else{
document.getElementById('foo').classList.toggle('highlight');
stopAnimation = false;
timer = 0;
console.log("Highlight removed");
}
}else{
document.getElementById('foo').classList.toggle('highlight');
stopAnimationFn(animationTimer);
}
}
const stopAnimationFn = (n)=>{
for (let i = 1; i <= n; i++) {
setTimeout(() =>{
console.log(i);
timer = i;
if(stopAnimation && timer==animationTimer){
document.getElementById('foo').classList.toggle('highlight');
stopAnimation = false;
timer = 0;
console.log("Highlight removed");
}
}, i * 1000)
}
}
function timerSet(i) {
setTimeout(function(){
timer=i;
console.log(timer);
},1000);
}
#foo {
background: blue;
color: white;
}
#keyframes unhighlight {
from {
background: red;
padding-left:0;
}
to {
background: blue;
padding-left:500px;
}
}
#foo.highlight {
animation-duration: 5s;
animation-name: unhighlight;
}
<p id="foo">
Hello!
</p>
<button onclick="playStopAnimation()">
Click
</button>

transition for translateY(0) working, but not translateY(-50px)

I'm having some trouble with a transition on a transform. I'm making a tic-tac-toe game like in the freecodecamp front-end challenges: https://codepen.io/freeCodeCamp/full/KzXQgy.
I've been able to create most layout things no problem, but am having an issue with my transition on a div that shows which players' turn it is after hitting the reset button. Right now I'm just working on two player mode, so I click two players, then X or O, and then the tic-tac-toe board shows up and a div transform: translateY(-50px) to indicate whether it's Player 1 or Player 2's turn (based on a random number variable I set up). The first time through the div transition's perfectly. Then I hit the Reset All div and it takes me back to the beginning to choose how many players again. And the div transitions the transform: translateY(0) perfectly back to it's starting position. Where I'm struggling is, now when I cycle through the options again, if it's Player 1 or Player 2's turn again, the transition never happens and the div just transforms -50px up with the translateY.
I've tried everything I could think of, JS setting up the transition, resetting the transition, moving the transition to be on different classes, adding and removing a class that only has a transition on it. Can't figure it out, but the weird thing is, whenever I hit the "Reset All", the transform transitions back to 0px normally. Here's my codepen: https://codepen.io/rorschach1234/pen/dZMaJg?editors=0111. I know it's still very rough, but just can't figure out this transition problem. Really appreciate any help. Thanks everyone!
My relevant Html:
<div class="container">
<div class="turns">
<div class="turns__left turns__box"></div>
<div class="turns__right turns__box"></div>
</div>
</div>
My relevant CSS:
.turns {
width: 100%;
display: flex;
position: absolute;
justify-content: space-around;
top: 0;
left: 0;
z-index: -1;
&__box {
width: 40%;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 1.3em;
font-family: sans-serif;
transition: transform 1s .3s ease;
}
&__left {
background-color: $color-turn-left;
//transition: transform 1s .3s ease;
}
&__right {
background-color: $color-turn-right;
//transition: transform 1s .3s ease;
}
}
My Javascript:
let numOfPlayers = document.querySelectorAll(".player .choices h2");
let playerScreen = document.querySelector(".player");
let markerScreen = document.querySelector(".markers");
let singlePlayer = true;
let backBtn = document.querySelector(".markers__back");
let gameScreen = document.querySelector(".game");
let playerOne; let playerTwo; let activePlayer;
let turnBoxes = document.querySelectorAll(".turns__box");
let resetBtn = document.querySelector(".scoreboard__reset");
game();
function game() {
playerModeSelection();
markerSelection();
}
function boardChange(active, inactive) {
inactive.style.opacity = "0";
inactive.style.zIndex = "0";
active.style.opacity = "1";
active.style.zIndex = "1";
}
//One or Two player Selection & transition to Marker Selector
function playerModeSelection() {
for (let i = 0; i < numOfPlayers.length; i++) {
numOfPlayers[i].addEventListener("click", function() {
if(i === 1) {
singlePlayer = false;
}
boardChange(markerScreen, playerScreen);
})
}
}
function markerSelection() {
//Back Button Functionality
backBtn.addEventListener("click", function() {
boardChange(playerScreen, markerScreen);
})
//Listen for choice of X or O
for (let i = 0; i < markers.length; i ++) {
markers[i].addEventListener("click", function() {
boardChange(gameScreen, markerScreen);
if (i === 1) {
playerArr = ["O", "X"];
}
//Starts Two Player Game; Here begin is the function that calls transition
if(!singlePlayer) {
twoPlayerMode();
}
})
}
}
function twoPlayerMode() {
activePlayer = Math.floor(Math.random() * 2);
turnBoxes[activePlayer].textContent = "Go Player " + (activePlayer + 1) + "!";
turnBoxes[activePlayer].style.transform = "translateY(-50px)";
resetBtn.addEventListener("click", function() {
boardChange(playerScreen, gameScreen);
turnBoxes[activePlayer].style.transform = "translateY(0px)";
})
}

React animate component when it render and when its state change

For animation i write the code like this but this code is not working for me. Whant to know this is a css problem or my code style needs update. or how to do animation on a component
render: function() {
var len = Object.keys(Interfaces.previewContainer.state.sourceImg).length;
var imgContainer = [];
for (var i = 0; i < len; i++) {
imgContainer.push(React.createElement(PreviewImgContainer, {
key: i,
flipParentClass: this.state.flipParentClass,
i_d: 'SelectedTemplate_' + i,
dataactionstring: 'selecttemplatetype-' + this.state.dataactionstring['src' + i],
sourceImg: this.state.sourceImg['src' + i],
ref: 'PreviewImgContainer',
alter: this.state.alter
}));
}
return (
React.createElement(
ReactCSSTransitionGroup, {
className:'flipParent',
transitionEnterTimeout: 250,
transitionLeaveTimeout: 250,
transitionName: 'flipped' // 'flipParent flipped' is a class name
},
React.createElement('div', {
className: 'previewContainer',
style: {
marginLeft: this.state.marginLeft
},
ref: 'previewContainer'
}, imgContainer)
)
);
}
please let me know where i am making a mistake or where i have to update the code
.flipParent {
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform 0.5s ease 0s;
width: 100%;
}
/* .flipped is for flipping (x-axis) the element */
.flipped {
transform: rotateY(180deg);
}
Maybe you have to implement these
.flipped-enter
.flipped-enter.flipped-enter-active
.flipped-leave
.flipped-leave.flipped-leave-active
The below screenshot is from the official react guide

Resources