過使用WordPress的經驗,我們發現主題設計師經常過度使用某種功能。當他們需要的只是一些簡單的CSS時,他們正在尋找瘋狂的WordPress過濾器和鉤子來完成任務。WordPress默認生成很多CSS類。(WordPress CSS備忘單)。其中一個CSS類區域是body類樣式。在本文中,我們將解釋WordPress體類101以及為初學主題設計者分享一些有用的提示和技巧。
什么是WordPress Body Class?
Body Class(body_class)是一個WordPress函數,它為body元素提供不同的類,因此主題作者可以使用CSS有效地設置其網站的樣式。HTML body標簽主要存在于header.php文件中,該文件在每個頁面上加載。編碼主題時,可以將body_class函數附加到body元素,如下所示:
<body <?php body_class($class); ?>>
通過添加這個小元素,您可以靈活地使用CSS輕松修改每個頁面的外觀。根據所加載頁面的類型,WordPress會自動添加相應的類。例如,如果您在存檔頁面上,如果您選擇使用它,WordPress將自動將存檔類添加到body元素。幾乎每一頁都能做到這一點。以下是WordPress可能添加的常見類的一些示例:
.rtl {}
.home {}
.blog {}
.archive {}
.date {}
.search {}
.paged {}
.attachment {}
.error404 {}
.single postid-(id) {}
.attachmentid-(id) {}
.attachment-(mime-type) {}
.author {}
.author-(user_nicename) {}
.category {}
.category-(slug) {}
.tag {}
.tag-(slug) {}
.page-parent {}
.page-child parent-pageid-(id) {}
.page-template page-template-(template file name) {}
.search-results {}
.search-no-results {}
.logged-in {}
.paged-(page number) {}
.single-paged-(page number) {}
.page-paged-(page number) {}
.category-paged-(page number) {}
.tag-paged-(page number) {}
.date-paged-(page number) {}
.author-paged-(page number) {}
.search-paged-(page number) {}
如何使用WordPress Body Class
在大多數情況下,WordPress body類是主題設計者經常忽略的簡單解決方案。大約一年前,我需要設置僅在特定頁面上設置WordPress菜單項的樣式。我決定深入挖掘以找到一個過濾器,為菜單項添加一個特殊的導航類,這些菜單項將使用條件語句添加。例如,如果它是單個頁面,則將“Blog”類添加到菜單項。我花了很多時間搞清楚所有這些并寫一篇關于它的帖子,有用戶指出可以很容易地用現有的體類來完成它,如下所示
.single #navigation .leftmenublog div{display: inline-block !important;}
注意:它如何使用在所有單個帖子頁面中添加的.single body類。
給你另一個例子。一旦用戶想要一種基于類別頁面更改其網站上的徽標圖像的方法。包括他在內的大多數主題設計都是使用CSS(#header .logo)加載徽標。我建議如何使用.category-slug body類來改變他的徽標:
.category-advice #header .logo{background: url(images/logo-advice.png) no-repeat !important;}
.category-health #header .logo{background: url(images/logo-health.png) no-repeat !important;}
希望現在您已經掌握了如何在主題設計中利用類。但是等等,這會變得更加強大,因為有一種方法可以使用過濾器添加自定義主體類。
如何添加自定義正文類
WordPress有一個過濾器,您可以使用它在需要時添加自定義正文類。向您展示如何使用過濾器添加一個body類,然后再向您展示特定的用例場景,以便每個人都可以在同一頁面上。
因為body類是特定于主題的,所以你需要在functions.php文件中編寫一個自定義函數,如下所示:
function
my_class_names(
$classes
) {
// add 'class-name' to the $classes array
$classes
[]=
'test-class-name'
;
// return the $classes array
return
$classes
;
}
//Now add test class to the filter
add_filter(
'body_class'
,
'my_class_names'
);
上面的代碼將在您網站的每個頁面上的body標簽中添加一個“test-class-name”類。該函數的真正強大之處在于條件標記。您可以說只在單頁等上添加XYZ類。
將類別類添加到單個帖子頁面
假設您要自定義每個類別的單個帖子頁面。根據自定義的類型,您可以為其使用正文類。為了簡單起見,我們假設您要根據單個帖子的類別更改頁面的背景顏色。您可以通過向單個帖子頁面視圖添加類別類來實現。將以下函數粘貼到主題的functions.php文件中:
// add category nicenames in body class
function category_id_class($classes) {
global $post;
foreach((get_the_category($post->ID)) as $category)
$classes[]=$category->category_nicename;
return $classes;
}
add_filter('body_class', 'category_id_class');
上面的代碼將在您的正文類中為單個帖子頁面添加類別類。然后,您可以根據需要使用css類來設置樣式。
在WordPress主題的Body Class中添加Page Slug
將以下代碼粘貼到主題的functions.php文件中:
//Page Slug Body Class
function add_slug_body_class( $classes ) {
global $post;
if ( isset( $post ) ) {
$classes[]=$post->post_type . '-' . $post->post_name;
}
return $classes;
}
add_filter( 'body_class', 'add_slug_body_class' );
瀏覽器檢測和瀏覽器特定的正文類
有時您可能需要特殊的CSS樣式才能在某些瀏覽器中正常工作(HINT:Internet Explorer)。好吧彌敦道賴斯提出了一個非常酷的解決方案,基于用戶正在訪問從網站瀏覽器上增加了瀏覽器的具體類體類。
您所要做的就是將以下代碼粘貼到functions.php文件中:
<?php
add_filter('body_class','browser_body_class');
function browser_body_class($classes) {
global $is_lynx, $is_gecko, $is_IE, $is_opera, $is_NS4, $is_safari, $is_chrome, $is_iphone;
if($is_lynx) $classes[]='lynx';
elseif($is_gecko) $classes[]='gecko';
elseif($is_opera) $classes[]='opera';
elseif($is_NS4) $classes[]='ns4';
elseif($is_safari) $classes[]='safari';
elseif($is_chrome) $classes[]='chrome';
elseif($is_IE) $classes[]='ie';
else $classes[]='unknown';
if($is_iphone) $classes[]='iphone';
return $classes;
}
?>
然后,您可以使用以下類:
.ie .navigation {some item goes here}
更多用例:
還有更多用例,這篇文章它的重點是幫助新主題開發人員理解WordPress體類的基礎知識及其優點。用例僅限于您的想象力。已經看到商業主題開發人員(Genesis)使用body類來提供各種布局選項。例如,全頁面布局,補充工具欄內容,內容邊欄等。這樣,用戶可以輕松切換頁面布局而不必擔心外觀問題,因為已經使用CSS.0進行了處理
還可以看到一些開發人員添加HTTP請求來加載他們提供的各種配色方案的CSS文件。在很多情況下,變化也很小。當一個人可以完成工作時,沒有必要加載3個樣式表。只需使用body類。
傳統的網頁開發中,JavaScript通常被放置在HTML文檔的<script>標簽內,而在大多數情況下,建議將這些<script>標簽放置在</body>標簽之前,即在<body>標簽的尾部引入JavaScript代碼。這種做法有以下幾個原因:
瀏覽器在解析HTML文檔時是按順序執行的,當遇到<script>標簽時,瀏覽器會停止解析HTML,去加載并執行JavaScript代碼。如果將<script>標簽放在<head>標簽中,那么在JavaScript加載和執行期間,HTML解析會暫停,頁面呈現會延遲,給用戶帶來不好的體驗。而將<script>標簽放在</body>標簽之前,可以確保頁面的HTML結構已經完全加載和解析,然后再去加載和執行JavaScript代碼,不會影響頁面的呈現速度和用戶體驗。
將JavaScript代碼放在</body>標簽之前,可以確保頁面的其他內容(如HTML結構、CSS樣式、圖片等)都已加載完畢,這樣可以最大程度地減少JavaScript加載和執行的時間。因為JavaScript通常會操作和修改頁面的DOM結構,如果在DOM還未完全加載的情況下執行JavaScript代碼,可能會導致JavaScript無法找到或操作相關的DOM元素,造成錯誤或異常。通過將JavaScript代碼放在</body>標簽之前,可以確保DOM已經完全加載,并且用戶可以盡早地看到頁面內容,提高了網頁的整體加載速度。
在舊版的Internet Explorer(IE)瀏覽器中,將<script>標簽放在<head>標簽中時,可能會導致JavaScript無法正常工作。這是因為舊版IE瀏覽器在解析HTML文檔時,會在遇到<script>標簽時立即執行其中的代碼,并且在繼續解析HTML文檔之前必須等待JavaScript代碼的加載和執行完成。如果JavaScript代碼比較大或執行時間較長,用戶在這段時間內將看不到頁面內容,給用戶造成不好的體驗。通過將JavaScript代碼放在</body>標簽之前,可以規避這個問題,確保頁面內容能夠盡快呈現給用戶。
綜上所述,將JavaScript代碼放在</body>標簽之前是一種較好的習慣,可以優化頁面加載時間,提高用戶體驗,并確保腳本在正確的上下文中執行,避免兼容性問題。然而,隨著Web開發技術的不斷發展,我們可以根據具體的需求和使用的工具選擇合適的方式來引入JavaScript代碼,以達到更好的性能和開發體驗。
vent Loop(事件循環)是前端工程師經常討論到的話題。
作者:lzaneli,騰訊 CDC 前端開發工程師,博客:Lzane.com
Microtasks(微任務)是事件循環中一類優先級比較高的任務,本文通過一個有趣的例子探索其運行時機。從兩年前被動接受知識 "當瀏覽器JS引擎調用棧彈空的時候,才會執行 Microtasks 隊列",到兩年后主動深入探索源碼后了解到的 "當 V8 執行完調用要返回 Blink 時,由于 MicrotasksScope 作用域失效,在其析構函數中檢查 JS 調用棧是否為空,如果為空就會運行 Microtasks。"。同時文章中介紹了用于探索瀏覽器運行原理的一些工具。
剛學前端那會學習事件循環,說事件循環存在的意義是由于 JavaScript 是單線程的,所以需要事件循環來防止JS阻塞,讓網絡請求等I/O操作不阻塞主線程。
而 Microtasks 是一類優先級比較高的任務,我們不能像 Macrotasks(宏任務) 一樣插入 Macrotasks 隊列末端,等待多個事件循環后才執行,而需要插入到 Microtasks 的隊列里面,在本輪事件循環中執行。
比如下面這個有趣的例子:
document.body.innerHTML=`
<button id="btn" type="button">btn</button>
`;
const button=document.getElementById('btn')
button.addEventListener('click',()=>{
Promise.resolve().then(()=>console.log('promise resolved 1'))
console.log('listener 1')
})
button.addEventListener('click',()=>{
Promise.resolve().then(()=>console.log('promise resolved 2'))
console.log('listener 2')
})
// 1. 手動點擊按鈕
// button.click() // 2. 解開這句注釋,用JS觸發點擊行為
當我手動點擊按鈕的時候,大家覺得瀏覽器的輸出是下面的A還是B?
大家可以在這里試一下:
https://codesandbox.io/static/img/play-codesandbox.svg
當我將上面代碼中的最后一行注釋打開,使用JS觸發點擊行為的時候,瀏覽器的輸出是A還是B?
大家覺得上面1、2兩種情況的輸出順序是否一樣?
答案非常有意思
為什么會出現這種情況呢? 這個 Microtasks 的運行時機有關。兩年前當我帶著這個問題搜索資料并詢問大佬的時,大佬告訴我:
當瀏覽器JS引擎調用棧彈空的時候,才會執行Microtasks隊列
按照這個結論,我使用 Chrome Devtool 中的 Performance 做了一次探索
人工點擊的時候輸出為 listener1 -> promise resolved 1 -> listener2 -> promise resolved 2 。
在JS代碼中觸發點擊時輸出為 listener1 -> listener2 -> promise resolved 1 -> promise resolved 2
Chrome Devtool 中的 Performance 是一個 sample profiler (采樣分析儀),即它的運行機制是每1ms暫停一下vm,將當前的調用棧記錄下來,最后利用這部分信息做出可視化。
由于它是一種 sample 的機制,所以在兩個 sample 之間的運行狀態可能會被丟失,所以我們在使用這個工具的時候可以
強烈建議大家學會使用這個工具,本文例子的 profile 結果文件也會文章最后給到大家,大家有興趣可以導入試一試。
兩年的時間過去了,在上周整理筆記的時候,我開始質疑這一個知識,"當瀏覽器 JS 引擎調用棧彈空的時候,才會執行 Microtasks 隊列"。
因為它其實是個表現,我想知道瀏覽器和 JS 引擎到底是怎么實現這樣的機制的。
因此我使用chrome://tracing進行探索,
下面探索基于 Chrome Version 88.0.4324.192 (Official Build) (x86_64),不同瀏覽器的實現有不同
注意,chrome://tracing 中的v8.xxx小寫v開頭的為 Blink 的調用,V8.xxx大寫的V才是真正的V8引擎。
tracing 工具還有一個非常好用的功能,點擊下圖中的放大鏡,就可以直接打開 Chromium Code Search 查看 Chromium 的源碼。這個工具也自帶搜索功能,可以查看函數的聲明、定義以及調用。
下面源碼的探索基于commit e8b6574c 的Chromium,并且為了簡化隱藏了無關的代碼,用...替代
比如我們在上面的 tracing 里面看到有v8.callFunction的調用,我們點擊可以找到這個這個函數調用,是在 Blink 中調用 V8 的入口。
third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc
v8::MaybeLocal<v8::Value> V8ScriptRunner::CallFunction(
v8::Local<v8::Function> function,
ExecutionContext* context,
v8::Local<v8::Value> receiver,
int argc,
v8::Local<v8::Value> args[],
v8::Isolate* isolate
){
...
TRACE_EVENT0("v8", "v8.callFunction"); // 這就是我們在 tracing 中看到的 v8.callFunction
...
v8::MicrotaskQueue* microtask_queue=ToMicrotaskQueue(context); // 拿到 microtask 隊列
...
v8::MicrotasksScope microtasks_scope(isolate, microtask_queue,
v8::MicrotasksScope::kRunMicrotasks); // 這個 scope 很可疑,這里構造之后在這個函數后面并沒有使用
...
probe::CallFunction probe(context, function, depth);
v8::MaybeLocal<v8::Value> result=function->Call(isolate->GetCurrentContext(), receiver, argc, args); // 函數調用
CHECK(!isolate->IsDead());
...
}
這里類型為v8::MicrotasksScope的變量很可疑,在創建之后并沒有在后續的函數里面使用,所以我們來看一下他的聲明和定義
v8/include/v8.h
/**
* This scope is used to control microtasks when MicrotasksPolicy::kScoped
* is used on Isolate. In this mode every non-primitive call to V8 should be
* done inside some MicrotasksScope.
* Microtasks are executed when topmost MicrotasksScope marked as kRunMicrotasks
* exits.
* kDoNotRunMicrotasks should be used to annotate calls not intended to trigger
* microtasks.
*/
class V8_EXPORT V8_NODISCARD MicrotasksScope {
public:
enum Type { kRunMicrotasks, kDoNotRunMicrotasks };
MicrotasksScope(Isolate* isolate, Type type);
MicrotasksScope(Isolate* isolate, MicrotaskQueue* microtask_queue, Type type);
~MicrotasksScope(); // 注意這個析構函數
...
上面這段注釋告訴我們,這個類是用來控制 Microtasks 的(當 MicrotasksPolicy::kScoped這個策略被使用的時候,我們在后面會拎出來講,這里大家先默認 Blink 是設置了這個策略)。
這里的析構函數非常的可疑,因為我們在前面一步發現變量microtasks_scope創建之后并沒有在后續的函數里面使用,而析構函數會在變量被銷毀時執行。我們繼續來看 v8::MicrotasksScope 的定義
v8/src/api/api.cc
MicrotasksScope::~MicrotasksScope() {
if (run_) {
microtask_queue_->DecrementMicrotasksScopeDepth(); // 這里將函數調用棧減少一層
if (MicrotasksPolicy::kScoped==microtask_queue_->microtasks_policy() && // 這里檢查策略是否是 MicrotasksPolicy::kScoped
!isolate_->has_scheduled_exception()) {
DCHECK_IMPLIES(isolate_->has_scheduled_exception(),
isolate_->scheduled_exception()== i::ReadOnlyRoots(isolate_).termination_exception());
microtask_queue_->PerformCheckpoint(reinterpret_cast<Isolate*>(isolate_)); // 這一步嘗試執行 Microtasks 隊列
}
}
...
}
v8/src/execution/microtask-queue.cc
void MicrotaskQueue::PerformCheckpoint(v8::Isolate* v8_isolate) {
if (!IsRunningMicrotasks() && !GetMicrotasksScopeDepth() && // 注意,這一步檢查了調用棧是否為空
!HasMicrotasksSuppressions()) {
Isolate* isolate=reinterpret_cast<Isolate*>(v8_isolate);
RunMicrotasks(isolate); // 執行隊列中的Microtasks
isolate->ClearKeptObjects();
}
}
...
int MicrotaskQueue::RunMicrotasks(Isolate* isolate) {
...
{
TRACE_EVENT_CALL_STATS_SCOPED(isolate, "v8", "V8.RunMicrotasks"); // 我們在 tracing 里面也可以看到這個輸出
maybe_result=Execution::TryRunMicrotasks(isolate, this,
&maybe_exception);
processed_microtask_count= static_cast<int>(finished_microtask_count_ - base_count);
}
...
}
到這里我們就知道了 Microtasks 的運行時機了,當 V8 執行完調用要返回 Blink 時,由于 MicrotasksScope 作用域失效,在其析構函數中檢查 JS 調用棧是否為空,如果為空的話就會運行 Microtasks。
下圖為完整的調用路徑
觀察到的現象即是 "當瀏覽器 JS 引擎調用棧彈空的時候,才會執行 Microtasks 隊列"
所以現在我如果問你,是不是 Macrotasks(宏任務)執行完才會執行 Microtasks 呢? 答案顯然是否定的,如同這個例子,我們的 Macrotask 是處理點擊輸入,而 Microtasks 在其中被執行了兩次。
用JS觸發點擊事件其實也是同理的,同樣是使用V8::MicrotasksScope的析構函數來進行調用,只是前面幾次都因為調用棧非空(GetMicrotasksScopeDepth),所以等到最后面才執行。
那是不是所有使用V8引擎的應用 Microtasks 的運行時機都是一樣的呢?答案是否定的,Microtasks 的運行時機是由V8::MicrotasksPolicy來決定的。
v8/include/v8.h
/**
* Policy for running microtasks:
* - explicit: microtasks are invoked with the
* Isolate::PerformMicrotaskCheckpoint() method;
* - scoped: microtasks invocation is controlled by MicrotasksScope objects;
* - auto: microtasks are invoked when the script call depth decrements
* to zero.
*/
enum class MicrotasksPolicy { kExplicit, kScoped, kAuto };
由上面的源碼注釋我們可以知道
third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
static void InitializeV8Common(v8::Isolate* isolate) {
...
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kScoped);
...
}
chrome://tracing/ 是一個 structural profiler 或叫 CPU profiler,與 Chrome Devtool performance 的 sample profiler 不同,他是由代碼中主動的去埋點打印出來的,所以每一次函數調用都會被記錄下來,不會像sample profiler一樣漏掉采樣時刻之間的狀態。
使用方法如下,首先進入 chrome://tracing 點擊右上角的 Record,勾選住你想 profile 的組件。
然后去到你的 demo 頁執行你想要探索的操作,回到 tracing 頁面點 Stop,接著在 Processes 里面篩選掉其他 Tab(標簽頁)的信息。
最后使用鍵盤 w(放大)s(縮小)a(左移)d(右移)探索
強烈建議大家學會使用這個工具,本文例子的 profile 結果文件也會文章最后給到大家,大家有興趣可以導入試一試。
Event Loop(事件循環)是前端工程師經常討論到的話題,往深挖可以挖出 JS 如何實現異步、requestAnimationFrame、瀏覽器渲染機制、Macrotasks、Microtasks等等問題。
本文主要探索了Microtasks的運行時機,我從兩年前被動接受知識 "當瀏覽器JS引擎調用棧彈空的時候,才會執行 Microtasks 隊列"
到兩年后主動使用工具深入探索源碼后了解到的 "當 V8 執行完調用要返回 Blink 時,由于 MicrotasksScope 作用域失效,在其析構函數中檢查 JS 調用棧是否為空,如果為空就會運行 Microtasks。"
這也是計算機最吸引我的地方,當你每隔一段時間回來看一個東西的時候,都能夠更往深一步,發現到更神奇的原理,也可以夠感受到自己的進步。
在探索的過程中還使用了一些工具,如 Chrome Devtool Performance、Chrome tracing、Chromium Code Search 等,希望感興趣的同學,也可以使用這些工具,更深入的探索瀏覽器內部原理。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。