قبل از اینکه حرفه خود را توسعه دهم، یکسری کار موشن گرافیک در After Effects انجام دادم. اما حتی با این پس زمینه، من هنوز انیمیشن سازی در وب را بسیار گیج کننده می دانستم.
گرافیک های ویدئویی در یک نسبت خاص طراحی شده و سپس خارج می شوند. انجام شده! اما هیچ "تنظیمات صادرات" در وب وجود ندارد. ما فقط کد را به جهان منتقل می کنیم و انیمیشن های ما باید با هر دستگاهی که روی آن قرار می گیرند سازگار شوند.
پس بیایید با انیمیشن واکنشگرا صحبت کنیم! چگونه به بهترین شکل به انیمیشن سازی در وب وحشی نزدیک شویم؟ ما برخی از رویکردهای کلی، برخی نکات خاص GSAP و برخی از اصول حرکت را پوشش خواهیم داد. بیایید با کادربندی شروع کنیم…
چگونه از این انیمیشن استفاده خواهد شد؟
مقاله Zach Saucier در مورد انیمیشن واکنش گرا توصیه می کند قبل از پرش به کد، یک گام به عقب بردارید تا در مورد نتیجه نهایی فکر کنید.
آیا انیمیشن یک ماژول خواهد بود که در چندین بخش از برنامه شما تکرار شود؟ آیا اصلاً نیاز به مقیاس بندی دارد؟ در نظر گرفتن این موضوع می تواند به تعیین روشی که در آن یک انیمیشن باید مقیاس بندی شود کمک کند و شما را از هدر دادن تلاش جلوگیری کند.
این یک توصیه عالی است. آ بزرگ بخشی از طراحی انیمیشن ریسپانسیو این است که بدانیم آن انیمیشن نیاز به مقیاس بندی دارد یا نه، و سپس انتخاب رویکرد مناسب از همان ابتدا.
اکثر انیمیشن ها در دسته های زیر قرار می گیرند:
- درست شد: انیمیشن هایی برای چیزهایی مانند آیکون ها یا لودرهایی که اندازه و نسبت ابعاد یکسانی را در همه دستگاه ها حفظ می کنند. اینجا جای نگرانی نیست! مقداری از پیکسل ها را در آن کد سخت کنید و به روز خود ادامه دهید.
- سیالات: انیمیشن هایی که نیاز به تطبیق سیال در دستگاه های مختلف دارند. اکثر انیمیشن های طرح بندی در این دسته قرار می گیرند.
- هدفمند: انیمیشنهایی که مختص یک دستگاه یا اندازه صفحهنمایش خاص هستند، یا در یک نقطه شکست بهطور قابل ملاحظهای تغییر میکنند، مانند انیمیشنهای فقط رومیزی یا تعاملاتی که متکی بر تعامل خاص دستگاه هستند، مانند لمس یا شناور کردن.
انیمیشن های روان و هدفمند به روش های مختلف تفکر و راه حل نیاز دارند. بیا یک نگاهی بیندازیم…
انیمیشن سیال
As اندی بل می گوید: مربی مرورگر باشید، نه مدیر میکرو آن - به مرورگر قوانین و نکات محکمی بدهید، سپس اجازه دهید برای افرادی که از آن بازدید می کنند، تصمیم درست بگیرد.
(اینجا هستند اسلایدها از آن ارائه.)
انیمیشن سیال همه چیز در مورد اجازه دادن به مرورگر برای انجام کار سخت است. بسیاری از انیمیشن ها فقط با استفاده از واحدهای مناسب از همان ابتدا می توانند به راحتی با زمینه های مختلف سازگار شوند. اگر اندازه این قلم را تغییر دهید، می توانید ببینید که انیمیشن با استفاده از واحدهای دید با تنظیم مرورگر به صورت روان مقیاس می شود:
جعبه بنفش حتی در نقاط شکست مختلف عرض را تغییر می دهد، اما از آنجایی که از درصد برای جابجایی آن استفاده می کنیم، انیمیشن نیز همراه با آن مقیاس می شود.
متحرک سازی ویژگی های چیدمان مانند left
و top
میتواند باعث ایجاد جریانهای مجدد طرحبندی و انیمیشن «جانکی» پریشان شود، بنابراین در صورت امکان به تبدیلها و کدورتها بچسبید.
با این حال، ما فقط به این واحدها محدود نمی شویم - بیایید به برخی از احتمالات دیگر نگاهی بیندازیم.
واحدهای SVG
یکی از چیزهایی که من در مورد کار با SVG دوست دارم این است که ما می توانیم از واحدهای کاربری SVG برای انیمیشن استفاده کنیم که خارج از جعبه پاسخگو هستند. سرنخ در نام است واقعا - مقیاس پذیر گرافیک برداری. در SVG-land، همه عناصر در مختصات خاصی رسم می شوند. فضای SVG مانند یک قطعه بی نهایت کاغذ گراف است که می توانیم عناصر را مرتب کنیم. را viewBox
ابعاد کاغذ نموداری را که می توانیم ببینیم را مشخص می کند.
viewBox="0 0 100 50”
در این نسخه ی نمایشی بعدی، SVG ما viewBox
is 100
واحدهای گسترده و 50
واحد قد این بدان معناست که اگر عنصر را توسط متحرک سازی کنیم 100
واحدها در امتداد محور x، همیشه به اندازه کل عرض SVG اصلی خود حرکت می کند، مهم نیست که SVG چقدر بزرگ یا کوچک باشد! برای دیدن نسخه نمایشی اندازه آن را تغییر دهید.
متحرک سازی عنصر فرزند بر اساس عرض کانتینر والد در HTML-land یک فریب کوچک است. تا به حال، ما مجبور بودیم عرض والد را با جاوا اسکریپت بگیریم، که در هنگام متحرک سازی به اندازه کافی آسان است. from
موقعیتی دگرگون شده، اما زمانی که در حال انیمیشن سازی هستید کمی دلپذیرتر است to
جایی که در دمو زیر می بینید. اگر نقطه پایانی شما یک موقعیت تبدیل شده است و اندازه صفحه را تغییر می دهید، باید آن موقعیت را به صورت دستی تنظیم کنید. آشفته…🤔
اگر مقادیر تغییر اندازه را تنظیم می کنید، به یاد داشته باشید رد کردن، یا حتی پس از اتمام تغییر اندازه مرورگر، عملکرد را فعال کنید. شنوندگان تغییر اندازه در هر ثانیه تعداد زیادی رویداد را منتشر می کنند، بنابراین به روز رسانی ویژگی های هر رویداد برای مرورگر کار بسیار زیادی است.
اما، این سرعت گیر انیمیشن به زودی به گذشته تبدیل می شود! درام رول لطفا… 🥁
واحدهای کانتینری! چیزهای دوست داشتنی در زمانی که من این را می نویسم، آنها فقط در کروم و سافاری کار می کنند - اما شاید تا زمانی که شما این را بخوانید، ما نیز فایرفاکس داشته باشیم. در این نسخه ی نمایشی بعدی آنها را در عمل بررسی کنید. ببین اون بچه های کوچولو میرن! آیا این انیمیشن هیجان انگیز نیست که نسبت به عناصر اصلی باشد!
این داده های پشتیبانی مرورگر از می توانم استفاده کنم، که دارای جزئیات بیشتری است. یک عدد نشان می دهد که مرورگر از این ویژگی در آن نسخه و بالاتر پشتیبانی می کند.
دسکتاپ
کروم | فایرفاکس | IE | لبه | سیاحت اکتشافی در افریقا |
---|---|---|---|---|
105 | نه | نه | 105 | 16.0 |
موبایل / تبلت
اندروید کروم | اندروید فایرفاکس | آندروید | سافاری iOS |
---|---|---|---|
106 | نه | 106 | 16.0 |
انتقال چیدمان سیال با FLIP
همانطور که قبلاً اشاره کردیم، در SVG-land هر عنصر به طور منظم در یک شبکه قرار می گیرد و واقعاً به راحتی قابل جابجایی است. در سرزمین HTML بسیار پیچیده تر است. برای ایجاد طرحبندیهای واکنشگرا، ما از روشهای مختلف موقعیتیابی و سیستمهای چیدمان استفاده میکنیم. یکی از مشکلات اصلی انیمیشن سازی در وب همین است زیاد تغییر در چیدمان غیرممکن است. شاید یک عنصر نیاز به حرکت از موقعیت داشته باشد relative
به fixed
، یا برخی از فرزندان یک محفظه انعطاف پذیر باید به آرامی در اطراف درگاه دید جابجا شوند. شاید حتی یک عنصر نیاز داشته باشد که مجدداً والد شود و به موقعیت کاملاً جدیدی در DOM منتقل شود.
مشکل است، نه؟
خوب. تکنیک FLIP اینجاست تا روز را نجات دهد. به ما این امکان را می دهد که به راحتی این چیزهای غیرممکن را متحرک کنیم. فرض اصلی این است:
- نام خانوادگی: موقعیت اولیه عناصر درگیر در انتقال را بگیرید.
- نام: عناصر را حرکت دهید و موقعیت نهایی را بگیرید.
- معکوس کردن: تغییرات بین حالت اول و آخر را انجام دهید و برای برگرداندن عناصر به موقعیت اصلی خود، تبدیل ها را اعمال کنید. این باعث می شود به نظر برسد که عناصر هنوز در داخل هستند اول موقعیت اما در واقع اینطور نیستند.
- بازی: تبدیل های معکوس را حذف کرده و به آنها متحرک کنید جعلی اول دولت به آخر دولت است.
در اینجا یک نسخه آزمایشی با استفاده از پلاگین FLIP GSAP است که تمام کارهای سنگین را برای شما انجام می دهد!
اگر می خواهید کمی بیشتر در مورد اجرای وانیلی بدانید، به پل لوئیس سر بزنید پست های وبلاگ - او مغز پشت تکنیک FLIP است.
SVG مقیاس پذیر سیال
تو مرا گرفتی... این نیست واقعا یک نکته انیمیشن اما تنظیم صحیح صحنه برای انیمیشن خوب ضروری است! SVG به طور پیشفرض فوقالعاده خوب مقیاس میشود، اما ما میتوانیم مقیاس آن را کنترل کنیم preserveAspectRatio
، که وقتی نسبت تصویر عنصر SVG و viewBox
نسبت ابعاد متفاوت است. این کار بسیار به همان روشی است background-position
و background-size
خواص در CSS اعلان از یک مقدار تراز تشکیل شده است (background-position
) و یک دیدار or تکه مرجع (background-size
).
در مورد آن مراجع Meet و Slice - slice
مثل این است background size: cover
و meet
مثل این است background-size: contain
.
preserveAspectRatio="MidYMax slice"
- در وسط محور x، پایین محور y تراز کنید و برای پوشش دادن کل نما، مقیاس را افزایش دهید.preserveAspectRatio="MinYMin meet"
- در سمت چپ محور x، بالای محور y تراز کنید و در حالی که کل آن را حفظ کنید، مقیاس را افزایش دهید.viewBox
قابل رویت.
تام میلر با استفاده از این کار یک گام جلوتر می برد overflow: visible
در CSS و یک عنصر حاوی برای نشان دادن "stage left" و "stage right" در حالی که ارتفاع را محدود نگه می دارد:
برای انیمیشنهای SVG واکنشگرا، استفاده از جعبه نمایش SVG برای ایجاد نمایی که در زیر یک پهنای مرورگر مشخص برش داده میشود، مفید است، در حالی که وقتی مرورگر بازتر از آن است، بیشتر انیمیشن SVG را در سمت راست و چپ نشان میدهد. آستانه. ما میتوانیم با اضافه کردن سرریز قابل مشاهده در SVG و ترکیب آن با a به این هدف برسیم
max-height
بسته بندی برای جلوگیری از پوسته پوسته شدن بیش از حد عمودی SVG.
بوم پوسته پوسته شدن سیال
Canvas برای انیمیشن های پیچیده بسیار کارآمدتر است مقدار زیادی قطعات متحرک نسبت به SVG یا HTML DOM متحرک است، اما ذاتاً پیچیدهتر نیز هست. شما باید برای آن دستاوردهای عملکردی کار کنید! برخلاف SVG که دارای واحدهای واکنشگرای دوستداشتنی و مقیاسبندی خارج از جعبه است، باید کمی مدیریت شود و کمی مدیریت شود.
من دوست دارم خودم را تنظیم کنم به طوری که تقریباً به همان روش SVG (من ممکن است تعصب داشته باشم) با یک سیستم واحد دوست داشتنی برای کار در داخل و نسبت تصویر ثابت کار می کند.
همچنین باید هر بار که چیزی تغییر می کند دوباره ترسیم شود، بنابراین به یاد داشته باشید که ترسیم مجدد را تا زمانی که مرورگر تغییر اندازه یا بازگرداندن تمام کند به تاخیر بیاندازید!
جورج فرانسیس این را نیز کنار هم بگذارید کتابخانه کوچک دوست داشتنی که به شما اجازه می دهد تا بوم را تعریف کنید viewBox
صفت و preserveAspectRatio
- دقیقاً مانند SVG!
انیمیشن هدفمند
گاهی اوقات ممکن است نیاز داشته باشید که رویکردی کمتر روان و جهت دارتر به انیمیشن خود داشته باشید. دستگاه های تلفن همراه دارای املاک و مستغلات بسیار کمتری هستند و از نظر عملکرد انیمیشن کمتری نسبت به دستگاه های رومیزی دارند. بنابراین منطقی است که انیمیشن های کاهش یافته را به کاربران تلفن همراه ارائه دهیم، حتی بدون انیمیشن:
گاهی اوقات بهترین انیمیشن ریسپانسیو برای موبایل بدون انیمیشن است! برای UX موبایل، اجازه دادن به کاربر برای مصرف سریع محتوا در مقابل انتظار برای پایان انیمیشن را در اولویت قرار دهید. انیمیشن های موبایلی باید محتوا، ناوبری و تعاملات را به جای تأخیر در آن تقویت کنند. اریک ون هولتز
برای انجام این کار، میتوانیم از پرسوجوهای رسانهای برای هدفیابی اندازههای خاص ویوپورت استفاده کنیم، درست مانند زمانی که در حال استایلسازی با CSS هستیم! در اینجا یک نسخه نمایشی ساده نشان می دهد که یک انیمیشن CSS با استفاده از پرس و جوهای رسانه ای و یک انیمیشن GSAP که با آن مدیریت می شود نشان می دهد. gsap.matchMedia()
:
سادگی این دمو یک سری جادو را پنهان می کند! انیمیشن های جاوا اسکریپت برای اینکه فقط در یک اندازه صفحه نمایش به درستی کار کنند نیاز به تنظیمات و پاکسازی بیشتری دارند. من در گذشته وحشتهایی را دیدهام که در آن افراد فقط با استفاده از CSS انیمیشن را از دید پنهان میکنند opacity: 0
، اما انیمیشن همچنان با استفاده از منابع در پسزمینه پراکنده میشود. 😱
اگر اندازه صفحه دیگر مطابقت نداشت، انیمیشن باید کشته شود و برای جمعآوری زباله منتشر شود، و عناصر متاثر از انیمیشن باید از هر گونه سبکهای درون خطی معرفیشده توسط حرکت پاک شوند تا از تضاد با دیگر استایلها جلوگیری شود. تا gsap.matchMedia()
، این یک پروسه گیج کننده بود. ما باید هر انیمیشن را پیگیری میکردیم و همه اینها را به صورت دستی مدیریت میکردیم.
gsap.matchMedia()
در عوض به شما این امکان را می دهد که کد انیمیشن خود را به راحتی در تابعی قرار دهید که فقط در صورت خاصی اجرا می شود پرس و جو رسانه مسابقات. سپس، زمانی که دیگر مطابقت ندارد، تمام انیمیشن های GSAP و ScrollTriggers در آن تابع به طور خودکار برگردانده می شود. پرس و جو رسانه ای که انیمیشن ها در آن ظاهر می شوند همه کار سخت را برای شما انجام می دهد. این در GSAP 3.11.0 است و یک تغییر دهنده بازی است!
ما فقط محدود به اندازه صفحه نمایش نیستیم. وجود دارد هزاران ویژگی رسانه ای وجود دارد به قلاب!
(prefers-reduced-motion) /* find out if the user would prefer less animation */
(orientation: portrait) /* check the user's device orientation */
(max-resolution: 300dpi) /* check the pixel density of the device */
در نسخه ی نمایشی زیر ما یک چک برای اضافه کرده ایم prefers-reduced-motion
به طوری که کاربرانی که انیمیشن را ناآرام می دانند، از چیزهایی که در اطراف وسوسه می شوند اذیت نشوند.
و دموی سرگرم کننده دیگر تام میلر را ببینید که در آن او از نسبت تصویر دستگاه برای تنظیم انیمیشن استفاده می کند:
فکر کردن خارج از جعبه، فراتر از اندازه صفحه نمایش
فکر کردن به انیمیشنهای واکنشگرا بیشتر از اندازههای صفحه نمایش است. دستگاههای مختلف امکان تعاملهای متفاوتی را فراهم میکنند، و زمانی که این موضوع را در نظر نمیگیرید، به راحتی میتوانید درگیر آن شوید. اگر حالت های شناور را در CSS ایجاد می کنید، می توانید از آن استفاده کنید hover
ویژگی رسانه برای آزمایش اینکه آیا کاربر است یا خیر اصلی مکانیسم ورودی می تواند روی عناصر شناور باشد.
@media (hover: hover) {
/* CSS hover state here */
}
چند توصیه از جیک وایتلی:
اغلب اوقات ما انیمیشنهای خود را بر اساس عرض مرورگر قرار میدهیم و این فرض سادهلوحانه را ایجاد میکنیم که کاربران دسکتاپ حالتهای شناور را میخواهند. من شخصاً در گذشته مشکلات زیادی داشتم که در آن به طرحبندی دسکتاپ > 1024 پیکسل تغییر میدادم، اما ممکن بود تشخیص لمسی را در JS انجام دهم - که منجر به عدم تطابق در جایی که چیدمان برای دسکتاپ بود، اما JS برای تلفنهای همراه بود. این روزها برای اطمینان از یکسانی و کنترل پروفسورهای iPad یا سطوح ویندوز (که میتواند نوع نشانگر را بسته به پایین بودن یا نبودن روکش تغییر دهد) به شناور و اشارهگر تکیه میدهم.
/* any touch device: */
(hover: none) and (pointer: coarse)
/* iPad Pro */
(hover: none) and (pointer: coarse) and (min-width: 1024px)
سپس پرسوجوهای طرحبندی CSS و جستارهای جاوا اسکریپت خود را با هم ترکیب میکنم، بنابراین دستگاه ورودی را به عنوان عامل اصلی در نظر میگیرم. پشتیبانی با عرض، به جای مخالف.
نکات ScrollTrigger
اگر از GSAP استفاده می کنید پلاگین ScrollTrigger، یک ابزار کوچک مفید وجود دارد که می توانید به راحتی قابلیت های لمسی دستگاه را تشخیص دهید: ScrollTrigger.isTouch
.
0
- بدون لمس (فقط اشاره گر/موس)1
- فقط لمسی دستگاه (مثل تلفن)2
- دستگاه می تواند بپذیرد لمس ورودی و ماوس / اشاره گر (مانند تبلت های ویندوزی)
if (ScrollTrigger.isTouch) {
// any touch-capable device...
}
// or get more specific:
if (ScrollTrigger.isTouch === 1) {
// touch-only device
}
نکته دیگری برای انیمیشنهای پیمایشی واکنشگرا…
دمو زیر یک گالری تصاویر را به صورت افقی جابجا می کند، اما بسته به اندازه صفحه نمایش، عرض آن تغییر می کند. اگر در نیمه راه یک انیمیشن پاک شده، اندازه صفحه را تغییر دهید، می توانید با انیمیشن های شکسته و مقادیر قدیمی مواجه شوید. این یک سرعت گیر معمولی است، اما به راحتی قابل حل است! محاسباتی که به اندازه صفحه نمایش بستگی دارد را در یک مقدار کاربردی قرار داده و تنظیم کنید invalidateOnRefresh:true
. به این ترتیب، ScrollTrigger زمانی که مرورگر تغییر اندازه میدهد، آن مقدار را دوباره برای شما محاسبه میکند.
نکته جالب GSAP جایزه!
در دستگاههای تلفن همراه، نوار آدرس مرورگر معمولاً هنگام اسکرول نشان داده و پنهان میشود. این به عنوان یک رویداد تغییر اندازه محسوب می شود و a را خاموش می کند ScrollTrigger.refresh()
. این ممکن است ایده آل نباشد زیرا می تواند باعث جهش در انیمیشن شما شود. GSAP 3.10 اضافه شد ignoreMobileResize
. این بر نحوه رفتار نوار مرورگر تأثیر نمی گذارد، اما مانع می شود ScrollTrigger.refresh()
از شلیک برای تغییر اندازه های عمودی کوچک در دستگاه های فقط لمسی.
ScrollTrigger.config({
ignoreMobileResize: true
});
اصول حرکت
فکر میکنم بهترین روشهایی را که باید هنگام کار با حرکت در وب در نظر بگیرید، به شما واگذار کنم.
دوری و آسایش
یک چیز کوچک اما مهم که به راحتی با انیمیشن واکنشگرا فراموش می شود، رابطه بین سرعت، حرکت و فاصله است! انیمیشن خوبیه باید از دنیای واقعی تقلید کند تا احساس باورپذیری داشته باشید، و در دنیای واقعی برای پیمودن مسافت بیشتری طول می کشد. به مسافتی که انیمیشن خود طی می کند توجه کنید و مطمئن شوید که مدت زمان و سهولت استفاده شده در زمینه انیمیشن های دیگر منطقی است.
همچنین اغلب میتوانید برای نشان دادن شتاب افزایشیافته، تسکین چشمگیرتری را برای عناصر با سفر بیشتر اعمال کنید:
برای موارد استفاده خاص، تنظیم مدت زمان به صورت پویاتر بر اساس عرض صفحه ممکن است مفید باشد. در این نسخه ی نمایشی بعدی ما از آن استفاده می کنیم gsap.utils
برای بستن مقداری که از جریان برمیگردیم window.innerWidth
در یک محدوده معقول، سپس ما آن عدد را با مدت زمان ترسیم می کنیم.
فاصله و کمیت
نکته دیگری که باید در نظر داشت فاصله و تعداد عناصر در اندازه های مختلف صفحه نمایش است. نقل قول استیون شاو:
اگر نوعی انیمیشن محیطی دارید (پارالاکس، ابرها، درختان، کوفتهها، تزئینات و غیره) که در اطراف پنجره قرار گرفتهاند، مطمئن شوید که بسته به اندازه صفحه نمایش، مقیاس و/یا کمیت را تنظیم میکنند. نمایشگرهای بزرگ احتمالاً به عناصر بیشتری نیاز دارند که در سرتاسر آن پخش شده باشند، در حالی که صفحههای کوچک فقط به چند عنصر برای همان جلوه نیاز دارند.
من دوست دارم چگونه اوفر ویشنیا به انیمیشن به عنوان یک صحنه فکر می کند. افزودن و حذف عناصر نه تنها باید رسمی باشد، بلکه می تواند بخشی از طراحی رقص کلی باشد.
هنگام طراحی انیمیشنهای واکنشگرا، چالش این نیست که چگونه محتوای یکسانی را در نما بهگونهای جمع کنیم که «تناسب» داشته باشد، بلکه این است که چگونه مجموعه محتوای موجود را مدیریت کنیم تا همان هدف را بیان کنیم. این بدان معناست که آگاهانه انتخاب کنید که کدام قطعه محتوا اضافه شود و کدام یک حذف شود. معمولاً در دنیای انیمیشن چیزها فقط به داخل یا خارج از کادر نمیآیند. منطقی است که عناصری را به عنوان ورود یا خروج از «مرحله» در نظر بگیریم، و آن گذار را به گونه ای متحرک سازیم که حس بصری و موضوعی داشته باشد.
و این مقدار زیادی است. اگر نکتههای واکنشگرایانه دیگری برای انیمیشن دارید، آنها را در بخش نظرات مطرح کنید. اگر چیز فوق العاده مفیدی وجود داشته باشد، آنها را به این مجموعه اطلاعات اضافه می کنم!
ضمیمه
یک یادداشت دیگر از تام میلر در حالی که داشتم این مقاله را آماده می کردم:
من احتمالاً با این نکته برای مقاله انیمیشن های واکنش گرا شما دیر کرده ام، اما به شدت توصیه می کنم "قبل از ساختن همه انیمیشن ها را نهایی کنید". من در حال حاضر در حال بازسازی برخی از انیمیشن های سایت با "نسخه های موبایل" هستم. خدا را شکر برای
gsap.matchMedia
... اما من مطمئناً ای کاش می دانستیم که از ابتدا طرح بندی/انیمیشن های موبایل جداگانه وجود دارد.
فکر میکنم همه ما از این نکته قدردانی میکنیم که این نکته برای «برنامهریزی از قبل» در آخرین لحظه به دست آمد. با تشکر، تام، و با آرزوی موفقیت در این بازسازی ها.