Shadow (เงา) — เอฟเฟกต์ภาพในอินเทอร์เฟซมือถือที่สร้างภาพลวงตาของความลึกและลำดับชั้นระหว่างองค์ประกอบ เงาแสดงให้เห็นว่าองค์ประกอบใดอยู่สูงกว่าในระนาบ z และด้วยเหตุนี้จึงมีความสำคัญหรือโต้ตอบได้มากกว่า ตาม Material Design Guidelines (Google, 2026) เงาของระบบใช้ความสูงหลักห้าระดับ (elevation): 0 dp (เรียบ), 1–4 dp (ปุ่ม), 6–12 dp (การ์ด), 16–24 dp (ไดอะล็อก), 24+ dp (หน้าต่างโมดอล) อ่านเพิ่มเติมเกี่ยวกับความสูงขององค์ประกอบได้ในบทความเกี่ยวกับ Elevation
ประเด็นสำคัญ
เงา (shadow) ในอินเทอร์เฟซผู้ใช้คือเอฟเฟกต์ภาพที่จำลองแสงและความลึก เงาถูกสร้างขึ้นโดยการวาดสำเนาที่มืดและเบลอขององค์ประกอบที่ขยับจากต้นฉบับ ในการออกแบบมือถือ เงาทำหน้าที่สามอย่าง: แสดงลำดับชั้น (องค์ประกอบที่สูงกว่า = เงาที่ยาวกว่า), ส่งสัญญาณการโต้ตอบ (ปุ่มที่มีเงาดูเหมือนกดได้), แยกเนื้อหาออกจากพื้นหลัง (การ์ดที่มีเงาอ่านเป็นบล็อกแยกต่างหาก)
ใน Material Design ของ Google เงาเชื่อมโยงอย่างแยกไม่ออกกับแนวคิดของ elevation แต่ละองค์ประกอบครอบครองความสูงที่แน่นอนในพิกัด z และเงาคือการฉายของแสงจากด้านบน Human Interface Guidelines ของ Apple ไม่ได้กำหนดระบบความสูงที่เข้มงวด แต่แนะนำให้ใช้เงาเพื่อสร้างลำดับชั้นภาพและความลึกในอินเทอร์เฟซ
จากการศึกษาของ Nielsen Norman Group (2024) เงาที่วางอย่างถูกต้องช่วยลดเวลาในการค้นหาองค์ประกอบที่โต้ตอบได้ 24% เงายังช่วยให้ผู้ใช้ที่มีความบกพร่องทางการมองเห็นแยกแยะขอบเขตขององค์ประกอบได้ อย่างไรก็ตาม การใช้เงามากเกินไป (3+ ระดับบนหน้าจอ) จะเพิ่มภาระทางความคิดและทำให้การรับรู้แย่ลง
บน iOS เงาจะถูกดำเนินการผ่านคุณสมบัติ CALayer: shadowColor กำหนดสีเงา, shadowOffset — การขยับใน CGSize (x, y), shadowRadius — รัศมีการเบลอ, shadowOpacity — ความทึบจาก 0 ถึง 1 เพื่อประสิทธิภาพ ขอแนะนำให้ตั้งค่า shadowPath — UIBezierPath ที่อธิบายรูปร่างเงา เพื่อให้ Core Animation ไม่คำนวณโดยอัตโนมัติ
// UIKit: การกำหนดค่าเงาผ่าน CALayer
import UIKit
let cardView = UIView()
cardView.backgroundColor = .systemBackground
cardView.layer.cornerRadius = 12
// การกำหนดค่าเงา
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.15
cardView.layer.shadowOffset = CGSize(width: 0, height: 4)
cardView.layer.shadowRadius = 8
"> การเพิ่มประสิทธิภาพ: shadowPath ที่ชัดเจน
cardView.layer.shadowPath = UIBezierPath(
roundedRect: cardView.bounds,
cornerRadius: 12
).cgPath
ใน SwiftUI เงาจะถูกตั้งค่าผ่านตัวปรับแต่ง .shadow() ด้วยพารามิเตอร์ color, radius, x, y SwiftUI ใช้ shadowPath โดยอัตโนมัติและจัดการประสิทธิภาพการเรนเดอร์ สำหรับเงาขั้นสูง (เงาด้านใน, เงาหลายชั้น) ให้ใช้ overlay() กับ LinearGradient
// SwiftUI: เงาผ่านตัวปรับแต่ง shadow
import SwiftUI
VStack {
Text("การ์ดที่มีเงา")
.padding()
.background(Color.white)
.cornerRadius(16)
.shadow(
color: .black.opacity(0.15),
radius: 10,
x: 0,
y: 4
)
}
ประสิทธิภาพของเงาบน iOS: หากไม่ได้ตั้งค่า shadowPath Core Animation จะคำนวณเงาตามรูปร่างของเลเยอร์ทุกเฟรม ซึ่งอาจทำให้เกิดความล่าช้าระหว่างแอนิเมชัน สำหรับองค์ประกอบคงที่ ให้ตั้งค่า shadowPath เสมอ สำหรับองค์ประกอบที่เคลื่อนไหว ให้ใช้ shouldRasterize = true บน CALayer เพื่อแคชเงาในเลเยอร์แยกต่างหาก ใน SwiftUI การทำ rasterization จะถูกจัดการผ่าน .drawingGroup()
ใน Android เงาใน Material Design ถูกดำเนินการผ่าน elevation — คุณสมบัติ View ที่กำหนดความสูงขององค์ประกอบในพิกัด z ระบบจะเรนเดอร์เงาโดยอัตโนมัติตาม elevation และ outlineProvider (โครงร่างขององค์ประกอบ) ยิ่ง elevation สูงเท่าไหร่ เงาก็ยิ่งใหญ่และเบลอมากขึ้นเท่านั้น สำหรับ View ที่ไม่สืบทอด Material Design (เช่น AppCompat) เงาจะทำงานเมื่อเปิดใช้งานการเร่งด้วยฮาร์ดแวร์เท่านั้น
<!-- XML: elevation ผ่านแอตทริบิวต์ -->
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="6dp">
<!-- เนื้อหาของการ์ด -->
</androidx.cardview.widget.CardView>
สำหรับ เงาที่กำหนดเอง ในระบบ View ของ Android ให้ใช้ OutlineProvider ViewOutlineProvider อนุญาตให้กำหนดรูปร่างตามอำเภอใจผ่าน outline.setRoundRect() หรือ outline.setPath() หากไม่มี OutlineProvider เงาจะถูกเรนเดอร์ตามรูปร่างสี่เหลี่ยมของ View ซึ่งไม่คำนึงถึงการปัดมุม
// Kotlin: OutlineProvider ที่กำหนดเองสำหรับเงา
import android.graphics.Outline
import android.view.View
import android.view.ViewOutlineProvider
val myView: View = findViewById(R.id.myView)
myView.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(
0, 0, view.width, view.height,
12f // รัศมีมุม
)
}
}
myView.elevation = 8f // ความสูงของเงาเป็น px
myView.translationZ = 4f // ความสูงเพิ่มเติมเมื่อกด
ใน Jetpack Compose เงาจะถูกจัดการผ่าน Modifier.shadow() และ elevation ในส่วนประกอบ Card และ Surface Modifier.shadow() รับ elevation และ shape ซึ่งแตกต่างจากระบบ View Compose จะเรนเดอร์เงาในไปป์ไลน์การเรนเดอร์ของตัวเอง ซึ่งให้พฤติกรรมที่คาดเดาได้มากขึ้นในทุกเวอร์ชันของ Android
// Jetpack Compose: เงาผ่าน Modifier.shadow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
Card(
modifier = Modifier.shadow(
elevation = 6.dp,
shape = RoundedCornerShape(12.dp),
clip = false
),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
Text("Card ที่มีเงากำหนดเอง")
}
การใช้เงาที่ถูกต้อง ช่วยปรับปรุงการรับรู้อินเทอร์เฟซ ในขณะที่การใช้ที่ไม่ถูกต้องสร้างสัญญาณรบกวนทางภาพและลดประสิทธิภาพ Material Design กำหนดระดับ elevation ห้าระดับ: 0 dp (ไม่มีเงา), 1–4 dp (ปุ่ม, ชิป), 6–12 dp (การ์ด, เมนู), 16–24 dp (ไดอะล็อก, แผ่นด้านล่าง), 24+ dp (หน้าต่างโมดอล, FAB)
| ระดับ | Elevation | องค์ประกอบ | ลักษณะของเงา |
|---|---|---|---|
| 0 | 0 dp | ข้อความ, ไอคอน, พื้นหลัง | ไม่มีเงา, ชั้นเรียบ |
| 1 | 1–4 dp | ปุ่ม, ช่องป้อนข้อมูล | เงาเบา, การขยับเล็กน้อย |
| 2 | 6–12 dp | การ์ด, Snackbar | เงาปานกลาง, การเบลอที่สังเกตได้ |
| 3 | 16–24 dp | ไดอะล็อก, เมนู | เงาลึก, การเบลอมาก |
| 4 | 24+ dp | หน้าต่างโมดอล | เงาสูงสุด, องค์ประกอบลอย |
แนวปฏิบัติที่ดีที่สุด: (1) อย่าใช้เงากับองค์ประกอบที่ไม่ควรดึงดูดความสนใจ (พื้นหลัง, ตัวแบ่ง), (2) ความสูงของเงาควรสอดคล้องกับความสำคัญขององค์ประกอบ — ยิ่งสำคัญยิ่งสูง, (3) เมื่อกดปุ่ม ให้ลด elevation ด้วยแอนิเมชันเพื่อการตอบสนองทางสัมผัส, (4) หลีกเลี่ยงเงาบนพื้นหลังสีเข้ม — ใช้ overlay (ทำให้สว่าง/มืด) แทนเงา ใน Material You (Android 12+) เงาจะปรับตามธีม: บนพื้นหลังสีเข้ม elevation จะถูกแทนที่ด้วยชั้นสี (surface tint)
ใน Flutter เงาจะถูกดำเนินการผ่าน BoxShadow ภายใน BoxDecoration BoxShadow รับ color, offset, blurRadius และ spreadRadius Flutter รองรับเงาหลายชั้น: การส่งรายการ BoxShadow ช่วยให้สร้างเอฟเฟกต์ที่ซับซ้อน — ตัวอย่างเช่น เงาภายนอก + การเน้นภายใน PhysicalModel เป็นทางเลือกที่จำลองเงาทางกายภาพตามรูปร่าง (BeveledRectangleBorder, CircleBorder)
// Flutter: เงาผ่าน BoxDecoration + BoxShadow
import 'package:flutter/material.dart';
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.0),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
offset: Offset(0, 4),
blurRadius: 8.0,
spreadRadius: 0,
),
],
),
child: Text('คอนเทนเนอร์ที่มีเงา'),
)
// PhysicalModel สำหรับเงาทางกายภาพ
PhysicalModel(
color: Colors.white,
elevation: 6.0,
shadowColor: Colors.black.withOpacity(0.2),
borderRadius: BorderRadius.circular(12.0),
child: Text('แบบจำลองทางกายภาพที่มีเงา'),
)
ประสิทธิภาพ: BoxShadow ที่มีเงาหลายชั้นอาจทำให้เกิดการวาดซ้ำในการเปลี่ยนแปลงแต่ละครั้ง สำหรับองค์ประกอบคงที่ ให้ใช้ PhysicalModel — มันจะแคชเงาในเลเยอร์แยกต่างหาก (RepaintBoundary) ใน Flutter 3.16+ รองรับ Material 3 elevation พร้อมเงาที่ปรับเปลี่ยนได้ ซึ่งเปลี่ยนสีและการเบลอตามธีม (สว่าง/มืด)
คำถามที่พบบ่อย
Elevation คือความสูงเชิงตัวเลขขององค์ประกอบในพิกัด z ในขณะที่ shadow (เงา) คือเอฟเฟกต์ภาพที่สร้างโดยความสูงนี้ ใน Android elevation คือคุณสมบัติ View ที่สร้างเงาโดยอัตโนมัติ ในการออกแบบ elevation และ shadow มักใช้แทนกันได้ แต่ในทางเทคนิค elevation คือสาเหตุและ shadow คือผลลัพธ์
ตรวจสอบ: (1) View มี elevation มากกว่า 0, (2) ตั้งค่า outlineProvider แล้ว (หาก View ไม่ได้มาจากไลบรารี Material), (3) เปิดใช้งานการเร่งด้วยฮาร์ดแวร์ (android:hardwareAccelerated="true" ในไฟล์ manifest) CardView จัดการ outlineProvider โดยอัตโนมัติ สำหรับ View ที่กำหนดเอง ให้เรียก view.outlineProvider = ViewOutlineProvider.BACKGROUND
CALayer ไม่รองรับเงาด้านในโดยตรง ใช้การรวมกัน: CAGradientLayer ด้วยสีดำและความโปร่งใสภายในมาสก์ หรือวาง UIImageView ทับด้วยเงาด้านในแบบแรสเตอร์ ใน SwiftUI เงาด้านในถูกดำเนินการผ่าน overlay ด้วย LinearGradient และ blendMode(.multiply)
ใน Android ใช้ translationZ สำหรับแอนิเมชัน: ในสภาวะหยุดนิ่ง elevation = 2 dp, เมื่อกด translationZ = 4 dp (ความสูงรวม 6 dp) บน iOS ให้ทำแอนิเมชัน shadowOffset และ shadowRadius พร้อมกันกับการแปลงปุ่ม ใน SwiftUI ใช้ withAnimation ด้วยการเปลี่ยน elevation ผ่าน Modifier.shadow() ใน Flutter ใช้ AnimatedContainer ด้วยการเปลี่ยน BoxShadow
Material Design แนะนำไม่เกิน สามระดับ elevation บนหน้าจอเดียว หากมีองค์ประกอบที่มีเงามากกว่านั้น ให้จัดกลุ่มด้วยความสูงเดียวกัน จำนวนเงาที่มากเกินไปสร้างสัญญาณรบกวนทางภาพและลดประสิทธิภาพการเรนเดอร์บนอุปกรณ์ระดับกลางและประหยัด
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ