I'm trying to write a function that creates a grid layout based on multiple arrangements (as opposed to just the 12 column grid) in Sass (scss syntax). The for loop inside works properly on its own, but when I wrap it in a function it no longer works. I'm new to using Sass functions, so maybe I'm messing up the syntax? Or is this just not possible? I'm just trying to avoid having to write a new for loop for each layout I want to achieve. Thanks in advance.
#function create-grid($num-cols) {
#for $col from 1 through $num-cols {
.col-#{$col}-of-#{$num-cols} {
width: percentage($col / $num-cols);
}
}
}
Sass functions return a value.
If you want to generate css programatically like this, you want to use a mixin.
https://www.sitepoint.com/sass-basics-the-mixin-directive/
#mixin grid($num-cols) {
#for $col from 1 through $num-cols {
.col-#{$col}-of-#{$num-cols} {
width: percentage($col / $num-cols);
}
}
}
#include grid(12)
For anyone who stumbles across this post, the final product is:
#mixin create-grid($num-cols, $col-gutter) {
#for $col from 1 through $num-cols {
.col-#{$col}-of-#{$num-cols} {
width: calc(
((100% - ((#{$num-cols} - 1) * #{$col-gutter})) / #{$num-cols}) *
#{$col} +
((#{$col} - 1) * #{$col-gutter})
);
}
}
}
Thanks a lot to #HorusKol for introducing me to the world of mixins!
Related
I am trying to convert some mixins from less to sass.
I am not very skilled in either of these languages, so I'm not sure at all if I'm doing it right.
The original Less mixins:
.for(#i, #n) {.-each(#i)}
.for(#n) when (isnumber(#n)) {.for(1, #n)}
.for(#i, #n) when not (#i = #n) {
.for((#i + (#n - #i) / abs(#n - #i)), #n);
}
.for(#array) when (default()) {.for-impl_(length(#array))}
.for-impl_(#i) when (#i > 1) {.for-impl_((#i - 1))}
.for-impl_(#i) when (#i > 0) {.-each(extract(#array, #i))}
I have identified three main issues:
1) how do I translate guards in sass? Is it correct to gather mixins with the same number of arguments in one single mixin and write conditional blocks inside it?
2) how does when(default()) works? I've been trying to find a good explanation in the documentation but couldn't find any.
3) is there a function in sass which is equivalent to less extract?
Thank you very much!
First thing first, the mixin that you've given in question is one that was created by seven-phases-max to imitate the for and for-each loops because Less doesn't have built-in functions for them. Unlike Less, Sass already has built-in #for and #each directives to perform looping and so I'd recommend you to not spend time on converting these Less mixins to Sass.
Below are Sass samples for a for and a for-each loop:
For:
.column {
#for $i from 1 through 5 { /* execute the loop 5 times */
&-#{$i} { /* create .column-1 to .column-5 using selector interpolation */
width: 20%;
left: (($i - 1) / 5 * 100%);
}
}
}
For Each:
$country-code-list: US, IN, FR, SG; /* list for iteration */
.flag {
#each $country-code in $country-code-list { /* iterate for each value in the list */
&-#{$country-code} { /* create .flag-US, .flag-IN etc using selector interpolation */
background-image: url(http://yoursite.com/#{$country-code}.png);
}
}
}
You can try out the above samples # SassMeister.com and see the compiled output.
Now coming to your questions,
1) how do I translate guards in sass? Is it correct to gather mixins with the same number of arguments in one single mixin and write conditional blocks inside it?
The Less guards would translate to #if (conditional statements) in Sass. Below is an example:
#mixin list-style($val) {
#if ($val == i) { list-style-type: lower-roman; }
#else if ($val == I) { list-style-type: upper-roman; }
#else { list-style-type: decimal };
}
#demo { #include list-style(2); }
The second part of your question is probably opinion based as each one would have their own way of doing things. I'd personally prefer grouping them inside one mixin and writing conditional blocks.
2) how does when(default()) works?
The default() guard in Less is like the typical else or default: (in switch-case) statements. The mixin with this as guard gets called only when no other mixin with the same name and the same no. of arguments is matched.
For example, have a look at the below code. Here when the value passed as argument to the mixin is anything other than i or I neither of the first two mixin guards are matched and so those mixins do not get executed. In such a case, the third one (the default()) will get executed and set the list style type as decimal.
.mixin-list-style(#val) when (#val = i) { list-style-type: lower-roman; }
.mixin-list-style(#val) when (#val = I) { list-style-type: upper-roman; }
.mixin-list-style(#val) when (default()) { list-style-type: decimal; }
#demo {
.mixin-list-style(1); /* this will result in list-style-type: decimal */
}
3) is there a function in sass which is equivalent to less extract?
Yes, there is a nth() function in Sass which is equivalent to the Less extract() function. Below is a sample:
$country-code-list: US, IN, FR, SG;
.flag-FR {
$country-code: nth($country-code-list, 3); /* extract 3rd item from country-code-list */
background-image: url(http://yoursite.com/#{$country-code}.png);
}
Note: You don't need this with #each loop because Sass automatically extracts the item and assigns to the variable for each iteration.
I would like to know, if in sass is allowed to write a recursion mixin like this Less example :
.size($val) when ($val < 200 ) {
width: $val;
height: $val;
.size($val + 50px);
}
I'm trying something like this, but i don't have any output
#mixin test($val) {
width: $val;
height: $val;
#for $i from 1 through 5 {
#include test($val);
}
}
.block {
#include test(5);
}
Unlike your LESS example, your Sass mixin does not have a way to break out of the loop.
#mixin test($val) {
width: $val;
height: $val;
#if $val > 0 {
#include test($val - 1);
}
}
In sass, you can create recursive functions, but your example is wrong.
Looking at your code, I see that you are in infinite loop, because $var variable is not used in the #for iterator. You only iterates from 1 to 5 infinitely.
The recursive approach here is the same as another languages, you have to use the parameter to call the function again, and when this variable is the same as 0 (for example), returns a value ... its value is used to the last call of the function to set the new value ... and again the first step.
Here are examples of Sass recursive functions:
https://gist.github.com/paramburu/9613303
http://hugogiraudel.com/2013/08/08/advanced-sass-list-functions/#section-3
Hope it helps.
Regards.
I've stumbled across a situation where a SASS loop would be great. I have a load of <div>'s, each one has the a unique class that follows the pattern of .band-(number), a simplified version of my HTML would look like this...
<div class="band-1"></div>
<div class="band-2"></div>
<div class="band-3"></div>
etc.
Each of these elements has a unique background-image, but the naming convention of the image follows that of the divs themselves. My CSS needs to output this...
.band-1 {
background-image:url('../img/image_01.png');
}
.band-2 {
background-image:url('../img/image_02.png');
}
.band-3 {
background-image:url('../img/image_03.png');
}
How would I go about outputting this in a concise way? Thanks in advance.
You can solve your problem with a for loop:
#for $i from 1 through 15 {
#if $i < 10 {
.band-#{$i} {
background-image:url("../img/image_0#{$i}.png");
}
} #else {
.band-#{$i} {
background-image:url("../img/image_#{$i}.png");
}
}
}
DEMO
I have an error in my sass file, I've got no idea what it's not working...
Here's the message I get from calling .class{ col(6);
error scss/components.scss (Line 18: Invalid CSS after "...gin:7px 0; col": expected "{", was "(6);")
Here's the function and variables used to create the function (sorry if it's a bit confusing):
$columnWidth:40;
$numberOfColumns:16;
$gutterWidth: 20;
$fullWidth:($columnWidth * $numberOfColumns) + ($gutterWidth * $numberOfColumns);
#function perc($target) {
#return (($target / $fullWidth) * 100%);
}
#function gw($n, $fluid: false) {
$calculatedValue: ($n * $columnWidth + ($n - 1) * $gutterWidth);
#if $fluid == false {
#return $calculatedValue + px;
} #else {
#return perc($calculatedValue);
}
}
#function col($n, $fluid: false){
#return unquote("width: gw($n, $fluid);");
}
All im trying to do, is re use the gw() function, so that I can use it in css to output the number of columns as a width css property, ie grid(4); would output width: 200px;.
the function gw works because it generates my grid css properly, however I want to define a global function to use everywhere. Hence the col() function.
A mixin is like a function, but does not return anything other than its body, which can be full blown SCSS.
So you can fix this by making col a mixin, like so:
#mixin col($n, $fluid: false){
width: gw($n, $fluid);
}
Then you can call:
.class{ #include col(6) };
Which produces:
.class {
width: 340px; }
Functions can only be used to return simple types (string, color, number, etc.) or lists of simple types. It cannot be used to return a property/value (eg. "color: red"): all you can ever get when you do this is a string or a list of strings (and strings cannot be used this way). Mucking around with lists of strings is more work for you than simply using a mixin.
#function foo() {
#return color red;
}
.foo {
$kv: foo();
#{nth($foo, 1)}: #{nth($foo, 2)}
}
Compare to:
#mixin foo() {
color: red;
}
.foo {
#include foo;
}
There's nothing inherently non-reusable about your gw() function (in fact, it is more reusable than the impossible function you're dreaming of): it returns a numeric value that can be used for any property. In fact, using it in place of a mixin or a function that returns a list of strings is the most efficient way of going about what you're looking for:
.foo {
width: gw(1);
}
I try to iron out the sub-pixel rounding error for quite a while now, but so far i've failed miserably again and again. I try to accomplish that endeavour with the help of Sass and Susy. The mixin i've used in my last try i got from the Susy issue tracker on Github (i've used space, columns as well as push on the margin-left/right property like suggested there):
#mixin isolate($location, $context: $columns, $dir: 'ltr') {
#if $dir == 'ltr' {
margin-right: -100%;
margin-left: space($location, $context);
}
#else if $dir == 'rtl' {
margin-left: -100%;
margin-right: space($location, $context);
}
}
My Scss looks like the following:
.imggrid{
#include with-grid-settings($gutter: 0.1%){
$y:2;
li{
#include span-columns(2,12);
#for $x from 1 through 30
{
&:nth-child(#{$x}){
#include isolate($y,12);
$y: $y + 2;
#if $y > 12{
$y: 2;
}
}
}
#include nth-omega(6n);
}
}
}
First i've created a custom gutter for the image grid. Then i've defined a variable y to iterate up in the steps of two to be able to call the isolate mixin (isolate(2,12) isolate (4,12) etc). For values larger than 12 the value gets reset to two within the for loop in the end. Then i span a column for each li walking through the 30 images. Each time calling the iterating isolate mixin. After the for loop i've appended the #include nth-omega(6n); to get a new line after each sixth element.
But somehow it doesn't work at all. The first four rows are missing the first image and on the last row the first five elements are missing. Any ideas and suggestions are appreciated. Thanks Ralf
UPDATE: I adjusted these mixins to be 1-indexed rather than 0-indexed. I think that is the more common.
You're close, but the math isn't quite right. It get's a bit complex to keep everything straight, so I wrote up a mixin to take care of it for you. I also adjusted the isolate mixin so that it uses the existing Susy $from-direction variable:
#mixin isolate($location, $context: $total-columns, $from: $from-direction) {
$to: opposite-position($from);
margin-#{$to}: -100%;
margin-#{$from}: space($location - 1, $context);
}
#mixin isolate-list($columns, $context: $total-columns, $from: $from-direction) {
$position: 1;
$line: floor($context / $columns);
#include span-columns($columns, $context, $from: $from);
#for $item from 1 through $line {
$nth: '#{$line}n + #{$item}';
&:nth-child(#{$nth}) {
#include isolate($position, $context, $from);
#if $position == 1 { clear: $from; }
$position: $position + $columns;
#if $position > $context { $position: 1; }
}
}
}
Use it just like span-columns, width & context. That's all there is too it:
.imggrid{
li{
#include isolate-list(4,12);
}
}
That should work with any width, in any context, for any number of list items. Cheers!