انیمیشنهای اسکرول با CSS خالص؛ خداحافظ IntersectionObserver
نویسنده: آرش لطیفی
با scroll-driven animations و scroll-triggered (پیشنهاد Interop 2027) میشود انیمیشن ورود المنتها را بدون یک خط جاوااسکریپت ساخت. پشتیبانی مرورگرها و کد.
TL;DR — انیمیشنهای scroll-driven از Interop 2026 حالا در همه مرورگرهای مدرن پشتیبانی میشوند، و scroll-triggered animations (ترگبر لحظهای) هم کاندیدای Interop 2027 است؛ یعنی بخش بزرگی از جاوااسکریپت IntersectionObserver شما قابل حذف است.
دو مفهوم متفاوت که همیشه قاطی میشوند
جاش کومو (Josh W. Comeau) در پست «The Future of CSS» (اکتبر ۲۰۲۶) این تمایز را خوب گفته:
- Scroll-driven animations: انیمیشن با اسکرول شما «اسکراب» میشود — مثل fade شدن یک المنت هنگام حرکت در viewport. این بخشی از Interop 2026 بود و طبق همین پست حالا در همه مرورگرها پیاده شده است.
- Scroll-triggered animations: وقتی کاربر به یک نقطه مشخص رسید، انیمیشن «یک بار» با مدتزمان خودش اجرا میشود — مثل سنسور مجاورت. این هنوز پیشنهاد (proposal) برای Interop 2027 است.
روش فعلی برای نوع دوم، IntersectionObserver در جاوااسکریپت است — که جاش هم میگوید «یه دردسره». طبق MDN، روشهای قدیمیتر (چه IO چه getBoundingClientRect در حلقه) روی main thread اجرا میشوند و با تعداد زیاد المان، اسکرول را سنگین میکنند؛ مزیت اصلی IO این بود که callback را خارج از مسیر اسکرول اجرا میکند. حالا CSS حتی آن را هم لازم نمیکند.
انیمیشن scroll-driven: پایه کار
سه چیز لازم دارید: یک @keyframes، یک animation-timeline: view() و یک animation-range. مثلاً fade-in کارت محصول هنگام ورود به دید:
اینجا view() یعنی تایملاین بر اساس موقعیت خود .card نسبت به viewport است و animation-range: entry 0% entry 60% میگوید انیمیشن از لحظه ورود شروع و تا ۶۰٪ ورود کامل شود. کلمه both هم جلوی پرش اولیه را میگیرد.
برای نوار پیشرفت که با کل صفحه اسکرول میشود، به جای view() از scroll() استفاده کنید:
نکته مهم: در حالت scroll-driven، duration معنایی ندارد — سرعت انیمیشن را سرعت اسکرول کاربر تعیین میکند. به همین دلیل linear تقریباً همیشه انتخاب درست است.
Scroll-triggered: الان چه کار کنیم؟
تا وقتی proposal در همه مرورگرها برسد، برای «یکبار اجرا شدن» میشود با animation-range و fill mode تقلب کرد — انیمیشن را خیلی کوتاه تعریف میکنیم و اجازه نمیدهیم بعد از رد شدن از محدوده برگردد:
چون animation-timeline تایم واقعی را حذف میکند، رفتار دقیقاً مثل IO نمیشود؛ اگر انیمیشن باید «بعد از رسیدن، با زمان خودش» اجرا شود، فعلاً همان IntersectionObserver (یا CSS animation-trigger که در پیشنهاد ۲۰۲۷ است) پاسخ میدهد. ویژگی جدید هنوز experimental است — در staging تست کنید نه روی production.
جدول: کدام تکنیک برای کدام حالت؟
| حالت | راهحل درست | وضعیت پشتیبانی (اکتبر ۲۰۲۶) |
|---|---|---|
| افکتهای اسکرابشونده (پارالاکس، نوار پیشرفت) | animation-timeline: scroll()/view() | همه مرورگرهای مدرن (Interop 2026) |
| توقف/شروع لحظهای انیمیشن هنگام رسیدن | scroll-triggered / animation-trigger | هنوز proposal، کاندیدای Interop 2027 |
| لود تنبل تصاویر | loading="lazy" + IO fallback | بومی |
| اسلایدرهای «عنوان ظاهر شو بعد بپر» | فعلاً IO در JS | تا رسیدن trigger رسمی |
نکات عملی
- همیشه
bothرا درanimationبگذارید تا المنت قبل از شروع تایملاین نامرئی/جابجا نباشد. - حتماً fallback فکر کنید: در مرورگرهای قدیمی، انیمیشن یا اجرا نمیشود یا state اولیه میماند؛ با
@supports (animation-timeline: view())خروجی پیشفرض قابل قبول بگذارید. view()برای المانهای داخل containerهای اسکرولشونده هم کار میکند — کافیستscroll-scopeرا درست تنظیم کنید.- اگر از فریمورک animation (مثل GSAP یا Framer Motion) فقط برای reveal ساده استفاده میکنید، شاید کل بسته قابل حذف باشد — برای فروشگاهها یعنی باندل کوچکتر و LCP بهتر.
چکلیست مهاجرت از IntersectionObserver به CSS
- [ ] هر IO در کد را دستهبندی کردهاید: scrub لازم است یا trigger یکبار؟
- [ ] موارد scrub به
animation-timeline: view()/scroll()منتقل شده - [ ] موارد trigger فعلاً با IO ماندهاند (یا
animation-triggerپشت فلگ تست شده) - [ ]
@supportsبرای مرورگرهای بدون پشتیبانی گذاشتهاید - [ ] انیمیشنها با
prefers-reduced-motionغیرفعال میشوند - [ ] در Chrome، Safari و Firefox واقعاً تست کردهاید
FAQ
Q: scroll-driven در همه مرورگرها کار میکند؟
طبق پست جاش کومو، بخشی از Interop 2026 بوده و با موفقیت در همه مرورگرها پیاده شده. برای نسخههای قدیمیتر باز هم @supports و fallback داشته باشید.
Q: scroll-triggered را الان میشود استفاده کرد؟
به عنوان proposal در Chromium این نسخهها در دسترس است اما هنوز استاندارد نشده و کاندیدای Interop 2027 است — پشت فلگ تست کنید و روی production متکی نباشید. جایگزین فعلی همان IntersectionObserver است.
Q: حذف IO واقعاً پرفورمنس را بهتر میکند؟
IO خودش نسبت به روشهای قدیمی (getBoundingClientRect در scroll handler) سبک بود و خارج از main thread کار میکند. سود اصلی حذف IO، کمشدن جاوااسکریپت و سادگی کد است، نه یک جهش در FPS.