افکت موجی هنگام کلیک با CSS
افکت موجی (ripple effect) یک انیمیشن بصری است که هنگام کلیک یا لمس روی دکمهها و کارتها از نقطه تعامل شروع شده و به اطراف گسترش مییابد. این افکت از طراحی متریال گوگل الهام گرفته شده و تجربه کاربری را جذابتر و پاسخگوتر میکند. در این مقاله به پیادهسازیهای مختلف — تنها با CSS و با کمک JavaScript کمینه —، نکات بهینهسازی، دسترسپذیری و موارد استفاده میپردازیم.
چرا از افکت موجی استفاده کنیم؟
- بازخورد بصری بلافاصله پس از تعامل کاربر را فراهم میکند.
- توجه را به عنصر درگیر جلب کرده و حس پاسخگویی افزایش مییابد.
- به طراحی ظاهری و حرفهای بودن رابط کمک میکند.
محدودیتها و انتخاب تکنیک
افکت موجی که موقع کلیک دقیقا از نقطه تماس آغاز شود معمولاً به کمی JavaScript نیاز دارد تا مختصات کلیک محاسبه و در CSS متغیر شود. اگر طراحی شما نیاز به موجی از مرکز عنصر دارد، میتوان فقط با CSS و pseudo-element آن را ساخت.
مثال 1 — افکت موجی ساده و CSS-only (موج از مرکز)
<button class="ripple-btn">Click me</button>
.ripple-btn{
position: relative;
overflow: hidden;
padding: 12px 20px;
border: none;
background: #1976d2;
color: #fff;
border-radius: 6px;
cursor: pointer;
}
.ripple-btn::after{
content: "";
position: absolute;
left: 50%;
top: 50%;
width: 10px;
height: 10px;
background: rgba(255,255,255,0.4);
border-radius: 50%;
transform: translate(-50%,-50%) scale(1);
opacity: 0;
transition: transform 600ms ease, opacity 600ms ease;
pointer-events: none;
}
.ripple-btn:active::after{
transform: translate(-50%,-50%) scale(60);
opacity: 1;
transition: transform 600ms ease, opacity 600ms ease;
}این کد یک دکمه ایجاد میکند که هنگام فشار (active) یک pseudo-element (بعد از) را بزرگ میکند تا افکت موجی از مرکز پدید آید. مزیت این روش سادگی و نداشتن جاوااسکریپت است، اما نقطه شروع موج همیشه مرکز دکمه خواهد بود و نه مکان دقیق کلیک.
مثال 2 — موجی از نقطه کلیک با CSS متغیر و JavaScript کمینه
<button class="ripple-pt" aria-label="Ripple button">Click me</button>
.ripple-pt{
position: relative;
overflow: hidden;
padding: 12px 20px;
border: none;
background: #009688;
color: white;
border-radius: 6px;
cursor: pointer;
}
.ripple-pt::after{
content: "";
position: absolute;
left: var(--x, 50%);
top: var(--y, 50%);
width: var(--size, 10px);
height: var(--size, 10px);
background: rgba(255,255,255,0.35);
border-radius: 50%;
transform: translate(-50%,-50%) scale(1);
opacity: 0;
pointer-events: none;
transition: transform 600ms ease, opacity 600ms ease;
will-change: transform, opacity;
}
.ripple-pt.ripple-active::after{
transform: translate(-50%,-50%) scale(60);
opacity: 1;
}
/* JavaScript (minimal)
document.querySelectorAll('.ripple-pt').forEach(btn => {
btn.addEventListener('click', function(e){
const rect = this.getBoundingClientRect();
const size = Math.max(rect.width, rect.height) * 0.2;
const x = (e.clientX - rect.left) + 'px';
const y = (e.clientY - rect.top) + 'px';
this.style.setProperty('--x', x);
this.style.setProperty('--y', y);
this.style.setProperty('--size', size + 'px');
this.classList.remove('ripple-active');
// Force reflow to restart animation
void this.offsetWidth;
this.classList.add('ripple-active');
// Remove class after animation
setTimeout(()=> this.classList.remove('ripple-active'), 650);
});
});در این نمونه، با ثبت رویداد click مختصات کلیک نسبت به عنصر محاسبه شده و در CSS variables قرار میگیرد. سپس با افزودن کلاس فعال، pseudo-element از آن نقطه گسترش مییابد. این روش ترکیبی مزیت کنترل دقیق نقطه شروع موج را دارد و همچنان بار پردازشی کمی به همراه دارد.
نسخه بهینهشده و توصیههای عملکردی
/* Optimized CSS */.ripple-pt::after{
will-change: transform, opacity;
transform-origin: center center;
backface-visibility: hidden;
-webkit-transform: translate(-50%,-50%) scale(1);
transition: transform 450ms cubic-bezier(.2,.8,.2,1), opacity 450ms linear;
}در این بهینهسازی از will-change برای اطلاع به مرورگر درباره متغیرهایی که تغییر خواهند کرد استفاده شده تا GPU compositing فعال شود و بازپخش (repaint) کاهش پیدا کند. همچنین با ترکیب easing مناسب و مدت زمان معقول، انیمیشن روانتر و سریعتر اجرا میشود.
دسترسپذیری و رفتار برای کاربران خاص
- از aria-label مناسب برای دکمهها استفاده کنید تا خوانندگان صفحهخوان معنا را منتقل کنند.
- برای کاربرانی که prefers-reduced-motion فعال کردهاند، انیمیشن را کاهش یا غیرفعال کنید:
@media (prefers-reduced-motion: reduce){
.ripple-pt::after{
transition: none;
opacity: 0;
}
}این قطعه تضمین میکند که کاربرانی که حرکت کمتری را ترجیح میدهند، با مشکلی مواجه نشوند.
نکات فنی و بهترین روشها
- از transform: scale و opacity استفاده کنید نه تغییر ابعاد width/height برای کاهش layout thrashing.
- از will-change فقط در عناصر کم و بهطور موقتی استفاده کنید تا مصرف حافظه زیاد نشود.
- برای buttonها اطمینان حاصل کنید که فوکوس و استیتهای کیبورد (Enter/Space) نیز موج را ایجاد کنند تا دسترسپذیری حفظ شود.
- برای عناصر بسیار بزرگ، اندازه موج را بر اساس قطر بزرگترین بعد تعیین کنید تا موج کل سطح را بپوشاند.
نمونهی تکمیلی: فعالسازی از طریق کیبورد
btn.addEventListener('keydown', function(e){
if(e.key === 'Enter' || e.key === ' '){
// simulate click behavior: compute center or use last pointer pos
}
});برای دسترسی کامل، باید موج هنگام فشردن کلید Enter یا Space نیز فعال شود — مخصوصاً برای کاربرانی که از صفحهکلید استفاده میکنند.
مقایسه روشها
| روش | مزیت | معایب |
|---|---|---|
| CSS-only (مرکزی) | ساده، نیاز به JS ندارد | نقطه شروع ثابت؛ کمتر تعاملی |
| CSS + JS (مختصات کلیک) | شروع از نقطه دقیق کلیک، طبیعیتر | یک Event listener اضافه و مقدار کمی پردازش |
موارد استفاده واقعی و توصیههای طراحی
- دکمهها و آیتمهای تعاملی در اپلیکیشنهای موبایل و وب.
- کارتها، لیستها و آیتمهای قابل انتخاب برای برجستهسازی تعامل.
- در پنلهای با تعداد زیاد آیتم، از موجهای سبک و کوتاه استفاده کنید تا منابع مصرفی پایین بماند.
جمعبندی و توصیه نهایی
افکت موجی میتواند تجربه کاربری را بهطور محسوسی بهبود دهد اگر بهینه، دسترسپذیر و بهجا استفاده شود. برای شروع، اگر به نقطه کلیک دقیق نیاز ندارید از نسخه CSS-only استفاده کنید. در صورتی که دقت و طبیعی بودن بیشتر مدنظر است، از ترکیب CSS متغیر همراه با JavaScript کمینه بهره ببرید. همواره به عملکرد، کاهش چشمک و دسترسپذیری توجه کنید.
آیا این مطلب برای شما مفید بود ؟




