ข้ามไปยังเนื้อหา
ธีม

สไตล์ของ Child Theme ใน WordPress ไม่โหลด

style.css ของ child theme ไม่โหลดเพราะ WordPress ไม่ enqueue ให้คุณอัตโนมัติ ต้องลงทะเบียน stylesheet ของ child theme ด้วยตัวเองใน

เผยแพร่แล้ว

style.css ของ child theme ไม่โหลดเพราะ WordPress ไม่ได้ enqueue ไฟล์นี้ให้คุณ ต้องลงทะเบียน stylesheet ของ child theme ด้วยมือใน functions.php ของ child theme เอง และ enqueue นี้เกือบจะเจอบั๊กหนึ่งในสองแบบเสมอ คือไม่มีเลย หรือโหลดโดยไม่ได้ระบุ parent stylesheet เป็น dependency ทำให้กฎ CSS ของคุณไปตกอยู่ผิดตำแหน่งใน cascade แล้วถูกเขียนทับ แก้ enqueue ให้ถูก สไตล์ก็กลับมา ส่วนทริค @import เก่าที่คุณน่าจะก็อปมาจากบทความสอนเมื่อปี 2013 นั้นเป็นวิธีแก้ที่ผิด

ทำไมถึงเกิดขึ้น

Child theme แบบคลาสสิกมีไฟล์ที่จำเป็นสองไฟล์ คือ style.css ที่มี header block ระบุชื่อ parent และ functions.php บรรทัด Template: ใน header บอก WordPress ว่าจะให้สืบทอด template มาจาก parent ตัวไหน นั่นคือสิ่งเดียวที่ WordPress ทำให้อัตโนมัติ มันอ่าน header ตั้งค่า template fallback แล้วก็จบ มันไม่เคย enqueue ไฟล์ style.css ของ child ให้บนหน้าเว็บเลย

ในขณะเดียวกัน parent theme จะ enqueue stylesheet ของตัวเอง จาก functions.php ของตัวเอง โดย hook ไว้กับ wp_enqueue_scripts functions.php ของ child จะรันก่อน แล้วตามด้วยของ parent จากนั้นทุกอย่างที่อยู่บน hook นั้นก็จะทำงาน ถ้า child ของคุณไม่เคยเรียก wp_enqueue_style สำหรับไฟล์ของตัวเอง เบราว์เซอร์ก็จะไม่มีทางร้องขอไฟล์นั้น หน้าเว็บจึงดูเหมือน parent theme ดิบ ๆ เพราะมันก็เป็นอย่างนั้นจริง ๆ

ความล้มเหลวแบบที่สองแนบเนียนกว่า คุณ enqueue child stylesheet ไปแล้ว มันโหลดขึ้นมา แต่การ override ของคุณไม่มีผลอะไรเลย นั่นคือปัญหาลำดับการโหลด (load order) เมื่อกฎสองกฎมี specificity เท่ากัน กฎที่โหลด ทีหลัง จะชนะ ถ้า CSS ของ child โหลดก่อนของ parent parent ก็จะชนะทุกครั้งที่เสมอกัน dependency array บน wp_enqueue_style คือสิ่งที่บังคับลำดับ

อีกเรื่องที่คนมักเข้าใจผิด get_template_directory_uri() ชี้ไปที่โฟลเดอร์ของ parent theme ส่วน get_stylesheet_directory_uri() ชี้ไปที่โฟลเดอร์ของ theme ที่ กำลังใช้งานอยู่ (child) ถ้าสลับสองตัวนี้กัน คุณจะ enqueue style.css ของ parent ซ้ำเป็นครั้งที่สอง และไม่แตะไฟล์ของ child เลย

วิธีแก้ ตามลำดับ

อย่างแรก ตรวจสอบว่าจริง ๆ แล้วอะไรกำลังโหลดอยู่ เปิดหน้าที่เสีย ดู source แล้วค้นหาชื่อโฟลเดอร์ของ child theme WordPress จะเรนเดอร์สไตล์ที่ enqueue ไว้เป็น <link id="handle-css" href="..."> โดย id คือ handle ของคุณบวกกับ -css ถ้า style.css ของ child ไม่อยู่ใน <head> เลย แสดงว่า enqueue ไม่ได้ทำงาน แต่ถ้ามันอยู่ตรงนั้นแต่สไตล์ไม่มีผล แสดงว่าเป็นปัญหา dependency/ลำดับ การเช็คเพียงจุดเดียวนี้บอกได้ว่าคุณเจอบั๊กแบบไหนในสองแบบ

อย่างที่สอง หา handle จริงของ parent theme สมัยใหม่ส่วนใหญ่ (Astra, GeneratePress, ธีมตระกูล Twenty-*) จะ enqueue stylesheet ของตัวเองไว้แล้วภายใต้ handle เฉพาะ เช่น astra-theme-css, generate-style-css, twenty-twenty-four-style เป็นต้น คุณต้องได้ string ที่ตรงเป๊ะตัวนั้น ให้ grep parent theme หรืออ่านจาก <link id="...-css"> ใน view source (ตัด -css ท้ายออก)

อย่างที่สาม เขียน enqueue สำหรับกรณีทั่วไปที่ parent โหลด stylesheet ของตัวเองอยู่แล้ว คุณ ไม่ต้อง enqueue parent ซ้ำ คุณแค่ enqueue child และระบุ handle ของ parent เป็น dependency เพื่อให้ของคุณโหลดทีหลัง

<?php
add_action( 'wp_enqueue_scripts', 'child_enqueue_styles', 20 );

function child_enqueue_styles() {
    wp_enqueue_style(
        'child-style',
        get_stylesheet_directory_uri() . '/style.css',
        array( 'astra-theme-css' ), // <-- the parent's REAL handle
        wp_get_theme()->get( 'Version' )
    );
}

ถ้า parent theme ไม่ได้ enqueue stylesheet ของตัวเอง (ธีมเก่าหรือธีมแบบมินิมอลบางตัวยังพึ่ง @import อยู่) ก็ให้ enqueue ทั้งสองไฟล์ และทำให้ child ขึ้นต่อ parent

<?php
add_action( 'wp_enqueue_scripts', 'child_enqueue_styles' );

function child_enqueue_styles() {
    wp_enqueue_style(
        'parent-style',
        get_template_directory_uri() . '/style.css'
    );
    wp_enqueue_style(
        'child-style',
        get_stylesheet_directory_uri() . '/style.css',
        array( 'parent-style' ),
        wp_get_theme()->get( 'Version' )
    );
}

อาร์กิวเมนต์ wp_get_theme()->get('Version') คือตัว cache-buster ของคุณ ปรับเลขที่บรรทัด Version: ใน header ของ style.css ใน child แล้วเบราว์เซอร์จะดึงไฟล์มาใหม่ ถ้าข้ามมันไป คุณจะแก้ CSS แล้วสาบานว่าไม่มีอะไรเปลี่ยนทั้งที่จริงมันแค่ถูก cache ไว้

ถ้า functions.php มี syntax error ทั้งไฟล์จะตายเงียบ ๆ และ enqueue ของคุณจะไม่รันเลย ซึ่งจะแสดงอาการเหมือน “สไตล์ไม่โหลด” เป๊ะ ๆ เปิด WP_DEBUG หรือวาง debug.log ของคุณลงใน ตัวถอดรหัส error log ของ WordPress เพื่อหา fatal ให้เจอเร็ว ๆ

สิ่งที่ไม่ควรทำ

อย่าใช้ @import คำแนะนำยุค Codex บอกให้ใส่ @import url("../parenttheme/style.css"); ไว้ด้านบนสุดของ style.css ใน child มันใช้ได้ก็จริง แต่ช้า เพราะเบราว์เซอร์เริ่มดาวน์โหลด CSS ของ parent ไม่ได้จนกว่าจะ parse CSS ของ child เสร็จก่อน ทำให้สอง request รันแบบต่อเนื่องแทนที่จะขนานกัน handbook ของ WordPress เองแนะนำไม่ให้ทำแบบนี้มาหลายปีแล้ว ถ้าบทความสอนบอกให้คุณทำแบบนี้ บทความนั้นแหละคือสาเหตุที่สไตล์ของคุณกระตุก

อย่า hardcode handle เป็น 'parent-style' แล้ว enqueue ไฟล์ parent แบบมั่ว ๆ สนิปเป็ตชื่อดังจาก Codex ทำแบบนี้เป๊ะ และมันผิดสำหรับธีมใดก็ตามที่ลงทะเบียน stylesheet ของตัวเองไว้ภายใต้ handle อื่นแล้ว คุณจะจบลงด้วยการโหลด CSS ของ parent สองครั้ง และ dependency ของคุณชี้ไปที่ handle ที่อาจไม่มีอยู่จริง

อย่าเรียก wp_enqueue_style ที่ระดับบนสุดของ functions.php มันต้องรันอยู่ภายในฟังก์ชันที่ hook ไว้กับ wp_enqueue_scripts ถ้าเรียกเร็วเกินไป มันจะไม่ทำอะไรเลย

อย่าก็อป functions.php ทั้งไฟล์ของ parent มาใส่ใน child ทั้งสองไฟล์โหลดพร้อมกัน โดยของ child รันก่อน การก็อปมาจะทำให้เกิดการประกาศฟังก์ชันซ้ำและเกิด fatal error

ข้อควรระวังอีกอย่าง ทั้งหมดนี้ว่าด้วยธีมแบบ คลาสสิก (PHP-template) ถ้า parent ของคุณเป็น block theme การจัดสไตล์ส่วนใหญ่จะไหลผ่าน theme.json และ Site Editor และการ override ด้วย style.css จะทำงานต่างออกไป ผมจะเชื่อวิธีนี้น้อยลงและทดสอบตรง ๆ แทนที่จะเหมาเอาว่า enqueue นี้คือทางแก้

ยังติดอยู่ไหม

ยืนยันว่าบรรทัด Template: ใน header ของ style.css ใน child ตรงกับชื่อ โฟลเดอร์ ของ parent เป๊ะ ๆ โดยแยกตัวพิมพ์เล็กพิมพ์ใหญ่ ไม่ใช่ชื่อที่แสดง (display name) ของ parent จากนั้นเช็ค view source อีกครั้ง handle อยู่ครบ โหลดทีหลัง parent และ version query string ถูกปรับแล้ว ถ้า <link> อยู่ตรงนั้น โหลดเป็นตัวสุดท้าย แต่ CSS ยังไม่มีผล มันก็ไม่ใช่ปัญหา enqueue อีกต่อไปแล้ว มันคือ specificity และการแก้ก็ย้ายเข้าไปอยู่ในตัวกฎเอง