aboutsummaryrefslogtreecommitdiff
path: root/files/zh-tw/web/http/browser_detection_using_the_user_agent/index.html
blob: 4fed2d0727274734f86555dc2d578944c8a91e86 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
---
title: 透過用戶代理偵測瀏覽器
slug: Web/HTTP/Browser_detection_using_the_user_agent
translation_of: Web/HTTP/Browser_detection_using_the_user_agent
---
<div>{{HTTPSidebar}}</div>

<p>針對不同的瀏覽器給予不同的網頁或服務,通常不是好主意:網路的原意,是要讓所有人都能訪問,無論他們使用何種瀏覽器或何種設備。你的網站可以透過基於(瀏覽器)功能可用性的漸進增強法開發,而不是特別指定某種瀏覽器。</p>

<p>不過瀏覽器與標準並不是完美的,有些特殊情況依舊需要偵測瀏覽器。透過用戶代理(user agent)去偵測瀏覽器看似簡單,要做好卻頗為困難。這份文件會盡可能引導你正確處理這種事。</p>

<div class="note">
<p>因為很重要所以再說一次:實行用戶代理嗅探(User Agent sniffing)通常不是好主意。問題通常都會有更好、更通用的解決方法!</p>
</div>

<h2 id="使用瀏覽器偵測前應當考慮什麼">使用瀏覽器偵測前應當考慮什麼</h2>

<p>在考慮透過用戶代理字串,去偵測使用瀏覽器時,首先要盡可能避免這種用法。先從認清<strong>為什麼</strong>要這麼做開始。</p>

<dl>
 <dt>你正針對某瀏覽器的特定錯誤奮戰著?</dt>
 <dd>去專業論壇閱讀或提問:你不太可能是第一個碰上問題的人。另外,去找專家、或只是與你有不同觀點的人問問看,也會對你的除錯思路有所幫助。如果問題看來頗為罕見,你應該去檢查這個錯誤是不是透過缺陷跟蹤管理系統(bug tracking system:<a class="link-https" href="https://bugzilla.mozilla.org">Mozilla</a><a class="external" href="http://bugs.webkit.org">WebKit</a><a href="https://www.chromium.org/issue-tracking">Blink</a><a class="link-https" href="https://bugs.opera.com/">Opera</a>)報告到瀏覽器供應商。瀏覽器供應商很重視錯誤報告,相關分析也可能提示該錯誤的其他解決辦法。</dd>
 <dt>你正試圖檢查某個特定功能是否存在?</dt>
 <dd>你的網站需要用到某些瀏覽器不支援的功能,並給這些用戶功能更少,但你知道能正常顯示的網站。這類用戶代理嗅探的理由非常糟糕,因為大多數的瀏覽器,最終都有可能支援該功能。對所有瀏覽器都予以測試也不實際。<strong>絕對不要</strong>用戶代理嗅探、功能偵測是<strong>永遠</strong>的替代方案。</dd>
 <dt>你希望給不同的瀏覽器不同的 HTML?</dt>
 <dd>這種作法通常不太好,不過有時候卻是必要的。在此種情況下,你首先要分析是否真該這麼做。你能藉由加入某些無語意的 {{ HTMLElement("div") }}{{ HTMLElement("span") }} 元素避免嗎?與難以完成的用戶代理偵測比起來,HTML 整潔性的稍稍降低變得相當值得。另外,請重新構思你的設計:你能藉由漸進增強或是流動排版(fluid layouts)來消除用戶代理偵測的需求嗎?</dd>
</dl>

<h2 id="避免用戶代理偵測">避免用戶代理偵測</h2>

<p>如果要避免用戶代理偵測,有以下選項!</p>

<dl>
 <dt>功能偵測</dt>
 <dd>功能偵測使你無須弄清是哪種瀏覽器在渲染你的網頁,只須檢查需要的具體功能是否能用。如果不能用,就採取備用方案。在極少數的情況下,各瀏覽器行為有所不同。面對這種情況,不要偵測用戶代理,而是用實作測試來檢查瀏覽器 API、並搞清楚用法。最近有個好例子:<a href="https://www.chromestatus.com/feature/5668726032564224">Chrome 針對正規表達式,添加了實驗性的 lookbehind 支援</a>,但其他瀏覽器並不支援。你可能以為要這麼用:</dd>
 <dd>
 <pre class="brush: js notranslate">// 這個程式以特殊表示法把字串分開來

if (navigator.userAgent.indexOf("Chrome") !== -1){
    // 好,這用戶應該是支援 look-behind regexps
    // 不要在不支援該功能的瀏覽器使用 /(?&lt;=[A-Z])/
    // 因為瀏覽器都會解析整個腳本,包括從未執行過的代碼部分。
    // 進而讓不支援該功能的瀏覽器拋出語法錯誤。
    var camelCaseExpression = new RegExp("(?&lt;=[A-Z])");
    var splitUpString = function(str) {
        return (""+str).split(camelCaseExpression);
    };
} else {
    /* 這個語法的性能差得多,但能動 */
    var splitUpString = function(str){
        return str.replace(/[A-Z]/g,"z$1").split(/z(?=[A-Z])/g);
    };
}
console.log(splitUpString("fooBare")); // ["fooB", "are"]
console.log(splitUpString("jQWhy")); // ["jQ", "W", "hy"]</pre>

 <p>但這程式其實很糟糕、考慮也很不周到。如果 Chrome 把 lookbehind 這功能移走呢?如果其他瀏覽器支援了 lookbehind 正規表達式呢?如果其他瀏覽器在用戶代理名字內,混入了 <em>Chrome</em> 呢?這個列表會因此,讓可怕的錯誤不斷發生。因此,你應該用以下的功能檢測:</p>

 <pre class="brush: js notranslate">var isLookBehindSupported = false;
try {
    isLookBehindSupported = !!new RegExp("(?&lt;=)");
} catch(e){
    // 不支援的瀏覽器會出現 lookbehind expressions err
}
var splitUpString = isLookBehindSupported ? function(str) {
    return (""+str).split(new RegExp("(?&lt;=[A-Z])"));
} : function(str) {
    return str.replace(/[A-Z]/g,"z$1").split(/z(?=[A-Z])/g);
};
</pre>

 <p>這程式<strong>一定</strong>會讓瀏覽器在不嗅探用戶代理的情況下測試功能。要作類似這樣的事情,<strong>完全沒有</strong>動用用戶代理嗅探的理由。</p>

 <p>最後,上面的程式碼還附帶一個必須考量的,有關跨瀏覽器的關鍵問題:不要在不支援的瀏覽器,使用到要測試的API。這聽來簡單,但有時候不是這樣:同樣以上面為例,在簡寫正規表達式使用 lookbehind(如 <code>/reg/igm</code>)會讓不支援該功能瀏覽器的解析器出錯。因此,你需要使用 <em>new RegExp("(?&lt;=look_behind_stuff)");</em> 而非 <em>/(?&lt;=look_behind_stuff)/</em>,哪怕 lookbehind 已經支援了。</p>
 </dd>
 <dt>漸進增強(Progressive Enhancement)</dt>
 <dd>此設計技術與網站開發的「層次」有關:它運用下而上的途徑、從簡單的層次開始,透過一連串的層次,漸漸增強網站的能力。</dd>
 <dt>優雅降級(Graceful degradation)</dt>
 <dd>這種由上而下的途徑,是先在建造網站時,就用上所有需要的功能,再調整到令舊版瀏覽器也能執行。這種途徑與漸進增強相比,難度更高、效率也更糟,不過在某些情況下也可能更管用。</dd>
 <dt>行動設備偵測(Mobile Device Detection)</dt>
 <dd>
 <p>檢查是否透過行動設備上網,大概是用戶代理嗅探最常見的用途與誤用。偵測後要作什麼事,卻往往是被忽略的問題所在。開發者通常透過用戶代理嗅探,將用戶設備導向至易於觸碰的小螢幕,以便加強網站體驗。</p>

 <p>用戶代理這方面有時有用,但問題是所有設備不完全相同:有些行動設備的尺寸很大、有些桌機有一小塊觸控螢幕、有些人使用完全是不同世界的智慧型電視、甚至還有藉由翻轉平板、來動態改變設備長寬的人!</p>

 <p>因此,用戶代理嗅探絕不是好辦法。但是,還有更好的選擇:例如使用 <a href="/zh-TW/docs/Web/API/Navigator/maxTouchPoints">Navigator.maxTouchPoints</a> 來檢查用戶設備有沒有觸控螢幕;接著在 <em>if (!("maxTouchPoints" in Navigator)) { /*程式寫在這*/}</em> 時,就切回用戶代理檢查。利用這個訊息,來檢查設備有沒有觸控螢幕。</p>

 <p>不要為了觸控設備,就換掉整個排版。這只會讓自己更費工、維護更頭痛;而是加點讓觸摸更便利的東西:像是好按的按鈕(這可以透過在 CSS 增加字體大小完成)。以下是針對 #exampleButton 在手機環境時,增加 1em 的程式範例:</p>
 </dd>
 <dd>
 <pre class="brush: js notranslate">var hasTouchScreen = false;
if ("maxTouchPoints" in navigator) {
    hasTouchScreen = navigator.maxTouchPoints &gt; 0;
} else if ("msMaxTouchPoints" in navigator) {
    hasTouchScreen = navigator.msMaxTouchPoints &gt; 0;
} else {
    var mQ = window.matchMedia &amp;&amp; matchMedia("(pointer:coarse)");
    if (mQ &amp;&amp; mQ.media === "(pointer:coarse)") {
        hasTouchScreen = !!mQ.matches;
    } else if ('orientation' in window) {
        hasTouchScreen = true; // depedicated, but good fallback
    } else {
        // Only as a last resort, fall back to user agent sniffing
        var UA = navigator.userAgent;
        hasTouchScreen = (
            /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
            /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA)
        );
    }
}
if (hasTouchScreen)
    document.getElementById("exampleButton").style.padding="1em";</pre>
 </dd>
 <dd>
 <p>針對螢幕尺寸,則使用 <em>window.innerWidth</em><em>window.addEventListener("resize", function(){ /*更新螢幕尺寸依賴的東西*/ })</em></p>

 <p>不要刪減小螢幕能看到的資訊,這只會激怒被逼著切到桌面版的用戶們;而是應該針對小螢幕,提供行列更少,但頁面更長的資訊;針對大螢幕,則提供行列更多,但頁面更短的資訊。這種效果能透過 <a href="/zh-TW/docs/Learn/CSS/CSS_layout/Flexbox">flexboxes</a> 實現。如果需要有限支援舊版本,請使用<a href="/zh-TW/docs/Learn/CSS/CSS_layout/Floats">floats</a> 屬性。</p>
 </dd>
 <dd>
 <p>另外,試著把不相關或不重要的資訊放到下面、然後把資料放得有意義。然後雖然有點離題,但下面的詳細示例,可能會給你有力的見解和想法,放棄用戶代理嗅探。</p>

 <p>我們先想像一個由各種貓貓或狗狗的訊息框,所組成的頁面;每個訊息框都有圖片、概覽、還有歷史趣聞;而圖片即使在大螢幕上,也要保持最大的合理尺寸。為了讓內容有意義的排列在一起,所有的貓貓訊息框都和狗狗訊息框分開、兩種動物都不會混在一起。在大螢幕上,會節省具有多列的空間,從而減少了圖片左右兩側的間距。訊息框則會透過平分而被拆分為多列。</p>

 <p>現在我們能假設在原始碼裡面,狗狗訊息框都在上面、而貓貓訊息框都在下面。而這兩個框框都在同一個父元素之下。很明顯,有一個狗狗訊息框,就在貓貓訊息框的上面。第一個方法,就是使用水平的 <a href="/zh-TW/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a> 把內容組合起來。這樣,當頁面顯示給最終用戶時,狗狗訊息框就在頁面上方、而貓貓訊息框就在頁面下方;第二個方法,就是使用 <a href="/zh-TW/docs/Web/CSS/Layout_cookbook/Column_layouts">Column</a> layout and resent 把所有的狗狗與貓貓排到右邊。在這種情況下,就能給沒有 flexboxes/multicolumns 的老舊版本提供適當的呈現:他們會呈現一列非常寬的框。</p>

 <p>再考慮一下這個例子:如果有人是想來看貓貓的,那我們就可以在原始碼裡面,把貓貓放到狗狗的上面。這樣一來,更多的人就可以在更小的螢幕上(內容折疊成一列)更快找到需要的內容。</p>
 </dd>
 <dd>
 <p>接著,確保程式是動態性的。用戶可以翻轉手機,以改變頁面長寬;或是未來使用某些類似功能的怪設備。不要為了用戶翻轉行為焦頭額爛、在使用開發工具確實檢查前也不要自滿。實踐的最佳辦法,就是在一個函式內透過螢幕尺寸,把所有可移動內容的程式分開。而分開這些程式的觸發點,則放在頁面載入、或觸動 <a href="/zh-TW/docs/Web/API/Window/resize_event">resize</a> 事件時。如果載入新佈局頁面前,需要在函式內計算很多東西,請考慮對事件偵聽器使用 debouncing 以避免過度呼叫。</p>

 <p>另請注意,<code>(max-width: 25em)</code>, <code>not all and (min-width: 25em)</code>, and <code>(max-width: 24.99em)</code>是不一樣的:<code>(max-width: 25em)</code> 會排除 <code>(max-width: 25em)</code>;而 <code>not all and (min-width: 25em)</code> 則包含了 <code>(max-width: 25em)</code><code>(max-width: 24.99em)</code> 是仆街版的 <code>not all and (min-width: 25em)</code>。不要用 <code>(max-width: 24.99em)</code>,因為在字型很大、或解析度很高時,版面<em>可能</em>會跑掉。謹慎選擇正確的 media query、以及在 Javascript 正確使用 &gt;=, &lt;=, &gt;, &lt; 等運算符。因為 Javascript 可能把這些東西都混為一談,然後你的網站就會在某些尺寸下亂閃亂排。因此,徹底測試在不同寬高下,網站會怎麼改變,以確保佈局不出錯。</p>
 </dd>
</dl>

<h2 id="把用戶代理嗅探搞到最好">把用戶代理嗅探搞到最好</h2>

<p>在探討所有能替代用戶代理嗅探的方法後,還是可能會有合理的理由,用到用戶代理嗅探。</p>

<p>其中一個例子,就是透過用戶代理嗅探,提供觸控螢幕的支援。詳請參閱上面的「行動設備偵測」章節。另一個例子,則是修復在沒有自動更新功能的瀏覽器上,所發生的錯誤。Windows 的 Internet Explorer 與 iOS 的 Webkit 就是個好實例。</p>

<p>Internet Explorer 在第九代以前,有著各種難以置信的問題。問題涵蓋了渲染、CSS、API 等方方面面。不過 IE9 之前的版本,是個相當<s>機車</s>特殊的例外。我們可以輕易透過該瀏覽器的特定功能,檢測到相關訊息。</p>

<p>蘋果強迫所有瀏覽器使用 Webkit 核心,所以 Webkit 的情形更糟糕;用戶也無法在舊設備上,得到更新的瀏覽器。大多數錯誤都能找出來,但某些錯誤,需要花更多時間抓出來。在這種情況下,使用用戶代理嗅探來可能是更有益的。</p>

<pre class="brush: js notranslate">var UA=navigator.userAgent, isWebkit=/\b(iPad|iPhone|iPod)\b/.test(UA) &amp;&amp;
               /WebKit/.test(UA) &amp;&amp; !/Edge/.test(UA) &amp;&amp; !window.MSStream;

var mediaQueryUpdated = true, mqL = [];
function whenMediaChanges(){mediaQueryUpdated = true}

var listenToMediaQuery = isWebkit ? function(mQ, f) {
    if(/height|width/.test(mQ.media)) mqL.push([mQ, f]);
    mQ.addListener(f), mQ.addListener(whenMediaChanges);
} : function(){};
var destroyMediaQuery = isWebkit ? function(mQ) {
    for (var i=0,len=mqL.length|0; i&lt;len; i=i+1|0)
        if (mqL[i][0] === mQ) mqL.splice(i, 1);
    mQ.removeListener(whenMediaChanges);
} : listenToMediaQuery;

var orientationChanged = false;
addEventListener("orientationchange", function(){
    orientationChanged = true;
}, PASSIVE_LISTENER_OPTION);

addEventListener("resize", setTimeout.bind(0,function(){
    if (orientationChanged &amp;&amp; !mediaQueryUpdated)
        for (var i=0,len=mqL.length|0; i&lt;len; i=i+1|0)
            mqL[i][1]( mqL[i][0] );
    mediaQueryUpdated = orientationChanged = false;
},0));</pre>

<h2 id="你想找到用戶代理的哪個資訊">你想找到用戶代理的哪個資訊</h2>

<p>因為用戶代理字串的差異處並沒有統一,這方面會頗為棘手。</p>

<h3 id="瀏覽器名稱">瀏覽器名稱</h3>

<p>當別人說要「偵測瀏覽器」的時候,他們通常要的是「偵測排版引擎」:你真的要偵測到用戶在使用 Firefox 抑或相對應的 SeaMonkey,或偵測到在使用 Chrome 抑或相對應的 Chromium 嗎?還是說只要偵測瀏覽器用的是 Gecko 或是 WebKit 排版引擎?如果你要的是後者,請直接看後面的章節。</p>

<p>雖然有 Internet Explorer 這個明顯的例外,多數瀏覽器通常會把瀏覽器名字與版本用成 <em>BrowserName/VersionNumber</em><em>瀏覽器名/版本名</em>)格式。然而,因為用戶代理不是只有瀏覽器名提供這種格式,你不能找到瀏覽器的名字,你只能檢查該名字是否為你要尋找的目標。也請注意瀏覽器還會「造假」:例如 Chrome 就會同時宣稱自己是 Chrome 與 Safari。因此,如果要找出 Safari 瀏覽器,你就要在找出 Safari 字串的同時,排除掉 Chrome 字串。此外,Chromium 也常常宣稱自己是 Chrome、而 Seamonkey 有時也會宣稱自己是 Firefox。</p>

<p>另請注意,不要針對 BrowserName 使用簡單的正規表達式,因為用戶代理可能有不是 Keyword/Value 的字串。例如 Safari 與 Chrome 在字串內就包含了 like Gecko(類似 Gecko)。</p>

<table class="standard-table">
 <thead>
  <tr>
   <th scope="col"></th>
   <th scope="col">必定包含</th>
   <th scope="col">必定不包含</th>
   <th scope="col">註解</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>Firefox</td>
   <td>Firefox/xyz</td>
   <td>Seamonkey/xyz</td>
   <td></td>
  </tr>
  <tr>
   <td>Seamonkey</td>
   <td>Seamonkey/xyz</td>
   <td></td>
   <td></td>
  </tr>
  <tr>
   <td>Chrome</td>
   <td>Chrome/xyz</td>
   <td>Chromium/xyz</td>
   <td></td>
  </tr>
  <tr>
   <td>Chromium</td>
   <td>Chromium/xyz</td>
   <td></td>
   <td></td>
  </tr>
  <tr>
   <td>Safari</td>
   <td>Safari/xyz</td>
   <td>Chrome/xyz or Chromium/xyz</td>
   <td>Safari 給出了兩個版本號、一個是偏技術性的 Safari/xyz token,另一個則是偏向用戶友好的 Version/xyz token</td>
  </tr>
  <tr>
   <td>Opera</td>
   <td>
    <p>OPR/xyz <sup>[1]</sup></p>

    <p>Opera/xyz <sup>[2]</sup></p>
   </td>
   <td></td>
   <td>
    <p><sup>[1]</sup> Opera 15+ (Blink-based engine)</p>

    <p><sup>[2]</sup> Opera 12- (Presto-based engine)</p>
   </td>
  </tr>
  <tr>
   <td>Internet Explorer</td>
   <td>; MSIE xyz;</td>
   <td></td>
   <td>Internet Explorer 並不使用 <em>BrowserName/VersionNumber</em> 格式</td>
  </tr>
 </tbody>
</table>

<p>當然這裡不保證沒有其他瀏覽器,騎劫其他瀏覽器的可能,例如過去的 Chrome 就騎劫過 Safari。這也是為什麼透過用戶代理字串來探測瀏覽器是靠不住的,它也只能用在探測版本號(不太可能有騎劫過去版本號的情形)。</p>

<h3 id="瀏覽器版本">瀏覽器版本</h3>

<p>瀏覽器版本通常,但不是每次,都把數值放在用戶代理字串的 <em>BrowserName/VersionNumber</em> token。把版本號放在 MSIE 之後的 Internet Explorer、還有加了 Version/<em>VersionNumber</em> token 的第十代以後 Opera 版本就是明顯的例子。</p>

<p>再次強調,因為無法確保尋找的瀏覽器會包含有效的數字,請確認你針對的瀏覽器,選取了正確的 token。</p>

<h3 id="排版引擎">排版引擎</h3>

<p>如同前述,多數情況下,找尋排版引擎(rendering engine)更為恰當。這能讓少有人知的瀏覽器,不致遭到排除在外。使用某一種排版引擎的瀏覽器,共享相同的網頁瀏覽:這種「一處有效、處處有效」的假設,是很公平的。</p>

<p>目前有五大主流的排版引擎:Trident, Gecko, Presto, Blink 與 WebKit。因為排版引擎嗅探頗為常見,許多用戶代理也會加入其他的排版引擎,以觸發探測。所以在偵測排版引擎的時候,當心別錯誤觸發。</p>

<table class="standard-table">
 <thead>
  <tr>
   <th scope="col"></th>
   <th scope="col">絕對有</th>
   <th scope="col"></th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>Gecko</td>
   <td>Gecko/xyz</td>
   <td></td>
  </tr>
  <tr>
   <td>WebKit</td>
   <td>AppleWebKit/xyz</td>
   <td>請注意 WebKit 瀏覽器會加上「like Gecko」字串。如果探測不加留意,就會錯誤觸發針對 Gecko 的情形。</td>
  </tr>
  <tr>
   <td>Presto</td>
   <td>Opera/xyz</td>
   <td><strong>注意:</strong>Presto 在 Opera15 以後不再使用(請參見 Blink)</td>
  </tr>
  <tr>
   <td>Trident</td>
   <td>Trident/xyz</td>
   <td>Internet Explorer 把這個 token 放在 User Agent String 的 <em>comment</em>(註解)部份</td>
  </tr>
  <tr>
   <td>EdgeHTML</td>
   <td>Edge/xyz</td>
   <td>The non-Chromium Edge puts its engine version after the <em>Edge/</em> token, not the application version.<br>
    <strong>Note:</strong> EdgeHTML is no longer used in Edge browser builds &gt;= version 79 (see 'Blink').</td>
  </tr>
  <tr>
   <td>Blink</td>
   <td>Chrome/xyz</td>
   <td></td>
  </tr>
 </tbody>
</table>

<h2 id="排版引擎版本">排版引擎版本</h2>

<p>除了 Gecko 這個著名的例外,多數排版引擎版本的 token 通常會是 <em>RenderingEngine/VersionNumber</em>(排版引擎/版本號)。Gecko 把版本號放在用戶代理內,位於 <code>rv:</code> 字串後的註解部份。但在 Gecko 14(攜帶版)或 Gecko 17(桌面版)以後,版本號也出現在 Gecko/version token 裡面(之前的版本則是寫建置日期、固定的日期則呼叫 GeckoTrail)。</p>

<h2 id="作業系統">作業系統</h2>

<p>大多數的用戶代理都會表明自己固定字符串在個作業系統上運行(儘管如 Firefox OS 這種以網路為中心的平台並沒有這樣做),不過格式的差異卻頗大。它是個固定字串,位於用戶代理註解部份的兩個分號間。對每個瀏覽器而言。這些字串是特定的。這些字串給出了作業系統、通常也給出他們的版本以及在哪個設備上運作(32位元或64位元、抑或 Mac 的 Intel/PPC)。</p>

<p>如同其他個案,這些字串可能在未來會有所變動,只應該用於檢測已經出現的瀏覽器。在瀏覽器的新版本出現後,也要進行技術研究,以確保程式能夠適應。</p>

<h3 id="手機、平板、桌機">手機、平板、桌機</h3>

<p>最常實行用戶代理嗅探的理由,是判別瀏覽器是在哪個設備執行。這麼做的目的是提供不同類型的 HTML 內容給不同類型的上網設備。</p>

<ul>
 <li>絕對不要假設某個瀏覽器或排版引擎,只在某種類型的設備執行。更不要對不同的瀏覽器或排版引擎,給予不同的預設值。</li>
 <li>也絕對不要用 OS token 來定義該瀏覽器在手機、平板、抑或桌機上執行。作業系統可能在不只一種設備運作。例如,Android 可以在手機、也可以在平板上運作。</li>
</ul>

<p>以下表格概括了主要的瀏覽器製造者,如何表明它們的瀏覽器在手機上運作:</p>

<table>
 <caption>主要瀏覽器的用戶代理字串</caption>
 <thead>
  <tr>
   <th scope="col">瀏覽器</th>
   <th scope="col">規則</th>
   <th scope="col">示例</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>Mozilla (Gecko, Firefox)</td>
   <td>註解內的 <a href="/zh-TW/docs/Gecko_user_agent_string_reference"><strong>Mobile</strong><strong>Tablet</strong> token</a></td>
   <td>Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0</td>
  </tr>
  <tr>
   <td>WebKit-based (Android, Safari)</td>
   <td>註解外的 <a href="https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/OptimizingforSafarioniPhone/OptimizingforSafarioniPhone.html#//apple_ref/doc/uid/TP40006517-SW3"><strong>Mobile Safari</strong> token</a></td>
   <td>Mozilla/5.0 (Linux; U; Android 4.0.3; de-ch; HTC Sensation Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30</td>
  </tr>
  <tr>
   <td>Blink-based (Chromium, Google Chrome, Opera 15+)</td>
   <td>註解外的 <a href="https://developers.google.com/chrome/mobile/docs/user-agent"><strong>Mobile Safari</strong> token</a></td>
   <td>Mozilla/5.0 (Linux; Android 4.4.2); Nexus 5 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Mobile Safari/537.36 OPR/20.0.1396.72047</td>
  </tr>
  <tr>
   <td>Presto-based (Opera 12-)</td>
   <td>
    <p>註解內的 <a href="http://my.opera.com/community/openweb/idopera/"><strong>Opera Mobi/xyz</strong> token</a> (Opera 12-)</p>
   </td>
   <td>
    <p>Opera/9.80 (Android 2.3.3; Linux; Opera Mobi/ADR-1111101157; U; es-ES) Presto/2.9.201 Version/11.50</p>
   </td>
  </tr>
  <tr>
   <td>Internet Explorer</td>
   <td>註解內的 <strong>IEMobile/xyz</strong></td>
   <td>Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0)</td>
  </tr>
 </tbody>
</table>

<p>總之,我們建議藉著找出用戶代理的「Mobi」字串,來偵測行動設備。</p>

<div class="note">
<p>如果設備尺寸夠大的話,它就不會標示「Mobi」。針對這種情形,你應該提供桌面版網站。另外,因為最近桌面設備的觸控螢幕越來越多,為了提供最佳習慣,網站應該支援觸控輸入。</p>
</div>