How can I have #if inside the value of a CSS property? - css

Is there a way to use Sass’ #if statement inside the value part of a property? I.e. make the following code work:
#mixin color-transition($color:true, $background:true, $border:true, $duration: 0.2s) {
transition:
#if ($color) {
color $duration linear,
}
#if ($background) {
background-color $duration linear,
}
#if ($border) {
border-color $duration linear,
}
;
}
I can’t move transition: inside of the if clause because in that case the last of them would overwirte the former …

You could simply concatenate the answer and print it afterwards...
#mixin color-transition($color:true, $background:true, $border:true, $duration: 0.2s) {
$transition: '';
#if ($color) {
$transition: $transition + 'color #{$duration} linear';
}
#if ($background) {
#if ($transition != ''){ $transition: $transition + ','; }
$transition: $transition + 'background-color #{$duration} linear';
}
#if ($border) {
#if ($transition != ''){ $transition: $transition + ','; }
$transition: $transition + 'border-color #{$duration} linear ';
}
transition: unquote($transition);
}
But of course, you could just simply do it like this:
#mixin color-transition($color:0s, $background:0s, $border:0s){
transition: color $color linear, background $background linear, border $border linear;
}
Which allows you to call them all in one go:
#include color-transition(1s,1s,2s);
With an output like this:
transition: color 1s linear, background 1s linear, border 2s linear;
Since the default is 0s, technically you have no transition set for those values, although it might overwrite preset values somewhere else, so I see why it might not be ideal, just an idea.

#mixin color-transition($color:true, $background:true, $border:true, $duration: 0.2s) {
$transition : '';
#if ($color) {
$transition : 'color #{$duration} linear,'
}
#if ($background) {
$transition : '#{$transition} background-color #{$duration} linear,'
}
#if ($border) {
$transition : '#{$transition} border-color #{$duration} linear,'
}
#if (str-length($transition) > 0) {
transition: unquote(str-slice($transition, 1, -2));
}
}
Then you can #include color-transition() in any CSS rule. Tested.
Edit
Now the property isn't showed if there is no transition, and bugs fixed.

You can't use a control directive in the middle of a statement like that. You have to append a string or list variable inside of your #if statements and then write out the finished property at the very end.
Your specific use case would be best solved by passing in a list of the properties you want to modify:
#mixin color-transition($duration: 0.2s, $properties...) {
$collector: ();
// optional
#if length($properties) == 0 {
$properties: color, background-color, border-color;
}
#each $p in $properties {
$collector: append($collector, $p $duration linear, comma);
}
transition: $collector;
}
Usage:
.foo {
#include color-transition();
}
.bar {
#include color-transition(0.2s, color);
}
.buzz {
#include color-transition(0.2s, background-color, border-color);
}
Output:
.foo {
transition: color 0.2s linear, background-color 0.2s linear, border-color 0.2s linear;
}
.bar {
transition: color 0.2s linear;
}
.buzz {
transition: background-color 0.2s linear, border-color 0.2s linear;
}
Variation to allow for optional duration:
#mixin color-transition($properties...) {
// optional
#if length($properties) == 0 {
$properties: color, background-color, border-color;
}
$duration: 0.2s; // default value
$collector: ();
#each $p in $properties {
#if type-of($p) == number {
$duration: $p;
} #else {
$collector: append($collector, $p $duration linear, comma);
}
}
transition: $collector;
}
.foo {
#include color-transition(color);
}
As a quirky side-effect, you can specify different durations of each property:
.bar {
#include color-transition(color, 0.3, background);
}

Related

How I can make animation for background?

I have a menu form. To add and remove items from this menu, I use React Transition Group
ReactJS:
<TransitionGroup>
{
menu.map(meal =>
<CSSTransition
key={meal.id}
timeout={500}
classNames="meMeals"
>
<Meal meal={meal} deleteFromMenu={deleteMealFromMenu}/>
</CSSTransition>
)
}
</TransitionGroup>
CSS:
.meMeals-enter {
opacity: 0;
transform: translateY(-30px);
}
.meMeals-enter-active {
opacity: 1;
transform: translateY(0);
transition: all 500ms ease-in;
}
.meMeals-exit {
opacity: 1;
transform: translateY(0);
}
.meMeals-exit-active {
opacity: 0;
transform: translateY(-30px);
transition: all 500ms ease-in;
}
and I am completely satisfied with the behavior of the menu items.
Now I want the background element (grey) as well as the add button to move smoothly as the menu item appears or disappears. How can i do this?
I solved the problem by writing a method that is not directly related to the TransitionGroup, but works in parallel. I also set my window:
transition: 0.5s;
whatever the animation
Now I call this method every time the list changes....
function replaceMenuSize(value) {
const menuSize = menuEditorRef.current.getBoundingClientRect().height
if (value > 0) {
menuEditorRef.current.setAttribute("style", "height: " + (menuSize + 41) + "px")
} else {
menuEditorRef.current.setAttribute("style", "height: " + (menuSize - 41) + "px")
}
}

How can I animate from an alternating keyframe animation to a fixed value

Is it possible for an element that is being animated using keyframes to animate to a fixed value for that property? My use case is a draggable element whose scale is pulsing until clicked, but when clicked it should have a fixed size. I would like the element to animate to that new size.
This example uses styled-components.
const pulse = keyframes`
from {
transform: scale(1);
}
to {
transform: scale(1.05);
}
`
const AnimatedThing = styled(div)`
${({ isDragging }) =>
isDragging ?
'transform: scale(1.2);' :
`animation: ${pulse} 0.5s alternate infinite;`
}
`
As you can see in the code, when isDragging is true, scale is set to 1.2. Right now it just jumps to that value, but I want it to animate to that value.
I tried using css variables and animating those, and couldn't get that to work either. That code is a bit more complex but you can see it here
const pulse = keyframes`
from {
transform: scale(var(--pulse-start-scale));
}
to {
transform: scale(var(--pulse-end-scale))
}
`
const AnimatedGroup = styled(div)`
#property --pulse-start-scale {
syntax: '<number>';
inherits: false;
initial-value: 1;
}
#property --pulse-end-scale {
syntax: '<number>';
inherits: false;
initial-value: 1.05;
}
transition: --pulse-start-scale --pulse-end-scale 1s;
animation: ${pulse} 0.5s alternate infinite;
${({ $active }) =>
$active &&
`
--pulse-end-scale: 1.2;
--pulse-start-scale: 1.2;
`}
`

What is wrong with ReactCSSTransitionGroup?

I have this code inside render() function in my component:
..
<ReactCSSTransitionGroup
transitionName="testing"
transitionEnterTimeout={600}
transitionLeaveTimeout={600}>
<div>testing animations!</div>
</ReactCSSTransitionGroup>
..
And i have this code in my CSS file:
.testing-enter {
animation: slideIn 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.testing-leave {
animation: slideOut 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
}
#keyframes slideIn {
0% {
opacity 0
transform translate3d(-50px, 0, 0)
}
100% {
opacity 1
transform translate3d(0, 0, 0)
}
}
#keyframes slideOut {
0% {
opacity: 1
transform: translate3d(0, 0, 0)
}
100% {
opacity: 0
transform: translate3d(50px, 0, 0)
}
}
I just want my div block to slideIn from the right, but nothing happens! Could not find the wrong piece of code, it looks like everything is ok.
You have static content inside the <ReactCSSTransitionGroup/> tag. You need to have dynamic content. Let's say you need to render <div>testing animations!</div> upon mouse click. You need to assign the children to a variable and modify the variable upon mouse click.
let myDiv = buttonClicked ? <div>testing animations!</div> : <div></div>
<ReactCSSTransitionGroup
transitionName="testing"
transitionEnterTimeout={600}
transitionLeaveTimeout={600}>
{myDiv}
</ReactCSSTransitionGroup>
If no actions are performed and you simply need to animate it on initial mount, use transition-appear instead.
ReactCSSTransitionGroup provides the optional prop transitionAppear,
to add an extra transition phase at the initial mount of the
component. There is generally no transition phase at the initial mount
as the default value of transitionAppear is false. The following is an
example which passes the prop transitionAppear with the value true.
<ReactCSSTransitionGroup transitionName="example" transitionAppear={true} transitionAppearTimeout={500}>
<div>testing animations!</div>
</ReactCSSTransitionGroup>
.example-appear {
opacity: 0.01;
}
.example-appear.example-appear-active {
opacity: 1;
transition: opacity .5s ease-in;
}

Advanced SCSS/SASS mixin/function

I've made a pretty nice chaining effect, that I would love to turn into a mixin, or function, but I can't wrap my head around how to build it. Searched everywhere but I can't assemble the puzzle.
The output should look like this
{
opacity: 0;
transform: translateY(3em);
#keyframes moveUp {
from {
opacity: 0;
transform: translateY(3em);
} to {
opacity: 1;
transform: translateY(0);
}
}
.inview ~ & {
animation: moveUp 1s forwards;
#for $i from 1 through 20 {
&:nth-child(#{$i}) {
animation-delay: (0.1 * $i) + s
}
}
}
}
My current attempt (doesn't compile), looks like this:
#mixin inviewChainAnimation($animationName, $from, $to, $duration, $delay, $count:20) {
$from;
#keyframes #{$animationName} {
from {
$from;
}
to {
$to
}
}
.inview ~ & {
animation: #{$animationName} #{$duration} forwards;
#for $i from 1 through #{$count} {
&:nth-child(#{$i}) {
animation-delay: (#{$delay} * $i) + s
}
}
}
}
How can I get two objects ($from and $to) passed through a function. Is that even possible?
Have you tried SASS-maps to render the declarations? For example:
$mapFrom: (opacity: 0, transform: translateY(3em));
$mapTo: (opacity: 1, transform: translateY(0));
And then in your mixin using of #each directive:
#each $key, $value in $from {
#{$key}: #{$value};
}
But then there is another problem. If I try to parse the modified mixin, I get the following error:
Error: "20" is not an integer.
on line 22 of test.scss, in `inviewChainAnimation'
from line 34 of test.scss
The error occurs at this line:
#for $i from 1 through #{$count} {
To solve this change #{count} to $count. The same for #{$delay}. That's it. Here is the final working mixin:
#mixin inviewChainAnimation($animationName, $from, $to, $duration, $delay, $count: 20) {
#each $key, $value in $from {
#{$key}: #{$value};
}
#keyframes #{$animationName} {
from {
#each $key, $value in $from {
#{$key}: #{$value};
}
}
to {
#each $key, $value in $to {
#{$key}: #{$value};
}
}
}
.inview ~ & {
animation: #{$animationName} #{$duration} forwards;
#for $i from 1 through $count {
&:nth-child(#{$i}) {
animation-delay: ($delay * $i) + s
}
}
}
}
Use of the mixin:
.container {
#include inviewChainAnimation('foo', $mapFrom, $mapTo, .15, .1);
}
If you have the need to pass only one set of css properties, you can use #content to simplify your mixin. See for an example at Passing css property:value into a mixins argument.

How to make a CSS3 animation reusable?

Without JQuery.
I have the following code but it will only work on one click. It will not be able to be used twice:
#-moz-keyframes lulse {
0%{
-moz-transform:scale(1);
}
20%{
-moz-transform:scale(1.5);
}
100% {
-moz-transform:scale(1);
}
}
.pinto {
-moz-animation-name: lulse;
-moz-animation-duration: .2s;
-moz-animation-iteration-count: 1;
-moz-animation-timing-function: linear;
-moz-animation-direction: normal;
}
Javascript:
onclick="changeClass(this.id);
function changeClass(a)
{
document.getElementById(a).className += "pinto";
}
Hmm, on the surface it looks like you should be able to remove the class at the beginning of your click handler if it exists. Specifically:
function changeClass(a) {
var elementA = document.getElementById(a);
var regEx = new RegExp('(\\s|^)pinto(\\s|$)');
elementA.className = elementA.className.replace(regEx,' ');
elementA.className += " pinto";
}
This will remove the class and reapply it, which should restart the effect.

Resources