使用 JSON 让 Sass 与 JavaScript 交谈

Avatar of Les James
Les James

DigitalOcean 为您的旅程各个阶段提供云产品。立即开始使用 $200 免费积分!

以下是 Les James 的客座文章。与我们许多人一样,Les 一直在寻找适合他的响应式图像解决方案。在这篇文章中,他分享了一种他发现的技术,他可以在其中将“命名”媒体查询从 CSS 传递到 JavaScript,JavaScript 使用这些名称来替换适合该媒体查询的相应图像。甚至可以使用 Sass 自动执行此过程。HTML 纯粹主义者可能会对图像上缺少真正的 src 感到反感,但 到目前为止,我们必须做我们必须做的事情。

当前 提出的响应式图像解决方案 要求您将媒体查询值内联到 HTML 标签中。

<picture>
    <source src="large.jpg" media="(min-width: 45em)" />
    <source src="med.jpg" media="(min-width: 18em)" />
    <source src="small.jpg" />
    <img src="small.jpg" alt="" />
</picture>

这对我有问题。我喜欢将媒体查询放在一个地方,即 CSS。上面的例子对我来说感觉不可维护,这可能与我使用 RWD 的方法有关。我使用 Frameless grid 方法来创建布局。我告诉 Sass 我想要布局中的列数,它会生成一个媒体查询来适应它。这意味着我只考虑列数,实际上我不知道媒体查询的实际值。

在将此方法应用于响应式图像时,我需要一种方法来提供有关断点周围的元数据。因此,当我创建一个断点时,我给它一个标签。我最近喜欢 small、medium 和 large 这样的标签,但标签可以是 任何你想要的。关键是使用有意义的东西来命名断点。目标是将这些名称与我 HTML 中定义的匹配源配对。第一步是将我们的标签格式化为 JavaScript 可以解析的内容。

Sass 到 JSON

当我创建一个断点时,我会调用我创建的 Sass 混合器。列数创建一个 min-width 媒体查询来适应该列数。标签为我们的媒体查询命名。

@include breakpoint(8, $label: "medium") {
    /* medium size layout styles go here */
}

断点混合器将该标签传递给一个函数,该函数将其格式化为 JSON 字符串。

@function breakpoint-label($label) {
    @return '{ "current" : "#{$label}" }';
}

JSON 到 CSS

现在我们已经将标签转换为 JSON,如何将其添加到 CSS 中呢?字符串的自然匹配是 CSS 生成的内容。我使用 body::before 来保存我的字符串,因为它是我最不可能在前端实际使用的地方。以下是标签从断点混合器进入 CSS 的方式。

@if($label) { body::before { content: breakpoint-label($label); } }

不幸的是,我必须支持旧浏览器,它们在使用 JavaScript 读取我们的 CSS 生成的内容时会有问题。因此,我们必须将 JSON 放在另一个地方,以获得更广泛的兼容性。为此,我将把我们的 JSON 添加到 head 中作为字体系列。

@if($label) {
    body::before { content: breakpoint-label($label); }
    .lt-ie9 head { font-family: breakpoint-label($label); }
}

CSS 到 JS

我们的布局标签现在位于 CSS 中的 JSON 字符串中。要使用 JavaScript 读取它,我们需要使用 getComputedStyle 这个朋友。让我们创建一个函数来获取我们的 JSON,然后对其进行解析。

function getBreakpoint() {
    var style = null;
    if ( window.getComputedStyle &amp;&amp; window.getComputedStyle(document.body, '::before') ) {
        style = window.getComputedStyle(document.body, '::before');
        style = style.content;
    }
    return JSON.parse(style);
}

对于不支持 getComputedStyle 的浏览器,我们需要添加一个小补丁并获取 head 字体系列。

function getBreakpoint() {
    var style = null;
    if ( window.getComputedStyle &amp;&amp; window.getComputedStyle(document.body, '::before') ) {
        style = window.getComputedStyle(document.body, '::before');
        style = style.content;
    } else {
        window.getComputedStyle = function(el) {
            this.el = el;
            this.getPropertyValue = function(prop) {
                var re = /(\-([a-z]){1})/g;
                if (re.test(prop)) {
                    prop = prop.replace(re, function () {
                        return arguments[2].toUpperCase();
                    });
                }
                return el.currentStyle[prop] ? el.currentStyle[prop] : null;
            };
            return this;
        };
        style = window.getComputedStyle(document.getElementsByTagName('head')[0]);
        style = style.getPropertyValue('font-family');
    }
    return JSON.parse(style);
}

我们的函数现在存在一个主要问题。我们的 JSON 作为字符串传递,这意味着它被括在引号中,但使用哪种引号取决于你使用的浏览器。WebKit 将字符串包装在单引号中传递。Firefox 使用双引号将字符串包装起来,这意味着它会转义 JSON 中的双引号。IE8 做了一些很奇怪的事情,在字符串末尾添加了 ; }。为了解决这些不一致问题,我们需要另一个函数在解析之前规范化我们的 JSON。

function removeQuotes(string) {
    if (typeof string === 'string' || string instanceof String) {
        string = string.replace(/^['"]+|\s+|\\|(;\s?})+|['"]$/g, '');
    }
    return string;
}

现在,在我们 getBreakpoint 函数的返回值中解析 JSON 之前,我们只需将字符串传递给 removeQuotes 函数。

return JSON.parse( removeQuotes(style) );

图像源匹配

JavaScript 现在可以读取我们为每个断点定义的标签。在这一点上,将该标签与响应式图像源匹配是微不足道的。以以下图像为例。

<img data-small="small.jpg" data-large="large.jpg" />

当活动媒体查询为 small 时,我们可以让 JavaScript 将其与 data-small 匹配并将图像的源设置为 small.jpg。如果您为每个断点都声明了一个源,这非常有效,但正如您在我们的示例中看到的,我们没有为 medium 定义源。这是一个非常常见的情况。较小的图像通常可以在较大的布局中使用。也许中等的布局只是添加了一个侧边栏,而我们的图像大小没有改变。那么 JavaScript 如何知道在布局为中等时拉取较小的源呢?为此,我们需要排序。

Sass 列表到 JavaScript 数组

每次创建断点时,我们都可以将该标签存储在 Sass 列表中。在我们的断点混合器中,我们可以将标签附加到我们的列表中。

$label-list: append($label-list, $label, comma);

假设我们的列表默认为预填充的移动标签 small,以下断点将创建一个 small, medium, large 列表。

@include breakpoint(8, $label: "medium");
@include breakpoint(12, $label: "large");

在 Sass 中声明断点的顺序将决定标签的顺序。让我们将这个 Sass 列表作为数组添加到我们的 JSON 函数中。

@function breakpoint-label($label) {
    $label-list: append($label-list, $label, comma);
    @return '{ "current" : "#{$label}", "all": [#{$label-list}] }';
}

现在,除了 JavaScript 知道当前断点标签之外,它还可以查看我们传递的数组并了解它们的顺序。因此,如果布局为 medium 并且在我们的图像上没有找到匹配的数据属性,JavaScript 可以找到我们标签数组中的 medium,并在该数组中向后遍历,直到找到匹配的源。

总结

这个响应式图像解决方案对我来说非常重要。通过用标签标记媒体查询值,我创建了灵活性简便性。如果我更改断点的大小,我只需要在一个地方更改它,即我的 Sass。我的 HTML 图像源不依赖于媒体查询的值,只依赖于它们的名称。

虽然我使用 Sass 实现了这一点,但这实际上不是必需的。您可以手动将 JSON 字符串写入媒体查询,Sass 只会帮助自动执行该过程。这是一个 简单的 Pen,它使用纯 CSS。

Check out this Pen!

要查看此技术的更强大、生产质量的实现,请查看我的框架 Breakpoint 的代码。

请注意,如果您希望您的图像标签有效,那么请使用诸如 src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D" 之类的源来对其进行填充,并且请确保您提供了一个

我希望分享这篇文章能激发更多关于抽象响应式图像的复杂性和可维护性的讨论和想法。我很想知道您的想法,以及您是否有改进它的方法。

我们都站在巨人的肩膀上。Jeremy Keith 的文章 Conditional CSS 和 Viljami Salminen 的 detectMQ 为此提供了很多功劳和灵感。

关于 Sass & JSON 协同工作的更多信息

  • Kitty Giraudel 有一个 ,用于从 Sass 中对 JSON 进行编码和解码。
  • Robert Balicki 有一个 ,用于将 JSON 转换为 Sass 变量。