使用 Sass 从媒体查询中扩展选择器

我有一个 item 类和一个紧凑的“修饰符”类:

.item { ... }
.item.compact { /* styles to make .item smaller */ }

没关系。但是,我想添加一个 @media查询,它强制 .item类在屏幕足够小时变得紧凑。

第一个想法是,这是我试图做的:

.item { ... }
.item.compact { ... }
@media (max-width: 600px) {
.item { @extend .item.compact; }
}

但这会产生以下错误:

不能“从@media 内部扩展外部选择器 @ 在同一个指令中扩展选择器。

如何使用 SASS 而不必采用复制/粘贴样式来完成这项工作?

59282 次浏览

Could you restructure?

.compact { //compact-styles }
.item {}
.item.compact { @extend .compact }


@media (max-width: 600px) {
.item { @extend .compact; }
}

If I understand the documentation correctly, that should work. I think the reason the way you're trying won't work is that it doesn't see .item.compact when it's parsing the @extend, but that's an uninformed guess, so take that with a truck load of salt! :)

The simple answer is: you can't because Sass can't (or won't) compose the selector for it. You can't be inside of a media query and extend something that's outside of a media query. It certainly would be nice if it would simply take a copy of it instead of trying to compose the selectors. But it doesn't so you can't.

Use a mixin

If you have a case where you're going to be reusing a block of code inside and outside of media queries and still want it to be able to extend it, then write both a mixin and an extend class:

@mixin foo {
// do stuff
}


%foo {
@include foo;
}


// usage
.foo {
@extend %foo;
}


@media (min-width: 30em) {
.bar {
@include foo;
}
}

Extend the selector within a media query from the outside

This won't really help your use case, but it is another option:

%foo {
@media (min-width: 20em) {
color: red;
}
}


@media (min-width: 30em) {
%bar {
background: yellow;
}
}


// usage
.foo {
@extend %foo;
}


.bar {
@extend %bar;
}

Wait until Sass lifts this restriction (or patch it yourself)

There are a number of ongoing discussions regarding this issue (please don't contribute to these threads unless you have something meaningful to add: the maintainers are already aware that users desire this functionality, it's just a question of how to implement it and what the syntax should be).

I believe SASS/SCSS does not support the @extend directive inside of a media query. http://designshack.net/articles/css/sass-and-media-queries-what-you-can-and-cant-do/

You might need to use a mixin instead, though the code bloat needs to be weighed against your objective.

For the record, here is how I ended up solving the problem with only duplicating generated styles once:

// This is where the actual compact styles live
@mixin compact-mixin { /* ... */ }


// Include the compact mixin for items that are always compact
.item.compact { @include compact-mixin; }


// Here's the tricky part, due to how SASS handles extending
.item { ... }
// The following needs to be declared AFTER .item, else it'll
// be overridden by .item's NORMAL styles.
@media (max-width: 600px) {
%compact { @include compact-mixin; }


// Afterwards we can extend and
// customize different item compact styles
.item {
@extend %compact;
/* Other styles that override %compact */
}
// As shown below, we can extend the compact styles as many
// times as we want without needing to re-extend
// the compact mixin, thus avoiding generating duplicate css
.item-alt {
@extend %compact;
}
}

This is the cleanest, partial solution I've found. It takes advantage of @extend where possible and falls back to mixins when inside media queries.

Cross-Media Query @extend Directives in Sass

See the article for full details but the gist is that you call a mixin 'placeholder' that then decides whether to output @extend or an @include.

@include placeholder('clear') {
clear: both;
overflow: hidden;
}


.a {
@include _(clear);
}
.b {
@include _(clear);
}
.c {
@include breakpoint(medium) {
@include _(clear);
}
}

Ultimately it may not be better than just using mixins, which is currently the accepted answer.

I use breakpoints, but it's the same idea:

@mixin bp-small {
@media only screen and (max-width: 30em) {
@content;
}

How to use it:

.sidebar {
width: 60%;
float: left;
@include bp-small {
width: 100%;
float: none;
}
}

There is a text about mixins where you can find out more about this option.