Advanced SCSS/SASS mixin/function - css

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.

Related

How to insert an ampersand conditionally in a SASS mixin?

Below is some sass Im trying out, The css generated is correct, Im just trying to reduce my mixin duplication. The two mixins genProps and genPropsAmp are exactly the same except that the Amp mixin uses the & to prefix the rule with &. I tried to just add another var to the original mixin $amp: false and prefix my rule with an if so it became #if $amp {&}.#{$class}-... but that didnt work nor did trying to have it as an interpolation. Is there away of conditionaly placing the ampersand so I can just reuse the one mixin?
SASS:
$max_size: 0;
$sub_suffixes: '', 'q', 'h', 't';
$sub_divs: '', '.25', '.5', '.75';
#mixin genProps($class, $props, $val_prefix: r, $max_size: $max_size, $sub_suffixes: $sub_suffixes) {
#for $i from 0 through $max_size {
#each $sub_suffix in $sub_suffixes {
.#{$class}-#{$i}#{$sub_suffix} {
#each $prop in $props {
#{$prop}: var(--#{$val_prefix}-#{$i}#{$sub_suffix});
}
}
}
}
}
#mixin genPropsAmp($class, $props, $val_prefix: r, $max_size: $max_size, $sub_suffixes: $sub_suffixes) {
#for $i from 0 through $max_size {
#each $sub_suffix in $sub_suffixes {
&.#{$class}-#{$i}#{$sub_suffix} {
#each $prop in $props {
#{$prop}: var(--#{$val_prefix}-#{$i}#{$sub_suffix});
}
}
}
}
}
.root{
#include genProps(f, font-size, a);
#include genPropsAmp(m, margin);
}
CSS:
/* without amp */
.root .f-0 {font-size: var(--a-0);}
.root .f-0q {font-size: var(--a-0q);}
/*...*/
/* with amp */
.root.m-0 {margin: var(--r-0);}
.root.m-0q {margin: var(--r-0q);}
/*...*/
You can use a ternary operator: if($amp, "&", "")
#mixin genProps($class, $props, $amp, $val_prefix: r, $max_size: $max_size, $sub_suffixes: $sub_suffixes) {
#for $i from 0 through $max_size {
#each $sub_suffix in $sub_suffixes {
#{if($amp, "&", "")}.#{$class}-#{$i}#{$sub_suffix} {
#each $prop in $props {
#{$prop}: var(--#{$val_prefix}-#{$i}#{$sub_suffix});
}
}
}
}
}
#mixin genProps($class, $props, $amp: false, $val_prefix: r, $max_size: $max_size, $sub_suffixes: $sub_suffixes) {
#for $i from 0 through $max_size {
#each $sub_suffix in $sub_suffixes {
#if $amp {
&.#{$class}-#{$i}#{$sub_suffix} {
#each $prop in $props {
#{$prop}: var(--#{$val_prefix}-#{$i}#{$sub_suffix});
}
}
} #else {
.#{$class}-#{$i}#{$sub_suffix} {
#each $prop in $props {
#{$prop}: var(--#{$val_prefix}-#{$i}#{$sub_suffix});
}
}
}
}
}
}
this is the best Iv got so far, not great as theres still a lot of duplication in the if elses but at least I only have one mixin now

Sass For Loop over Nth Items

I'm currently working on a SASS for loop to loop over nth images(50 for example). For every nth-of-type I'd like to increase the transition delay by 50ms. The starting point is 250ms and it seems that the for loop I currently have in the works is not incrementing by 50ms and remains at 250ms at all times.
$time: 250ms;
#for $i from 1 through 50 {
img:nth-of-type(#{$i}) {
transition-delay: $time(#{$i}) + 50ms;
}
}
If anyone has any suggestions or could lend a hand, that would be greatly appreciated. Thank you in advance.
If you're going to use a mixin, you can use a default argument
#mixin transitionDelay($default: 200) {
#for $i from 1 through 50 {
&:nth-of-type(#{$i}) {
transition-delay: #{($i * 50) + $default}ms;
}
}
}
Then include it with an argument...
.cool { #include transitionDelay(200); }
or without
.cool { #include transitionDelay; }
$time: 250;
#for $i from 1 through 50 {
img:nth-of-type(#{$i}) {
$itemType: $time + ($i - 1) * 50;
transition-delay: #{$itemType}ms;
}
}
You could probably achieve the same without a helper variable, but I think it makes things cleaner.
I changed some of the logic to accommodate my needs but here's a revised version of my loop.
#mixin transitionDelay {
#for $i from 1 through 50 {
&:nth-of-type(#{$i}) {
transition-delay: #{$i * 45}ms;
}
}
}

SASS and data attribute multiple

I have a problem with the nesting of SAS to make multiple selections, nose much about it, I hope you can help me and understand (because I do not write very good English).
SASS mixin:
#mixin data($x) {
$sel: &;
$collector: ();
#for $i from 1 through length($sel) {
$s: nth($sel, $i);
$last: nth($s, -1);
#if str-slice($last, -1) == "]" {
// if is just the bare attribute with no value, $offset will be -1, otherwise it will be -2
$offset: -1;
$current-x: $x;
#if str-slice($last, -2) == '"]' {
// this attribute already has a value, so we need to adjust the offset
$offset: -2;
} #else {
// no attribute value, so add the equals and quotes
$current-x: '="' + $x + '"';
}
$last: str-slice($last, 1, $offset - 1) + $current-x + str-slice($last, $offset);
$collector: append($collector, set-nth($s, -1, $last), comma);
} #else {
// following line will append $x to your non-attribute selector
$collector: append($collector, selector-append($s, $x), comma);
// the following line will not change your non-attribute selector at all
//$collector: append($collector, $s, comma);
}
}
#at-root #{$collector} {
#content;
}
}
SASS:
[data-content] {
#include data("content") {
background: black;
}
}
Output:
[data-content="content"] {
background: black;
}
The problem is I can not nest more than one item, for example does not work:
[data-content] {
#include data("content", "menu") {
background: black;
}
}
Output:
[data-content="content"],
[data-content="menu"] {
background: black;
}
Any way to solve?
You can always do something like this if you don't mind having to specify your selectors instead of passing them through as variables.
[data-content="content"], [data-content="menu"]{
#include data() {
background: black;
}
}

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

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);
}

Darkening a SCSS Mixin based on percentage

I have a sass mix-in for my angular to do list, that lightens the color of the app as you go, 1 being dark blue, 50 being white.
How can I go about making the hover make each item's background 50-60% darker?
My mixin:
#mixin shades-of-blue ( $count, $startcolor ) {
#for $i from 0 through $count {
$background-color: lighten( $startcolor, $i + $i );
.tasks:nth-of-type(#{$i}) {
background-color: $background-color;
}
}
}
#include shades-of-blue( 50, #2d89ef);
You should probably be using mix instead of lighten/darken, otherwise everything is white at around 26.
#mixin shades-of-blue ( $count, $startcolor ) {
#for $i from 0 through $count {
$background-color: mix(white, $startcolor, $i + $i);
.tasks:nth-of-type(#{$i}) {
background-color: $background-color;
&:hover {
background-color: mix(black, $background-color, 50);
}
}
}
}
#include shades-of-blue( 50, #2d89ef);
You can try it out here: http://sassmeister.com/

Resources