服务器端芥末切分

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

在网页设计中,“芥末切分”一词 源于 BBC 的开发者,他们希望为不同设备上的不同浏览器提供网站的不同体验。 他们专门试图避免使用 User Agent 检测

但如今,随着访问该网站的用户代理数量如此之多,这变成了徒劳无功的练习。

相反,他们使用功能检测。 Modernizr 是一个经典的(很棒的)例子,但单点功能测试本身可以非常小巧简单。 这就是 BBC 用于确定浏览器是否通过芥末切分测试的逻辑

if('querySelector' in document
     && 'localStorage' in window
     && 'addEventListener' in window) {
     // bootstrap the javascript application
     }

如果该逻辑失败,网站仍然会加载他们所谓的 **核心体验**。 如果该逻辑通过,将加载其他资源以提供 **增强体验**。

非常酷。

加载额外的 CSS 和 JavaScript 非常容易

有几种方法可以做到这一点,通常涉及到资源的 XHR。 Filament Group 有些非常小巧、专门用于此的脚本:loadCSSloadJS

通过 XHR 加载少量额外的 HTML 也同样容易。 但…

在客户端加载完全不同的文档太难了

假设您不需要额外的 CSS、脚本或少量 HTML。 您想要的是完全不同的文档。

您的“核心体验”和“增强体验”是完全不同的 HTML、CSS 和 JavaScript 集。 尝试在客户端执行此操作意味着尝试加载一个非常基础的文档,然后尝试基本上重新创建浏览器解析器的运行方式。 首先,您进行芥末切分,然后使用 XHR 获取您需要的正确 HTML 集,然后将其放入 DOM 或等待您使用 XHR 获取 CSS,以避免出现未样式化文档的闪现。 然后使用 XHR 获取所有脚本,并确保按顺序执行它们(很棘手)。

很难实现。

允许服务器根据芥末切分信息提供正确的文档

如果服务器知道芥末切分的結果,您就可以避免所有这些麻烦,并立即提供正确的文档。

这就是客户端芥末切分的关键:它只能在客户端完成。 **但是...您可以将这些数据保存到 cookie 中,服务器可以读取 cookie。**

如果您有一个可以依赖的 cookie,您可以在网站的路由中执行类似以下操作

<?php
  // This is just a fake routing/controller kinda setup.
  if (isset($_COOKIE["mustard"])) {
    // Enhanced experience
    if ($_COOKIE["mustard"] == true) {
      include_once("enhanced.php");
    // Core experience
    } else {
      include_once("core.php");
    }
  // No cookie = core experience
  } else {
    include_once("core.php");
  }
?>

那么在第一次页面视图中会发生什么?

这是棘手的部分。 在第一次页面视图中,该 cookie 不存在。 您只需让后续页面提供正确的体验,但这可能无法接受。

**最具争议的部分来了:**如果您没有 cookie,但可以确定浏览器支持 cookie 且已启用,您可以刷新页面。

刷新页面?!你在开玩笑吧?

完全合理的疑问:刷新页面怎么可能是一种良好的用户体验? 刷新页面不是速度很慢吗? 您不会陷入刷新循环吧?

我认为所有这些问题都可以解决。

在文档的 **最顶部**,如果该 cookie **不存在** 并且浏览器 **支持** cookie

  1. 使用 JavaScript 进行芥末切分并保存数据到 cookie 中
  2. 如果芥末切分数据告诉您应该加载不同的文档:阻止页面加载/执行任何其他操作(window.stop();)并刷新(location.reload(true);)。

刷新后,服务器将拥有该 cookie。

当文档执行的第一件事时,它会非常快,我发现几乎无法察觉。 这就是我们在 CodePen 上用于编辑器页面的方法,请看

在一个新的隐身窗口(未保存任何 cookie)中重新加载页面。 桌面视图实际上是默认视图,但刷新后会加载移动视图,因为进行了芥末切分。

避免刷新循环的技巧是,仅在确定支持并启用了 cookie 时才执行 JavaScript 的那部分代码。

芥末切分脚本

这是一个仅测试屏幕宽度的芥末切分。 请记住,芥末切分可以是您想测试的任何东西,只要您可以在客户端测试即可。

(function() {

  // If the browser supports cookies and they are enabled
  if (navigator.cookieEnabled) {

    // Set the cookie for 3 days
    var date = new Date();
    date.setTime(date.getTime() + (3 * 24 * 60 * 60 * 1000));
    var expires = "; expires=" + date.toGMTString();

    // This is where we're setting the mustard cutting information.
    // In this case we're just setting screen width, but it could
    // be anything. Think http://modernizr.com/
    document.cookie = "screen-width=" + window.outerWidth + expires + "; path=/";

    /*
      Only refresh if the WRONG template loads.

      Since we're defaulting to a small screen,
      and we know if this script is running the
      cookie wasn't present on this page load,
      we should refresh if the screen is wider
      than 700.

      This needs to be kept in sync with the server
      side distinction
    */
    if (window.outerWidth > 700) {

      // Halt the browser from loading/doing anything else.
      window.stop();

      // Reload the page, because the cookie will now be
      // set and the server can use it.
      location.reload(true);

    }

  }

}());

事实上,如果 cookie 已经存在,我们 **根本不需要加载该脚本**,因为如果存在,我们就知道已经加载了正确的页面。

<?php
    // Run this script as high up the page as you can,
    // but only if the cookie isn't already present.
    if (isset($_COOKIE["screen-width"]) == 0) { ?>
      <script src="mobile-mustard.js"></script>
<?php } ?>

可能的方案

  • **第一次访问的普通访客:**没有 cookie。 芥末切分脚本将运行并快速刷新页面。 他们将根据切分结果获得正确的文档。
  • **重复访客:**cookie 已经存在。 他们将根据切分结果获得正确的文档。
  • **带有错误 cookie 的访客:**也许他们使用的是桌面浏览器,但首次加载页面时浏览器窗口非常窄,但他们之后将其变宽了。 我们可以使用 CSS @media 查询检测到这一点,并提供一个链接来更正问题(查看演示)。
  • **关闭 cookie 的访客:**我们会提供我们选择的文档。 可能不正确。 根据数据提供最有可能的案例。
  • **JavaScript 未运行的访客:**我们会提供我们选择的文档。 可能不正确。 根据数据提供最有可能的案例。

可能存在的问题

我必须声明,我并不认为这是解决此问题的最佳方案。 事实上,如果您处于可以完全在客户端执行所有操作的情况下,那可能更好。

以下是此解决方案可能存在的一些问题

  • 也许重新加载的速度比我认为的慢。 我没有像应该做的那样测试非常老旧/非常慢的设备。
  • HTML 缓存可能是一个问题。 在使用这种方法的网站上构建演示时,我亲身经历了这个问题。 服务器提供了一个缓存的文档,然后确定该文档不正确并进行刷新,从而导致了令人头疼的刷新循环。 解决方法:不要缓存此页面的 HTML,或 根据切分结果重定向到子域名
  • 安全设置 阻止服务器端访问 JavaScript 创建的 cookie。 如果您无法控制这一点,那将是一个问题。

不过,我在生产环境中确实使用了这种技术,并且在几个月内都没有遇到过问题,所以我对此非常满意。

演示和代码库

这里有一个演示,以及 GitHub 上的代码,如果你发现任何错误。

此外,我认为 客户端提示 应该是这里的救星,但我现在不确定它如何适用于这种情况。