Shadow: แก่นแท้ การสร้างเงาในแอปพลิเคชันมือถือ

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-02-28 เวลาอ่าน: 9 นาที

Shadow (เงา) — เอฟเฟกต์ภาพในอินเทอร์เฟซมือถือที่สร้างภาพลวงตาของความลึกและลำดับชั้นระหว่างองค์ประกอบ เงาแสดงให้เห็นว่าองค์ประกอบใดอยู่สูงกว่าในระนาบ z และด้วยเหตุนี้จึงมีความสำคัญหรือโต้ตอบได้มากกว่า ตาม Material Design Guidelines (Google, 2026) เงาของระบบใช้ความสูงหลักห้าระดับ (elevation): 0 dp (เรียบ), 1–4 dp (ปุ่ม), 6–12 dp (การ์ด), 16–24 dp (ไดอะล็อก), 24+ dp (หน้าต่างโมดอล) อ่านเพิ่มเติมเกี่ยวกับความสูงขององค์ประกอบได้ในบทความเกี่ยวกับ Elevation

ประเด็นสำคัญ

  • เงา (shadow) — เอฟเฟกต์ภาพที่สร้างความลึกโดยการทำให้พื้นที่ใต้องค์ประกอบมืดลงพร้อมการเบลอขอบ
  • บน iOS เงาจะถูกตั้งค่าผ่านคุณสมบัติ CALayer: shadowColor, shadowOffset, shadowRadius, shadowOpacity
  • ใน Android เงาจะถูกจัดการผ่าน elevation (Material Design) หรือการเรนเดอร์แบบกำหนดเองผ่าน OutlineProvider
  • Jetpack Compose รองรับเงาผ่าน Modifier.shadow() และ elevation ใน Card/Surface
  • ใน Flutter เงาจะถูกดำเนินการผ่าน BoxShadow ใน BoxDecoration หรือผ่าน PhysicalModel

เงาใน UI คืออะไร?

เงา (shadow) ในอินเทอร์เฟซผู้ใช้คือเอฟเฟกต์ภาพที่จำลองแสงและความลึก เงาถูกสร้างขึ้นโดยการวาดสำเนาที่มืดและเบลอขององค์ประกอบที่ขยับจากต้นฉบับ ในการออกแบบมือถือ เงาทำหน้าที่สามอย่าง: แสดงลำดับชั้น (องค์ประกอบที่สูงกว่า = เงาที่ยาวกว่า), ส่งสัญญาณการโต้ตอบ (ปุ่มที่มีเงาดูเหมือนกดได้), แยกเนื้อหาออกจากพื้นหลัง (การ์ดที่มีเงาอ่านเป็นบล็อกแยกต่างหาก)

ใน Material Design ของ Google เงาเชื่อมโยงอย่างแยกไม่ออกกับแนวคิดของ elevation แต่ละองค์ประกอบครอบครองความสูงที่แน่นอนในพิกัด z และเงาคือการฉายของแสงจากด้านบน Human Interface Guidelines ของ Apple ไม่ได้กำหนดระบบความสูงที่เข้มงวด แต่แนะนำให้ใช้เงาเพื่อสร้างลำดับชั้นภาพและความลึกในอินเทอร์เฟซ

จากการศึกษาของ Nielsen Norman Group (2024) เงาที่วางอย่างถูกต้องช่วยลดเวลาในการค้นหาองค์ประกอบที่โต้ตอบได้ 24% เงายังช่วยให้ผู้ใช้ที่มีความบกพร่องทางการมองเห็นแยกแยะขอบเขตขององค์ประกอบได้ อย่างไรก็ตาม การใช้เงามากเกินไป (3+ ระดับบนหน้าจอ) จะเพิ่มภาระทางความคิดและทำให้การรับรู้แย่ลง

เงาบน iOS: CALayer และ SwiftUI

บน iOS เงาจะถูกดำเนินการผ่านคุณสมบัติ CALayer: shadowColor กำหนดสีเงา, shadowOffset — การขยับใน CGSize (x, y), shadowRadius — รัศมีการเบลอ, shadowOpacity — ความทึบจาก 0 ถึง 1 เพื่อประสิทธิภาพ ขอแนะนำให้ตั้งค่า shadowPath — UIBezierPath ที่อธิบายรูปร่างเงา เพื่อให้ Core Animation ไม่คำนวณโดยอัตโนมัติ

swift
// 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

swift
// 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: elevation และเงาที่กำหนดเอง

ใน Android เงาใน Material Design ถูกดำเนินการผ่าน elevation — คุณสมบัติ View ที่กำหนดความสูงขององค์ประกอบในพิกัด z ระบบจะเรนเดอร์เงาโดยอัตโนมัติตาม elevation และ outlineProvider (โครงร่างขององค์ประกอบ) ยิ่ง elevation สูงเท่าไหร่ เงาก็ยิ่งใหญ่และเบลอมากขึ้นเท่านั้น สำหรับ View ที่ไม่สืบทอด Material Design (เช่น AppCompat) เงาจะทำงานเมื่อเปิดใช้งานการเร่งด้วยฮาร์ดแวร์เท่านั้น

xml
<!-- 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
// 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

kotlin
// 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องค์ประกอบลักษณะของเงา
00 dpข้อความ, ไอคอน, พื้นหลังไม่มีเงา, ชั้นเรียบ
11–4 dpปุ่ม, ช่องป้อนข้อมูลเงาเบา, การขยับเล็กน้อย
26–12 dpการ์ด, Snackbarเงาปานกลาง, การเบลอที่สังเกตได้
316–24 dpไดอะล็อก, เมนูเงาลึก, การเบลอมาก
424+ dpหน้าต่างโมดอลเงาสูงสุด, องค์ประกอบลอย

แนวปฏิบัติที่ดีที่สุด: (1) อย่าใช้เงากับองค์ประกอบที่ไม่ควรดึงดูดความสนใจ (พื้นหลัง, ตัวแบ่ง), (2) ความสูงของเงาควรสอดคล้องกับความสำคัญขององค์ประกอบ — ยิ่งสำคัญยิ่งสูง, (3) เมื่อกดปุ่ม ให้ลด elevation ด้วยแอนิเมชันเพื่อการตอบสนองทางสัมผัส, (4) หลีกเลี่ยงเงาบนพื้นหลังสีเข้ม — ใช้ overlay (ทำให้สว่าง/มืด) แทนเงา ใน Material You (Android 12+) เงาจะปรับตามธีม: บนพื้นหลังสีเข้ม elevation จะถูกแทนที่ด้วยชั้นสี (surface tint)

เงาใน Flutter: BoxShadow และ PhysicalModel

ใน Flutter เงาจะถูกดำเนินการผ่าน BoxShadow ภายใน BoxDecoration BoxShadow รับ color, offset, blurRadius และ spreadRadius Flutter รองรับเงาหลายชั้น: การส่งรายการ BoxShadow ช่วยให้สร้างเอฟเฟกต์ที่ซับซ้อน — ตัวอย่างเช่น เงาภายนอก + การเน้นภายใน PhysicalModel เป็นทางเลือกที่จำลองเงาทางกายภาพตามรูปร่าง (BeveledRectangleBorder, CircleBorder)

dart
// 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 แตกต่างจาก shadow อย่างไร?

Elevation คือความสูงเชิงตัวเลขขององค์ประกอบในพิกัด z ในขณะที่ shadow (เงา) คือเอฟเฟกต์ภาพที่สร้างโดยความสูงนี้ ใน Android elevation คือคุณสมบัติ View ที่สร้างเงาโดยอัตโนมัติ ในการออกแบบ elevation และ shadow มักใช้แทนกันได้ แต่ในทางเทคนิค elevation คือสาเหตุและ shadow คือผลลัพธ์

ทำไมเงาไม่แสดงบน Android?

ตรวจสอบ: (1) View มี elevation มากกว่า 0, (2) ตั้งค่า outlineProvider แล้ว (หาก View ไม่ได้มาจากไลบรารี Material), (3) เปิดใช้งานการเร่งด้วยฮาร์ดแวร์ (android:hardwareAccelerated="true" ในไฟล์ manifest) CardView จัดการ outlineProvider โดยอัตโนมัติ สำหรับ View ที่กำหนดเอง ให้เรียก view.outlineProvider = ViewOutlineProvider.BACKGROUND

วิธีสร้างเงาด้านใน (inner shadow) บน iOS?

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 บนหน้าจอเดียว หากมีองค์ประกอบที่มีเงามากกว่านั้น ให้จัดกลุ่มด้วยความสูงเดียวกัน จำนวนเงาที่มากเกินไปสร้างสัญญาณรบกวนทางภาพและลดประสิทธิภาพการเรนเดอร์บนอุปกรณ์ระดับกลางและประหยัด

สรุป

  • เงา (shadow) คือเอฟเฟกต์ความลึกภาพตาม elevation ขององค์ประกอบในพิกัด z
  • บน iOS เงาจะถูกกำหนดค่าผ่าน CALayer: shadowColor, shadowOffset, shadowRadius, shadowOpacity
  • ใน Android เงาจะถูกจัดการผ่าน elevation และ OutlineProvider สำหรับรูปร่างที่กำหนดเอง
  • Material Design กำหนดระดับ elevation ห้าระดับ: 0, 1–4, 6–12, 16–24 และ 24+ dp
  • Jetpack Compose ใช้ Modifier.shadow() และ elevation ใน Card/Surface สำหรับจัดการเงา
  • Flutter ดำเนินการเงาผ่าน BoxShadow (BoxDecoration) และ PhysicalModel (เงาทางกายภาพ)
  • การเพิ่มประสิทธิภาพ: ตั้งค่า shadowPath (iOS), outlineProvider (Android), ใช้ PhysicalModel (Flutter)

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม