สอนวิธีเขียน CSS แบบ BEM

Suranart Niamcome
SiamHTML
Published in
5 min readJul 20, 2014
bem

เราคงจะเคยเจอปัญหาในการตั้งชื่อ class ให้กับ html element ต่างๆ กันมาบ้าง ทั้งเรื่องการตั้งชื่อ class อย่างไรให้ไล่โค้ดได้ง่าย รวมไปถึงการตั้งชื่อ class อย่างไรที่จะส่งผลดีในแง่ของ performance บทความนี้เราเลยจะมาทำความรู้จักกับวิธีการตั้งชื่อ class ที่กำลังมาแรงในขณะนี้ที่มีชื่อว่า BEM เราลองมาดูกันว่าวิธีนี้มันมีดีอะไร

BEM คืออะไร ?

BEM หรือ “Block Element Modifier” คือวิธีการตั้งชื่อ class อย่างเป็นระบบ เพื่อที่จะทำให้เราสามารถไล่โค้ดได้ง่าย และลดความซ้ำซ้อนของโค้ด โดยการตั้งชื่อ class ด้วยวิธี BEM นั้น จะดูจากหน้าที่ของ html element นั้นๆ ซึ่งแบ่งออกเป็น 3 แบบ ด้วยกัน ดังนี้

  • Blockกล่องอะไรก็ตามที่อยู่ได้โดยอิสระ (เช่น กล่อง search, กล่อง log in, เมนูหลัก เป็นต้น)
  • Elementองค์ประกอบต่างๆ ของ Block (หาก Block เป็นคน Element ก็หมายถึงแขนขาของคนนั่นเอง)
  • Modifierใส่ให้กับ Block หรือ Element ที่มีสไตล์เฉพาะตัว (Modifier ของ Block ก็เช่น คนที่เป็นผู้หญิง ส่วน Modifier ของ Element ก็เช่น แขนข้างซ้าย เป็นต้น)

เมื่อจำแนก html element นั้นๆ ได้แล้ว ว่าเป็นแบบไหน เราก็จะตั้งชื่อ class ตามรูปแบบนี้

/* สไตล์สำหรับ Block */
.block { }
/* สไตล์สำหรับ Element ต่างๆ ภายใน Block */
.block__element { }
/* สไตล์สำหรับ Block พิเศษ */
.block--modifier { }
/* สไตล์สำหรับ Element พิเศษ ภายใน Block */
.block__element--modifier { }
/* สไตล์สำหรับ Element ต่างๆ ภายใน Block พิเศษ */
.block--modifier__element { }
Workshop - เขียน CSS แบบ BEMเพื่อให้เห็นภาพมากขึ้น ลองดูตัวอย่างโค้ด html ต่อไปนี้<form>
<input type="text" placeholder="Keywords">
<input type="submit" value="Search">
</form>
<form>
<input type="text" placeholder="Username">
<input type="password" placeholder="Password">
<input type="submit" value="Log In">
</form>
เริ่มจากกำหนด Block และ Elementจากโค้ดด้านบน หากเราจะตั้งชื่อ class ด้วยวิธี BEM เราก็จะมอง form ทั้ง 2 เป็น Block และ input ต่างๆ ภายใน form ก็จะเป็น Element ให้เราเพิ่ม class ให้กับ html element ต่างๆ ตามรูปแบบของ BEM ที่ได้กล่าวไปก่อนหน้านี้ เราก็จะได้โค้ด html หน้าตาแบบนี้<form class="form">
<input class="form__input" type="text" placeholder="Keywords">
<input class="form__input" type="submit" value="Search">
</form>
<form class="form">
<input class="form__input" type="text" placeholder="Username">
<input class="form__input" type="password" placeholder="Password">
<input class="form__input" type="submit" value="Log In">
</form>
ใส่ Modifier หากต้องการกำหนดสไตล์พิเศษส่วน Modifier นั้น เราจะใช้เฉพาะกรณีที่เราต้องการจะใส่สไตล์พิเศษให้กับ Block หรือ Element เท่านั้น ซึ่งในตัวอย่างนี้ เราอาจจะใช้แยกความแตกต่างระหว่างช่อง input กับปุ่ม submit ก็ได้ โค้ด html ก็จะได้แบบนี้<form class="form">
<input class="form__input" type="text" placeholder="Keywords">
<input class="form__input form__input--submit" type="submit" value="Search">
</form>
<form class="form">
<input class="form__input" type="text" placeholder="Username">
<input class="form__input" type="password" placeholder="Password">
<input class="form__input form__input--submit" type="submit" value="Log In">
</form>
จะเห็นว่าที่ปุ่ม submit ของเราจะมีอยู่ 2 class ด้วยกัน .form__input เป็นการบอกว่า html element นี้ เป็น input ของ form นะ ส่วน .form__input--submit เป็นการบอกว่า input ของ form อันนี้ เป็นแบบ submit นะ
แต่ถ้าเราอยากให้ 2 ฟอร์มนี้ มีหน้าตาต่างกันด้วย ก็ให้เราใช้ Modifier มาช่วยเหมือนเดิม<form class="form form--search">
<input class="form__input" type="text" placeholder="Keywords">
<input class="form__input form__input--submit" type="submit" value="Search">
</form>
<form class="form form--login">
<input class="form__input" type="text" placeholder="Username">
<input class="form__input" type="password" placeholder="Password">
<input class="form__input form__input--submit" type="submit" value="Log In">
</form>
เช่นเดียวกับตัวอย่างก่อนหน้านี้ ที่แต่ละฟอร์มต่างมีอยู่ 2 class เช่นกัน คือ class กลางๆ ที่บอกว่านี่คือฟอร์มนะ กับอีก class หนึ่ง ที่เอาไว้บอกว่าฟอร์มนี้เป็นฟอร์มอะไร
ส่วนในกรณีที่เราต้องการให้ปุ่ม submit ของทั้ง 2 ฟอร์ม มีหน้าตาต่างกัน เราก็จะเขียนแบบนี้<form class="form form--search">
<input class="form__input" type="text" placeholder="Keywords">
<input class="form__input form__input--submit form--search__submit" type="submit" value="Search">
</form>
<form class="form form--login">
<input class="form__input" type="text" placeholder="Username">
<input class="form__input" type="password" placeholder="Password">
<input class="form__input form__input--submit form--login__submit" type="submit" value="Log In">
</form>
จะเห็นว่าเราจำเป็นต้องเพิ่ม class ที่ 3 ให้กับปุ่ม submit เพื่อเป็นการบอกว่าปุ่ม submit นี้ เป็นของฟอร์มสำหรับ search นะ ส่วนปุ่ม submit นี้ เป็นของฟอร์มสำหรับ login นะ
อย่างไรก็ตาม การตั้งชื่อ class นั้น พยายามอย่าให้เกิน 3 ระดับ เช่น block__element--modifier หรือ block--modifier__element เพราะถ้ามันยาวกว่านี้เราก็จะเริ่มดูลำบากแล้วBlock สามารถอยู่ในอีก Block ได้ภายใน Block สามารถมี Block อื่นอยู่ได้ด้วย ลองดูตัวอย่างต่อไปนี้<header class="header">
<form class="header__search form form--search">
...
</form>
<form class="header__login form form--login">
...
</form>
</header>
จากโค้ดด้านบน เรามอง header เป็น Block ก็จะได้ว่าฟอร์มทั้ง 2 เป็น Element ของ header แต่ในขณะเดียวกัน เราก็มองว่าฟอร์มทั้ง 2 เอง ก็เป็น Block ที่มี input ต่างๆ เป็น Element ด้วยเหมือนกัน
หากเราสังเกตดีๆ จะเห็นว่าที่ form ทั้ง 2 นั้น มี class ทั้งในบทบาทของ Element และในบทบาทของ Block ดังนั้น เพื่อให้การกำหนดสไตล์มีระบบ ที่ .header__search และ .header__login ให้เราใส่สไตล์โดยมองว่ามันเปนส่วนหนึ่งของ header เช่น สไตล์เกี่ยวกับการจัดตำแหน่ง เป็นต้น ส่วนที่ form ให้เราใส่สไตล์โดยมองว่ามันเป็นกล่องฟอร์มสำหรับกรอกข้อมูล เราจะเห็นว่า class ที่มีบทบาทต่างกัน แม้จะถูกระบุที่ html element เดียวกัน แต่สไตล์ที่กำหนดก็จะไม่เหมือนกันเขียน CSS Selectorsเมื่อกำหนด class ทั้งหมดเรียบร้อยแล้ว ก็จะมาถึงขั้นตอนการเขียน css selector แบบนี้/* สไตล์ของกล่อง header */
.header { }
/* สไตล์ของกล่อง search ที่อยู่ในกล่อง header */
.header__search { }
/* สไตล์ของกล่อง login ที่อยู่ในกล่อง header */
.header__login { }
/* สไตล์ทั่วไปของกล่อง form */
.form { }
/* สไตล์ทั่วไปของ input ในกล่อง form */
.form__input { }
/* สไตล์พิเศษของกล่อง form ชนิด search */
.form--search { }
/* สไตล์พิเศษของกล่อง form ชนิด login */
.form--login { }
/* สไตล์ของ input แบบ submit ในกล่อง form */
.form__input--submit { }
/* สไตล์ของปุ่ม submit ในกล่อง form ชนิด search */
.form--search__submit { }
/* สไตล์ของปุ่ม submit ในกล่อง form ชนิด login */
.form--login__submit { }
ประโยชน์ของ BEM ก็คือ เพียงแค่เราดู selector คร่าวๆ เราก็จะรู้ได้ทันทีเลยว่า selector เหล่านั้น หมายถึง html element ไหน ทั้งนี้ก็เป็นเพราะว่าเราได้ตั้งชื่อ class อย่างมีระบบนั่นเอง และหากสังเกตดีๆ เราก็จะเห็นว่าการตั้งชื่อ class ด้วยวิธี BEM นั้น จะทำให้เราไม่ต้องใช้ descendant selector เลย ซึ่งมันจะส่งผลดีในแง่ของ performance ไปโดยปริยาย (รายละเอียดเพิ่มเติม สามารถอ่านได้ที่บทความ เขียน CSS Selectors อย่างไรให้มีประสิทธิภาพ ?)
แล้วอย่างนี้ ชื่อ Class จะไม่ยาวเกินไปหรือ ?แน่นอนว่ามีข้อดีก็ย่อมมีข้อเสีย ซึ่งข้อเสียที่เห็นชัดๆ เลยของ BEM ก็คือชื่อ class ที่ค่อนข้างยาวนั่นเอง แต่ถ้ามาพิจารณาดูดีๆ แล้ว การตั้งชื่อ class แบบ BEM นั้น ดูจะมีประโยชน์มากกว่า จากตัวอย่างก่อนหน้านี้ สมมติว่าเราไม่ได้ใช้วิธี BEM แล้วเราเขียน selector แบบนี้.header { }/* เทียบเท่า .header__search, .form และ .form--search */
.form-search { }
/* เทียบเท่า .form__input */
.form-search input { }
/* เทียบเท่า .form__input--submit และ .form--search__submit */
.form-search .submit { }
/* เทียบเท่า .header__login, form และ form--login */
.form-login { }
/* เทียบเท่า .form__input */
.form-login input { }
/* เทียบเท่า .form__input--submit และ .form--login__submit */
.form-login .submit { }
หากเราไม่ได้ใช้วิธี BEM ถึงแม้ว่าชื่อ class จะสั้นกว่า และมีจำนวน class น้อยลงก็จริง แต่ก็ต้องแลกมาด้วยความซ้ำซ้อนของโค้ด รวมไปถึง descendant selector ลองนึกดูว่าหากเรามีหลายๆ ฟอร์ม การตั้งชื่อ class แบบนี้จะทำให้เราต้องสร้าง css rule ใหม่ขึ้นมาพอสมควร เพราะ css rule ที่มีอยู่เดิมนั้นแทบจะไม่สามารถเอามา reuse ได้เลย การตั้งชื่อ class แบบมีระบบอย่างวิธี BEM จึงดูจะน่าสนใจกว่า
วิธีใช้ Sass กับการเขียนแบบ BEM
sass
หากใครใช้ Sass อยู่แล้ว การใช้ BEM จะยิ่งน่าสนใจเข้าไปอีก เนื่องจาก Sass ได้เพิ่มฟีเจอร์ที่ช่วยอำนวยความสะดวกในการเขียนด้วยวิธี BEM มาโดยเฉพาะ ลองดูตัวอย่างต่อไปนี้ (หากพบว่า Sass ของเรายังไม่รองรับการเขียนแบบด้านล่างนี้ ให้อัพเดท Sass เป็นเวอร์ชันล่าสุดเสียก่อน).header {
&__search {
/* .header__search */
}
&__login {
/* .header__login */
}
}
.form {
&--search {
/* .form--search */
&__submit {
/* .form--search__submit */
}
}
&--login {
/* .form--login */
&__submit {
/* .form--login__submit */
}
}
&__input {
/* .form__input */
&--submit {
/* .form__input--submit */
}
}
}
เมื่อนำโค้ด Sass ด้านบนนี้ไป compile เราก็จะได้โค้ด css เหมือนกับที่เราใช้ BEM เลย จะเห็นว่าการรองรับ BEM ของ Sass นั้น ช่วยให้เราไล่โค้ดได้ง่ายขึ้นไปอีก เพราะเราจะดูออกได้อย่างง่ายดายเลยว่าอะไรเป็น Block, Element และ Modifier
อีกประเด็นหนึ่งที่น่าสนใจก็คือ การใช้ BEM จะช่วยลดปัญหาการใช้ nested selector ซ้อนกันมากเกินไปในการเขียน Sass ได้เป็นอย่างดี (คือถึงแม้ว่าโค้ด Sass แบบ BEM จะดูเป็น nested selector ก็จริง แต่พอ compile ออกมาจะไม่มี descendant selector เลย)บทสรุปการเขียน CSS ด้วยวิธี BEMการเขียน css โดยใช้การตั้งชื่อ class แบบ BEM นั้นมีข้อดีตรงที่เราจะมีหลักในการตั้งชื่อ ซึ่งทำให้สะดวกในการมาไล่โค้ดในภายหลัง นอกจากนั้น การใช้ BEM ยังมีส่วนช่วยลดความซ้ำซ้อนของโค้ด และทำให้การเขียน selector มีประสิทธิภาพมากยิ่งขึ้น ข้อเสียเพียงอย่างเดียวของ BEM ก็คือชื่อของ class ที่ออกจะยาวและเยอะไปนิด แต่ก็ถือเป็นเรื่องที่เล็กน้อยมากเมื่อเทียบกับประโยชน์ที่เราได้รับจากมัน และหากใครยังสงสัยอยู่ว่า BEM นั้นดีจริงหรือเปล่า ก็ขอให้ดูจากการที่ Sass ถึงกับออกมารองรับการตั้งชื่อ class แบบนี้โดยเฉพาะเลย นั่นก็พอจะทำให้เราเชื่อมั่นได้ว่า BEM น่าจะมีอนาคตที่ดีพอสมควร

--

--