Are conditional transitions possible with ngAnimate? - css

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{}

Related

Why wont animation reset and play again after class is added dynamically to the element for the second time

this code changes the background of the div every time a button is pressed but the new background is supposed to fade in so I set an animation. If you press red, and then another color, it makes the transition the first time, but not from the second on. The background will just appear without animation.
function changit(color) {
document.getElementById('cont').classList = color;
}
#keyframes appear {
0% {opacity:0;}
100% {opacity:1;}
}
#cont {
position: relative;
width: 100px;
height: 100px;
display: block;
}
#cont::before {
width: 93%;
height: 100%;
position: sticky;
content: "";
display: block;
}
.red::before, .blue::before, .black::before { transition: opacity 2s ease;animation:appear 2s; }
.red::before {
background: url(https://cdn.sstatic.net/Img/teams/teams-illo-free-sidebar-promo.svg) no-repeat;
}
.blue::before {
background: url(https://www.gravatar.com/avatar/24780fb6df85a943c7aea0402c843737?s=64&d=identicon&r=PG) no-repeat;
}
.black::before {
background:url(https://cdn.sstatic.net/Sites/stackoverflow/Img/subcommunities/intel.svg?v=0371bf2f3b96) no-repeat;
}
<div id="cont">
</div>
<button onclick="changit('red');">make red</button>
<button onclick="changit('blue');">make blue</button>
<button onclick="changit('black');">make black</button>
isnt the animation supposed to play again if the element changes class? How can I reset the animation to start again every time the element changes class to achieve this effect?
The animation property value actually never changes, with all the classes it's always the same and there is no point in time where it's unset, so it won't fire again.
You need to force the CSS engine sees that it did change. For this you can remove the class altogether force what is called a "reflow", which is when the CSS engine recalculates all the element's boxes in the page, and then only, set the class back:
function changit(color) {
const elem = document.getElementById('cont');
elem.classList = "";
elem.offsetWidth; // force reflow
elem.classList = color;
}
function changit(color) {
const elem = document.getElementById('cont');
elem.classList = "";
elem.offsetWidth; // force reflow
elem.classList = color;
}
#keyframes appear {
0% {opacity:0;}
100% {opacity:1;}
}
#cont {
position: relative;
width: 100px;
height: 100px;
display: block;
}
#cont::before {
width: 93%;
height: 100%;
position: sticky;
content: "";
display: block;
}
.red::before, .blue::before, .black::before { transition: opacity 2s ease;animation:appear 2s; }
.red::before {
background: url(https://cdn.sstatic.net/Img/teams/teams-illo-free-sidebar-promo.svg) no-repeat;
}
.blue::before {
background: url(https://www.gravatar.com/avatar/24780fb6df85a943c7aea0402c843737?s=64&d=identicon&r=PG) no-repeat;
}
.black::before {
background:url(https://cdn.sstatic.net/Sites/stackoverflow/Img/subcommunities/intel.svg?v=0371bf2f3b96) no-repeat;
}
<div id="cont">
</div>
<button onclick="changit('red');">make red</button>
<button onclick="changit('blue');">make blue</button>
<button onclick="changit('black');">make black</button>
Or, since you're using JS anyway, use the WebAnimations API:
function changit(color) {
const elem = document.getElementById('cont');
elem.classList = color;
elem.animate(
[ { opacity: 0 }, { opacity: 1 } ],
{ duration: 2000, repeat: 1 }
);
}
function changit(color) {
const elem = document.getElementById('cont');
elem.classList = color;
elem.animate(
[ { opacity: 0 }, { opacity: 1 } ],
{ duration: 2000, repeat: 1 }
);
}
#cont {
position: relative;
width: 100px;
height: 100px;
display: block;
}
#cont::before {
width: 93%;
height: 100%;
position: sticky;
content: "";
display: block;
}
.red::before {
background: url(https://cdn.sstatic.net/Img/teams/teams-illo-free-sidebar-promo.svg) no-repeat;
}
.blue::before {
background: url(https://www.gravatar.com/avatar/24780fb6df85a943c7aea0402c843737?s=64&d=identicon&r=PG) no-repeat;
}
.black::before {
background:url(https://cdn.sstatic.net/Sites/stackoverflow/Img/subcommunities/intel.svg?v=0371bf2f3b96) no-repeat;
}
<div id="cont">
</div>
<button onclick="changit('red');">make red</button>
<button onclick="changit('blue');">make blue</button>
<button onclick="changit('black');">make black</button>

::-webkit-scrollbar is it possible to make the scrollbar-thumb grow instead moving?

I would like my scroll to work like this when the user scrolls. e.g to start to fill up instead of moving.
Is it possible to make the scroll-thumb grow or to style the scrollbar-track-piece different before and after the thumb?
Here is a small example how to implement this loader
window.addEventListener("scroll", (e) => {
var html = document.documentElement;
let step = 100 / (html.scrollHeight - window.innerHeight);
let loader = document.getElementById("scrollprogress");
loader.style.width = (step * html.scrollTop) + "%";
})
#scrollprogress {
height: 5px;
position: fixed;
left: 0;
top: 0;
background: orange;
}
.backgr {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 5px;
background: lightgrey;
z-index: -1;
}
.box {
height: 3000px;
}
<div id="scrollprogress"></div>
<div class="backgr"></div>
<div class="box"></div>
You can approximate this using negative box shadow:
body::-webkit-scrollbar {
width: 1em;
}
body::-webkit-scrollbar-thumb {
background-color: orange;
box-shadow:-1000vmax -1000vmax 0px 1000vmax orange;
}
body {
width:300vw;
height:300vh;
background:linear-gradient(60deg,red,blue,orange);
margin:0;
}
html {
background:#fff;
}

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.

React and CSS Transitions, am I missing something?

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.

Setting the opacity (maybe using 'background' property) when form is selected

I want an overlay of the whole screen except the form so people can focus on it.
This solution would be ideal, but the form won't change back.
body {
opacity: .4
}
.form:focus {
opacity: 1 /* crap, doesn't work */
}
This doesn't really work
some-element {
background: rgba(0, 0, 0, .4)
}
You can use a bit of javascript to select and interact with the parent of your inputs, as well as the overlay. You can toggle active class so you can manage your CSS with actual CSS, which I find cleaner.
$(document).ready(function() {
var overlay = $('#overlay');
$('input').focusin(function(){
$(this).parent().addClass('active');
overlay.addClass('active');
});
$('input').focusout(function(){
$(this).parent().removeClass('active');
overlay.removeClass('active');
});
});
form {
position: relative;
background-color: white;
padding: 20px;
}
form.active {
z-index: 100;
}
#overlay {
position: absolute;
top: 0; left: 0;
width: 100%;
height: 100%;
display: none;
z-index: 0;
background-color: rgba(0,0,0,0.5);
}
#overlay.active {
display: block;
z-index: 10;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<form>
<input type="text" name="" value="">
</form>
<div id="overlay"></div>
</body>

Resources