آموزش کامل سلکتور :has() در CSS | والد بر اساس فرزند + ۲۰ مثال جادویی
خلاصه مطلب
سلکتور :has() یکی از قدرتمندترین افزودههای CSS مدرن است که به شما اجازه میدهد یک المان را بر اساس محتوای داخلیاش انتخاب کنید. در این مقاله، از مفاهیم پایه تا تکنیکهای پیشرفته مانند فرمهای هوشمند، گالری فیلتر شونده، کارتهای شرطی و منوهای تعاملی را بدون یک خط جاوااسکریپت یاد میگیرید.
مقدمه: سلکتور :has() چیست و چرا انقلابی است؟
برای سالها، CSS یک محدودیت بزرگ داشت: نمیتوانستید یک المان والد را بر اساس فرزندانش انتخاب کنید. مثلاً نمیتوانستید بگویید "اگر این div داخلش تصویر دارد، پسزمینه آن را عوض کن". این کار فقط با جاوااسکریپت ممکن بود.
سلکتور :has() این محدودیت را برای همیشه از بین برده است.
مشکلات قبل از :has():
-
نمیتوانستید فرمی را که ورودی نامعتبر دارد، هایلایت کنید
-
نمیتوانستید کارتی را که تصویر ندارد، متفاوت نشان دهید
-
نمیتوانستید منویی را که آیتم فعال دارد، استایل خاص بدهید
حالا با :has() میتوانید:
-
والد را بر اساس فرزند انتخاب کنید
-
فرمهای هوشمند بسازید (بدون JS)
-
گالری فیلتر شونده طراحی کنید
-
کارتهای شرطی بسازید
-
و خیلی چیزهای دیگر...
نحو (Syntax) سلکتور :has()
| نوع | نحو | مثال | توضیح |
|---|---|---|---|
| پایه | parent:has(child) | div:has(img) | divهایی که داخلشان img دارند |
| ترکیبی | parent:has(.class) | div:has(.active) | divهایی که داخلشان کلاس active دارند |
| با فرزند مستقیم | parent:has(> child) | div:has(> p) | divهایی که فرزند مستقیم p دارند |
| چند شرط | parent:has(A, B) | div:has(img, .video) | divهایی که img یا video دارند |
| با not | parent:has(:not(...)) | div:has(:not(.empty)) | divهایی که فرزند غیر خالی دارند |
| تو در تو | parent:has(child:has(...)) | div:has(span:has(img)) | divهایی که span دارند که آن span img دارد |
۲۰+ مثال کاربردی سلکتور :has()
مثال ۱: سادهترین حالت – استایل والد بر اساس وجود تصویر
اگر کارتی داخلش تصویر داشت، حاشیه طلایی بگیرد.
.card:has(img) { border: 3px solid gold; box-shadow: 0 5px 20px rgba(0,0,0,0.2); } .card:not(:has(img)) { border: 1px solid #ddd; opacity: 0.7; }
<div class="card"> <img src="photo.jpg"> <p>این کارت تصویر دارد → حاشیه طلایی</p> </div> <div class="card"> <p>این کارت تصویر ندارد → حاشیه ساده</p> </div>
مثال ۲: فرم هوشمند – هایلایت کردن فرم با ورودی نامعتبر
بدون جاوااسکریپت، فرمی که ورودی نامعتبر دارد را مشخص کنید.
.form-group:has(input:invalid) { border-left: 4px solid #e74c3c; background-color: #ffe6e6; padding-left: 10px; } .form-group:has(input:valid) { border-left: 4px solid #2ecc71; } input:required:invalid { border-color: #e74c3c; } input:required:valid { border-color: #2ecc71; }
<div class="form-group"> <label>ایمیل (اجباری)</label> <input type="email" required> <!-- اگر ایمیل نامعتبر باشد، این div هایلایت میشود --> </div>
مثال ۳: هایلایت کردن کل فرم وقتی هر ورودی خطا دارد
اگر هر جای فرم ورودی نامعتبر وجود داشت، کل فرم را استایل کن.
form:has(input:invalid, textarea:invalid) { background-color: #fff0f0; border: 2px solid #e74c3c; padding: 20px; border-radius: 12px; } form:has(input:valid, textarea:valid) { background-color: #f0fff0; border: 2px solid #2ecc71; }
مثال ۴: نمایش پیام خطا فقط وقتی ورودی نامعتبر است
پیام خطا را به صورت پیشفرض مخفی کن و فقط در صورت نامعتبر بودن نشان بده.
.error-message { display: none; color: #e74c3c; font-size: 12px; margin-top: 5px; } .input-group:has(input:invalid) .error-message { display: block; } .input-group:has(input:valid) .error-message { display: none; }
<div class="input-group"> <input type="email" required> <div class="error-message">لطفا ایمیل معتبر وارد کنید</div> </div>
مثال ۵: کارت محصول با تخفیف – هایلایت خودکار
اگر کارتی داخلش برچسب تخفیف داشت، استایل ویژه بگیرد.
.product-card:has(.discount-badge) { background: linear-gradient(135deg, #fff5e6, #ffe6cc); border: 2px solid #f39c12; transform: scale(1.02); } .discount-badge { background: #e74c3c; color: white; padding: 5px 10px; border-radius: 20px; font-size: 12px; }
مثال ۶: گالری فیلتر شونده – بدون جاوااسکریپت
با کلیک روی دکمهها (از طریق رادیو باتن) تصاویر فیلتر میشوند.
/* پنهان کردن رادیو باتنها */ .filter-input { display: none; } /* نمایش همه تصاویر به صورت پیشفرض */ .gallery-item { display: block; } /* فیلتر بر اساس مقدار رادیو باتن */ #filter-all:checked ~ .gallery:has(.gallery-item) { /* همه را نشان بده */ } #filter-cat:checked ~ .gallery .gallery-item:not(:has(img[data-category="cat"])) { display: none; } #filter-dog:checked ~ .gallery .gallery-item:not(:has(img[data-category="dog"])) { display: none; }
<input type="radio" name="filter" id="filter-all" checked> <label for="filter-all">همه</label> <input type="radio" name="filter" id="filter-cat"> <label for="filter-cat">گربه</label> <input type="radio" name="filter" id="filter-dog"> <label for="filter-dog">سگ</label> <div class="gallery"> <div class="gallery-item"><img data-category="cat" src="cat1.jpg"></div> <div class="gallery-item"><img data-category="dog" src="dog1.jpg"></div> </div>
مثال ۷: تغییر استایل منو بر اساس آیتم فعال
اگر منویی داخلش لینک active داشت، استایل متفاوت بگیرد.
.nav-menu:has(a.active) { background-color: #3498db; border-radius: 10px; } .nav-menu a.active { color: white; font-weight: bold; } .nav-menu:has(a.active)::before { content: "⭐ فعال "; color: gold; }
مثال ۸: استایل کردن سطر جدول بر اساس دادههای خاص
اگر یک سطر جدول مقدار خاصی داشت، هایلایت شود.
tr:has(td.status-paid) { background-color: #d4edda; color: #155724; } tr:has(td.status-pending) { background-color: #fff3cd; color: #856404; } tr:has(td.status-canceled) { background-color: #f8d7da; color: #721c24; }
مثال ۹: تغییر استایل والد بر اساس تعداد فرزندان
اگر یک لیست بیش از ۵ آیتم داشت، اسکرول بگیرد.
.list-container:has(li:nth-child(6)) { max-height: 200px; overflow-y: auto; border: 1px solid #ddd; }
مثال ۱۰: هایلایت کردن کامنتهای دارای لینک
اگر یک کامنت داخلش لینک داشت، استایل ویژه بگیرد.
.comment:has(a) { background-color: #e8f4f8; border-left: 4px solid #3498db; } .comment:has(a[href^="https://spam.com"]) { background-color: #ffcccc; display: none; /* مخفی کردن کامنت اسپم */ }
مثال ۱۱: فرم ثبتنام هوشمند – فعال کردن دکمه فقط وقتی همه فیلدها پر هستند
.form-container:has(input:placeholder-shown) button[type="submit"] { opacity: 0.5; pointer-events: none; } .form-container:not(:has(input:placeholder-shown)) button[type="submit"] { opacity: 1; pointer-events: auto; }
مثال ۱۲: انتخاب مقالههای دارای تصویر شاخص
در صفحه بلاگ، مقالههای دارای تصویر را متفاوت نشان بده.
.blog-post:has(.featured-image) { grid-column: span 2; background: linear-gradient(135deg, #667eea, #764ba2); color: white; } .blog-post:not(:has(.featured-image)) { background: #f8f9fa; }
مثال ۱۳: تغییر استایل سایدبار بر اساس محتوا
اگر سایدبار ویجت خاصی داشت، عرض آن را تغییر بده.
.sidebar:has(.weather-widget) { width: 300px; } .sidebar:has(.ad-banner) { background-color: #fff9e6; }
مثال ۱۴: استایل کردن بخش نظرات دارای پاسخ ادمین
.comment-thread:has(.admin-reply) { background-color: #f0f7ff; border: 2px solid #3498db; border-radius: 12px; padding: 15px; } .admin-reply { background-color: #e8f4f8; margin-left: 30px; padding: 10px; border-radius: 8px; }
مثال ۱۵: انتخاب کارتهایی که دکمه خرید دارند
.product-card:has(.buy-button) { box-shadow: 0 10px 30px rgba(0,0,0,0.15); } .product-card:not(:has(.buy-button)) { opacity: 0.5; filter: grayscale(0.5); }
مثال ۱۶: فرم جستجوی پیشرفته – نمایش نتایج بر اساس تایپ
.search-container:has(input[value=""]) .results { display: none; } .search-container:has(input[value]:not([value=""])) .results { display: block; }
مثال ۱۷: استایل کردن هدر بر اساس وضعیت کاربر (لاگین/لاگ اوت)
.header:has(.user-avatar) { background-color: #2c3e50; } .header:not(:has(.user-avatar)) { background-color: #e74c3c; } .header:has(.user-avatar) .login-btn { display: none; } .header:has(.user-avatar) .user-menu { display: block; }
مثال ۱۸: انتخاب المانهای دارای فرزند خاص با nth-child
/* انتخاب divهایی که حداقل 3 تا p دارند */ div:has(p:nth-child(3)) { background-color: #ffe6cc; } /* انتخاب divهایی که دقیقاً 1 تا p دارند */ div:has(p:only-child) { border: 2px dashed #3498db; }
مثال ۱۹: سلکتور :has() تو در تو (Nested)
/* divهایی که داخلشان span است و آن span داخلش img دارد */ .container:has(span:has(img)) { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; }
مثال ۲۰: انتخاب بر اساس وجود چند شرط همزمان
/* کارتی که هم تصویر دارد و هم قیمت تخفیف خورده */ .product-card:has(img, .discounted-price) { border: 3px solid #f39c12; transform: scale(1.03); transition: all 0.3s; } /* فرمی که هم ورودی ایمیل دارد و هم ورودی رمز عبور */ form:has(input[type="email"], input[type="password"]) { max-width: 400px; margin: 0 auto; }
مثال ۲۱: انتخاب والد بر اساس فرزند با کلاس خاص (بسیار کاربردی)
/* هر section که داخلش یک div با کلاس error وجود دارد */ section:has(.error) { border: 2px solid #e74c3c; background-color: #fff0f0; } /* هر article که داخلش یک h2 وجود دارد */ article:has(h2) { margin-top: 40px; }
مثال ۲۲: ترکیب :has() با :not() برای شرط منفی
/* divهایی که داخلشان img دارند اما img خالی نیست */ .card:has(img:not([src=""])) { opacity: 1; } /* ورودیهایی که placeholder ندارند */ .input-group:has(input:not([placeholder])) { border: 2px solid #f39c12; }
پروژه عملی: ساخت داشبورد مدیریت تسک با :has()
/* پروژه کامل: داشبورد مدیریت تسک بدون جاوااسکریپت */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: system-ui, sans-serif; background: #f0f2f5; padding: 20px; } /* کانتینر اصلی */ .dashboard { max-width: 1200px; margin: 0 auto; } /* استایل تسکها */ .task { background: white; border-radius: 12px; padding: 16px; margin-bottom: 12px; transition: all 0.3s; border: 1px solid #e0e0e0; } /* تسکهای اولویت بالا */ .task:has(.priority-high) { border-left: 5px solid #e74c3c; background: #fff5f5; } /* تسکهای اولویت متوسط */ .task:has(.priority-medium) { border-left: 5px solid #f39c12; } /* تسکهای اولویت پایین */ .task:has(.priority-low) { border-left: 5px solid #2ecc71; } /* تسکهای کامل شده */ .task:has(.status-completed) { opacity: 0.6; text-decoration: line-through; background: #e8e8e8; } /* تسکهای دارای ددلاین نزدیک */ .task:has(.deadline-soon) { background: #fff0e6; animation: pulse 1s infinite; } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.01); } } /* تسکهای دارای تگ مهم */ .task:has(.tag-important) { box-shadow: 0 5px 15px rgba(231, 76, 60, 0.2); } /* فرم اضافه کردن تسک – هوشمند */ .task-form:has(input:placeholder-shown) .submit-btn { opacity: 0.5; cursor: not-allowed; } .task-form:not(:has(input:placeholder-shown)) .submit-btn { opacity: 1; cursor: pointer; background: #3498db; } /* فیلتر تسکها با رادیو باتن */ .filter-input { display: none; } .filter-label { padding: 8px 16px; background: #ddd; border-radius: 20px; cursor: pointer; margin: 0 5px; } #filter-all:checked ~ .task-list .task { display: block; } #filter-high:checked ~ .task-list .task:not(:has(.priority-high)) { display: none; } #filter-completed:checked ~ .task-list .task:not(:has(.status-completed)) { display: none; } #filter-important:checked ~ .task-list .task:not(:has(.tag-important)) { display: none; } /* هایلایت کردن دستهبندی فعال */ .filter-container:has(#filter-all:checked) .filter-label[for="filter-all"] { background: #3498db; color: white; } .filter-container:has(#filter-high:checked) .filter-label[for="filter-high"] { background: #e74c3c; color: white; }
<div class="dashboard"> <h1>📋 مدیریت تسکها</h1> <div class="filter-container"> <input type="radio" name="filter" id="filter-all" class="filter-input" checked> <label for="filter-all" class="filter-label">همه</label> <input type="radio" name="filter" id="filter-high" class="filter-input"> <label for="filter-high" class="filter-label">اولویت بالا</label> <input type="radio" name="filter" id="filter-completed" class="filter-input"> <label for="filter-completed" class="filter-label">کامل شده</label> <input type="radio" name="filter" id="filter-important" class="filter-input"> <label for="filter-important" class="filter-label">مهم</label> </div> <div class="task-list"> <div class="task"> <div class="priority-high">اولویت: بالا</div> <div class="tag-important">⭐ مهم</div> <h3>پروژه CSS را تمام کن</h3> <div class="deadline-soon">ددلاین: فردا</div> </div> <div class="task"> <div class="priority-medium">اولویت: متوسط</div> <h3>بررسی ایمیلها</h3> </div> <div class="task"> <div class="priority-low">اولویت: پایین</div> <div class="status-completed">✓ کامل شده</div> <h3>خرید کتاب</h3> </div> </div> <div class="task-form"> <input type="text" placeholder="تسک جدید..."> <button class="submit-btn">➕ اضافه کن</button> </div> </div>
جدول مرجع سریع سلکتور :has()
| نیاز | کد | توضیح |
|---|---|---|
| انتخاب والد دارای فرزند خاص | parent:has(child) | پایه |
| انتخاب والد دارای کلاس خاص در فرزند | div:has(.active) | کاربردیترین |
| انتخاب والد با فرزند مستقیم | div:has(> p) | فقط فرزند سطح اول |
| انتخاب والد بدون فرزند خاص | div:not(:has(img)) | شرط منفی |
| انتخاب والد با چند شرط | div:has(img, .video) | OR منطقی |
| انتخاب والد با شرط تو در تو | div:has(span:has(img)) | nested |
| فرم با ورودی نامعتبر | form:has(input:invalid) | هوشمندسازی فرم |
| کارت دارای تخفیف | .card:has(.discount) | تجارت الکترونیک |
11. ویژگیها و بخشهای مختلف
-
✅ ۲۲ مثال کاربردی از ساده تا پیشرفته
-
✅ پروژه عملی کامل (داشبورد مدیریت تسک)
-
✅ فرمهای هوشمند بدون جاوااسکریپت
-
✅ گالری فیلتر شونده با رادیو باتن
-
✅ جدول مرجع سریع برای تمام حالات
-
✅ ترکیب با :not() و سایر سلکتورها
-
✅ سلکتور تو در تو (nested)
-
✅ کدهای کاملاً قابل کپی و اجرا
۱۴. بخش تمرین عملی (چالش نهایی)
💡 چالش نهایی (پروژه محور):
با استفاده از سلکتور :has() یک سیستم کامنتگذاری هوشمند بسازید که شامل:
فرم ثبت کامنت که دکمه ارسال فقط وقتی هر دو فیلد (نام و نظر) پر شدهاند فعال شود
هایلایت کردن کامنتهای دارای لینک (با رنگ متفاوت و آیکون)
نمایش پاسخ ادمین به صورت توکار و تغییر استایل کامنت والد
فیلتر کردن کامنتها با رادیو باتن (همه / فقط دارای لینک / فقط دارای پاسخ ادمین / فقط جدید)
کامنت اسپم (کامنتهای دارای لینکهای خاص) به صورت خودکار مخفی شوند
کدهای خود را در بخش نظرات به اشتراک بگذارید. بهترین پروژهها را در مقاله بعدی معرفی میکنیم.
کلمات کلیدی:
سلکتور has، CSS has selector، والد بر اساس فرزند، فرم هوشمند CSS، فیلتر گالری با CSS، parent selector CSS، CSS پیشرفته
نظرات (0)