Access #mixin variable outside of #mixin - sass

So I've got a #mixinthat lets me define my logo width and gives me an automatic height based on the ratio of my logo image.
#mixin m-logoSize($width) {
width: $width;
height: floor($width / 4.72727272727273);
}
I'm trying to find out if there's a way for me to access the $width variable, so I can assign it to a different property like so.
// sass
.logo {
#include m-logoSize(300px);
background-size: $width $height;
}
// translates to this css
.logo {
width: 300px;
height: 63px;
background-size: 300px 63px;
}
Is there any wizardry that makes this possible. Any help is appreciated.
Thanks in advance!

I think you are better off setting global variables for these two values, then you can reach them anywhere.
Example
// Global variables
$logo-width: 0 !default;
$logo-height: floor($logo-width / 4.72727272727273) !default;
// Mixin
#mixin m-logoSize() {
width: $logo-width;
height: $logo-height;
}
// Sass
$logo-width: 300px;
.logo {
#include m-logoSize();
background-size: $logo-width $logo-height;
}
Update
To change the value at different breakpoints you would just set the variable in the scope of the media query.
Responsive Example
.logo {
#media screen and (max-width: 600px) {
$logo-width: 150px;
#include m-logoSize();
background-size: $logo-width $logo-height;
}
#media screen and (min-width: 601px) {
$logo-width: 300px;
#include m-logoSize();
background-size: $logo-width $logo-height;
}
}

Related

SCSS: Can I only #include in response to a successful #media?

With SCSS files, I am attempting to make a responsive layout by setting variables in response to an #media query.
I currently have two #media queries, and I was hoping that only one of them would proceed to call the #mixin, with the map defined for the specific situation: mobile or desktop.
My code is :
$page-header-height : 45px; // some dummy defaults
$page-subheader-height: 45px;
$page-footer-height : 50px;
$mobile-varmap : (
"page-header-height" : 50px,
"page-subheader-height": 50px
);
$desktop-varmap : (
"page-header-height" : 90px,
"page-subheader-height": 120px
);
#mixin setvariables($map) {
$page-header-height: map-get($map, "page-header-height") !global;
$page-subheader-height: map-get($map, "page-subheader-height") !global;
$page-footer-height: 50px;
}
$screen-size-mobile: 360px;
$screen-size-tablet: 600px;
#media screen and (min-width:$screen-size-mobile) {
body {
#include setvariables($mobile-varmap);
}
}
#media screen and (min-width:$screen-size-tablet) {
body {
#include setvariables($desktop-varmap);
}
}
div.page-wrapper {
display: grid;
grid-template-areas: 'page-header''page-subheader''page-content''page-footer';
grid-template-columns: auto;
grid-template-rows: $page-header-height $page-subheader-height 1fr $page-footer-height;
max-height: calc(100vh - 55px); // TODO: use variables to calc these
min-height: calc(100vh - 50px);
overflow: none;
}
I had expected that this would lead to $page-header-height, etc, being set according to the matching #media query, but the result is that whichever #media query appears last in the code, determines the values which are produced.
What would I need to do in order to call setvariables() with the varmap that corresponds to the screen size?
NB: I only added the body tags to the #media queries in response to viewing some examples - I'm not sure that they are correcty used or indeed necessary.
Your code is fine, but as i understand it, you expect the variables set via #include setvariables($desktop-varmap); to dynamically respond to device width... which will not happen because the two blocks with the media query do not render anything, the variables are just changed at compile time.
A possible way to build what you want is to have only one configuration map which then could be iterated over like
#mixin setvariables($map) {
$page-header-height: map-get($map, "page-header-height") !global;
$page-subheader-height: map-get($map, "page-subheader-height") !global;
$page-footer-height: 50px !global;
}
$responsive-vars: (
mobile: (
min-width: 360px,
page-header-height: 50px,
page-subheader-height: 50px
),
desktop: (
min-width: 600px,
page-header-height: 90px,
page-subheader-height: 120px
)
);
#each $alias, $map in $responsive-vars {
#media screen and (min-width: map-get($map, 'min-width')) {
#include setvariables($map);
div.page-wrapper {
display: grid;
grid-template-areas: 'page-header''page-subheader''page-content''page-footer';
grid-template-columns: auto;
grid-template-rows: $page-header-height $page-subheader-height 1fr $page-footer-height;
max-height: calc(100vh - 55px); // TODO: use variables to calc these
min-height: calc(100vh - 50px);
overflow: none;
}
}
}
However this is not the most practical approach, since mostly you want to tweak only some values and have other declarations for all devices. Instead, i'd recommend to drop the setvariables() mixin entirely and access the maps directly like:
// https://css-tricks.com/snippets/sass/deep-getset-maps/#deep-get
#function map-deep-get($map, $keys...) {
#each $key in $keys {
$map: map-get($map, $key);
}
#return $map;
}
$responsive-vars: (
mobile: (
min-width: 360px,
page-header-height: 50px,
page-subheader-height: 50px
),
desktop: (
min-width: 600px,
page-header-height: 90px,
page-subheader-height: 120px
)
);
div.page-wrapper {
display: grid;
grid-template-areas: 'page-header''page-subheader''page-content''page-footer';
grid-template-columns: auto;
max-height: calc(100vh - 55px); // TODO: use variables to calc these
min-height: calc(100vh - 50px);
overflow: none;
#media screen and (min-width: map-deep-get($responsive-vars, 'mobile', 'min-width')) {
grid-template-rows:
map-deep-get($responsive-vars, 'mobile', 'page-header-height')
map-deep-get($responsive-vars, 'mobile', 'page-subheader-height')
1fr
map-deep-get($responsive-vars, 'mobile', 'page-footer-height');
}
#media screen and (min-width: map-deep-get($responsive-vars, 'desktop', 'min-width')) {
grid-template-rows:
map-deep-get($responsive-vars, 'desktop', 'page-header-height')
map-deep-get($responsive-vars, 'desktop', 'page-subheader-height')
1fr
map-deep-get($responsive-vars, 'desktop', 'page-footer-height');
}
}
This may look like a lot of code at first glance, but it renders far less css.

Translating media queries from LESS to SCSS

I'm trying to update some old LESS style sheets into SCSS but am running into an issue translating media queries.
The LESS variables are as follows:
#lg: ~"(min-width: 1201px)";
#md: ~"(min-width: 993px)";
#sm: ~"(min-width: 769px)";
#xs: ~"(max-width: 768px)";
And are referenced in this manner:
.modal-dialog {
margin: 100px auto;
#media #md {
width: 880px;
}
}
I've translated this into the following SCSS variables:
$lg: "#{min-width: 1201px}";
$md: "#{min-width: 993px}";
$sm: "#{min-width: 769px}";
$xs: "#{max-width: 768px}";
and the following usage:
.modal-dialog {
margin: 100px auto;
#media ($md) {
width: 880px;
}
}
The code in question belongs to a .Vue component and is being compiled using SASS Loader FWIW. The code compiles (no errors) but the actual queries don't seem to be working. Thanks!
In SCSS / SASS the recommended approach to storing full media queries as variables is as follows:
$lg: "(min-width: 1201px)";
.modal-dialog {
margin: 100px auto;
#media #{$lg} {
width: 880px;
}
}
Which compiles to
.modal-dialog {
margin: 100px auto;
}
#media (min-width: 1201px) {
.modal-dialog {
width: 880px;
}
}

Styles for multiple breakpoints in SASS

I've defined two mixins in SASS that allow me to place media queries easily. The problem I'm encountering is that I'm repeating myself frequently across many queries. That is to say, some of my style changes are the same for tablet and mobile breakpoints and others are different. Example:
.foo
float: left
width: 50%
+tablet()
float: none
display: block
width: 100%
+mobile()
float: none
display: block
width: 100%
Where my mixins are defined like this:
=tablet
#media (min-width: #{$mobile-width} + 1) and (max-width: #{$tablet-width})
#content
=mobile
#media (max-width: #{$mobile-width})
#content
I'd love to do something like this:
...
+tablet(), +mobile
float: none
display: block
width: 100%
That doesn't compile, so what is the best way to keep my SASS stylesheets DRY?
You can define mobile and tablet medias as strings and then concatenate these strings.
Scss can be easily converted to sass.
$mobile-width: 320px;
$tablet-width: 760px;
// Media queries as strings
$tablet: "(min-width: #{$mobile-width + 1}) and (max-width: #{$tablet-width})";
$mobile: "(max-width: #{$mobile-width})";
// Converts a list to a string with delimiters between elements
#function join-list($list, $separator: ", ") {
$result-string: "";
#each $item in $list {
// Index of the current item of `$list` list
$index: index($list, $item);
$result-string: $result-string + $item;
// If this is not the last item, adds separator
#if ($index != length($list)) {
$result-string: $result-string + $separator;
}
}
#return $result-string;
}
#mixin get-media($medias...) {
#media #{join-list($medias, " and ")} {
#content;
}
}
.foo {
float: left;
width: 50%;
#include get-media($mobile, $tablet) {
// or #include get-media($mobile) {
// or #include get-media($tablet) {
float: none;
display: block;
width: 100%;
}
}
Css output:
.foo {
float: left;
width: 50%;
}
#media (max-width: 320px) and (min-width: 321px) and (max-width: 760px) {
.foo {
float: none;
display: block;
width: 100%;
}
}
SassMeister demo.
As per #Stefan F's comment, the easiest thing to do in this case was to create a third mixin called (something like): +both() which encapsulated the mobile and tablet sizing. (I'm answering this myself only because he did not and it has been some time.)
Example:
=both
#media (max-width: #{$tablet-width})
#content
Usage:
.foo
float: left
width: 50%
+both()
float: none
display: block
width: 100%

Media queries in Sass

I am wondering if there is a way to write media queries in sass, so I can give a certain style between let's say: 300px to 900px
in css it looks like this
#media only screen and (min-width: 300px) and (max-width: 900px){
}
I know I can write
#media (max-width: 900px)
in sass but how to make that range?
$small: 300px;
$medium: 900px;
.smth {
//some CSS
#media screen and (max-width: $small) {
//do Smth
}
#media screen and (min-width: $medium) {
//do Smth
}
}
Something like this?
This is what I use for a Mixin with sass, it allows me to quickly reference the breakpoint that I want. obviously you can adjust the media query list to suite your project mobile fist etc.
But it will jin multiple queries for you as I believe you're asking for.
$size__site_content_width: 1024px;
/* Media Queries */ Not necessarily correct, edit these at will
$media_queries : (
'mobile' : "only screen and (max-width: 667px)",
'tablet' : "only screen and (min-width: 668px) and (max-width: $size__site_content_width)",
'desktop' : "only screen and (min-width: ($size__site_content_width + 1))",
'retina2' : "only screen and (-webkit-min-device-pixel-ratio: 2) and (min-resolution: 192dpi)",
'retina3' : "only screen and (-webkit-min-device-pixel-ratio: 3) and (min-resolution: 288dpi)",
'landscape' : "screen and (orientation:landscape) ",
'portrait' : "screen and (orientation:portrait) "
);
#mixin for_breakpoint($breakpoints) {
$conditions : ();
#each $breakpoint in $breakpoints {
// If the key exists in the map
$conditions: append(
$conditions,
#{inspect(map-get($media_queries, $breakpoint))},
comma
);
}
#media #{$conditions} {
#content;
}
}
Use it like this in your scss:
#masthead {
background: white;
border-bottom:1px solid #eee;
height: 90px;
padding: 0 20px;
#include for_breakpoint(mobile desktop) {
height:70px;
position:fixed;
width:100%;
top:0;
}
}
Then this will compile to:
#masthead {
background: white;
border-bottom: 1px solid #eee;
height: 90px;
padding: 0 20px;
}
#media only screen and (max-width: 667px), only screen and (min-width: 1025px) {
#masthead {
height: 70px;
position: fixed;
width: 100%;
top: 0;
}
}
Check this out for scss.
https://github.com/Necromancerx/media-queries-scss-mixins
Usage
.container {
#include xs {
background: blue;
}
#include gt-md {
color: green
}
}
Demo: Stackblitz
Based on Angular FlexLayout MediaQueries
$small: 300px;
$medium: 900px;
#media screen and (min-width: $small) and (max-width: $medium) {
//css code
}

Finding the height of multiple elements in SCSS [duplicate]

I'm trying to combine the use of a Sass variable with #media queries as follows:
$base_width:1160px;
#media screen and (max-width: 1170px) {$base_width: 960px;}
#media screen and (min-width: 1171px) {$base_width: 1160px;}
$base_width is then defined at various points in the stylesheet width percentage-based measurements to produce fluid layouts.
When I do this, the variable seems to be recognized properly but the conditions for the media query are not. For example, the above code produces an 1160px layout regardless of screen width. If I flip-flop the #media statements like so:
#media screen and (min-width: 1171px) {$base_width: 1160px;}
#media screen and (max-width: 1170px) {$base_width: 960px;}
It produces a 960px layout, again regardless of screen width. Also note that if I remove the first line of $base_width: 1160px; it returns an error for an undefined variable. Any ideas what I'm missing?
This is simply not possible. Since the trigger #media screen and (max-width: 1170px) happens on the client-side.
Achieving your expected result would only be possible if SASS grabbed all rules and properties in your stylesheet containing your $base_width variable and copied/changed them accordingly.
Since it won't work automatically you could do it by hand like this:
#media screen and (max-width: 1170px)
$base_width: 960px // you need to indent it to (re)set it just within this media-query
// now you copy all the css rules/properties that contain or are relative to $base_width e.g.
#wrapper
width: $base_width
...
#media screen and (min-width: 1171px)
$base_width: 1160px
#wrapper
width: $base_width
...
This is not really DRY but the best you can do.
If the changes are the same every time you could also prepare a mixin containing all the changing values, so you wouldn't need to repeat it. Additionally you can try to combine the mixin with specific changes. Like:
#media screen and (min-width: 1171px)
+base_width_changes(1160px)
#width-1171-specific-element // additional specific changes, that aren't in the mixin
display: block
And the Mixin would look like this
=base_width_changes($base_width)
#wrapper
width: $base_width
Similar to Philipp Zedler's answer, you can do it with a mixin. That lets you have everything in a single file if you want.
#mixin styling($base-width) {
// your SCSS here, e.g.
#Contents {
width: $base-width;
}
}
#media screen and (max-width: 1170px) {
#include styling($base-width: 960px);
}
#media screen and (min-width: 1171px) {
#include styling($base-width: 1160px);
}
This isn't possible with SASS, but it is possible with CSS variables (or CSS custom properties). The only drawback is browser support – but there's actually a PostCSS plugin - postcss-css-variables - that "flattens" the use of CSS variables (which gives you support for older browsers, too).
The following example works great with SASS (and with postcss-css-variables you get support for older browsers too).
SCSS
$mq-laptop: 1440px;
$mq-desktop: 1680px;
:root {
--font-size-regular: 14px;
--gutter: 1rem;
}
// The fact that we have to use a `max-width` media query here, so as to not
// overlap with the next media query, is a quirk of postcss-css-variables
#media (min-width: $mq-laptop) and (max-width: $mq-desktop - 1px) {
:root {
--font-size-regular: 16px;
--gutter: 1.5rem;
}
}
#media (min-width: $mq-desktop) {
:root {
--font-size-regular: 18px;
--gutter: 1.75rem;
}
}
.my-element {
font-size: var(--font-size-regular);
padding: 0 calc(var(--gutter) / 2);
}
This would result in the following CSS. The repetitive media queries will increase the file size, but I have found that the increase is usually negligible once the web server applies gzip (which it will usually do automatically).
CSS
.my-element {
font-size: 14px;
padding: 0 calc(1rem / 2);
}
#media (min-width: 1680px) {
.my-element {
padding: 0 calc(1.75rem / 2);
}
}
#media (min-width: 1440px) and (max-width: 1679px) {
.my-element {
padding: 0 calc(1.5rem / 2);
}
}
#media (min-width: 1680px) {
.my-element {
font-size: 18px;
}
}
#media (min-width: 1440px) and (max-width: 1679px) {
.my-element {
font-size: 16px;
}
}
Edit: Please do not use this solution. The answer by ronen is much better.
As a DRY solution, you can use the #import statement inside a media query, e.g. like this.
#media screen and (max-width: 1170px) {
$base_width: 960px;
#import "responsive_elements";
}
#media screen and (min-width: 1171px) {
$base_width: 1160px;
#import "responsive_elements";
}
You define all responsive elements in the file included using the variables defined in the media query. So, all you need to repeat is the import statement.
With #ronen's great answer and a map, there's some real power available:
#mixin styling($map) {
.myDiv {
background: map-get($map, 'foo');
font-size: map-get($map, 'bar');
}
}
#media (min-height: 500px) {
#include styling((
foo: green,
bar: 50px
));
}
#media (min-height: 1000px) {
#include styling((
foo: red,
bar: 100px
));
}
It's now possible to have lots more DRY media queries targeting .myDiv with a bunch of different values.
Map docs: https://sass-lang.com/documentation/functions/map
Example map usage: https://www.sitepoint.com/using-sass-maps/
I had the same problem.
The $menu-width variable should be 240px on the mobile view #media only screen and (max-width : 768px) and 340px on the desktop view.
So i have simply created two variables:
$menu-width: 340px;
$menu-mobile-width: 240px;
And here is how i have used it:
.menu {
width: $menu-width;
#media only screen and (max-width : 768px) {
width: $menu-mobile-width;
}
}
Two recommendations
1
Write your "default" CSS statements to be for small screens and only use media queries for larger screens. There's usually no need for a max-width media query.
Example (assuming the element has class "container")
#mixin min-width($width) {
#media screen and (max-width: $width) {
#content;
}
}
.container {
width: 960px;
#include min-width(1170px) {
width: 1160px;
}
}
2 Use CSS variables to solve the problem, if you can.
#mixin min-width($width) {
#media screen and (max-width: $width) {
#content;
}
}
:root {
--container-width: 960px;
#include min-width(1170px) {
--container-width: 1160px;
}
}
.container {
width: var(--container-width);
}
Note:
Since it will have the width of 1160px when the window has a width of 1170px, it may be better to use a width of 100% and max-width of 1160px, and the parent element might have a horizontal padding of 5px, as long as the box-sizing property is set to border-box. There are a lot of ways to solve the problem. If the parent is not a flex or grid container you might use .container { margin: auto }.
This is also possible with %placeholders.
%placeholders can be wrapped in media queries. So you could set up multiple variables to use at different screen sizes, and then the placeholders would automagically pre-process accordingly. I'm using some mixins to shorten my media query declarations here also.
In your _vars.scss file:
$width-1: 960px;
$width-2: 1160px;
In your _placeholders.scss file:
%variable-site-width {
#media screen and (max-width: 1170px) { width: $width-1; }
#media screen and (min-width: 1171px) { width: $width-2; }
}
In your page.scss file:
.wrapper. { #extend %variable-site-width; background: red; etc... }
And this will compile to something similar to:
#media screen and (max-width: 1170px) {
.wrapper { width: 960px; }
}
#media screen and (min-width: 1171px) {
.wrapper { width: 1160px; }
}
Voila!
I use this technique extensively for things like variable font sizes and a raft of other things.

Resources