Search code examples
sassmedia-queriesscss-mixins

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.


Solution

  • 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.