CSS本地变量在媒体查询中无法正常工作

340

我正在尝试在媒体查询中使用CSS变量,但它无法正常工作。

:root {
  --mobile-breakpoint: 642px;
}

@media (max-width: var(--mobile-breakpoint)) {

}

35
仅供通过谷歌搜索浏览此内容的用户澄清:您可以在媒体查询范围内使用CSS自定义属性,但不能在媒体查询声明中使用它们。 - David Deprost
想象一下,如果你在媒体查询的:root中修改了 --mobile-breakpoint,那将会是一个递归故障。 - Seangle
11个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
267

根据 规范

var() 函数可以用于元素上任何属性的值中的任何部分。但是,var() 函数不能用作属性名称、选择器或除了属性值之外的任何其他内容。 (这样做通常会产生无效的语法,或者意义与变量无关的值)

所以,你不能在媒体查询中使用它。

这很合理。因为你可以将--mobile-breakpoint设置为例如:root,也就是<html>元素,并从那里继承到其他元素。但是,媒体查询不是一个元素,它不会从<html>继承,因此无法工作。

CSS变量并非旨在实现此目的。你可以使用CSS预处理器代替。


220
答案的确是正确的,因为当前规范不支持在媒体查询中使用CSS变量,但是错误的是CSS变量的目的并不只是这个。CSS变量的创造正是为了减少重复和魔法数字,详情请参阅https://www.w3.org/TR/css-variables-1/#intro。 - mikemaccana
30
顺便问一下,这怎么不是属性值呢?它是max-width的属性值!完全应该可以工作! - user9645
11
@user9645 实际上并不是这样的。虽然你所写的文本相同,但它们实现的功能完全不同,不能简单地将其视为 max-width 属性。CSS 属性声明充当“setter”,它为元素设置一个值,而媒体查询中的 max-width 则是一个“getter”:它请求某物的当前宽度(然后将其与提供的值进行比较)。作为媒体查询的文本 max-width: 1000px (以一些非有效伪代码的形式)就像是 viewport.getWidth() <= 1000px,而作为属性声明,则是 element.setMaxWidth(1000px) - Taederias
2023年有任何更新吗? - eastwater

139

正如Oriol所回答的, CSS变量Level 1中的var()当前无法在媒体查询中使用。然而,最近有了一些发展将解决这个问题。一旦CSS环境变量模块Level 1被标准化和实现,我们就能够在所有现代浏览器中在媒体查询中使用env()变量。

CSS Working Group (CSSWG) 在一个新标准中(目前处于草案阶段)对env()进行了编码:CSS Environment Variables Module Level 1(请参见此GitHub评论此评论以获取更多信息)。该草案明确说明了变量在媒体查询中的使用情况:

因为环境变量不依赖于从特定元素中提取的任何值,所以它们可以在没有明显元素可供提取的地方使用,例如@media规则,在那里var()函数将无效。

如果你阅读了规范并有疑虑,或者想要表达对媒体查询用例的支持,你可以在问题#2627问题#3578任何带有“css-env-1”标签的CSS GitHub问题中发表意见。 GitHub问题#2627GitHub问题#3578专注于媒体查询中的自定义环境变量。
最近,CSS工作组决定CSS变量级别2将支持使用 env()的用户定义环境变量,并尝试使它们在媒体查询中有效。该小组在2017年9月iPhone X的正式公告之前,Apple首次提出标准用户代理属性后不久就解决了这个问题(另请参见WebKit:Timothy Horton撰写的“为iPhone X设计网站”)。其他浏览器代表随后同意它们将在许多设备上普遍适用,例如具有出血边缘的电视显示器和油墨打印。 (env()曾经被称为constant(),但现已弃用。您可能仍会看到引用旧名称的文章,例如Peter-Paul Koch的这篇文章。)过了几周,Mozilla的Cameron McCormack意识到这些环境变量可以在媒体查询中使用,而Google的Tab Atkins Jr.意识到用户定义的环境变量将特别有用作为全局、不可覆盖的根变量,可以在媒体查询中使用。现在,Apple的Dean“Dino”Jackson将与Atkins一起编辑第2级。 你可以订阅 w3c/csswg-drafts GitHub issue #1693 上的更新(如果需要查看相关历史细节,请展开CSSWG Meeting Bot’s resolutions中嵌入的会议记录并搜索“MQ”,表示“媒体查询”)。

117
你可以使用@media查询你的:root语句!
:root {
     /* desktop vars */
}
@media screen and (max-width: 479px) {
    :root {
        /* mobile vars */
    }
}

截至本文发布时,该功能在最新版本的Chrome、Firefox和Edge中完全可用。

限制之一:如果您需要将值作为变量访问 - 例如在其他地方进行计算 - 您将需要一个变量,并且需要在媒体查询和变量声明中定义该变量两次。


1
我不完全确定你在暗示什么?这意味着没有支持,对吗? - windmaomao
@windmaomao 浏览器不支持在媒体规则中使用CSS变量,但是它们支持这个答案中展示的技巧,即在媒体规则中声明变量。我猜这个答案没有展示的是实际使用变量。你可以(例如)在答案中的每个:root中声明不同的--text-size值,然后稍后像这样做:h1 { font-size: var(--text-size) },它将使用适当的值。 - undefined

34

显然,不能像那样使用本机CSS变量。这是其中一种限制

一个聪明的方法是在媒体查询中更改您的变量,以影响所有样式。我建议阅读这篇文章

:root {
  --gutter: 4px;
}

section {
  margin: var(--gutter);
}

@media (min-width: 600px) {
  :root {
    --gutter: 16px;
  }
}

我不明白“在媒体查询中更改变量”的意思,你能给个例子吗? - user7122183
7
是的,我刚刚做了,它在我链接的文章中。我知道这不是你期望的,但 CSS 变量不能用于定义媒体查询 - Cohars
这很重要。想象一下在@media规则中使用自定义属性的值,然后还想在同一断点更改该自定义属性的值。这很奇怪...但它们是自定义属性,而不仅仅是变量。因此,似乎没有以这种方式创建它的原因比规范更好地解释了。最好的方法是组合示例。 - sheriffderek

23
媒体查询的5级规范定义了自定义媒体查询,几乎可以满足您的需求。它允许您定义类似于 CSS 变量的断点,并在不同的地方使用它们。 以下是规范中的示例:
@custom-media --narrow-window (max-width: 30em);

@media (--narrow-window) {
  /* narrow window styles */
}
@media (--narrow-window) and (script) {
  /* special styles for when script is allowed */
}

目前还没有对此提供支持,因此我们必须等待才能使用此功能。


18

实现您想要的一种方式是使用npm包postcss-media-variables

如果您愿意使用npm包,可以在此处查看相同的文档:

示例

/* input */
:root {
  --min-width: 1000px;
  --smallscreen: 480px;
}
@media (min-width: var(--min-width)) {}
@media (max-width: calc(var(--min-width) - 1px)) {}

@custom-media --small-device (max-width: var(--smallscreen));
@media (--small-device) {}

通过使用PostCSS,您还可以使用cssnext http://cssnext.io/features/#custom-media-queries - sebilasse
1
@sebilasse:custom-media-queries无法解决不能将CSS变量作为媒体查询断点的主要问题。 - zhirzh

9

简短回答

您可以使用JavaScript更改媒体查询的值,并将其设置为css变量的值。

// get value of css variable
getComputedStyle(document.documentElement).getPropertyValue('--mobile-breakpoint'); // '642px'

// search for media rule
var mediaRule = document.styleSheets[i].cssRules[j];

// update media rule
mediaRule.media.mediaText = '..'

长回答

我编写了一个小脚本,您可以在页面上包含它。它将每个媒体规则的值为1px替换为CSS变量--replace-media-1px的值,值为2px的规则替换为--replace-media-2px,以此类推。这适用于使用and连接的媒体查询withmin-widthmax-widthheightmin-heightmax-height

JavaScript:

function* visitCssRule(cssRule) {
    // visit imported stylesheet
    if (cssRule.type == cssRule.IMPORT_RULE)
        yield* visitStyleSheet(cssRule.styleSheet);

    // yield media rule
    if (cssRule.type == cssRule.MEDIA_RULE)
        yield cssRule;
}

function* visitStyleSheet(styleSheet) {
    try {
        // visit every rule in the stylesheet
        var cssRules = styleSheet.cssRules;
        for (var i = 0, cssRule; cssRule = cssRules[i]; i++)
            yield* visitCssRule(cssRule);
    } catch (ignored) {}
}

function* findAllMediaRules() {
    // visit all stylesheets
    var styleSheets = document.styleSheets;
    for (var i = 0, styleSheet; styleSheet = styleSheets[i]; i++)
        yield* visitStyleSheet(styleSheet);
}

// collect all media rules
const mediaRules = Array.from(findAllMediaRules());

// read replacement values
var style = getComputedStyle(document.documentElement);
var replacements = [];
for (var k = 1, value; value = style.getPropertyValue('--replace-media-' + k + 'px'); k++)
    replacements.push(value);

// update media rules
for (var i = 0, mediaRule; mediaRule = mediaRules[i]; i++) {
    for (var k = 0; k < replacements.length; k++) {
        var regex = RegExp('\\((width|min-width|max-width|height|min-height|max-height): ' + (k+1) + 'px\\)', 'g');
        var replacement = '($1: ' + replacements[k] + ')';
        mediaRule.media.mediaText = mediaRule.media.mediaText.replace(regex, replacement);
    }
}

CSS:

:root {
  --mobile-breakpoint: 642px;

  --replace-media-1px: var(--mobile-breakpoint);
  --replace-media-2px: ...;
}

@media (max-width: 1px) { /* replaced by 642px */
  ...
}

@media (max-width: 2px) {
  ...
}

3

您可以使用matchMedia编程方式构建媒体查询:

const mobile_breakpoint = "642px";
const media_query = window.matchMedia(`(max-width: ${mobile_breakpoint})`);
function toggle_mobile (e) {
  if (e.matches) {
    document.body.classList.add("mobile");
  } else {
    document.body.classList.remove("mobile");
  }
}
// call the function immediately to set the initial value:
toggle_mobile(media_query);
// watch for changes to update the value:
media_query.addEventListener("change", toggle_mobile);

那么,不要在CSS文件中使用媒体查询,而是在body具有mobile类时应用所需的规则:

.my-div {
  /* large screen rules */
}

.mobile .my-div {
  /* mobile screen rules */
}

2

正如您可以阅读其他答案,目前还不可能实现。

有人提到自定义环境变量(类似于自定义 CSS 变量使用的 env() 而不是 var()),这个原则是可行的,但仍存在两个主要问题:

  • 浏览器支持不足
  • 目前还没有定义它们的方法(但可能在未来会有,因为这目前仅是非官方草案)

2
这不完全是使用变量在媒体查询中的答案,但它解决了在组件中需要多次指定宽度的问题。 我的解决方案结合了容器查询和媒体查询,并利用了具名容器的可能性。 首先,我们需要定义断点。
@media (min-width: 399px) {
  body {
    container-type: inline-size;
    container-name: xsmall;
  }
}

@media (min-width: 1079px) {
  body {
    container-type: inline-size;
    container-name: xsmall small;
  }
}

@media (min-width: 1439px) {
  body {
    container-type: inline-size;
    container-name: xsmall small medium;
  }
}

@media (min-width: 1999px) {
  body {
    container-type: inline-size;
    container-name: xsmall small medium desktop;
  }
}

在这里,我们可以使用多个容器名称来定义元素。使用移动优先的方法,在屏幕尺寸增大时添加样式。

现在,如果我们想要对小屏幕尺寸及更大的元素进行一些更改,可以添加以下样式定义。

@container small (min-width: 0) {
  ... some css
}
我个人认为,这个看起来有点粗糙的原因只是容器查询需要一个谓词,所以(min-width:0)是必需的才能使其正常工作。 通过这种方法,我们可以在一个地方指定屏幕断点,并且从组件中删除所有绝对屏幕尺寸查询。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,