Looking for improvement of slide down animation with angular.js - css

I do have a running code sample (http://jsfiddle.net/Fuunq/) which shows a slide down animation using angular.js (1.2.0). But there are two issues that I don't know how to solve:
a) When clicking on the 'Add item' link, the animation first moves down the 'Add item' link and then slides in the new item from the top. How can this be changed, that the 'Add item' link slides down together with the new appearing item?
b) How can I prevent that the items do fade in when the page gets loaded for the first time?
HTML
<div ng-controller="MyCtrl">
<div ng-repeat="item in items" class="animation-slide-down">
<div class="item">
<div>Name: {{item.name}}</div>
<div>Color: {{item.color}}</div>
</div>
</div>
Add item
</div>
CSS
.item {
margin-top: 5px;
padding: 15px;
background-color: #34ac54;
border: 1px solid black;
}
#-webkit-keyframes slide_down {
0% {
-webkit-transform: translateY(-100%);
opacity: 0;
z-index: -100;
}
99% {
z-index: -100;
}
100% {
-webkit-transform: translateY(0);
opacity: 1;
z-index: 0;
}
}
#keyframes slide_down {
0% {
transform: translateY(-100%);
opacity: 0;
z-index: -100;
}
99% {
z-index: -100;
}
100% {
transform: translateY(0);
opacity: 1;
z-index: 0;
}
}
.animation-slide-down.ng-enter {
-webkit-animation: slide_down 3s ease-in;
animation: slide_down 4s ease-in;
}
JS
var myApp = angular.module('myApp', ['ngAnimate']);
function MyCtrl($scope) {
$scope.name = 'foo';
$scope.items = [
{name: 'Item 1', color: 'blue'},
{name: 'Item 2', color: 'red'}
]
$scope.addItem = function() {
$scope.items.push({name: 'Another item', color: 'black'})
}
}
Thanks for your help!

a) as far as i know this can only be achieved with a negative margin-top transitions to your target value 5px. There is one problem with this: you have to know the exact height of one item.
#-webkit-keyframes slide_down {
0% {
margin-top: -68px;
opacity: 0;
z-index: -100;
}
99% {
z-index: -100;
}
100% {
margin-top:5px;
opacity: 1;
z-index: 0;
}
}
b) you can use the $animate service. The service has an enabled method, so you may disable or enable the animations at any time. here is your controller code that disables the animation on start and after the start enables the animation. the trick is the $timeout service. with that you defer activating the animations until the next digest cycle happens:
function MyCtrl($scope, $animate, $timeout) {
$animate.enabled(false);
$scope.name = 'foo';
$scope.items = [
{name: 'Item 1', color: 'blue'},
{name: 'Item 2', color: 'red'}
]
$scope.addItem = function() {
$scope.items.push({name: 'Another item', color: 'black'})
}
$timeout(function(){
$animate.enabled(true);
});
}
here is the working example:
http://jsfiddle.net/CKF47/

Related

React - fade in div, pause and fade out div

In my React app, I am trying to fade a div in, wait a bit, and fade it back out. Everything is working great except the fade out.
My SCSS looks like this:
$orange-color: #DD7C15;
$white-color: #FFFFFF;
$black-color: #222222;
.App {
font-family: sans-serif;
text-align: center;
}
.message-banner {
position: fixed;
bottom: 0;
left: 0;
z-index: 100000;
width: 100vw;
color: $orange-color;
font-size: 4em;
text-align: center;
background-color: $white-color;
border: 2px solid $black-color;
opacity: 0.9;
animation: fadeIn 2s ease-in;
&.hide {
opacity: 0;
animation: fadeOut 2s ease-out;
}
}
#keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 0.9;
}
}
#keyframes fadeOut {
0% {
opacity: 0.9;
}
100% {
opacity: 0;
}
}
And my relevant React code:
const showBanner = () => {
setMessageBannerText("My sweet awesome banner!!");
setTimeout(() => {
setMessageBannerText("");
}, 3000);
};
const bannerClasses =
messageBannerText === "" ? "message-banner hide" : "message-banner";
I've created a sandbox showing what I am talking about.
https://codesandbox.io/s/brave-grass-q1y6j
Issue :
The animation is working fine but you are removing the content setMessageBannerText(""); while the animation, so it's not visible
Solution :
So instead of making content blank, you should maintain the state for animation
1) Solution :
const steps = {
0: "", // <--- blank coz message-banner has animation itself
1: "message-banner",
2: "message-banner hide"
};
export default function App() {
const messageBannerText = "My sweet awesome banner!!";
const [animationStep, setAnimationStep] = useState(0);
const showBanner = () => {
setAnimationStep(1);
setTimeout(() => {
// setMessageBannerText(""); // <---- issue
setAnimationStep(2);
}, 3000);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={showBanner}>Show Banner</button>
<MessageBanner text={messageBannerText} classes={steps[animationStep]} />
</div>
);
}
WORKING DEMO :
2) Solution : ( with css changes, but you still need to follow above changes )
.message-banner {
position: fixed;
bottom: 0;
left: 0;
z-index: 100000;
width: 100vw;
color: $orange-color;
font-size: 4em;
text-align: center;
background-color: $white-color;
border: 2px solid $black-color;
opacity: 0;
&.animate {
opacity: 0;
animation: fadeInOut 5s ease-out;
}
}
// single animation for fade in and fade out
#keyframes fadeInOut {
0% {
opacity: 0;
}
30% {
opacity: 0.9;
}
70% {
opacity: 0.9;
}
100% {
opacity: 0;
}
}
const [show, setShow] = useState(false);
const showBanner = () => {
if (!show) { // <--- just for safe side, if animation is going on, then ignore state change
setShow(true);
setTimeout(() => {
setShow(false);
}, 5000);
}
};
const bannerClasses = show ? "message-banner animate" : "message-banner";
WORKING DEMO :
Hey I have edited your sandbox to achieve the result you desire:-
Changes:-
1) Added show and hide classes.
2) Introduced a boolean state for transition rather than depending on text because your message-banner div doesn't have its own height or width. We will simply let the text stay but hide the div away from the user.
3) Instead of animation, used transition since you're simply toggling between two states and you want to stick with those for rest of your application. With animation, you will have to do some more tricks for them to stick. Plus animation is useful for more complex scenario.

page transitions without React-Router

This should be so simple, but I've been breaking my head over this for days. I'm trying to animate my page transitions. The problem is the docs SUCK. I've followed them over and over and tried every which way, but can't get it to work.
What I want to do is slide my pages gracefully either right or left, and fade the one that is unmounting gracefully out behind it. Simple right? I am NOT using React Router for my pages.
I've tried a variety of solutions for this, but the problem seems to be on the unmounting. When the page is replaced, the existing page gets unmounted before it can transition out. I'm posting my attempt with react-transition-group, though at this point, I'll accept any other solution that works. I'm not sure react-transition-group is being actively maintained actually, because there are numerous other postings for help with 0 responses.
So on my app container I want to put something like this:
<App>
<PageSlider>
<Page key={uniqueId} /> <==this will be "swapped" through (Redux) props
</PageSlider>
So, from what I've read, I have to use a TransitionGroup container as my PageSlider for this, so that it will manage the entering and exiting of my page. So here goes:
class PageSlider extends Component {
constructor(props) {
super(props);
}
render() {
return (
<TransitionGroup
component="div"
id="page-slider"
childFactory={child => React.cloneElement(child,
{classNames: `page-${this.props.fromDir}`, timeout: 500}
)}
>
{this.props.children}
</TransitionGroup>
);
}
}
I also read I need to do a "child Factory" to enable the exiting stuff. There was absolutely no example of this I could find in the docs. Since the pages will come from different directions, I will pass to this the direction from which I want to slide the page, which will tell the page what class it gets.
Now, as for the page itself, I have wrapped it in a CSSTransition like so. There were no good examples in the docs of how this all gets passed down, so I'm really confused what to do here:
class Page extends Component {
constructor(props) {
super(props);
}
render() {
return (
<CSSTransition> <==????????
{this.props.children} Do props get passed down?
</CSSTransition> Which ones?
); Does "in" get taken care of?
}
}
And just to finish the styles will be applied in CSS something like this:
.page {
display: flex;
flex-direction: column;
height: 100%;
position: absolute;
top: 0;
bottom: 0;
-webkit-transition: all 500ms ease-in-out;
transition: all 500ms ;
}
//go from this
.page-right-enter {
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);
}
//to this
.page-right-enter-active {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
//exiting go from this
.page-right-exit {
opacity: 1;
}
//to this
.page-right-exit-active {
opacity: 0;
}
All of these components will be connected through Redux so they know when a new page has been triggered and which direction has been called.
Can someone PLEEEEASE help me on this? I've literally spent days and tried every library out there. I'm not wedded to react-transition-group! Any library that works on the unmount I'll try. Why is this not easier?
OK. Well, I struggled with this for WAAAAY too long. I finally dumped react-transition-group, and went pure CSS. Here's my solution.
PageSlider.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
require('./transitions.scss');
const BlankPage = (props) => <div className="placeholder"></div>;
class PageSlider extends Component {
constructor(props) {
super(props);
this.state = {
nextRoute: props.page,
pages: {
A: {key: 'A', component: BlankPage, className: 'placeholder'},
B: {key: 'B', component: BlankPage, className: 'placeholder'},
},
currentPage: 'A'
};
}
componentDidMount() {
//start initial animation of incoming
let B = {key: 'b', component: this.state.nextRoute, className: 'slideFromRight'}; //new one
let A = Object.assign({}, this.state.pages.A, {className: 'slideOutLeft'}); //exiting
this.setState({pages: {A: A, B: B}, currentPage: 'B'});
}
componentWillReceiveProps(nextProps) {
if (nextProps.page != this.state.nextRoute) {
this.transition(nextProps.page, nextProps.fromDir);
}
}
transition = (Page, fromDir) => {
if (this.state.nextRoute != Page) {
let leavingClass, enteringClass;
let pages = Object.assign({}, this.state.pages);
const current = this.state.currentPage;
const next = (current == 'A' ? 'B' : 'A');
if (fromDir == "right") {
enteringClass = 'slideFromRight';
leavingClass = 'slideOutLeft';
} else {
enteringClass = 'slideFromLeft';
leavingClass = 'slideOutRight';
}
pages[next] = {key: 'unique', component: Page, className: enteringClass};
pages[current].className = leavingClass;
this.setState({pages: pages, nextRoute: Page, currentPage: next});
}
}
render() {
return (
<div id="container" style={{
position: 'relative',
minHeight: '100vh',
overflow: 'hidden'
}}>
{React.createElement('div', {key: 'A', className: this.state.pages.A.className}, <this.state.pages.A.component />)}
{React.createElement('div', {key: 'B', className: this.state.pages.B.className} , <this.state.pages.B.component />)}
</div>
);
}
}
PageSlider.propTypes = {
page: PropTypes.func.isRequired,
fromDir: PropTypes.string.isRequired
};
export default PageSlider;
transition.scss
.placeholder {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
background: transparent;
-webkit-animation: slideoutleft 0.5s forwards;
-webkit-animation-delay: 10;
animation: slideoutleft 0.5s forwards;
animation-delay: 10;
}
.slideFromLeft {
position: absolute;
left: -100vw;
width: 100vw;
height: 100vh;
-webkit-animation: slidein 0.5s forwards;
-webkit-animation-delay: 10;
animation: slidein 0.5s forwards;
animation-delay: 10;
}
.slideFromRight {
position: absolute;
left: 100vw;
width: 100vw;
height: 100vh;
-webkit-animation: slidein 0.5s forwards;
-webkit-animation-delay: 10;
animation: slidein 0.5s forwards;
animation-delay: 10;;
}
.slideOutLeft {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
-webkit-animation: slideoutleft 0.5s forwards;
-webkit-animation-delay: 10;
animation: slideoutleft 0.5s forwards;
animation-delay: 10;
}
.slideOutRight {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
-webkit-animation: slideoutright 0.5s forwards;
-webkit-animation-delay: 10;
animation: slideoutright 0.5s forwards;
animation-delay: 10;
}
#-webkit-keyframes slidein {
100% { left: 0; }
}
#keyframes slidein {
100% { left: 0; }
}
#-webkit-keyframes slideoutleft {
100% { left: -100vw; opacity: 0 }
}
#keyframes slideoutleft {
100% { left: -100vw; opacity: 0}
}
#-webkit-keyframes slideoutright {
100% { left: 100vw; opacity: 0}
}
#keyframes slideoutright {
100% { left: 100vw; opacity: 0}
}
Passing in the next component, which is my react Page component, called like so:
app.js
<div id="app">
<PageSlider page={this.state.nextRoute} fromDir={this.state.fromDir}/>
</div>

CSS Transition timing of two separate elements off in Chrome

How can I fix this timing issue in chrome? In firefox, the closing animation goes smoothly but in chrome the two elements are off. I cannot place the elements in one container to transition.
notice the white border between the off canvas and the button in the slide. They are a bit off.
$(document).ready(function () {
var OffCanvasWidget = {
options: {
id: 'OffCanvasWidget',
trigger: $('[data-offcanvas-trigger]'),
close: $('[data-offcanvas-close]')
},
init: function () {
this.addListeners();
},
addListeners: function () {
var that = this;
this.options.trigger.on('click', function (event) {
event.preventDefault();
that.toggleOffCanvas();
});
},
toggleOffCanvas: function () {
$('body').toggleClass('offcanvas-show');
}
};
OffCanvasWidget.init();
});
.trigger {
transition: transform .7s ease;
display: inline-block;
transform: rotate3d(0, 0, 1, -90deg) perspective(1px) scale(1.001);
position: fixed;
right: -22px;
top: 30vh;
}
.offcanvas-show .trigger {
transform: translateX(-218px) rotate3d(0, 0, 1, -90deg) perspective(1px) scale(1.001);
}
.offcanvas {
transform: translateX(100%);
position: fixed;
transition: transform .7s ease;
top: 0;
right: 0;
bottom: 0;
left: auto;
width: 200px;
}
.offcanvas-show .offcanvas {
transform: translateX(0);
}
.trigger, .offcanvas {
background-color: black;
color: white;
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#" class="trigger" data-offcanvas-trigger>Follow us</a>
<div class="offcanvas">
</div>
Why not just make the 'tab' element a little wider so it 'underlaps' the tray enough so as to not notice any gap :)

Infinite CSS3 Animation when hover ends

I have some arrows image which are linked to kind of "Back to previous page".
I want to animate those arrows with a slight translation when hover it.
My trouble is when hover ends, the arrow instantly goes back to his initial state without any "transition".
Do you know how to do that ?
Thanks.
Here's my code :
#keyframes movingArrow {
0% {
transform: translateX(0);
}
50% {
transform: translateX(-10%);
}
100% {
transform: translateX(0);
}
}
.moving-arrow:hover img {
animation: movingArrow 1s infinite;
}
I tried to show demo for you at codepen https://codepen.io/navdeepsingh/pen/oEwyqP Have a look
const movingArrow = document.querySelector('.moving-arrow');
movingArrow.addEventListener('mouseover', function() {
this.classList.remove('no-animation');
});
.moving-arrow {
border: 1px solid;
border-color: #000 transparent #000 #000;
border-radius: 50%;
width: 100px;
height: 100px;
}
#keyframes movingArrow {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
#keyframes movingArrowReset {
0% {
transform: rotate(360deg);
}
100% {
transform: rotate(0);
}
}
/* This you need */
.moving-arrow {
animation: movingArrowReset 1s;
}
.moving-arrow:hover {
animation: movingArrow 1s infinite;
}
.no-animation {
-webkit-animation: none !important;
animation: none !important;
}
<div class="moving-arrow no-animation"></div>
Thanks for your help !
A friend helped me by pausing the animation when hover ends, it's not exactly what was targeted but this state is alright for me.
Using anime.js & this codepen
<body>
<h3 class="moving-arrow">← Back</h3>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.js"></script>
</body>
--
const movingArrow = document.querySelector('.moving-arrow');
let animation = anime({
targets: movingArrow,
translateX: '-1%',
direction: 'alternate',
duration: 500,
loop: true,
easing: 'easeInCubic',
autoplay: false
});
movingArrow.addEventListener('mouseover', () => {
console.log("mouseover")
animation.play()
})
movingArrow.addEventListener('mouseleave', () => {
console.log("onmouseleave", animation)
animation.pause()
})

How can I simultaneously apply multiple rotate transforms by toggling classes?

I'm trying to apply various transforms to a given element or set of elements by clicking buttons to toggle classes on and off. This works for most things, but not when I try to toggle on more than one rotate transform. Once an element has two separate rotate transform classes (rotateX, rotateZ), the transforms stop working as I expect.
Please see my example here: http://jsfiddle.net/XTVd7/9/
CSS
#box {
width: 100px;
height: 100px;
border: 1px solid blue;
transition: all 1s;
}
.flip {
-webkit-transform: rotateX(360deg);
-moz-transform: rotateX(360deg);
-ms-transform: rotateX(360deg);
-o-transform: rotateX(360deg);
}
.spin {
-webkit-transform: rotateZ(360deg);
-moz-transform: rotateZ(360deg);
-ms-transform: rotateZ(360deg);
-o-transform: rotateZ(360deg);
}
jQuery
$("#flipBtn").on("click", function() {
$("#box").toggleClass("flip");
});
$("#spinBtn").on("click", function() {
$("#box").toggleClass("spin");
});
I'd expect that I could flip or spin the box at any time by toggling either class on or off. This is not the case once both classes have been added to it.
If you want to toggle multiple values for the same property, you have to manually construct the property text (fiddle):
function createToggleFunction(element$) {
var transforms = {
flip: 'rotateX(120deg)',
spin: 'rotateZ(180deg)',
};
var state = {};
return function(transform) {
state[transform] ^= true;
var text = "";
for(var key in transforms) {
if(state[key]) text += transforms[key] + ' ';
}
element$.css('transform', text);
};
}
var toggle = createToggleFunction($('#box'));
$("#flipBtn").on("click", function() {
toggle('flip');
});
$("#spinBtn").on("click", function() {
toggle('spin');
});
A simpler solution would be to have two nested elements to represent the "box", with one of the transformations applying to the outer one and one applying to the inner one:
(See this edited version of jblasco's fiddle)
#spinner {
width: 100px;
height: 100px;
transition: all 1s;
}
#flipper {
width: 100%;
height: 100%;
transition: all 1s;
border: 1px solid blue;
}
HTML:
<div id="spinner">
<div id="flipper">
a
</div>
</div>
JQuery:
$("#flipBtn").on("click", function() {
$("#flipper").toggleClass("flip");
});
$("#spinBtn").on("click", function() {
$("#spinner").toggleClass("spin");
});

Resources