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 گوگل، سایه‌ها به طور جدایی‌ناپذیری با مفهوم elevation (ارتفاع) مرتبط هستند. هر عنصر ارتفاع مشخصی در مختصات Z دارد و سایه برآمدگی نور از بالا است. Human Interface Guidelines اپل یک سیستم ارتفاع سختگیرانه را الزام نمی‌کند، اما استفاده از سایه‌ها برای ایجاد سلسله‌مراتب بصری و عمق در رابط‌ها را توصیه می‌کند.

بر اساس تحقیق Nielsen Norman Group (2024)، سایه‌های درست قرارگرفته زمان جستجوی یک عنصر تعاملی را ۲۴٪ کاهش می‌دهند. سایه‌ها همچنین به کاربران دارای اختلالات بینایی در تشخیص مرزهای عناصر کمک می‌کنند. با این حال، استفاده بیش از حد از سایه‌ها (۳+ سطح در یک صفحه) بار شناختی را افزایش داده و درک را کاهش می‌دهد.

سایه‌ها در iOS: CALayer و SwiftUI

در iOS سایه‌ها از طریق ویژگی‌های CALayer پیاده‌سازی می‌شوند: shadowColor رنگ سایه را تعیین می‌کند، shadowOffset — جابه‌جایی در CGSize (x, y)، shadowRadius — شعاع محو شدن، shadowOpacity — کدورت از ۰ تا ۱. برای کارایی توصیه می‌شود همچنین 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>

برای سایه‌های سفارشی در Android View System از 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  // ارتفاع سایه بر حسب پیکسل
myView.translationZ = 4f  // ارتفاع اضافی هنگام کلیک

در Jetpack Compose سایه‌ها از طریق Modifier.shadow() و elevation در کامپوننت‌های Card و Surface مدیریت می‌شوند. Modifier.shadow() elevation و shape را می‌پذیرد. برخلاف View System، 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 با سایه‌های چندگانه می‌تواند در هر تغییر باعث بازرسم (repaint) شود. برای عناصر ایستا از PhysicalModel استفاده کنید — آن سایه را در یک لایه جداگانه کش می‌کند (RepaintBoundary). در Flutter 3.16+ پشتیبانی از Material 3 elevation با سایه‌های تطبیقی که رنگ و محو شدگی را بسته به تم (روشن/تاریک) تغییر می‌دهند اضافه شده است.

سوالات متداول

Elevation چه تفاوتی با سایه دارد؟

Elevation — ارتفاع عددی عنصر در مختصات Z است، در حالی که shadow (سایه) — اثر بصری ایجاد شده توسط این ارتفاع است. در Android elevation — ویژگی View است که به طور خودکار سایه تولید می‌کند. در طراحی، elevation و shadow اغلب به عنوان اصطلاحات قابل‌جایگزین استفاده می‌شوند، اما از نظر فنی elevation علت و shadow معلول است.

چرا سایه در Android نمایش داده نمی‌شود؟

بررسی کنید: (۱) آیا View دارای elevation بیشتر از ۰ است، (۲) آیا outlineProvider تنظیم شده است (اگر View از کتابخانه Material نیست)، (۳) آیا شتاب‌دهی سخت‌افزاری فعال است (android:hardwareAccelerated="true" در مانیفست). CardView به طور خودکار outlineProvider را مدیریت می‌کند. برای Viewهای سفارشی view.outlineProvider = ViewOutlineProvider.BACKGROUND را فراخوانی کنید.

چگونه سایه داخلی (inner shadow) در iOS ایجاد کنیم؟

CALayer به طور مستقیم از سایه‌های داخلی پشتیبانی نمی‌کند. از ترکیب استفاده کنید: CAGradientLayer با رنگ سیاه و شفافیت داخل ماسک (mask)، یا یک UIImageView با سایه داخلی raster قرار دهید. در 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 از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید