CSS应该总是在JavaScript之前吗?

在网上无数的地方,我看到了在JavaScript之前包含CSS的建议。推理通常是,这种形式的

在排序CSS和JavaScript时,您需要您的CSS先来。原因是渲染线程具有所有的样式信息,它需要呈现页面。如果JavaScript包含先来,JavaScript引擎必须先解析它继续下一组资源。这意味着渲染线程不能完全显示页面,因为它没有所有的它需要的风格。

我的实际测试揭示了一些完全不同的东西:

我的测试背带

我使用以下ruby脚本为各种资源生成特定的延迟:

require 'rubygems'require 'eventmachine'require 'evma_httpserver'require 'date'
class Handler  < EventMachine::Connectioninclude EventMachine::HttpServer
def process_http_requestresp = EventMachine::DelegatedHttpResponse.new( self )
return unless @http_query_string
path = @http_path_infoarray = @http_query_string.split("&").map{|s| s.split("=")}.flattenparsed = Hash[*array]
delay = parsed["delay"].to_i / 1000.0jsdelay = parsed["jsdelay"].to_i
delay = 5 if (delay > 5)jsdelay = 5000 if (jsdelay > 5000)
delay = 0 if (delay < 0)jsdelay = 0 if (jsdelay < 0)
# Block which fulfills the requestoperation = proc dosleep delay
if path.match(/.js$/)resp.status = 200resp.headers["Content-Type"] = "text/javascript"resp.content = "(function(){var start = new Date();while(new Date() - start < #{jsdelay}){}})();"endif path.match(/.css$/)resp.status = 200resp.headers["Content-Type"] = "text/css"resp.content = "body {font-size: 50px;}"endend
# Callback block to execute once the request is fulfilledcallback = proc do |res|resp.send_responseend
# Let the thread pool (20 Ruby threads) handle requestEM.defer(operation, callback)endend
EventMachine::run {EventMachine::start_server("0.0.0.0", 8081, Handler)puts "Listening..."}

上面的迷你服务器允许我为JavaScript文件(服务器和客户端)设置任意延迟和任意CSS延迟。例如,http://10.0.0.50:8081/test.css?delay=500给了我传输CSS的500毫秒延迟。

我使用以下页面进行测试。

<!DOCTYPE html><html><head><title>test</title><script type='text/javascript'>var startTime = new Date();</script><link href="http://10.0.0.50:8081/test.css?delay=500" type="text/css" rel="stylesheet"><script type="text/javascript" src="http://10.0.0.50:8081/test2.js?delay=400&amp;jsdelay=1000"></script></head><body><p>Elapsed time is:<script type='text/javascript'>document.write(new Date() - startTime);</script></p></body></html>

当我首先包含CSS时,页面需要1.5秒才能渲染:

CSS first

当我首先包含JavaScript时,页面需要1.4秒才能呈现:

JavaScript first

我在Chrome、Firefox和Internet Explorer中得到了类似的结果。然而,在Opera中,排序根本无关紧要。

似乎发生的事情是JavaScript解释器拒绝启动,直到所有CSS都下载完毕。因此,随着JavaScript线程获得更多的运行时间,首先包含JavaScript似乎更有效。

我错过了什么吗?将CSS包含放在JavaScript包含之前的建议是否不正确?

很明显,我们可以添加async或使用setTimeout来释放渲染线程,或将JavaScript代码放在页脚中,或使用JavaScript加载器。这里的重点是关于头部中基本JavaScript位和CSS位的顺序。

118601 次浏览

个人而言,我不会太强调这种“民间智慧”。过去可能是正确的东西现在很可能不是正确的。我会假设所有与网页解释和渲染相关的操作都是完全异步的(“获取”某物和“对其进行操作”是完全不同的事情,可能由不同的线程处理,),无论如何,完全超出了你的控制或关注范围。

我会将CSS引用放在文档的“head”部分,以及对外部脚本的任何引用。(有些脚本可能需要放在主体中,如果是这样,请强制要求它们。)

除此之外……如果你观察到“在这个/那个浏览器上,这个似乎比那个快/慢”,把这个观察视为一个有趣但无关紧要的好奇心,不要让它影响你的设计决策。太多的事情变化太快了。(有人想打赌在Firefox团队发布他们的产品的另一个临时版本之前会有多少分钟吗?是的,我也没有。)

您的测试是在您的个人计算机上执行的,还是在Web服务器上执行的?它是一个空白页面,还是一个包含图像、数据库等的复杂在线系统?您的脚本是在执行简单的悬停事件操作,还是它们是您的网站呈现和与用户交互的核心组件?这里有几件事需要考虑,当您冒险进行高质量的Web开发时,这些建议的相关性几乎总是成为规则。

“将样式表放在顶部,脚本放在底部”规则的目的是,一般来说,这是实现最佳渐进式渲染的最佳方式,这对用户体验至关重要。

除此之外:假设您的测试是有效的,并且您确实产生了与流行规则相反的结果,这并不奇怪,真的。每个网站(以及使整个内容出现在用户屏幕上所需的一切)都是不同的,互联网也在不断发展。

将CSS放在JavaScript之前有两个主要原因。

  1. 旧浏览器(Internet Explorer 6-7、Firefox 2等)在开始下载脚本时会阻止所有后续下载。因此,如果您有a.js后跟b.css,它们会按顺序下载:首先是a,然后是b。如果您有b.css后跟a.js,它们会并行下载,因此页面加载速度更快。

  2. 在下载所有样式表之前,什么都不会渲染——这在所有浏览器中都是这样的。脚本不同——它们会阻止页面中脚本标签下方的所有DOM元素的渲染。如果你把脚本放在HEAD中,那么这意味着整个页面将被阻止渲染,直到所有样式表和所有脚本都被下载。虽然阻止样式表的所有渲染是有意义的(这样你就可以第一次得到正确的样式并避免无样式内容FOUC的闪现),但阻止脚本整个页面的渲染是没有意义的。通常脚本不影响任何DOM元素或只是DOM元素的一部分。最好在页面中尽可能低地加载脚本,甚至更好地异步加载它们。

使用Cuzillion创建示例很有趣。例如,此页面在HEAD中有一个脚本,因此在下载完成之前整个页面都是空白的。然而,如果我们将脚本移动到BODY块的末尾,页面标题会呈现,因为这些DOM元素发生在SCRIPT标记上方,正如您在此页面上看到的那样。

这是一个非常有趣的问题。我总是把我的CSS<link href="...">放在JavaScript<script src="...">之前,因为“我有一次读到它更好。”所以,你是对的;是时候我们做一些实际的研究了!

我在Node.js中设置了自己的测试工具(代码如下)。基本上,我:

  • 确保没有HTTP缓存,因此浏览器必须在每次加载页面时进行完整下载。
  • 为了模拟现实,我包含了jQuery和H5BP CSS(所以有相当多的脚本/CSS需要解析)
  • 设置两个页面-一个在脚本之前使用CSS,一个在脚本之后使用CSS。
  • 记录<head>中的外部脚本执行所需的时间
  • 记录<body>中的内联脚本执行所需的时间,类似于DOMReady
  • 向浏览器发送CSS和/或脚本延迟500 ms。
  • 在三大浏览器中进行了20次测试。

搜索结果

首先,CSS文件延迟500 ms(单位为毫秒):

     Browser: Chrome 18    | IE 9         | Firefox 9CSS: first  last  | first  last  | first last=======================================================Header Exec |              |              |Average     | 583    36    | 559    42    | 565   49St Dev      |  15    12    |   9     7    |  13    6------------|--------------|--------------|------------Body Exec   |              |              |Average     | 584    521   | 559    513   | 565   519St Dev      |  15      9   |   9      5   |  13     7

接下来,我将jQuery设置为延迟500 ms而不是CSS:

     Browser: Chrome 18    | IE 9         | Firefox 9CSS: first  last  | first  last  | first last=======================================================Header Exec |              |              |Average     | 597    556   | 562    559   | 564   564St Dev      |  14     12   |  11      7   |   8     8------------|--------------|--------------|------------Body Exec   |              |              |Average     | 598    557   | 563    560   | 564   565St Dev      |  14     12   |  10      7   |   8     8

最后,我将两者 jQuery和CSS设置为延迟500 ms:

     Browser: Chrome 18    | IE 9         | Firefox 9CSS: first  last  | first  last  | first last=======================================================Header Exec |              |              |Average     | 620    560   | 577    577   | 571   567St Dev      |  16     11   |  19      9   |   9    10------------|--------------|--------------|------------Body Exec   |              |              |Average     | 623    561   | 578    580   | 571   568St Dev      |  18     11   |  19      9   |   9    10

结论

首先,需要注意的是,我的操作假设您的脚本位于文档的<head>中(而不是<body>的末尾)。关于为什么链接到文档的脚本在<head>中而不是文档的末尾,有各种各样的争论,但这超出了这个答案的范围。这严格来说是关于<script>是否应该在<head>中的<link>之前。

在现代DESKTOP浏览器中,看起来首先链接到CSS从未提供了性能提升。当CSS和脚本都延迟时,将CSS放在脚本之后会让您获得少量收益,但当CSS延迟时会让您获得巨大收益。(由第一组结果中的last列显示。)

鉴于链接到CSS最后似乎不会损害性能,但可以在某些情况下提供收益,您应该在之后链接到外部样式表,您仅在桌面浏览器上链接到外部脚本如果旧浏览器的性能不是问题。继续阅读移动情况。

为啥?

从历史上看,当浏览器遇到指向外部资源的<script>标记时,浏览器将停止解析超文本标记语言,检索脚本,执行它,然后继续解析超文本标记语言。相反,如果浏览器遇到外部样式表的<link>,它将继续解析超文本标记语言,同时获取CSS文件(并行)。

因此,广泛重复的建议是将样式表放在首位——它们将首先下载,并且要下载的第一个脚本可以并行加载。

然而,现代浏览器(包括我上面测试的所有浏览器)已经实现了推测解析,其中浏览器以超文本标记语言“向前看”并开始下载资源之前脚本下载并执行。

在没有推测解析的旧浏览器中,将脚本放在首位会影响性能,因为它们不会并行下载。

浏览器支持

推测解析首次实施于:(以及截至2012年1月使用此版本或更高版本的全球桌面浏览器用户的百分比)

总的来说,目前使用的大约85%的桌面浏览器支持推测加载。将脚本放在CSS之前将对15%的用户造成性能损失,这取决于您网站的特定受众。(请记住,这个数字正在缩小。)

在移动浏览器上,仅仅由于移动浏览器和操作系统的异构性,要获得明确的数字有点困难。由于投机性渲染是在WebKit 525(2008年3月发布)中实现的,几乎所有有价值的移动浏览器都基于WebKit,我们可以得出结论,“大多数”移动浏览器应该支持它。根据quirksmode,iOS2.2/Android 1.0使用WebKit 525。我不知道Windows Phone是什么样子。

然而,我在我的Android 4设备上运行了测试,当我看到与桌面结果相似的数字时,我将其连接到AndroidChrome的新远程调试器网络选项卡显示浏览器实际上正在等待下载CSS,直到JavaScript代码完全加载-换句话说,即使是最新版本的WebKit for Android似乎也不支持推测解析。我怀疑它可能由于移动设备固有的CPU,内存和/或网络限制而被关闭。

代码

原谅我的马虎-这是Q&D。

文件app.js

var express = require('express'), app = express.createServer(), fs = require('fs');
app.listen(90);
var file={};fs.readdirSync('.').forEach(function(f) {console.log(f)file[f] = fs.readFileSync(f);if (f != 'jquery.js' && f != 'style.css') app.get('/' + f, function(req,res) {res.contentType(f);res.send(file[f]);});});

app.get('/jquery.js', function(req,res) {setTimeout(function() {res.contentType('text/javascript');res.send(file['jquery.js']);}, 500);});
app.get('/style.css', function(req,res) {setTimeout(function() {res.contentType('text/css');res.send(file['style.css']);}, 500);});

var headresults={css: [],js: []}, bodyresults={css: [],js: []}app.post('/result/:type/:time/:exec', function(req,res) {headresults[req.params.type].push(parseInt(req.params.time, 10));bodyresults[req.params.type].push(parseInt(req.params.exec, 10));res.end();});
app.get('/result/:type', function(req,res) {var o = '';headresults[req.params.type].forEach(function(i) {o+='\n' + i;});o+='\n';bodyresults[req.params.type].forEach(function(i) {o+='\n' + i;});res.send(o);});

文件css.html

<!DOCTYPE html><html><head><title>CSS first</title><script>var start = Date.now();</script><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="test.js"></script></head><body><script>document.write(jsload - start);bodyexec=Date.now()</script></body></html>

文件js.html

<!DOCTYPE html><html><head><title>CSS first</title><script>var start = Date.now();</script><script src="jquery.js"></script><script src="test.js"></script><link rel="stylesheet" href="style.css"></head><body><script>document.write(jsload - start);bodyexec=Date.now()</script></body></html>

文件test.js

var jsload = Date.now();

$(function() {$.post('/result' + location.pathname.replace('.html','') + '/' + (jsload - start) + '/' + (bodyexec - start));});

jQueryjquery-1.7.1.min.js

我不会过多强调你得到的结果。我相信这是主观的,但我有理由向你解释,最好在JavaScript之前放入CSS。

在您的网站加载过程中,您会看到两种情况:

案例1:白屏→无样式网站→样式网站→交互→样式和交互网站

案例2:白屏→无样式网站→交互→样式网站→样式和交互网站

老实说,我无法想象有人会选择案例2。这意味着使用慢速互联网连接的访问者将面临一个未设计的网站,这允许他们使用JavaScript与之交互(因为它已经加载)。此外,通过这种方式,花费在未设计网站上的时间将最大化。为什么会有人想要这样?

它也工作得更好,如jQuery状态

"当使用依赖CSS样式属性值的脚本时,引用外部样式表或嵌入样式很重要引用脚本之前的元素”。

当文件以错误的顺序加载时(首先是JavaScript,然后是CSS),任何依赖于CSS文件中设置的属性(例如,div的宽度或高度)的JavaScript代码都不会正确加载。似乎在错误的加载顺序下,JavaScript“有时”知道正确的属性(也许这是由竞争条件引起的?)。这种影响似乎更大或更小,取决于所使用的浏览器。

我在JavaScript之前包含CSS文件是出于不同的原因。

如果我的JavaScript代码需要动态调整一些页面元素的大小(对于那些角落的情况,CSS实际上是在后面的一个主要部分),那么在JS运行后加载CSS可能会导致竞争条件,在应用CSS样式之前调整元素的大小,因此当样式最终生效时看起来很奇怪。如果我事先加载CSS,我可以保证事情按照预期的顺序运行,并且最终的布局是我想要的。

我们必须记住,新的浏览器已经在他们的JavaScript引擎,他们的解析器等方面工作,优化常见的代码和标记问题,在古代浏览器中遇到的问题(如Internet Explorer 8或之前)不再相关,不仅在标记方面,而且在JavaScript变量,元素选择器等的使用方面。

我可以看到在不远的将来,技术已经达到了性能不再是问题的地步。

我不确定您使用JavaScript时如何测试“渲染”时间。但是,考虑一下:

您网站上的一个页面是50 kB,这不是不合理的。用户在东海岸上,而您的服务器在西边。MTU肯定不是10k所以会有几次来回。接收您的页面和样式表可能需要1/2秒。通常(对我来说)JavaScript(通过jQuery插件等)不仅仅是CSS。还有当你的互联网连接在页面中途阻塞时会发生什么,但让我们忽略它(我偶尔会发生这种情况,我相信CSS渲染,但我不是100%确定)。

由于CSS位于中,因此可能有其他连接可以获取它,这意味着它可能会在页面完成之前完成。无论如何,在键入页面的其余部分和JavaScript文件(更多字节)期间,页面未设置样式,这使得站点/连接看起来很慢。

<强>即使 JavaScript解释器拒绝启动,直到CSS完成,下载JavaScript代码所需的时间,特别是当远离服务器时,正在削减CSS时间,这将使网站看起来不漂亮。

这是一个小的优化,但这就是它的原因。

Steve Souders已经已经给出了明确的答案,但是…

我想知道山姆的原始测试和乔希的重复测试是否有问题。

这两个测试似乎都是在低延迟连接上执行的,其中设置传输控制协议的成本微不足道。

这如何影响测试的结果我不确定,我想看看瀑布的测试在一个“正常”的延迟连接,但…

下载的第一个文件应该获取用于超文本标记语言页面的连接,下载的第二个文件将获取新连接。(提前刷新会改变这种动态,但这里没有这样做。)

在较新的浏览器中,第二个传输控制协议是推测性打开的,因此连接开销减少/消失。在旧的浏览器中,这不是真的,第二个连接将有打开的开销。

这如何/是否会影响测试的结果,我不确定。

更新时间2017-12-16

我不确定OP中的测试。我决定做一点实验,最终打破了一些神话。

同步<script src...>将阻止资源的下载直到它被下载并执行

这不再是事实。看看Chrome63生成的瀑布:

<head><script src="//alias-0.redacted.com/payload.php?type=js&amp;delay=333&amp;rand=1"></script><script src="//alias-1.redacted.com/payload.php?type=js&amp;delay=333&amp;rand=2"></script><script src="//alias-2.redacted.com/payload.php?type=js&amp;delay=333&amp;rand=3"></script></head>

Chrome网络检查员->瀑布

<link rel=stylesheet>不会阻止下载和执行下面的脚本

这是不正确的。样式表不会阻止下载,但会阻止脚本的执行(这里有一点解释)。看看Chrome63生成的性能图表:

<link href="//alias-0.redacted.com/payload.php?type=css&amp;delay=666" rel="stylesheet"><script src="//alias-1.redacted.com/payload.php?type=js&amp;delay=333&amp;block=1000"></script>

Chrome开发工具->性能


考虑到上述情况,OP中的结果可以解释如下:

CSS第一:

CSS Download  500 ms:<------------------------------------------------>JS Download   400 ms:<-------------------------------------->JS Execution 1000 ms:                                                  <-------------------------------------------------------------------------------------------------->DOM Ready   @1500 ms:                                                                                                                                                      ◆

JavaScript第一:

JS Download   400 ms:<-------------------------------------->CSS Download  500 ms:<------------------------------------------------>JS Execution 1000 ms:                                        <-------------------------------------------------------------------------------------------------->DOM Ready   @1400 ms:                                                                                                                                            ◆

在JavaScript之前包含CSS的建议是否无效?

如果你把它当作一个简单的建议就不会。但是如果你把它当作一个严格的规则,是的,它是无效的。

窗口

样式表加载块脚本执行,因此如果您有<script><link rel="stylesheet" ...>之后,页面将无法完成解析

  • 在加载样式表之前,DOMContentLoade不会触发。

看起来你需要知道每个脚本依赖于什么,并确保脚本的执行延迟到正确的完成事件之后。如果脚本只依赖于DOM,它可以在ondom旅途中恢复。如果它依赖于要加载的图像或要应用的样式表,那么如果我正确阅读了上面的参考,该代码必须推迟到onload事件之后。

我不认为一种袜子尺寸适合所有人,即使这是他们的销售方式,而且我知道一种鞋码不适合所有人。我不认为有一个明确的答案,首先加载样式或脚本。它更多的是逐案决定必须以何种顺序加载哪些内容,以及哪些内容可以推迟到以后,因为它们不在“关键路径”上。

观察者评论说,最好推迟用户交互的能力,直到页面漂亮。你们中的许多人在外面,你惹恼了感觉相反的同行。他们来到一个网站是为了完成一个目的,在等待无关紧要的东西加载完成的同时,延迟他们与网站交互的能力是非常令人沮丧的。我不是说你错了,只是你应该意识到,还有另一个派别不同意你的优先级。

这个问题尤其适用于所有放置在网站上的广告。我希望网站作者只呈现广告内容的占位符div,并确保他们的网站在onload事件中注入广告之前加载和互动。即使这样,我也希望看到广告连续加载,而不是一次加载,因为它们会影响我在加载臃肿的广告时滚动网站内容的能力。但这只是一个人的观点。

  • 了解您的用户及其价值。
  • 了解您的用户以及他们使用的浏览环境。
  • 知道每个文件做什么,以及它的先决条件是什么。让一切正常工作将优先于速度和美观。
  • 在开发时使用向您显示网络时间线的工具。
  • 在您的用户使用的每个环境中进行测试。可能需要动态(服务器端,在创建页面时)根据用户环境更改加载顺序。
  • 如有疑问,请更改顺序并再次测量。
  • 按照加载顺序混合样式和脚本可能是最佳的;不是所有的一个然后所有的其他。
  • 不仅要实验加载文件的顺序,还要实验加载文件的位置。?在身体中?在身体之后?DOM就绪/加载?加载?
  • 适当时考虑异步和延迟选项,以减少用户在能够与页面交互之前将经历的净延迟。测试以确定它们是否有帮助或伤害。
  • 在评估最佳加载顺序时,总会有权衡考虑。很漂亮响应只是一个。

我认为这并不适用于所有情况。因为CSS内容会并行下载,但JavaScript代码不能。考虑相同的情况:

而不是只有一个CSS内容,采取两个或三个CSS文件并尝试这些方法,

  1. CSS… CSS… JavaScript

  2. CSS… JavaScript… CSS

  3. JavaScript… CSS… CSS

我相信CSS… CSS… JavaScript会比其他所有人都提供更好的结果。

以下是之前所有主要答案的摘要

对于现代浏览器,把CSS内容放在任何你喜欢的地方。他们会分析你的超文本标记语言文件(他们称之为推测解析),并开始下载CSS与超文本标记语言解析并行。

对于旧浏览器,请继续将CSS放在顶部(如果您不想首先显示裸露但交互式的页面)。

对于所有浏览器,将JavaScript内容放在页面上尽可能低的位置,因为它会停止解析超文本标记语言。最好是异步下载(即Ajax调用)。

对于一个特定的情况,也有一些实验结果声称将JavaScript放在首位(而不是将CSS放在首位的传统智慧)可以提供更好的性能,但是没有给出任何逻辑推理,并且缺乏关于广泛适用性的验证,所以你现在可以忽略它。

所以,回答这个问题:是的。在JavaScript之前包含CSS的建议对现代浏览器来说是无效的。把CSS放在你喜欢的任何地方,并尽可能把JavaScript放在最后。

2020年答案:可能没关系

这里最好的答案是从2012年开始的,所以我决定自己测试一下。在Android的Chrome上,JS和CSS资源是并行下载的,我无法检测到页面渲染速度的差异。

我在我的博客上写了一个更详细的写作