React and CSS Transitions, am I missing something? - css

Am I missing something in this code example below:
What I want: I want the component to render in with a fade effect and render out with a fade out effect.
What I get: The component renders instantly.
// Overlay.jsx
const Overlay: React.FC<{isOpen: boolean, onClickOverlay: React.MouseEventHandler> = ({
isOpen,
onClickOverlay,
}: any) => {
const overlayClasses = classnames({
drawer__overlay: true,
drawer__overlay__open: isOpen,
drawer__overlay__closed: !isOpen,
});
return isOpen
? (<div
onClick={onClickOverlay}
className={overlayClasses}
/>
)
: null;
}
// Overlay.scss
.drawer {
&__overlay {
background: rgba(0, 0, 0, 0.75);
height: 100%;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
transition: opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1);
&__open {
opacity: 1;
}
&__closed {
opacity: 0;
}
}
}
My working theory on this is that the CSS isn't transitioning because the component is being mounted/unmounted. But not sure how to do this. I should let you know using react-transition-group has been a complete bust for me, so if you suggest that, please provide some examples.

Related

How to fade or create a slider image change effect with React

I'm building a website with React and Frame Motion and in the slider I can't make sure that when the image changes there is a transition or effect other than the simple detachment of the image change.
I tried to do the same thing using the img tag instead of the backgroundImage css property but still couldn't create the effect.
This is the component:
import { useState, useEffect, useCallback } from 'react';
import { motion } from 'framer-motion';
function ImgSlider({ slides, children }) {
const [current, setCurrent] = useState(0),
length = slides.length,
time = 4000;
const handleNext = useCallback(() => {
current + 1 >= length ?
setCurrent(0) :
setCurrent(current + 1);
}, [current, length]);
const handlePrev = () => {
current === 0 ?
setCurrent(length - 1) :
setCurrent(current - 1);
};
useEffect(() => {
const autoplay = setInterval(handleNext, time);
return () => clearInterval(autoplay);
}, [handleNext]);
return (
<motion.section
className="slider flex space-around g-6"
style={{ backgroundImage: `url(${slides[current]})` }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: .5, ease: 'easeInOut' }}
exit={{ opacity: 0 }}
>
{children}
<button className="prev" onClick={handlePrev}>
<span className="sr-only">Slide Precedente</span>
</button>
<button className="next" onClick={handleNext}>
<span className="sr-only">Slide Successiva</span>
</button>
</motion.section>
);
}
export default ImgSlider;
and his css:
/* Slider */
section.slider {
min-height: 95vh;
padding: 1rem;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
color: var(--bg);
}
section.slider button.prev,
section.slider button.next {
position: absolute;
right: 1rem;
top: 50%;
width: 3rem;
aspect-ratio: 1;
background-image: url(./imgs/arrow.svg);
background-repeat: no-repeat;
background-position: center;
opacity: .8;
transition: .3s ease-in-out;
}
section.slider button.prev {
transform: rotate(180deg);
left: 1rem;
right: unset;
}
section.slider button.prev:hover,
section.slider button.next:hover {
opacity: 1;
}
I don't know how I could do... Can anyone give me some suggestions?
Thank you :)

CSS - How to hide an element based on position within page?

I have a sticky element I've setup for a button on mobile devices. This is the code:
.sticky-btn {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
}
Works great but I don't want the button to show when the user is at the very top of the page. It would be perfect if it only appeared after you scrolled down say 20-30 pixels. Is it possible to accomplish this in CSS somehow?
Thanks!
Brian Preston. Unfortunately, we can't do this using only CSS. We should Javascript to add "sticky" class to sticky button and toggle button using that class.
.sticky-btn {
position: fixed;
bottom: 20px;
left: 50%;
opacity: 0;
visibility: hidden;
pointer-events: none;
transform: translateX(-50%);
transition: all .3s ease-in-out;
}
.sticky-btn.sticky {
opacity: 1;
visibility: visible;
pointer-events: all;
}
And JS code should be like this.
document.addEventListener('DOMContentLoaded', function () {
var scrollPosition = window.scrollY;
var stickyBtn = document.querySelector('.sticky-btn');
window.addEventListener('scroll', function() {
scrollPosition = window.scrollY;
if (scrollPosition >= 30) {
stickyBtn.classList.add('sticky');
} else {
stickyBtn.classList.remove('sticky');
}
});
});
Hope this helps. Happy coding ~ :)

Using React, how can I make the modal do an animated appear, without already being in the DOM?

I have a modal which appears on the press of a button and is animated as it is displayed.
This works fine, BUT only if the modal code is already in the DOM when the button is pressed.
You can see the example here:
https://codesandbox.io/s/loving-dan-7fwrkr
This is the problem: if the button adds the modal code to the DOM then the modal simply appears with no animation.
I've spent many hours trying various ways to make this work and the best I can come up with is to use window.setTimeout to trigger the animation 200 milliseconds after the modal code is added to the DOM. I don't like such a solution because it seems like a hack - I don't have any clear understanding why such a hack would work.
The example below demonstrates both cases.
Without the commented code, the animation works.
With the commented code, the modal simply appears without animation.
If anyone has any idea how to fix this it would be much appreciated.
My specific goal is to NOT have the modal code in the DOM prior to pressing a button to make it appear.
I've worked pretty hard to make the minimum possible example below, but it is still fairly large I apologise. If you have suggesting for cutting it further whilst still being relevant please let me know.
import ReactDOM from 'react-dom';
import React, {useState} from 'react';
const theStyle = `
.md-modal {
position: fixed;
top: 50%;
left: 50%;
width: 50%;
height: auto;
z-index: 2000;
visibility: hidden;
transform: translateX(-50%) translateY(-50%);
}
.md-show {
visibility: visible;
}
.md-overlay {
position: fixed;
width: 100%;
height: 100%;
visibility: hidden;
top: 0;
left: 0;
z-index: 1000;
opacity: 0;
background: rgba(143, 27, 15, 0.8);
transition: all 0.3s;
}
.md-show ~ .md-overlay {
opacity: 1;
visibility: visible;
}
.md-content {
color: #fff;
background: #e74c3c;
position: relative;
border-radius: 3px;
margin: 0 auto;
}
.md-content h3 {
opacity: 0.8;
}
.md-effect-1 .md-content {
transform: scale(0.7);
opacity: 0;
transition: all 0.3s;
}
.md-show.md-effect-1 .md-content {
transform: scale(1);
opacity: 1;
}
`
function App() {
const [getVisible, setVisible] = useState(false);
/*
THE MODAL APPEAR ANIMATION DOES NOT WORK WHEN THIS IS UNCOMMENTED
if (!getVisible) {
return (
<button onClick={() => setVisible(true)}>
show modal
</button>)
}
*/
return (
<>
<style>
{theStyle}
</style>
<div className={`md-modal md-effect-1 ${(getVisible) && "md-show"}`}>
<div className="md-content">
This is a modal window.<br/>
<button onClick={() => setVisible(false)} className="md-close">close</button>
</div>
</div>
<div onClick={() => setVisible(false)} className="md-overlay"/>
<button onClick={() => setVisible(true)} className="md-trigger">
show modal
</button>
</>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
I've had similar issues, the reason was that the transition does not trigger if the modal immediately gets the end value of being visible when you add it to the DOM.
I solved it by putting the transition into an #keyframes animation. Then, after adding the modal to the DOM, you use classList.add() to trigger the animation.
Something like this
.modal {
opacity:0
}
.animated {
animation: showModal 1s forwards easeOut
}
#keyframes showModal {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
JS after the modal is added to the DOM:
myModel.classList.add("animated")
Self answer to my future self.
With the answer of #Kokodoko as my start point, I gained a better understanding of how animation works in CSS/JS and rewrote my modal entirely so it now does what I want.
Here's the code:
import ReactDOM from 'react-dom';
import React, {useState} from 'react';
const theStyle = `
.animated {
animation: showModal .2s forwards
}
#keyframes showModal {
from {
opacity: 0;
transform: scale(0.7);
}
to {
opacity: 1;
transform: scale(1);
}
}
.modalOverlay {
z-index: 1500;
background: rgba(40,91,218,0.5); /* you must use this and not opacity because opacity changes the front color */
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-content: stretch;
align-items: center;
}
.modalContainer {
z-index: 1600;
order: 0;
flex: 0 1 auto;
align-self: auto;
}
#modalContent {
z-index: 1700;
opacity: 0;
color: #fff;
width: 500px;
height: 200px;
background: #e74c3c;
position: relative;
border-radius: 3px;
margin: 0 auto;
}
`
function Button() {
const [getVisible, setVisible] = useState(false);
return (
<div>
<button onClick={() => setVisible(true)}>
show modal
</button>
{(getVisible) && <Modal setVisible={setVisible}/>}
</div>
)
}
function Modal({setVisible}) {
React.useEffect(
//() => window.setTimeout(document.getElementById("modalContent").classList.add("animated"))
() => document.getElementById("modalContent").classList.add("animated")
, [])
const handleClickOnOverlay = (e) => {
// clicks on the are sent through to the background so we must prevent that
e.stopPropagation()
setVisible(false)
}
const handleClickOnContainer = (e) => {
// clicks on the modal are sent through to the background so we must prevent that
e.stopPropagation()
}
const handleClickOnModal = (e) => {
console.log('clicked on modal')
}
return (
<>
<style>
{theStyle}
</style>
<div onClick={handleClickOnOverlay} className="modalOverlay">
<div className={`modalContainer`} onClick={handleClickOnContainer}>
<div id="modalContent" onClick={handleClickOnModal}>
This is a modal window.<br/>
<button onClick={() => setVisible(false)} className="md-close">close</button>
</div>
</div>
</div>
</>
);
}
ReactDOM.render(<Button/>, document.getElementById('root'));

add transition effect in vue on backgroundImage

I've been searching for a couple of days to add a fade transition effect on the backgroundImage that I'm changing through a Vue app.
Here is the code snippet I've been testing on:
new Vue({
el: "#main",
data: {
images: [
"https://images.freeimages.com/images/large-previews/bfd/clouds-1371838.jpg",
"https://images.freeimages.com/images/large-previews/ffa/water-lilly-1368676.jpg",
"https://images.freeimages.com/images/large-previews/efb/lotus-flower-1382251.jpg"
],
current: 0,
show: false
},
methods: {
changeBG: function () {
if (this.current < this.images.length - 1) {
this.current = this.current + 1;
} else {
this.current = 0;
}
}
}
});
.main {
height: 800px;
width: 100%;
margin-left: auto;
margin-right: auto;
z-index: 0;
background-repeat: no-repeat;
background-size: cover;
background-position: center 0px;
}
button {
width: 200px;
height: 25px;
margin: 10px;
}
p.hello{
color: white;
margin: 10px;
font-size: 50px;
}
.fade-enter-active,
.fade-leave-active {
transition: all 2s linear;
}
.fade-enter-to,
.fade-leave {
opacity: 0;
}
.fade-enter,
.fade-leave-to {
opacity: 1;
}
/* hello example transition */
.slide-fade-enter-active {
transition: all 1s ease;
}
.slide-fade-leave-active {
transition: all 1s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter,
.slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<transition name="fade">
<div v-if="changeBG" class="main" id="main" :style="{ backgroundImage: 'url(\'' + images[current] + '\')' }">
<button v-on:click="changeBG">
changeBG
</button>
<div id="testFromGuide">
<button #click="show = !show">
toggleHello
</button>
<transition name="slide-fade">
<p class="hello" v-if="show">all your base are belong to us</p>
</transition>
</div>
</div>
</transition>
My first question if this is simply possible? The reason I'm using backgroundImage is because the website I'm using this on has a background that is most easy to handle responsively through this (always covering, no repeat and keeping it center). And my scond question would be, if not, is there a possibility to make it work with a background set as described here?
In the codepen I've added an example of the vue guide to make sure it works and nothing is else is wrong. And the example works perfectly. Can't seem to find the answer for my example but I've been beginning to suspect it is simply not possible or I can't seem to find why vue isn't detecting something is changing.
For Vue Transitions to work, you need to change the DOM elements. So this way would work if you were changing actual images out. In your example, you're only changing an attribute value. The DOM doesn't trigger a transition since its the same element.
However, you can use the :key attribute to convince VUE to replace the element thus giving you a transition between 2 elements.
You can also set the image with inline CSS like you're doing in the example. You'll still have to create the transition in your CSS.
Here's an example using Vue Transition
new Vue({
el: "#main",
data: {
currentID: 0,
images: [
'https://images.freeimages.com/images/large-previews/efb/lotus-flower-1382251.jpg',
'https://images.freeimages.com/images/large-previews/ffa/water-lilly-1368676.jpg',
'https://images.freeimages.com/images/large-previews/bfd/clouds-1371838.jpg'
]
},
methods: {
toggleImage: function(){
if(this.currentID < this.images.length-1){
this.currentID +=1
} else {
this.currentID = 0
}
}
}
});
body {
overflow: hidden;
}
.main {
position: relative;
}
img {
width: 100%;
height: auto;
display: block;
position: absolute;
-webkit-transition: all 3s ease;
transition: all 3s ease;
}
button {
z-index: 100;
position: relative;
width: 200px;
height: 25px;
margin: 20px;
}
/* prefix with transition name */
.slide-fade-enter-active {
opacity: 1;
z-index: 10;
}
.slide-fade-leave-active {
opacity: 1;
}
.slide-fade-enter,
.slide-fade-leave-to {
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<div class="main" id="main">
<transition name="slide-fade">
<!-- SRC comes from the array of images the :key is important for vue to believe its a 'new' DOM element and do the transition -->
<img v-bind:src="images[currentID]" v-bind:key="currentID" />
</transition>
<button #click="toggleImage">
Toggle Image
</button>
</div>
However, you don't get a lot of control over that since it uses image tags. Instead, it might be better to use a background image like this:
new Vue({
el: "#main",
data: {
currentID: 0,
images: [
'https://images.freeimages.com/images/large-previews/efb/lotus-flower-1382251.jpg',
'https://images.freeimages.com/images/large-previews/ffa/water-lilly-1368676.jpg',
'https://images.freeimages.com/images/large-previews/bfd/clouds-1371838.jpg'
]
},
methods: {
toggleImage: function(){
if(this.currentID < this.images.length-1){
this.currentID +=1
} else {
this.currentID = 0
}
}
}
});
.main {
/* make this the size of the window */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.theImage {
width: 100%;
height: 100%;
position: absolute;
background-color: #333;
background-size: contain;
background-repeat: no-repeat;
background-position: center center;
-webkit-transition: all 3s ease;
transition: all 3s ease;
}
button {
z-index: 100;
position: relative;
width: 200px;
height: 25px;
margin: 20px;
}
/* prefix with transition name */
.slide-fade-enter-active {
opacity: 1;
z-index: 10;
}
.slide-fade-leave-active {
opacity: 1;
}
.slide-fade-enter,
.slide-fade-leave-to {
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<div class="main" id="main">
<transition name="slide-fade">
<!-- SRC comes from the array of images the :key is important for vue to believe its a 'new' DOM element and do the transition -->
<div class="theImage" v-bind:style="{'background-image': 'url(' + images[currentID] + ')'}" v-bind:key="currentID"></div>
</transition>
<button #click="toggleImage">
Toggle Image
</button>
</div>
The answer was indeed to forget about vue transitions and let css do the work. A working example can be found here:
new Vue({
el: "#main",
data: {
show: false,
BG1: true,
BG2: false,
BG3: false
},
methods: {
changeBG: function(){
if (this.BG1 == true){
this.BG1 = false;
this.BG2 = true;
this.BG3 = false;
} else if (this.BG2 == true) {
this.BG1 = false;
this.BG2 = false;
this.BG3 = true;
} else if (this.BG3 == true) {
this.BG1 = true;
this.BG2 = false;
this.BG3 = false;
}
},
showBG1: function(){
if (this.BG1 == true){
return "";
} else {
return "transparent";
}
},
showBG2: function(){
if (this.BG2 == true){
return "";
} else {
return "transparent";
}
},
showBG3: function(){
if (this.BG3 == true){
return "";
} else {
return "transparent";
}
}
}
});
.main {
}
#bgs img.transparent {
opacity:0;
transform: translateY(-0.0px);
}
#bgs img{
/* Set rules to fill background */
min-height: 100%;
min-width: 1024px;
/* Set up proportionate scaling */
width: 100%;
height: auto;
/* Set up positioning */
position: fixed;
top: 0;
left: 0;
z-index: -1;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
}
#media screen and (max-width: 1024px) { /* Specific to this particular image */
#bgs img{
left: 50%;
margin-left: -512px; /* 50% */
}
}
button {
z-index: 100;
position: relative;
width: 200px;
height: 25px;
margin: 20px;
}
p.hello{
color: white;
margin: 40px;
font-size: 50px;
}
.fade-enter-active {
transition: all 1s ease;
}
.fade-leave-active {
transition: all 1s ease;
}
.fade-enter, .fade-leave-to{
transform: translateY(-5px);
opacity: 0;
}
/* hello example transition */
.slide-fade-enter-active {
transition: all 1s ease;
}
.slide-fade-leave-active {
transition: all 1s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter,
.slide-fade-leave-to {
transform: translateY(-5px);
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<div class="main" id="main">
<div id="bgs">
<img :class="showBG1()" src="https://images.freeimages.com/images/large-previews/efb/lotus-flower-1382251.jpg">
<img :class="showBG2()" src="https://images.freeimages.com/images/large-previews/ffa/water-lilly-1368676.jpg">
<img :class="showBG3()" src="https://images.freeimages.com/images/large-previews/bfd/clouds-1371838.jpg">
</div>
<button #click="changeBG">
changeBG
</button>
<div id="testFromGuide">
<button #click="show = !show">
toggleHello
</button>
<transition name="slide-fade">
<p class="hello" v-if="show">all your base are belong to us</p>
</transition>
</div>
</div>
It's not perfect yet as for every extra background I need to add a function and add an extra if else loop to the changeBG function. It would be more elegant if this could be done with a list variable but I've not found a way to make this work with the :class method. I hope to look into this at a later time.

Are conditional transitions possible with ngAnimate?

I have setup an animation in my application on ngView using ngAnimate. Every time there is a state change, the next view slides in from the right.
I would like to be able to reverse this, so that in some instances, the view slides in from the left (if the user is going back).
Since the only classes offered are .ng-enter, .ng-leave, and .*-active, what are my options? Is this even possible?
Here's a CodePen, OR see my sample code here:
html
<div ng-app="app">
<div ng-controller="DemoController as vm">
<div ui-view class="foo"></div>
</div>
</div>
css
html,
[ui-view] {
background: yellow;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
position: relative;
}
div {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100%;
width: 100%;
}
[ui-view].ng-enter,
[ui-view].ng-leave {
position: absolute;
left: 0;
right: 0;
transition:all .5s ease-in-out;
}
[ui-view].ng-enter {
opacity: 0;
transform:translate3d(100%, 0, 0);
/*transform:scale3d(0.5, 0.5, 0.5);*/
}
[ui-view].ng-enter-active {
opacity: 1;
transform:translate3d(0, 0, 0);
/*transform:scale3d(1, 1, 1);*/
}
[ui-view].ng-leave {
opacity: 1;
transform:translate3d(0, 0, 0);
}
[ui-view].ng-leave-active {
opacity: 0;
transform:translate3d(-100%, 0, 0);
}
javascript
'use strict';
angular.module('app', ['ui.router', 'ngAnimate'])
.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/one');
$stateProvider
.state('one', {
url: '/one',
template: '<div class="one"><h1>ONE!</h1><button ng-click="vm.two()">go to two</button></div>'
})
.state('two', {
url: '/two',
template: '<div class="two"><h1>TWO!</h1><button ng-click="vm.one()">go to one</button></div>'
});;
})
.controller('DemoController', DemoController);
DemoController.$inject = ['$state'];
function DemoController($state) {
this.one = function() {
$state.go('one');
}
this.two = function() {
$state.go('two');
}
}
I'm not going to do all the leg work to code this but will provide suggested way to do it.
Will require you to check history within $stateChangeStart and compare path to the next path
Toggle a class on ui-view based on match or not
Then write a set of css rules that are more specific to that class
[ui-view].ng-enter.fromRightClass{}

Resources