1. ホーム
  2. javascript

[解決済み] CSSは常にJavascriptより優先されるべきか?

2022-03-17 21:34:57

質問

ネット上では、JavaScriptの前にCSSを入れることを推奨しているのを数え切れないほど見かけました。その理由は一般的に この形式の :

CSSとJavaScriptの順番は、CSSを優先してください。 を先にします。その理由は、レンダリングスレッドがすべての スタイル情報は、ページをレンダリングするために必要です。もし、JavaScript が先にあると、JavaScript エンジンはそれをすべて解析してからでないと 次のリソースのセットに進みます。つまり、レンダリング をすべて持っていないため、ページを完全に表示することができません。 のスタイルが必要です。

実際にテストしてみると、まったく違うことがわかりました。

テストハーネス

私は以下のRubyスクリプトを使用して、様々なリソースに対して特定の遅延を生成しています。

require 'rubygems'
require 'eventmachine'
require 'evma_httpserver'
require 'date'

class Handler  < EventMachine::Connection
  include EventMachine::HttpServer

  def process_http_request
    resp = EventMachine::DelegatedHttpResponse.new( self )

    return unless @http_query_string

    path = @http_path_info
    array = @http_query_string.split("&").map{|s| s.split("=")}.flatten
    parsed = Hash[*array]

    delay = parsed["delay"].to_i / 1000.0
    jsdelay = 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 request
    operation = proc do
      sleep delay 

      if path.match(/.js$/)
        resp.status = 200
        resp.headers["Content-Type"] = "text/javascript"
        resp.content = "(function(){
            var start = new Date();
            while(new Date() - start < #{jsdelay}){}
          })();"
      end
      if path.match(/.css$/)
        resp.status = 200
        resp.headers["Content-Type"] = "text/css"
        resp.content = "body {font-size: 50px;}"
      end
    end

    # Callback block to execute once the request is fulfilled
    callback = proc do |res|
        resp.send_response
    end

    # Let the thread pool (20 Ruby threads) handle request
    EM.defer(operation, callback)
  end
end

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の転送に500msの遅延を与えています。

次のページを使ってテストしています。

<!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秒かかる。

JavaScriptを先に組み込むと、ページのレンダリングに1.4秒かかります。

Chrome、Firefox、Internet Explorerで同様の結果が得られます。しかし、Operaでは、順序は単に重要ではありません。

どうやら、CSSがすべてダウンロードされるまで、JavaScriptのインタプリタが起動を拒否しているようなのです。つまり、JavaScriptを先にインクルードしたほうが、JavaScriptスレッドの実行時間が長くなり、効率的なようです。

CSSインクルードをJavaScriptインクルードより先に配置するという推奨は正しくないのでしょうか?

<サブ 非同期を追加したり、レンダースレッドを解放するために setTimeout を使用したり、JavaScript コードをフッターに配置したり、JavaScript ローダーを使用したりできることは明らかです。ここでのポイントは、head内で必要不可欠なJavaScriptのビットとCSSのビットの順序についてです。

どのように解決する?

これは非常に興味深い質問です。 私はいつもCSSの <link href="..."> はJSの前に <script src="..."> というのも、以前その方がいいと書いてあったからです。

私はNodeで独自のテストハーネスをセットアップしました(コードは以下)。 基本的に、私は

  • HTTPキャッシュがないことを確認し、ページが読み込まれるたびにブラウザが完全なダウンロードを行うようにしました。
  • 現実をシミュレートするために、jQuery と H5BP CSS(なので、パースするスクリプト/CSSがちゃんとあります)
  • スクリプトの前にCSSがあるページと、スクリプトの後にCSSがあるページの2つを設定します。
  • の外部スクリプトにどれくらいの時間がかかったかを記録。 <head> を実行する。
  • のインラインスクリプトにかかった時間を記録。 <body> を実行することであり、これは DOMReady .
  • ブラウザへのCSSやスクリプトの送信を500ms遅延させました。
  • 3大ブラウザで20回テストを実施。

結果

まず、CSSファイルを500ms遅延させた状態。

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

次に、CSSの代わりにjQueryで500msずつ遅延させるように設定しました。

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

最後に 両方 jQueryとCSSで500msずつ遅延させる。

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

結論

まず、重要なこととして、私は、スクリプトを <head> の末尾ではなく)。 <body> ). スクリプトにリンクする理由については、さまざまな議論があります。 <head> しかし、それはこの答えの範囲外です。 これは、厳密に <script> の前に置く必要があります。 <link> の中で <head> .

最近のDESKTOPブラウザでは。 は、最初にCSSにリンクしているように見えます。 決して はパフォーマンスを向上させます。 CSSをスクリプトの後に配置すると、CSSとスクリプトの両方が遅延した場合はわずかな利益しか得られませんが、CSSが遅延した場合は大きな利益が得られます。 (これは last の列が表示されます)。

CSSを最後にリンクしてもパフォーマンスが落ちないことを考えると、しかし できる は、特定の状況下で利益をもたらします。 外部スタイルシートにリンクする必要があります。 外部スクリプトにリンクしている場合 デスクトップ・ブラウザーでのみ 古いブラウザのパフォーマンスが気にならない場合。 モバイル事情はこちらをお読みください。

なぜ?

歴史的に、ブラウザが <script> タグが外部リソースを指している場合、ブラウザは 停止 HTMLを解析してスクリプトを取得し、それを実行した後、HTMLの解析を続行します。 これに対して、ブラウザが <link> を外部スタイルシートのために使用する場合、それは 続ける は、CSS ファイルを取得する間に HTML を解析します (並行して)。

したがって、スタイルシートを最初に置くようにという広く言われるアドバイスがあります。スタイルシートが最初にダウンロードされ、最初にダウンロードされたスクリプトが並行してロードされる可能性があるからです。

しかし、最近のブラウザ(上記でテストしたすべてのブラウザを含む)は、このような問題を解決するために 投機的構文解析 ブラウザが HTML の先を読み、リソースのダウンロードを開始することです。 以前 スクリプトがダウンロードされ、実行されます。

投機的解析のない古いブラウザでは、スクリプトを先に置くと並行してダウンロードできないため、パフォーマンスに影響があります。

対応ブラウザ

投機的構文解析が初めて実装されたのは (2012年1月現在、このバージョン以上を使用している世界中のデスクトップ ブラウザの割合も一緒に)

  • Chrome 1 (WebKit 525) (100%)
  • IE 8 (75%)
  • Firefox 3.5 (96%)
  • サファリ 4 (99%)
  • オペラ 11.60 (85%)

現在使用されているデスクトップ用ブラウザのうち、合計で約85%が投機的ロードをサポートしています。 CSSよりスクリプトを優先させると、15%のユーザーにパフォーマンス上のペナルティが発生します。 グローバルに あなたのサイトの利用者層に応じて、ご利用ください。 (この数は減少していることを忘れないでください。)

モバイルブラウザの場合、モバイルブラウザとOSが異種混在しているため、明確な数字を出すのは少し難しいです。 投機的レンダリングは WebKit 525(2008 年 3 月リリース)で実装され、価値のあるモバイル ブラウザはほぼすべて WebKit をベースにしているので、次のように結論づけることができます。 すべき をサポートしています。 によると quirksmode iOS 2.2/Android 1.0 は WebKit 525 を使用しています。 Windows Phoneがどうなっているのか、まったくわかりません。

しかし Android 4端末でテストを行ったところ、デスクトップの結果と似たような数値が表示されましたが、この端末を素晴らしい新機能である リモートデバッガ ネットワークタブでは、JavaScriptが完全に読み込まれるまで、ブラウザが実際にCSSのダウンロードを待っていることがわかりました。 Android 用の WebKit の最新バージョンでも、投機的解析はサポートされていないようです。 モバイル機器特有の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));
});

jquery.jsは jquery-1.7.1.min.js