Extending within the @media directive
See original GitHub issueExtending within the @media directive
As this topic seems to pop-up quite often I decided to see if I could make it work without having to bother nex3 and all you other champs more than I’ve already done.
Here is what I came up with: Please comment 😃
Best, Jakob E
Note! mediaExtend causes deprecation warning mediaExtend Back-to-Hack-Edition added below
Mixins and config
// ====================================================================
// Breakpoints
// Define a global list of breakpoints
// (key, min-width, max-width),...
// Note! zero will omit the query
// ====================================================================
$breakpoints:
(all,0,0)
,(desktop,769,0)
,(tablet,481,768)
,(mobile,0,480);
// ====================================================================
// Media
// Prints out a media query from the breakpoints list
// ====================================================================
@mixin media($key){ // @content
$bp:null;
@for $i from 1 through length($breakpoints){
$item:nth($breakpoints,$i);
@if(nth($item,1)==$key){ $bp:$item; }
}
$min:nth($bp,2) * 1px;
$max:nth($bp,3) * 1px;
$mq:'screen and (min-width:'+$min+') and (max-width:'+$max+')';
@if($min and $max==0px){ $mq:'screen and (min-width:'+$min+')'; }
@if($min==0px and $max){ $mq:'screen and (max-width:'+$max+')'; }
@if($min==0px and $max==0px){ $mq:'all'; }
@media #{unquote($mq)}{ @content; }
}
// ====================================================================
// MediaExtend
// Places each extend within the each media directive of the breakpoint list
// ====================================================================
@mixin mediaExtend(){ // @content
@for $i from 1 through length($breakpoints){
$key:nth(nth($breakpoints,$i),1);
@include media($key){ @content; };
}
}
How to use
// ====================================================================
// Create the media extends.
// ... to test the output just add a regular extend
// @include mediaExtend(){ .test{ content:'test'}}
//
@include mediaExtend(){
%red { color:red; }
%blue { color:blue; }
%green { color:green; }
%yellow { color:yellow; }
}
// ====================================================================
// Using media as wrapper
//
@include media(all){
.foo { @extend %red; }
.bar { @extend %blue; }
}
@include media(mobile){
.foo { @extend %blue; }
.bar { @extend %yellow; }
}
@include media(tablet){
.foo { @extend %green; }
.bar { @extend %green; }
}
// CSS Output
// @media all {
// .foo { color: red; }
// .bar { color: blue; }
// }
// @media screen and (min-width: 481px) and (max-width: 768px) {
// .foo, .bar { color: green; }
// }
// @media screen and (max-width: 480px) {
// .foo { color: blue; }
// .bar { color: yellow; }
// }
// ====================================================================
// Wrapping media
//
.foo { @include media(tablet){ @extend %green } }
.bar { @include media(tablet){ @extend %green } }
// CSS Output
@media screen and (min-width: 481px) and (max-width: 768px) {
.foo, .bar { color: green; }
}
// --------------------------
// Note! when passing non-media-extend @content to the media mixin
// - CSS Output order is set by the include order not the order
// of the breakpoints list... Be careful if your list contains
// overlapping breakpoints
//
.foo { @include media(tablet){ content:'tablet'; } }
.foo { @include media(mobile){ content:'mobile'; } }
.foo { @include media(all) { content:'all'; } }
// CSS Output
// @media screen and (min-width: 481px) and (max-width: 768px) {
// .foo { content: 'tablet'; }
// }
// @media screen and (max-width: 480px) {
// .foo { content: 'mobile'; }
// }
// @media all {
// .foo { content: 'all'; }
// }
mediaExtend Back-to-Hack-Edition
While the mediaExtend solution above was nice and clean and made perfect sense it will cause a deprecation warning (crashed CodeKit) - sucks big time!!!
It looks as if we are still allowed to extend within the media directive as long as there are no naming conflicts with other extends. This solution uses a media prefix to create a sort of namespace. In no way as flexible, intuitive or as simple as the first version.
Note! the breakpoints list and media mixin remains the same
// ====================================================================
// mediaExtend
// ====================================================================
// Prevent multiple instantiations
$media-extend-first-run:true !default;
@mixin mediaExtend($args...){
// Arguments passed => extend
@if(length($args)>0){
// Loop thru arguments to see if any matches a key in the breakpoints list
// - if found add to keys list
$keys:();
@for $i from 1 through length($args){
@for $j from 1 through length($breakpoints){
@if(nth(nth($breakpoints,$j),1)==nth($args,$i)){
$keys:append($keys,nth($args,$i));
}
}
}
// If no breakpoints were passed we'll extend all items in the breakpoints list.
// Note! if extending within context scope must match
// @media all {
// @include mediaExtend(all, foo){ .... }
// }
$keys:if($keys==(),$breakpoints,$keys);
@for $i from 1 through length($args){
@for $j from 1 through length($keys){
$key:nth(nth($keys,$j),1);
@extend %#{$key+'_'+ nth($args,$i)}
}
}
}
// No arguments => create extends
@else if $media-extend-first-run {
$media-extend-first-run:false;
// Loop thru breakpoints
@for $i from 1 through length($breakpoints){
$key:nth(nth($breakpoints,$i),1);
// 1 Place extends within a media scope
// 2 Prefix extends to avoid conflicts
@include media($key){
// ==================================
// Add your all your extends here
// %#{$key+_+ EXTEND_NAME }{ ... }
// ==================================
%#{$key+_+ foo }{ content:'foo'; }
%#{$key+_+ bar }{ content:'bar'; }
%#{$key+_+ doh }{ content:'doh'; }
}
}
}
}
@include mediaExtend(); // Create extends
// ====================================================================
// Usage
// Instad of using @extend %foo we use the mediaExtend to handle the prefixing.
// ====================================================================
// -----------------------------------------------------------
// Example 1
// Calling without a scope (extend it all)
.myClass{ @include mediaExtend(foo); }
// CSS Output
@media all { .myClass { content: 'foo'; } }
@media screen and (min-width: 769px) { .myClass { content: 'foo'; } }
@media screen and (min-width: 481px) and (max-width: 768px) { .myClass { content: 'foo'; } }
@media screen and (max-width: 480px) { .myClass { content: 'foo'; } }
// -----------------------------------------------------------
// Example 2
// Passing one scope
.myClass{ @include mediaExtend(mobile, foo); }
// CSS Output
@media screen and (max-width: 480px) { .myClass { content: 'foo'; } }
// -----------------------------------------------------------
// Example 3
// Passing multiple scopes
.myClass{ @include mediaExtend(mobile, tablet, foo); }
// CSS Output
@media screen and (min-width: 481px) and (max-width: 768px) { .myClass { content: 'foo'; } }
@media screen and (max-width: 480px) { .myClass { content: 'foo'; } }
// -----------------------------------------------------------
// Example 4
// Passing multiple scopes and extends
.myClass{ @include mediaExtend(mobile, tablet, foo, bar); }
// CSS Output
@media screen and (min-width: 481px) and (max-width: 768px) {
.myClass { content: 'foo'; }
.myClass { content: 'bar'; }
}
@media screen and (max-width: 480px) {
.myClass { content: 'foo'; }
.myClass { content: 'bar'; }
}
// -----------------------------------------------------------
// Example 5
// Extending within context
@media all {
.myClass{ @include mediaExtend(all, foo); }
}
// CSS Output
@media all { .myClass { content: 'foo'; } }
// -----------------------------------------------------------
// Example 6
// Extending within context - using media mixin
@include media(all) {
.myClass{ @include mediaExtend(all, foo); }
}
// CSS Output
@media all { .myClass { content: 'foo'; } }
// -----------------------------------------------------------
// Example 7 - Error
// Extending within wrong context
@media all {
.myClass{ @include mediaExtend(mobile, foo); }
}
// No CSS Output
Constants
Just a small might-be-of-some-use note - use constants
$MEDIA_ALL:all;
$MEDIA_DESKTOP:desktop;
$MEDIA_TABLET:tablet;
$MEDIA_MOBILE:mobile;
$breakpoints:
($MEDIA_ALL,0,0)
,($MEDIA_DESKTOP,769,0)
,($MEDIA_TABLET,481,768)
,($MEDIA_MOBILE,0,480);
@include media($MEDIA_ALL){ .... }
@include mediaExtend($MEDIA_ALL){ ... }
Issue Analytics
- State:
- Created 10 years ago
- Comments:10 (1 by maintainers)
Top GitHub Comments
@entozoon actually it does work; I wrote that gist before Sass introduced the
!global
flag. This is required in order to modify a global variable from inside of a mixin — for example themq ()
mixin in that gist. Here’s the updated codehttp://www.sassmeister.com/gist/cd999df6a2712b1959d2b68d4505602b
I’m encountering this same issue with @extend and @media.
The solutions so far seem overthought. A less messy solution would be to create a new operator, @copy that just copies whatever you point it to. You can still use extend with the same class, however.
This knows to simply copy in the styles. Nothing fancy.