How to animate a MatDialog on close using scss/sass? - css

I am tending to override the scss-animations inside #angular/material/dialog. I have tried many ways and read many articles but I am still unable to reach to a solution. When the MatDialog popup opens, it has its own animations but when I close it, it closes immediately (without animation).
Firstly, the dialog opens by means of my NotifierService which looks like the following:
notifier.service.ts
#Injectable()
export class NotifierService {
...
constructor(
private dialog: MatDialog,
private dialogRef: MatDialogRef<OverlayComponent>
) { }
...
public open(notification: Notification): void {
this.dialogRef = this.dialog.open(NotificationComponent, {
...
panelClass: 'notifications-popup',
data: {
notification: notification
}
});
}
...
}
In my global scss file I have the following code:
styles.scss
...
#keyframes fadeOut {
0% { width: 85%; height: 85vh; }
100% { width: 0%; height: 0%; }
}
#keyframes fadeIn {
0% { width: 0vh; height: 0vh; }
100% { width: 85%; height: 85vh; }
}
...
div.notifications-popup {
animation: fadeIn 0.15s forwards !important;
animation-delay: 0.15s;
}
...
In my NotificationComponent I have the following above all:
notification.component.ts
...
#Component({ ... })
export class NotificationComponent {
...
public close(): void {
this.fadeOutOnClose(); // Theoretically this should animate the closure of the dialog??????
this.matDialogRef.close(this.data.overlay);
}
public fadeOutOnClose(): void {
const view = document.getElementsByClassName('notifications-popup');
for (let i = 0; i < view.length; i++) {
this.renderer.setStyle(view[i], 'animation', 'fadeOut 1s forwards !important');
this.renderer.setStyle(view[i], 'animation-delay', '1s');
}
}
...
}
...
Does anyone have any ideas about how to animate the closure of a MatDialog?

Unfortunately, it's not an easy task. That's why I chose this library for my projects:
https://www.npmjs.com/package/ng-dialog-animation

Related

There's some way to add fadein or slide effect on "react-dates-range" when i change between months?

I'm using the library https://github.com/hypeserver/react-date-range , react 17.0.2 and next 12.0.0
I would like that when I change from one month to another, instead of making a sudden change, to be able to configure a Fade-In, or a Slide, just as it happens in the AirBnb calendar.
Does anyone know how I can do this? Thank you very much and sorry for the inconvenience.
This is my actual behavior
This is something like what I want, but it could also be a fade in or something similar.
Exactly like Airbnb i believe its not possible because the react-date-range works a little different from what airbnb does.
But you can get similar behavior using SwitchTransition and CSSTransition from react-transition-group library. You can check their docs here.
First, here's the code sample with DateRangerPicker using CSSTransition between months.
And below the same code, with comments on what its doing:
CSS file:
/*
Overflow the Calendar container
.rdrDateRangeWrapper is a class that react-date-range creates internally
*/
.rdrDateRangeWrapper {
overflow-x: hidden;
}
/*
.rdrMonths is the class that react-date-range creates internally for the month container calendar
.fadeRightToLeft classes is related to react-transition-group
Created for transition from right to left
*/
.fadeRightToLeft-enter .rdrMonths {
opacity: 0;
transform: translateX(100%);
}
.fadeRightToLeft-enter-active .rdrMonths {
opacity: 1;
transform: translateX(0%);
}
.fadeRightToLeft-exit .rdrMonths {
opacity: 1;
transform: translateX(0%);
}
.fadeRightToLeft-exit-active .rdrMonths {
opacity: 0;
transform: translateX(-100%);
}
.fadeRightToLeft-enter-active .rdrMonths,
.fadeRightToLeft-exit-active .rdrMonths {
transition: opacity 100ms, transform 100ms;
}
/*
Same as fadeRightToLeft:
.fadeLeftToRight classes is related to react-transition-group
Created for transition from left to right
*/
.fadeLeftToRight-enter .rdrMonths {
opacity: 0;
transform: translateX(-100%);
}
.fadeLeftToRight-enter-active .rdrMonths {
opacity: 1;
transform: translateX(0%);
}
.fadeLeftToRight-exit .rdrMonths {
opacity: 1;
transform: translateX(0%);
}
.fadeLeftToRight-exit-active .rdrMonths {
opacity: 0;
transform: translateX(100%);
}
.fadeLeftToRight-enter-active .rdrMonths,
.fadeLeftToRight-exit-active .rdrMonths {
transition: opacity 100ms, transform 100ms;
}
Component file:
import { useState } from "react";
import { addDays, isAfter } from "date-fns";
import { DateRangePicker } from "react-date-range";
import { SwitchTransition, CSSTransition } from "react-transition-group";
import "react-date-range/dist/styles.css";
import "react-date-range/dist/theme/default.css";
import "./styles.css";
export default function App() {
const [state, setState] = useState([
{
startDate: new Date(),
endDate: addDays(new Date(), 7),
key: "selection"
}
]);
// state created to hold the first month that calendar is showing
const [shownDateChangeValue, setShownDateChangeValue] = useState(new Date());
// state created to check if use created next Month ou previous month
const [isNextMonth, setIsNextMonth] = useState(true);
return (
<SwitchTransition mode="out-in">
<CSSTransition
/*** call the transition when month changes ***/
key={shownDateChangeValue}
/*** code related to SwitchTransition ***/
addEndListener={(node, done) =>
node.addEventListener("transitionend", done, false)
}
/*** Set the transition class related to the user action ***/
classNames={isNextMonth ? "fadeRightToLeft" : "fadeLeftToRight"}
>
<DateRangePicker
onChange={(item) => {
setState([item.selection]);
}}
showSelectionPreview={true}
moveRangeOnFirstSelection={false}
months={2}
ranges={state}
direction="horizontal"
/*** set the current month ***/
shownDate={shownDateChangeValue}
/*** Change shownDateChangeValue and isNextMonth states, dispatching the transition ***/
onShownDateChange={(month) => {
/* check if user click next or previous month */
const isNext = isAfter(month, shownDateChangeValue);
setIsNextMonth(isNext ? true : false);
setShownDateChangeValue(month);
}}
/>
</CSSTransition>
</SwitchTransition>
);
}

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>

Can't select a bound attribute in Angular

I have a custom progress bar in angular. I'd like to bind to have a selector for the [value]. However if I bind the attribute in Angular it is no longer possible to select.
<progress-bar value="50"> works
<progress-bar [value]="value"> fails
The attribute doesn't exist in the second case it's just there as one of the ng-reflect-* attributes. Is this expected behavior?
component:
ProgressBar {
private currentValue;
#Input() set value(value: number) { this.currentValue = value; }
get value() { return this.currentValue; }
...
}
scss:
progress-bar {
display: block;
height: 4px;
width: 100%;
// determinate
&[value] {
background: map_get($color-palette, progress-bar-background);
.progress {
height: 100%;
background: map_get($color-palette, progress-bar-color);
animation: none;
transform-origin: top left;
transition: transform 250ms ease;
}
}
// indeterminate
&:not([value]) {
background: transparent;
}
}
Instead of property binding, you can use attribute binding:
<progress-bar [attr.value]="value">
export class ProgressBar {
constructor(private elementRef: ElementRef<HTMLElement>) {}
get value() {
const element = this.elementRef.nativeElement;
return element.getAttribute("value");
}
}
See this stackblitz for a demo.
A simpler solution is to set a class conditionally on the component with HostBinding:
export class ProgressBar {
private currentValue;
#Input()
get value() { return this.currentValue; }
set value(value: number) { this.currentValue = value; }
#HostBinding("class.has-value") get hasValue(): boolean {
return !!this.currentValue;
}
}
and to apply the styling according to the class selector:
progress-bar {
...
&.has-value {
...
}
&:not(.has-value) {
...
}
}
See this stackblitz for a demo.
You are trying to use a one way property binding between a object and method. Remove your getter and setter methods. They are kinda of useless since javascript doesn't have real private properties or methods.
ProgressBar {
#Input() public value: number
...
}

React CSSTransitionGroup doesn't add leave classes

I have a component that gets unmounted after ten seconds, and I just can't seem to get the leave-animations working with React CSSTransitionGroup. The appear classes gets added when the component mounts and those animations work well. However, the leave classes never gets added to the component on unmount. I've found several working jsfiddle examples, but the code doesn't work for me. I'm new to React so I'm hoping that someone can point me in the right direction. I've set the timeouts to be able to see if the classes gets added.
Main component:
this.state = {
renderBlankSlate: true,
//the rest of the initial state..
}
// This unmounts the component
componentDidMount() {
this.interval = setTimeout(() => this.setState({renderBlankSlate: false}), 10000);
}
{ this.state.renderBlankSlate ?
<ReactCSSTransisionGroup
component="div"
transitionName="slide"
transitionEnterTimeout={ 500 }
transitionAppear={ true }
transitionAppearTimeout={ 2000 }
transitionLeaveTimeout={ 5000 }
>
<BlankSlate />
</ReactCSSTransisionGroup>
: null }
CSS:
.slide-appear {
transform: translateX(110%);
height: 0;
opacity: 0;
}
.slide-appear.slide-appear-active {
transform: translateX(0);
height: 100%;
opacity: 1;
transition: all 2s ease-in;
}
.slide-leave {
transform: translateX(0);
}
.slide-leave.slide-leave-active {
transform: translateX(110%);
transition: 5s ease-in;
}
You probably want to add that ternary within the transition group.
<ReactCSSTransitionGroup
component="div"
transitionName="slide"
transitionEnterTimeout={ 500 }
transitionAppear={ true }
transitionAppearTimeout={ 2000 }
transitionLeaveTimeout={ 5000 }
>
{this.state.renderBlankSlate ? <BlankSlate /> : null}
</ReactCSSTransitionGroup>
The reason your leave animation isn't firing is because the Transition group is leaving as well

CSS animation onclick and reverse next onclick

I am using a spritesheet and keyframes to animate the image on a button when it is clicked.
When the button is clicked I want the frames to run in one direction and leave the button on the last image in the spritesheet, and when it is clicked again I want the same frames to run backwards, leaving the button on the first image on the spritesheet.
I am currently trying to use jquery to change the class on the button to an animating class when it is clicked, but this doesn't seem to be working.
fiddle: http://jsfiddle.net/CGmCe/10295/
JS:
function animate(){
$('.hi').addClass('animate-hi');
}
CSS:
.hi {
width: 50px;
height: 72px;
background-image: url("http://s.cdpn.io/79/sprite-steps.png");
}
.animate-hi {
animation: play 2s steps(10);
}
#keyframes play {
from { background-position: 0px; }
to { background-position: -500px; }
}
Make sure you are using an animation-capable browser. For me this works in Firefox.
The following might be just what you wanted:
http://jsfiddle.net/CGmCe/10299/
Code:
function animateButton() {
var button = $('.hi');
if (button.hasClass('animate-hi')) {
button.removeClass('animate-hi').addClass('animate-hi-reverse');
} else if (button.hasClass('animate-hi-reverse')) {
button.removeClass('animate-hi-reverse').addClass('animate-hi');
} else {
button.addClass('animate-hi');
}
};
$(document).ready(function() {
$('.hi').on("click", function() {
animateButton();
});
});
.hi {
width: 50px;
height: 72px;
background-image: url("http://s.cdpn.io/79/sprite-steps.png");
}
.animate-hi {
animation: play 2s steps(10);
}
.animate-hi-reverse {
animation: play-reverse 2s steps(10);
}
#keyframes play {
from {
background-position: 0px;
}
to {
background-position: -500px;
}
}
#keyframes play-reverse {
from {
background-position: -500px;
}
to {
background-position: 0px;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<img src="http://s.cdpn.io/79/sprite-steps.png" />
<button class="hi" type="button"></button>
Make a counter variable which checks if the button is clicked or not.
And based on the counter value add class to the element for example:
var counter=0;
$('.btn').on('click',function(){
if(counter=0)
{
$('.hi').addClass('animate-hi');
counter = 1;
}
else
{
counter = 0;
$('.hi').removeClass('animate-hi');
}
});
Make sure to declare the counter variable outside the function. Else every time its value initialized to 0.

Resources