Babel Coder

ตั้งชื่อคลาสใน CSS อย่างไรดี? จาก Global CSS สู่ BEM และ Local CSS

beginner

ปรัมปรา CSS

กาลครั้งหนึ่งนานมาแล้ว… เรานิยมอ้างถึง element ของ HTML ด้วย ID คิดอะไรไม่ออกก็ตั้งชื่อให้มันผ่าน ID แต่แล้ววันหนึ่งเมื่อผู้ใหญ่บอกว่า หนูๆ การจะเรียก element ใดๆด้วย ID มันเช้ยเชย เราจึงเริ่มตระหนักถึงความดีงามของการใช้ class เหนือ ID ด้วยความที่เราอยากให้ของเหมือนๆกันเช่นปุ่ม Submit มีรูปแบบเหมือนกันคือต้องการให้ปุ่มมีสีเขียว เราจึงยัดเยียดชื่อ class ให้ว่า submit-btn

ทุกอย่างเป็นไปได้สวยหากไม่ติดที่ลูกค้าอยากให้ Submit Button ของหน้า Order เป็นสีแดงเพื่อแสดงถึงความเร่าร้อนจนผู้ใช้อดใจไม่ไหวที่จะคลิก เราไม่สามารถสร้าง ID แล้วนำไปแปะกับ element ว่า order-button ได้เพราะเราเชื่อผู้ใหญ่มากเกินไป อีกทั้งไม่สามารถแก้สไตน์ของ submit-btn ได้เพราะจะกระทบกับตัวอื่น เหตุนี้เราจึงแยกออกเป็นสอง class คือ red-submit-button กับ green-submit-button แต่แล้ววันหนึ่งลูกค้ากลับอยากให้เปลี่ยนรูปแบบอีกนิดหน่อยเป็น… พอ! จบแค่นี้ นิทานเรื่องนี้สอนให้รู้ว่า การตั้งชื่อ class ของ element นั้นไม่ง่าย เพราะเราไม่รู้จะตั้งยังไงดีเพื่อไม่ให้กระทบกับ element ตัวอื่นๆที่เราดันตั้งชื่อให้เหมือนกัน

เป้าหมายของบทความนี้คือการนำเสนอวิธีตั้งชื่อ class เพื่อป้องกันไม่ให้ชื่อ class ของแต่ละ element ชนกัน อันจะนำไปสู่ปัญหาที่ว่า แก้ style ของ class เดียว เจ๊งทั้งโปรเจ็ค

ตั้งชื่อ class แบบ Bootstrap

Twitter Bootstrap เป็น Framework ชื่อดังที่ทุกวันนี้ไม่มีใครไม่รู้จักโดยเราจะนำเอกลักษณ์ในการตั้งชื่อคลาสมาศึกษาเพื่อช่วยกู้ชีวิตของเราต่อไป

.btn {
  display: inline-block;
  cursor: pointer;
  ...
  ...
}

.btn-success {
  color: #fff;
  background-color: #5cb85c;
  border-color: #4cae4c;
}

.btn-danger {
  color: #fff;
  background-color: #d9534f;
  border-color: #d43f3a;
}

ตัวอย่างข้างบนนี้นำมาจากการสร้างและใช้งาน Button ใน Bootstrap พบว่าในคอมโพแนนท์ของ Button นั้นจะแบ่ง class ออกเป็นสองประเภท ประเภทแรกคือ class พื้นฐานคือ btn เป็นตัวกำหนดสไตล์พื้นฐานทั้งหมดของปุ่มกด เช่น กำหนดว่าให้แสดงเม้าส์เป็นรูปนิ้วมือเมื่อลากเม้าส์ไปวางไว้บนปุ่มกด class ประเภทที่สองคือส่วนกำหนดพิเศษ ได้แก่ class btn-success และ btn-danger โดย class ประเภทนี้จะทำให้ปุ่มกดของเราแตกต่างจากตัวอื่น

<a href='javascript:void(0)' class='btn btn-danger'>กดฉันเลย ด่วน!</>

ตัวอย่างข้างบน เราต้องการแสดงปุ่มสีแดงที่เร่าร้อนเราจึงใช้ class พิเศษที่แสดงสีแดงบนปุ่ม นั่นคือ btn-danger และต้องไม่ลืมว่าปุ่มกดของเราต้องแสดงสไตน์ของ Button ด้วยจึงใช้ทั้ง btn และ btn-danger ควบคู่กัน

เราลองนำวิธีนี้มาใช้ในสถานการณ์สมมติกันบ้าง

กำหนดให้เว็บเพจของเราเป็นหน้าบทความประกอบด้วยสามส่วน

  • title แสดงชื่อบทความให้ใช้สีข้อความเป็นสีเขียว
  • content แสดงเนื้อหาให้ใช้สีดำ
  • และส่วนสุดท้ายคือ footer ใช้แสดงข้อมูลเบ็ดเตล็ดให้ข้อความเป็นสีเทา
  • นอกจากสามส่วนนี้แล้วให้แสดงเนื้อหาเป็นสีเหลือง

กำหนดให้บทความของเรามีสองประเภท

  • บทความยอดนิยม แสดงพื้นหลังเป็นสีน้ำเงินโดยให้ title มีขนาด 20px
  • บทความธรรมดาแสดงพื้นหลังเป็นสีขาวโดยให้ title มีขนาด 18px

จากข้อกำหนดข้างต้นจะได้ CSS ออกมาหน้าตาทำนองนี้

.article {
  color: yellow;
}

.article-default {
  background: white;
}

.article-popular {
  background: blue;
}

.article-default > .article-title {
  font-size: 18px;
}

.article-popular > .article-title {
  font-size: 20px;
}

.article-content {
  color: black;
}

.article-footer {
  color: gray;
}

ส่วนข้างล่างคือ HTML ที่เรานำ CSS ชุดนี้ไปใช้งาน

<article class='article article-popular'>
  <header>
    <h1 class='article-title'>
      การตั้งชื่อ class ให้ดูแลง่าย: จาก Bootstrap, Bem สู่ Local Class Name
    </h1>
  </header>
  <div class='article-content'>
    ...
  </div>
  <footer class='article-footer'>
  </footer>
</article>

นี้เป็นขั้นต้นของการตั้งชื่อคลาสให้ง่ายขึ้น สังเกตนะครับว่าเราไม่ได้ตั้งชื่อคลาสลอยๆขึ้นมา หากแต่นำชื่อของคอมโพแนนท์มาแปะไว้ข้างหน้า เช่น article-content สาเหตุที่ต้องทำเช่นนี้เพื่อกันไม่ให้ชื่อคลาสไปซ้ำกับหน้าอื่น เราอาจมีส่วน content ของหน้า Dashboard ก็ได้ ถ้าเราตั้งชื่อคลาสเพียงแค่ content แน่นอนว่ากระทบไปถึง content ในส่วนของ Dashboard ด้วยเช่นกัน

อย่างไรก็ตามการตั้งชื่อแบบนี้ยังมีข้อจำกัดอยู่ ประการแรกคือ web browser จะค้นหา element จากขวาไปซ้าย ในกรณีของ .article-popular > .article-title browser จะมองหา element ที่มีคลาสเป็น article-title ก่อน เมื่อพบแล้วจึงทำการตัดตัวเลือกคือเลือกเฉพาะ article-title ที่เป็นลูกโดยตรงของ article-popular ด้วยเหตุนี้ยิ่งซ้อน selector เข้าไปหลายชั้นเท่าไร ยิ่งเสียเวลาค้นหา(แม้จะไม่มากมายนักก็ตาม)

ปัญหาถัดมาคือความไม่มีระดับชั้นของชื่อคลาส ลองสังเกตชื่อต่อไปนี้ดูครับ article-popular กับ article-title เรารู้อยู่แล้วว่า popular เป็นประเภทของบทความ แต่ title เป็น attribute หนึ่งของบทความทำให้มันต้องอยู่ข้างใน article แต่ถ้าผมไม่บอกคุณหละ? คุณอาจจะมองว่า popular คือ attribute ของบทความก็ได้ดังนั้นมันควรจะซ้อนอยู่ข้างใน article เหมือนกับ title ซิ ที่ทำให้เราเข้าใจผิดเช่นนี้เพราะทั้ง title และ popular ดันใช้ - คั่นข้างหน้าเหมือนกันทั้งคู่ จึงแยกไม่ออกว่าอีลีเมนต์ตัวไหนควรอยู่ข้างในข้างนอก

BEM Methodology

ปัญหาที่กล่าวมาทั้งหมดนี้เราแก้ได้ด้วยเทพ BEM ครับ BEM เป็นเพียงวิธีการที่ใช้ต่อกรกับความยุ่งยากในการตั้งชื่อคลาสของ CSS เป็นเพียงแนวคิดที่เราต้องนำมาประยุกต์เองดังนี้

BEM มองทุกอย่างเป็น Blocks, Elements และ Modifiers

  • Blocks คือ element ที่เราคิดว่าสามารถแยกส่วนออกจาก element ตัวอื่นได้โดยที่ยังคงมีความหมายในตัวเอง เช่น header ที่ประกอบด้วยเมนู หากเราดึง header ออกจาก element ที่มันไปซ้อนอยู่แล้วนำไปวางที่จุดอื่น มันก็ยังคงมีความหมายในตัวมันคือเป็น header ที่มีเมนูให้ผู้ใช้งานคลิกได้อยู่เช่นเดิม
  • Elements คือส่วนหนึ่งของ Block ไม่สามารถแยกออกมาอยู่อิสระได้เพราะจะสูญเสียความหมายไป เช่น title ของ article หากเราแยกชื่อบทความออกจากบทความแล้วนำไปวางไว้ตำแหน่งอื่นในเพจ เราจะไม่ทราบทันทีว่าชื่อนี้คือชื่ออะไร กล่าวคือ Element จะเสียความหมายเมื่อแยกออกจาก Block
  • Modifiers คือเครื่องหมายพิเศษที่แปะไว้กับ Block หรือ Element เพื่อบอกว่า เห้ย Block หรือ Element ตัวนี้ผ่าเหล่านะเว้ย ไม่เหมือนชาวบ้านเขา เช่น ใช้ Modifier เพื่อแยกปุ่มสีแดงที่แสดงถึงความเร่าร้อนออกจากปุ่มสีเขียว

ถึงเวลาแปลงร่างด้วย BEM

// article เป็น Block ตั้งชื่อโดยไม่ต้องมี __ หรือ -- คั่น
.article {
  color: yellow;
}

// default ใช้บอกว่า article ประเภทนี้ต่างจากประเภทอื่นจึงเป็น Modifier
// ใช้ -- คั่นระหว่าง Modifier กับ Block (หรือ Element) ที่มันไปแปะอยู่
.article--default {
  background: white;
}

.article--popular {
  background: blue;
}

// content เป็น Element ของ Article เนื่องจากเราไม่สามารถแยกมันออกจาก Article ได้
// พูดง่ายๆคือมันเป็น attribute นึงของบทความนั่นแหละฮะ
// ใช้ __ คั่นไว้หน้า Element
.article__content {
  color: black;
}

.article__footer {
  color: gray;
}

// ใช้ __ คั่นเพื่อบอกว่า title เป็น Element ของ Block ที่ชื่อ Article
// เนื่องจากมี -- อยู่หน้า default จึงเป็นการสื่อว่าเป็น title ของ Article แบบ Default
.article--default__title {
  font-size: 18px;
}

.article--popular__title {
  font-size: 20px;
}

ถึงตรงนี้ทุกคนน่าจะเห็นด้วยกับผมว่าโค๊ดของเรามีลำดับชั้นแล้ว เพียงแค่เราเห็นชื่อคลาสเราจะบอกพฤติกรรมของมันได้ทันทีว่าเป็น Block, Element หรือ Modifier และสามารถแบ่งแยกได้หรือไม่ได้ ลองดูชื่อคลาสนี้นะครับ article--popular__title--uppercase คุณน่าจะเข้าใจทันทีว่าเป็นชื่อคลาสของ Element ที่ทำอะไร ใช่แล้วครับมันคือ Element title ที่ใช้ตัวอักษรเป็นตัวพิมพ์ใหญ่ทั้งหมดภายใต้ Block Article แบบยอดนิยม

ตัวอย่างข้างต้นนี้เขียนด้วย CSS หากคุณพบว่ามันยาวเกินไปและใช้คำเช่น article ฟุ่มเฟือยมากไป คุณสามารถใช้ SCSS เพื่อลดความซับซ้อนลงได้ดังนี้

.article {
  color: yellow;
  
  &--default {
    background: white;
    
    &__title {
      font-size: 18px;
    }
  }
  
  &--popular {
    background: blue;
    
    &__title {
      font-size: 20px;
    }
  }
  
  &__content {
    color: black;
  }
  
  &__footer {
    color: gray;
  }
}

ยาวไปไม่อ่าน

อะไรที่มันยาวไปคนเราไม่ชอบอ่านหรอกครับ แต่ขอร้องหน่อยนึง ช่วยอ่านบทความนี้ให้จบและติดตามอ่านบทความอื่นๆของผมด้วยนะครับ(ยิ้ม)

หลังจากได้เรียนรู้วิธีแบบ BEM แล้วผู้อ่านอาจคิดว่าชื่อคลาสหลายๆตัวมันช่างอ่านแล้วเป็น #@%$$$@#% เสียเหลือเกิน เช่น article--popular__title--uppercase กว่าจะแปลแล้วตีความจบก็ ❨╯°□°❩╯︵┻━┻ ถึงอย่างไรโปรดเชื่อเถอะครับว่าหากไม่สามารถใช้วิธีอื่นได้แล้ว BEM ยังเป็นแนวคิดที่ดีสำหรับคุณ

Inline Style และ Local CSS

เอาหละ มาถึงจุดสุดท้ายของบทความนี้เสียที หากคุณเป็นผู้หนึ่งที่ใช้ React หรือ Webpack ผมขอแสดงความดีใจด้วยเพราะคุณจะได้ประโยชน์จากการอ่านส่วนสุดท้ายของบทความนี้ คุณสามารถจบบทความนี้ได้ถ้าคุณไม่ได้ใช้ React หรือไม่คุ้นเคยกับ Webpack ครับ

วิธีการทั้งหมดที่ผมกล่าวไว้ก่อนหน้านี้เป็นวิธีการจัดการ CSS แบบที่เราเรียกว่า Global CSS นั่นคือเราตั้งชื่อคลาสให้ Element แล้วนำไฟล์ CSS นี้ไปใช้ในทุกๆหน้าเพจ ถ้าเพจไหนไม่มี Element ชื่อตรงกับของที่เราตั้งไว้ มันก็จะไม่แสดงสไตล์ออกมา แต่หากชื่อ Element ตรงกับที่เราตั้งกฎไว้ในไฟล์ CSS แล้ว สไตล์ที่ตั้งไว้ก็จะทำงานทันที นี่คือข้อเสียของ Global CSS กล่าวคือความวิบัติจะมาเยือนเมื่อชื่อคลาสของเพจหนึ่งดันไปเหมือนกับอีกเพจ แต่เราดันไม่ได้ต้องการสไตล์นั้นเลย พับผ่าซิ!

Inline Style เป็น Feature หนึ่งของ React ที่อนุญาติให้เราเขียน CSS แปะไว้กับคอมโพแนนท์ ได้เลย แน่นอนว่าข้อดีของมันคือสไตล์ของคอมโพแนนท์นั้นจะไม่ชนกับตัวอื่นแน่นอนเพราะไม่ได้ใช้ร่วมกับใคร ดังตัวอย่างข้างล่าง

class Article extends Component {
  articleStyles() {
    return {
      color: 'yellow'
    }
  }
  
  render() {
    return (
      <article style={this.articleStyles()}>
        ...
      </article>
    )
  }
}

ในการใช้งานจริงผมแนะนำให้ใช้ Inline Style Library ตัวอื่นจะดีกว่าของมาพร้อมกับ React เช่น Radium เนื่องจากมีการใช้งานที่ง่ายกว่าและความสามารถที่เหนือกว่าด้วยประการทั้งปวง

สุดท้ายนี้ผมขอแนะนำอีกวิธีในการจัดการ CSS คือ Local CSS ด้วยความสามารถจากcss-loader เราเพียงกำหนดสไตล์ให้กับ Element ของเราตามปกติ css-loader จะเปลี่ยนชื่อคลาสของเราเป็นรูปแบบอื่นที่รับประกันได้ว่าไม่เหมือนและไม่ชนกับใครแน่นอน เช่น ถ้าชื่อคลาสของเราเป็น .article css-loader อาจเปลี่ยนเป็น .234_fkku-333456 ให้อัตโนมัติตามกฎที่เราตั้งไว้กับ Webpack

{
  test: /\.css$/,
  loader: 'style!css?module' // ระบุ module เพื่อให้ css-loader ใช้งาน local css
}

เมื่อชื่อคลาสไม่ซ้ำกับคอมโพแนนท์อื่นแล้วจึงไม่มีความจำเป็นใดๆต้องกำหนดชื่อคลาสให้ยาวตามแบบฉบับของ BEM เราอาจเปลี่ยนแบบการเขียน Selector ของเราได้ใหม่ดังนี้

// เรามั่นใจว่า css-loader จะเปลี่ยน .content ของเราให้ไม่ซ้ำกับคอมโพแนนท์อื่น
// จึงไม่มีความจำเป็นใดๆต้องเพิ่ม article เข้าไปข้างหน้า เช่น article-content
.content {
  color: black;
}

ช้าก่อน… ข้ายังไม่ยอมจบบทความง่ายๆ

แม้การใช้ Local CSS ทำให้เราไม่ต้องตั้งชื่อคลาสยาวเป็นหางว่าว แต่ในความเป็นจริงเมื่อคอมโพแนนท์ของเราใหญ่ขึ้น บรรทัดเรายาวขึ้น การตั้งชื่อที่ดีย่อมทำให้ง่ายต่อการแก้และอ่านโค๊ด แน่นอนว่ามากกว่าการตั้งชื่อคลาสว่า content แล้วหันไปถามเพื่อนว่า content ไหนวะ $@&!!! หลายเท่าตัว

บทความนี้ผมได้เล่าถึงวิธีต่างๆในการออกแบบชื่อคลาสของ Selector ให้ง่ายขึ้น ถ้าถามว่าวิธีไหนดีสุดคงตอบได้ยาก ทุกวิธีล้วนมีข้อดีข้อเสีย โดยส่วนตัวแล้วผมแนะนำ BEM สำหรับ Global CSS และใช้ Local CSS สำหรับโปรเจคที่ใช้ React ไม่มีวิธีใดถูกผิดในการตั้งชื่อคลาส มันคือข้อตกลงของทีมคุณ ทำอย่างไรให้ทุกคนเข้าใจโค๊ดได้ง่ายและเขียนง่าย เมื่อทุกคนแฮปปี้การเขียนโค๊ดก็จะเป็นเรื่องสนุก

เอกสารอ้างอิง

BEM - Block Element Modifier. Retrieved April, 17, 2016, from http://getbem.com/introduction/

webpack. css loader module for webpack. Retrieved April, 17, 2016, from https://github.com/webpack/css-loader


แสดงความคิดเห็นของคุณ


Nuttawut Singhabut10 เดือนที่ผ่านมา

อ่านเพลินดีครับ ชอบๆ