I am trying to generate css class names using an #each loop in SASS and I am facing some confusion regarding the scope of a variable declared within the loop.
The variable $infix is meant to prefix the generated class name with a unique break-point-name ('sm','md','lg'...etc), only if the $break-point-width is not = 0
The problem is, the value of the $infix variable, ie, the break-point-name, is the same on each iteration of the loop, although I expected it would be unique per each iteration.
SCSS
$config: (
phone: (
break-point-width:0px,
break-point-name: xs
),
tablet: (
break-point-width:600px,
break-point-name: sm
),
laptop: (
break-point-width:900px,
break-point-name: md
),
desktop: (
break-point-width:1200px,
break-point-name:lg
),
);
#each $key, $map in $config {
$break-point-width: map_get($map, break-point-width);
$break-point-name: map_get($map, break-point-name);
$infix: if($break-point-width == 0px, null, -$break-point-name);
#media (min-width: $break-point-width) {
#for $i from 1 through 2 {
.foo#{$infix}-#{$i} {
content: 'bar';
}
}
}
}
Compiled CSS
#media (min-width: 0px) {
.foo-1 {
content: 'bar'; }
.foo-2 {
content: 'bar'; } }
#media (min-width: 600px) {
.foo-sm-1 {
content: 'bar'; }
.foo-sm-2 {
content: 'bar'; } }
#media (min-width: 900px) {
.foo-sm-1 {
content: 'bar'; }
.foo-sm-2 {
content: 'bar'; } }
#media (min-width: 1200px) {
.foo-sm-1 {
content: 'bar'; }
.foo-sm-2 {
content: 'bar'; } }
As seen in the compiled css output, the first iteration of the loop does not prefix the class name with the value of the $infix variable as was expected. In the remaining iterations all class names are prefixed with the 'sm' break-point-name. This is confusing, as it is seen the value of the $break-point-width variable is unique in each iteration, shown in the generated #media queries, but the value of the $break-point-name variable is the same in each iteration.
I am assuming the $infix variable, hence its value, is not scoped to each iteration of the loop and is only declared once.
I would really appreciate if someone could help me understand the cause of this behavior and how I could solve this problem.
With much thanks and appreciation,
Related
Based on this reply, I wrote:
$global-padding: 1em;
#media only screen and (max-width: 20em) {
$global-padding: 0.5em !global;
}
For some reason, the padding is always 0.5em, not only when the screen is at most 20em wide. What am I missing?
As far as I understand the documentation the global variable gets overwritten during compile time, as soon as the nested rule is processed - and not if any condition is met. This is not a runtime feature. All SCSS is compile to plain old CSS during compile time.
A variable declaration flagged as !global will always assign to the global scope.
So doing something like this:
$variable: foo;
.content {
$variable: bar !global;
value: $variable;
}
.sidebar {
value: $variable;
}
will produce
.content {
value: bar;
}
.sidebar {
value: bar;
}
What you are trying to do can be done with css custom variables:
:root {
--global-padding: 1em;
#media only screen and (max-width: 20em) {
--global-padding: 0.5em;
}
}
i am using the "vanilla" bootstrap 4 sass media query in my scss files:
#include media-breakpoint-up(xs){}
#include media-breakpoint-up(sm){}
#include media-breakpoint-up(lg){}
#include media-breakpoint-up(xl){}
i know that if i use the css width media query i can couple it with the orientation media query, but i want to use the sass framework.
I want to add the orientation media query in on of them, the XS one. thus it is specific. Because as you know bootsrap 4 is not supporting orientation query for now (strangely).
i tried to concatenat the "orientation query" with the "SASS bootstrap media query (xs)" in different way but i always have a sass error.
Thus What i did is to nest it in the SASS bootstrap media query (xs):
#include media-breakpoint-up(xs){
... some SCSS rules
#media (orientation: landscape){
header{
display:none !important;
}
.navbar{
display:none !important;
}
}
}
The problem i have even tought it is nested into the XS query is that it apply to all breakpoint. it s like it does nt take into account to be nested.
My question: how to concatenate the "orientation query" with the "SASS bootstrap media query (xs)"? Or how to make it specific to the XS breakpoint by nesting it.
Thank you
I've found the solution.
It's possible to combine sass mixin by nesting them, thus I've created the following mixin in my _mixins.scss file:
#mixin orientation($direction) {
$orientation-landscape: "(orientation:landscape)";
$orientation-portrait: "(orientation:portrait)";
#if $direction == landscape {
#media #{$orientation-landscape} { #content; }
}
#if $direction == portrait {
#media #{$orientation-portrait} { #content; }
}
}
Note: i didn't put the "and" in the variable value: "and (orientation:landscape)". SASS or bootstrap put it automatically i suppose.
Then in my SCCS file I've added the following rules:
#include media-breakpoint-down(sm) {
#include orientation(landscape) {
.path-frontpage header {
display: none !important;
}
.path-frontpage .navbar {
display: none !important;
}
}
}
Note: in my first post i was saying that the CSS rules I've nested was applied to all breakpoints, it s because when the CSS is generated the SASS Bootstrap 4 XS breakpoint of is not written, i suppose it's because the value is 0. thus the orientation media query was not combines with a min-width value. So i changed the value to a max-width instead of a min-width, as the Bootstrap 4 SM breakpoint have the 576px value.
The result in the CSS file is what i wanted:
#media (max-width: 767.98px) and (orientation: landscape) {
.path-frontpage header {
display: none !important;
}
.path-frontpage .navbar {
display: none !important;
}
}
I hope it will help the community.
I use this outside of Bootstrap. You should be able to use it with Bootstrap or any other framework, giving you more flexibility in your media queries.
// Extra map functions by Hugo Giraudel
#function map-deep-get($map, $keys...) {
#each $key in $keys {
$map: map-get($map, $key);
}
#return $map;
}
#function map-has-keys($map, $keys...) {
#each $key in $keys {
#if not map-has-key($map, $key) {
#return false;
}
}
#return true;
}
#function map-has-nested-keys($map, $keys...) {
#each $key in $keys {
#if not map-has-key($map, $key) {
#return false;
}
$map: map-get($map, $key);
}
#return true;
}
These are extra map functions Hugo Giraudel wrote up. map-deep-get is basically a simplified nested map-get function. map-has-keys is just like map-has-key, which is built-in to sass, but checks for multiple keys. map-has-nested-keys expands on that by checking for nested keys. This is crucial for this method. I'd definitely look into the extra Sass functions he's built. I've quite easily found use for just about all of them.
// Map
$sizes: (
null: (
breakpoint: 0,
container: 100%
),
xs: (
breakpoint: 480px,
container: 464px
),
sm: (
breakpoint: 768px,
container: 750px
),
md: (
breakpoint: 992px,
container: 970px
),
lg: (
breakpoint: 1200px,
container: 1170px
)
);
This is a simple breakpoint map. I usually use this as a base map for all settings on my projects, so I'll include things like base font-sizes and whatnot in it.
// Breakpoint mixin
#mixin break($screen-min: null, $screen-max: null, $orientation: null) {
$min: $screen-min;
$max: $screen-max;
$o: $orientation;
$query: unquote("only screen");
#if $min != null and $min != "" {
#if map-has-nested-keys($base, sizes, $screen-min) {
$min: map-deep-get($base, sizes, $screen-min, breakpoint);
}
#else {
$min: $screen-min;
}
#if is-number($min) {
$query: append($query, unquote("and (min-width: #{$min})"));
}
}
#if $max != null and $max != "" {
#if map-has-nested-keys($base, sizes, $screen-max) {
$max: map-deep-get($base, sizes, $screen-max, breakpoint);
}
#else {
$max: $screen-max;
}
#if is-number($max) {
$query: append($query, unquote("and (max-width: #{$max})"));
}
}
#if $orientation == landscape or $orientation == portrait {
$o: $orientation;
$query: append($query, unquote("and (orientation: #{$o})"));
}
#else {
$o: null;
}
#media #{$query} {
#content;
}
};
Here's the mixin. You can use the keys from the sizes map (xs, sm, md, lg) for the first two arguments, or you can use custom values (like 30em). The third argument accepts either landscape or portrait. You could even customize the mixin the make l = landscape and p = portrait if you wanted.
Additionally, if you only wanted, for example, an orientation, you could pass the arguments (null, null, landscape).
For clarity, here's some examples:
#include break(null, md, landscape) {
...
}
#include break(null, null, landscape) {
...
}
#include break(md) {
...
}
#include break(null, md) {
...
}
#include break(480px) {
...
}
Output:
#media only screen and (max-width: 992px) and (orientation: landscape) {
...
}
#media only screen and (orientation: landscape) {
...
}
#media only screen and (min-width: 992px) {
...
}
#media only screen and (max-width: 992px) {
...
}
#media only screen and (min-width: 480px) {
...
}
I want to use a mixin to give me the option to pass additional selectors as parameters as needed, in addition to the selectors already established. But I’m getting an error here. Not sure what I'm missing, unless you just can't use variables inside of a loop without passing a value to each item and I'm going about this all wrong.
#mixin light-section-text( $selectors: null ) {
#if ( $selectors ) {
#for $i from 1 through length( $selectors ) {
#{nth( $selectors, $i )},
}
}
p,
address,
li {
color: $white;
}
}
The desired output of #include light-section-text( "body", "strong", "strong a" ); in this case would be:
body,
strong,
strong a,
p,
address,
li {
color: #fff; }
First, you can't directly pass the selectors list to the mixin function as it would cause $selectors to be the first string. So you have to first declare a list variable and then pass that variable to the function.
Second, you should simply use the Placeholders functionality offered by Sass which makes use of #extend and the % character.
%color{
color: white;
}
#mixin light-section-text( $selectors: null ) {
#if ( $selectors ) {
#for $i from 1 through length( $selectors ) {
#{nth( $selectors, $i )}{
#extend %color;
}
}
}
p,
address,
li {
#extend %color;
}
}
$list-variable: "body", "strong", "strong a";
#include light-section-text($list-variable);
Alternate Method
You don't even need to use the mixin function as this task can be handled by sass placeholder alone.
$white : white;
%color{
color: $white;
}
$list-variable: "body", "strong", "strong a", "p", "address", "li";
#each $item in $list-variable {
#{$item} {
#extend %color;
}
}
This is a hard question, so I am aware that no one may come up with solution, but that's the problem I really need to solve in my framework.
I have a screen() mixin written in SCSS, which takes $size as an argument, to return any #content wrapped in a media query.
The problem occurs when one element #includes multiple screen() mixins, because resulting media queries will overwrite each other in the same order as they were included. How can I make sure the resulting media queries will be rendered in the correct order (biggest screen to smallest), even if I forget to include them in the right order?
http://sassmeister.com/gist/951520fa83d1e1c69c9d
#mixin screen(
$size: null
){
#if $size == md {
#media (max-width: 1024px) {
#content;
}
}
#if $size == sm {
#media (max-width: 768px) {
#content;
}
}
#if $size == xs {
#media (max-width: 320px) {
#content;
}
}
}
/* output should be 1024, 768, 320 */
.screen {
&:before {
// this should be included as the Last one
#include screen(xs){
content: "xs";
}
#include screen(sm){
content: "sm";
}
// this should be included as the First one
#include screen(md){
content: "md";
}
}
}
I tried to solve that issue by creating placeholder selectors in the right order %media-sm{...}, %media-xs {...}..., and #extend them from the mixin, but #content can't be passed through the #extend directive.
Another solution is a hard one - create an array of keys - sizes, and values - #contents and render them from another function.
No. Sass only does exactly what you tell it to do. If you want your styles to appear in a specific order, write them in that specific order.
Might be easier to pass in the media width you are trying to target:
#mixin media($width) {
#media only screen and (max-width: $width) {
#content;
}
}
#include media(320px) {
background: red;
}
This question already has answers here:
How to assign to a global variable in Sass?
(2 answers)
Closed 6 years ago.
Is it possible to update a value in a sass map from within a mixin so that the change is saved globally?
Eg
$obj: (
init: false
)
#mixin set($map) {
#if map-get($obj, init) != true {
// mixin hasn't been called before
$map: map-set($map, init, true);
}
#else {
// mixin has been called before
}
}
.test {
#include set($obj);
// sets the init value to true
}
.test-2 {
#include set($obj);
// init value has already been set to true
}
I'm not sure if I understood what you are trying to do, but your code seems to be fine (haven't tested it though), excepting that there is no map-set function, but you can create one or just use map-merge (check here: http://oddbird.net/2013/10/19/map-merge/). I hope that helps.
#update 1: I think I got your question now, you want to pass the reference through the mixin, so if you have multiple maps, you can send the one you want to update to the mixin, I don't think this is possible though, because no reference is kept, if you need to update the variable you have to link directly to it, for exemple, this works (tested):
$obj: (
init: false
);
#mixin set($map) {
#if map_get($map, init) != true {
$obj: map-set($map, init, true) !global;
body {
background-color: #000;
}
} #else {
body {
background-color: #ff0000;
}
}
}
#include set($obj);
#include set($obj);
But if you reference to $map instead of $obj (in this line $obj: map-set($map, init, true) !global;), then a new global map (called $map), will be created. And every time you call the mixin again, it will be replaced by the map you sent as a parameter.
#update 2: I found a way to do it, but you have to keep a global 'map of maps', and every time you update this guy, you send the name of the map you want to update as parameter, so I came up with the following code, it's tested and working fine :)
#function map-set($map, $key, $value) {
$new: ($key: $value);
#return map-merge($map, $new);
}
$maps: (
obj1: (
init: false
),
obj2: (
init: false
),
);
#mixin set($prop) {
#if map_get(map_get($maps, $prop), init) != true {
$obj: map-set(map_get($maps, $prop), init, true);
$maps: map-set($maps, $prop, $obj) !global;
body {
background-color: #000;
}
} #else {
body {
background-color: #ff0000;
}
}
}
#include set(obj1); //black
#include set(obj2); //black
#include set(obj1); //red
#include set(obj2); //red
source: myself
Following on from #Paulo Munoz
Here is the solution
#function map-set($map, $key, $value) {
$new: ($key: $value);
#return map-merge($map, $new);
}
$extend : ();
$obj : (
margin: 0,
padding: 10
);
#mixin set($map, $name) {
#if map-has-key($extend, $name) {
map: has-key;
// call placeholder class
} #else {
$extend: map-set($extend, $name, true) !global;
map: does-not-have-key;
// create placeholder class
// call placeholder class
}
}
.test {
#include set($obj, test);
}
.test-2 {
#include set($obj, test);
}
which generates
.test {
map: does-not-have-key;
}
.test-2 {
map: has-key;
}