How do you remove units of measurement from a Sass mixin equation? - ruby

I've written a very simple Sass mixin for converting pixel values into rem values (see Jonathan Snook's article on the benefits of using rems). Here's the code:
// Mixin Code
$base_font_size: 10; // 10px
#mixin rem($key,$px) {
#{$key}: #{$px}px;
#{$key}: #{$px/$base_font_size}rem;
}
// Include syntax
p {
#include rem(font-size,14);
}
// Rendered CSS
p {
font-size: 14px;
font-size: 1.4rem;
}
This mixin works quite well, but I'm a bit unsatisfied with the include syntax for it. See, I would much rather pass a pixel value into the include statement instead of a simple number. This is a small detail, but it would add semantic meaning to the include statement that currently doesn't exist. Here's what I get when I try to pass a pixel value into the include statement:
// Include syntax
p {
#include rem(font-size,14px);
}
// Rendered CSS
p {
font-size: 14pxpx;
font-size: 1.4pxrem;
}
Once Sass sees a pixel value being passed into an equation, it outputs the 'px'. I want to strip that unit of measure out as if I were using parseFloat or parseInt in JavaScript. How does one do so inside of a Sass mixin?

Here is a function you could use. Based of Foundation global helper functions.
#function strip-unit($num) {
#return $num / ($num * 0 + 1);
}
Sample use:
... Removed for brevity ...
#mixin rem( $key: font-size, $val ) {
#{$key}: strip-unit( $val ) * 1px;
#{$key}: ( strip-unit( $val ) / $base-font-size ) * 1rem;
}

Removing units from a number is done by division, just like in Algebra.
$base-font-size: 10px;
$rem-ratio: $base-font-size / 1rem;
#mixin rem($key,$px) {
#{$key}: $px;
#{$key}: $px/$rem-ratio;
}
// Include syntax
p {
#include rem(font-size,14px);
}
// Rendered CSS
p {
font-size: 14px;
font-size: 1.4rem;
}
Units in Sass can be treated as in real math, so px/px/rem goes to just rem. Enjoy it!

Related

Passing multiple arguments in SASS Mixin to output set of classes or single class

$base-space: 1rem !default;
$space-map : (
'1o9': $base-space,
'1o8': $base-space/8,
'1o4': $base-space/4,
'1o2': $base-space/2,
) !default;
#mixin containers($new-space-map) {
#each $name, $value in $new-space-map {
.container--#{$name}{
margin: $value 0;
}
}
}
#include containers($space-map);
This outputs 4 CSS classes.
I am trying to generate all 4 classes or only one class based on the name I pass as argument.
my output needs to be:
.container--1o9 {
margin: 1rem 0;
}
.container--1o8 {
margin: 0.125rem 0;
}
.container--1o4 {
margin: 0.25rem 0;
}
.container--1o2 {
margin: 0.5rem 0;
}
Or
.container--1o2 {
margin: 0.5rem 0;
}
based on the argument I pass.
To explain further; I am trying trying to use #include on same SASS Map in both the ways mentioned below depending on my need:
#include containers(<SASS map file>); //This I achieved
or
#include containers(<any key from SASS Map>); //This I cant
I am stuck here and not sure if this is achievable or not. Any help will be very much appreciated.
Remove the !Default. This will work as you expect it to
Link to sassmeister
https://www.sassmeister.com/gist/2839eaf64bd441e73a92aed6bb980465
Remember all 4 classes are being compiled into your CSS file. Just use the one you want

Passing arguments from a mixin to a content block

This open issue in the Sass queue seems to imply passing arguments to #content is not a feature yet, but Susy 2 seems to be able to do this. Tracking down how it's done is a bit of a rabbit hole though and I haven't figured it out yet. Perhaps someone can shed some light with a straightforward example? I want to create a custom mixin that will inherit a layout passed from susy-breakpoint() using a custom map.
Example: Defining a 4 column layout in a global Sass map, will return a width of 100% when a span of 4 is specified inside susy-breakpoint()'s #content. When a custom layout of 8 cols is passed to directly tosusy-breakpoint() via the $layout argument, the nested span() mixin picks up the new layout. But a custom nested mixin will not pick up the new layout. Why?
#import 'susy';
$susy: (
columns: 4,
);
#mixin inherit-layout($layout: 4) {
columns: $layout;
}
#include susy-breakpoint(30em) {
// nested code uses an 4-column grid from global map
.global-cols {
#include span(4);
#include inherit-layout();
}
}
#include susy-breakpoint(48em, $layout: 8) {
// nested code uses an 8-column grid from $layout
.inherited-cols {
#include span(4);
#include inherit-layout();
}
}
Compiled CSS:
#media (min-width: 30em) {
.global-cols {
width: 100%;
float: left;
margin-left: 0;
margin-right: 0;
columns: 4;
}
}
#media (min-width: 48em) {
.inherited-cols {
width: 48.71795%;
float: left;
margin-right: 2.5641%;
columns: 4;
}
}
Update:
I've discovered that making the default variable for the inherit-value() mixin the value of columns key on the existing $susy map allows the mixin to grab context. But why? And why doesn't it work with a different map or outside of susy-breakpoint()?
See here: http://sassmeister.com/gist/d86e217aca3aa8337b83
Susy doesn't pass any arguments to the #content — instead, we change the global variable at the start of the content block, and then change it back at the end:
$example: 4;
#mixin local($local) {
$old: $example;
$example: $local !global;
#content
$example: $old !global;
}
// out here, $example == 4
#include local(12) {
// in here, $example == 12
}

How to preserve units with SASS computed variable?

I try to create some relative font-size values that match "nearly" pixels sizes:
html { font-size: 62.5%; }
$font-10px: 1em/1.1em;
$font-11px: 1.1em/1.1em;
$font-12px: 12em/11em;
.x10 { font-size: $font-10px; }
.x11 { font-size: $font-11px; }
.x12 { font-size: $font-12px; }
However, the output of this sass snipet is:
.x10 {
font-size: 0.90909;
}
.x11 {
font-size: 1;
}
.x12 {
font-size: 1.09091;
}
As you can see, the unit (em) has been stripped.
This results in a incorrect value.
How should I declare my variables to contains the correct unit?
Dividing one length by another length always results in the unit being removed if the lengths are using the same units. So your options are:
Divide using one length and one integer: 1.1em / 1.1
Multiply the unit back on afterwards: 1.1em / 1.1em * 1em
Don't use division at all: 1em
Add PX to the end of your variable and surround the variable with #{ }. This is known as interpolation #{ } and treats the variable as plain css. Interpolation also helps to remove any spaces between the number and unit of measurement:
$base-font-size: 16;
body { font-size: (62.5% * base-font-size); }
$font-10px: 1em/1.1em;
$font-11px: 1.1em/1.1em;
$font-12px: 12em/11em;
.x10 { font-size: #{$font-10px}px; }
.x11 { font-size: #{$font-11px}px; }
.x12 { font-size: #{$font-12px}px; }
Result:
.x10 {
font-size: 0.90909px;
}
.x11 {
font-size: 1px;
}
.x12 {
font-size: 1.09091px;
}
Since you mentioned accessability in the SA talk, I recommend that you add one of the mixins in this blog post by Hugo Giraudel to your project to allow the use REM units while also providing backwards compatibility for older browsers.

Passing a variable from inside a mixin declaration into the attached content block?

In Ruby, you can easily pass a variable from inside a method into the attached code block:
def mymethod
(1..10).each { |e| yield(e * 10) } # Passes a number to associated block
end
mymethod { |i| puts "Here comes #{i}" } # Outputs the number received from the method
I would like to do the same thing in SASS mixin:
=my-mixin
#for $i from 1 to 8
.grid-#{$i}
#content
+my-mixin
color: nth("red green blue orange yellow brown black purple", $i)
This code won't work because $i is declared inside the mixin declaration and cannot be seen outside, where the mixin is used. :(
So... How do i leverage variables declared inside the mixin declaration?
When i work with a grid framework and media queries, i need this functionality badly. Currently i have to duplicate what's inside the mixin declaration every time i need it, violating the DRY rule.
UPD 2013-01-24
Here's a real-life example.
I have a mixin that cycles through breakpoints and applies the provided code once for every breakpoint:
=apply-to-each-bp
#each $bp in $bp-list
+at-breakpoint($bp) // This is from Susy gem
#content
When i use this mixin i have to use this $bp value inside #content. It could be like this:
// Applies to all direct children of container
.container > *
display: inline-block
// Applies to all direct children of container,
// if container does not have the .with-gutters class
.container:not(.with-gutters) > *
+apply-to-each-bp
width: 100% / $bp
// Applies to all direct children of container,
// if container has the .with-gutters class
.container.with-gutters > *
+apply-to-each-bp
$block-to-margin-ratio: 0.2
$width: 100% / ($bp * (1 + $block-to-margin-ratio) - $block-to-margin-ratio)
width: $width
margin-right: $width * $block-to-margin-ratio
&:nth-child(#{$bp})
margin-right: 0
But this won't work, because the value of $bp is not available inside #content.
Declaring the variable before calling the mixin won't help, because #content is parsed once and before the mixin is parsed.
Instead, EACH time i need that, i have to do two ugly thighs:
declare an ad-hoc mixin,
write the cycle, violating the DRY principle:
// Each of the following mixins is mentioned in the code only once.
=without-gutters($bp)
width: 100% / $bp
=with-gutters($bp)
$block-to-margin-ratio: 0.2
$width: 100% / ($bp * (1 + $block-to-margin-ratio) - $block-to-margin-ratio)
width: $width
margin-right: $width * $block-to-margin-ratio
&:nth-child(#{$bp})
margin-right: 0
// Applies to all direct children of container
.container > *
display: inline-block
// Applies to all direct children of container,
// if container does not have the .with-gutters class
.container:not(.with-gutters) > *
#each $bp in $bp-list
+at-breakpoint($bp) // This is from Susy gem
+without-gutters($bp)
// Applies to all direct children of container,
// if container has the .with-gutters class
.container.with-gutters > *
#each $bp in $bp-list // Duplicate code! :(
+at-breakpoint($bp) // Violates the DRY principle.
+with-gutters($bp)
So, the question is: is there a way to do this Ruby-style?
Variables in Sass have scope to them. They're only visible in the block they were created in. If you want the variable to be accessible both inside and outside of the mixin, it has to be defined in the global scope:
$var: 0;
#mixin test {
$var: $var + 1;
color: red;
}
.test {
$var: 5;
#include test;
#debug $var; // DEBUG: 6
}
As long as you don't care about the state of $var for very long, this should work out ok for your purposes.
For your example, this won't work because it looks like the #content is processed first. What you need is a mixin that's written differently:
#mixin test($properties...) {
#for $i from 1 to 8 {
.grid-#{$i} {
#each $p in $properties {
$list: nth($p, 2);
#if length($list) > 1 {
#{nth($p, 1)}: nth($list, $i);
} #else {
#{nth($p, 1)}: $list;
}
}
#content;
}
}
}
.test {
#include test(color (red green blue orange yellow brown black purple));
}
The generated CSS:
.test .grid-1 {
color: red;
}
.test .grid-2 {
color: green;
}
.test .grid-3 {
color: blue;
}
.test .grid-4 {
color: orange;
}
.test .grid-5 {
color: yellow;
}
.test .grid-6 {
color: brown;
}
.test .grid-7 {
color: black;
}
A mixin like this can be fed any number of arguments and still allows you to use #content if you wish.
I have run into this problem myself and AFAIK this is a current limitation in SASS.
So this is currently unavailable in Sass.
There's a relevant ticket in the Sass issue queue: https://github.com/nex3/sass/issues/871 It's in the planned state but will probably not make it until at least Sass 4.0.

Passing a list to a mixin in SASS as parameters?

I'm trying to make a SCSS stylesheet easily configurable by defining a set of constants that will be used in a number of mixins and with the Compass library. Ideally, I'd like to be able to do the following:
$item-bgs: linear-gradient(white, black), #ccc;
#mixin some-mixin() {
#include background-with-css2-fallback($item-bgs*);
}
The background-with-css2-fallback is a Compass mixin that accepts up to 10 params. I'm assuming that SASS does not currently support passing a list parameter as the argument list, otherwise Compass would probably use it, but I'm wondering if I can get the $item-bgs list to be the first 2 arguments to the background-with-css2-fallback mixin. Is there a way to do this currently, or is it even planned for SASS in the future?
It may not be supported by SASS natively, but Compass does support passing a list as the first argument to the background-with-css2-fallback mixin. If you look at the source for the mixin, you'll see that it uses a compact function that handles the logic for collapsing the arguments into a single list, whether passed individually or in a single list parameter.
For example, this works fine for me:
#import "compass";
$item-bgs: (linear-gradient(white, black), #ccc);
.test {
#include background-with-css2-fallback($item-bgs);
}
Examples of useing maps as arguments:
Example 1 (list)
#mixin transition($property...){
#if $property {
transition-property: $property;
}
#else {
transition-property: all;
}
transition-timing-function: ease-in-out;
transition-duration: .3s;
transition-delay: 0;
}
.btn {
color: black;
border: 1px solid black;
#include transition(color, border-color);
&:hover {
color: red;
border-color: red;
}
}
Example 2 (Custom params)
#use 'sass:meta';
#mixin example2($args...) {
#each $key, $value in meta.keywords($args) {
#{$key}: #{$value};
}
}
.shape {
#include example2($width:200px, $height:100px);
}
Example 3 (map)
#mixin colors($args:()) {
#if length($colors) > 0 {
#each $key, $val in $args{
.txt-#{$key} {
color: #{$value};
}
.bg-#{$key} {
background-color: #{$value};
}
}
}
}
$colors_map: (
primary: blue,
secondary: green,
accent: red,
light: white,
dark: black
);
#include colors($colors_map);

Resources