在网页设计中,“芥末切分”一词 源于 BBC 的开发者,他们希望为不同设备上的不同浏览器提供网站的不同体验。 他们专门试图避免使用 User Agent 检测
但如今,随着访问该网站的用户代理数量如此之多,这变成了徒劳无功的练习。
相反,他们使用功能检测。 Modernizr 是一个经典的(很棒的)例子,但单点功能测试本身可以非常小巧简单。 这就是 BBC 用于确定浏览器是否通过芥末切分测试的逻辑
if('querySelector' in document
&& 'localStorage' in window
&& 'addEventListener' in window) {
// bootstrap the javascript application
}
如果该逻辑失败,网站仍然会加载他们所谓的 **核心体验**。 如果该逻辑通过,将加载其他资源以提供 **增强体验**。
非常酷。
加载额外的 CSS 和 JavaScript 非常容易
有几种方法可以做到这一点,通常涉及到资源的 XHR。 Filament Group 有些非常小巧、专门用于此的脚本:loadCSS 和 loadJS。
通过 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
- 使用 JavaScript 进行芥末切分并保存数据到 cookie 中
- 如果芥末切分数据告诉您应该加载不同的文档:阻止页面加载/执行任何其他操作(
window.stop();
)并刷新(location.reload(true);
)。
刷新后,服务器将拥有该 cookie。
当文档执行的第一件事时,它会非常快,我发现几乎无法察觉。 这就是我们在 CodePen 上用于编辑器页面的方法,请看

避免刷新循环的技巧是,仅在确定支持并启用了 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 上的代码,如果你发现任何错误。
此外,我认为 客户端提示 应该是这里的救星,但我现在不确定它如何适用于这种情况。
刷新页面并不快,至少在我看来不够快。很多时候我访问 Codepen 时,看到了足够多的 UI,我可以开始将鼠标移向按钮……结果页面却重新加载了,让我措手不及。如果屏幕在测试运行时保持空白,倒也还好,但看到一个完全样式化的网站,然后又消失了,就不是那么美妙了。
这是在 Windows PC 上使用 Chrome 的情况。
如果可能的话,我很想得到一个快速的屏幕截图。如果我们谈论的是桌面 Chrome,它不应该刷新,因为我们实际上假设是桌面,并且只在小屏幕上进行重定向。我并不怀疑这种情况会发生,我只是需要更多信息,因为我几乎每天都在 CodePen 上工作,我从来没有见过这种情况。
我在一台相当过时的 Windows PC 上使用桌面 Chrome,我没有遇到这种情况。
我认为在长期 cookie 中存储屏幕宽度信息是不好的,因为它可能会因设备方向、分辨率更改或连接到笔记本电脑的外部显示器而发生变化。
演示通过(通过媒体查询)检测是否加载了错误的模板,并提供链接以更正它来解决这个问题。如果你愿意,你甚至可以自动更正它,这就是所有这些的妙处。
我猜 GIF 是 Codepen 在手机上应该的样子。好吧,至少对我来说,它不是这样的。我使用的是一台高质量但价格实惠的智能手机(阅读中国产,但不是苹果),屏幕尺寸为 1080×1920,运行 Android 4.2.1。
我一直看到桌面版本。这是与高密度显示器有关的吗?
1080 的宽度就是原因。如果从手机上的浏览器执行 window.outerWidth,它会告诉你“1080”?这很宽!
我们在 CodePen 上的芥末切割法不可否认地很原始。不过,我不会因为这个而否定这种技术,这只是一个简单的例子。这项技术正在做它应该做的事情——你可以根据自己的喜好来设计芥末切割法。
谢谢 Chris,.. 我喜欢这种方法,.. 它让你思考。你遇到过任何与 SEO/分析相关的问题吗?
即使你没有很快或永远没有时间这样做,看看你在评估 CodePen.io 的渐进增强和响应式设计时的笔记也会很有趣。
是什么让这里所需的功效和体验最适合使用自适应/芥末方法实现?
什么 Web 技术缺失了,或者是什么让响应式 CodePen.io 不可行?
我看到了两个问题
你将向搜索引擎提供哪个版本?没有 cookie,所以我假设只是“基本”版本——这是否足以让他们获取整个内容进行索引?(也许我们可以回到使用 UA 来嗅探搜索引擎机器人……)
或者,反过来说:他们可能会因为向他们展示“完整”版本而惩罚我们——但随后却向某些设备(在相同 URL 下)提供较少的内容?
我怀疑仅查询
navigator.cookieEnabled
并将其视为可行标志的可靠性。你已经提到了“安全设置”,这在这方面可能是一个问题,但我认为可能存在很多潜在的情况会导致此方法失败。例如,我的浏览器有一个设置,让我确认每个 cookie——使用它,
navigator.cookieEnabled
报告为 true,但我仍然可以拒绝 cookie。此外,可能存在防火墙/代理,它们会从发送到服务器的请求中过滤掉 cookie,或者浏览器扩展,它们会操纵浏览器对 cookie 的反应方式或navigator.cookieEnabled
的报告方式……所以我认为,最终陷入重定向循环的可能性非常大。(我可能至少会包含一个测试,读取一个刚刚通过document.cookie
设置回去的 cookie 值——至少可以更确定地了解问题客户端方面的情况。)你说你“好几个月都没有遇到过问题”——但你真的知道你的网站用户是否遇到了问题吗?(我不知道你根据什么可能的措施或硬数据得出这个假设/结论——只是说,没有收到任何投诉并不一定意味着没有问题。)
[嘿,这在预览中看起来更好——你的 Markdown 解析器按预期工作了吗?我使用数字创建了一个包含两项的列表,但它不见了……]
分析会更多地影响到这一点,但是,如果大多数用户默认情况下是“enhanced.php”(我假设 CodePen 的受众就是这样),你可以假设带有“未检查”cookie 的 enhanced.php,并使用异步 AJAX 请求,以便 enhanced.php 用户从一开始就顺利进行。只有 core.php 用户在异步请求完成后才需要从那时起进行刷新。
我见过的许多 Web 应用程序都会在用户功能完善时使用加载栏,但我从来不喜欢网站/应用程序上的初始加载栏。
最终,最重要的是对最终用户来说最有效(实际和感知上的)。老实说,我没有在 CodePen 上注意到任何问题,所以无论你使用什么方法,它似乎都能正常工作!
也许我错了,但为什么不用 AJAX 提供正确的部分?
它很直接,也很容易……
而且,如果你真的想向没有 JavaScript 的人展示东西,你可以向他们展示:a) 只有对其他人来说通用的东西,b) 将你想要的内容放在 noscript 标签中(我很久没用过它了……)
我错了?
我已经为自己创建了一种类似的东西,它似乎可以工作(你可以使用触摸事件和屏幕尺寸……但记住触摸事件问题和 Windows 8 可转换笔记本电脑)