ویژگی will-change
این امکان را می دهد که مرورگر را در مورد تغییرات یک عنصر زودتر باخبر کنیم تا اینکه بتواند خود را برای اجرای هرچه بهتر و بهینه تر آن تغییرات آماده کند.
برای درک بهتر این ویژگی نیاز است تا موضوعی را با هم بررسی کنیم.
مثال زیر را اجرا کنید. مربعی خواهید دید که 400 پیکسل به سمت راست حرکت می کند و بر می گردد. وابسته به دستگاهی که در حال استفاده از آن هستید می توانید متوجه شوید که حرکت، همراه با پرش می باشد. هرچه قدرت پردازش دستگاه شما ضعیف تر باشد این پرش ها واضح تر هستند. (مخصوصا در موبایل ها)
See the Pen junky animation by Mojtaba Seyedi (@seyedi) on CodePen.
حال دموی زیر را اجرا کنید. همان حرکت و همان مربع است اما روان و نرم تر:
See the Pen Smooth animation by Mojtaba Seyedi (@seyedi) on CodePen.
چه چیزی باعث این تفاوت شد؟
در انیمیشن اول از ویژگی left
برای تغییر مکان عنصر استفاده کردیم:
@keyframes anime-junky {
to {
left: 500px;
}
}
حال آنکه در دموی دوم از ویژگی transform برای این کار بهره گرفتیم:
@keyframes anime-smooth {
to {
transform: translate3d(400px,0,0);
}
}
تفاوت left
و transform
در چیست؟
مرورگر برای نمایش عناصر، آنها را یک بار بعد از بار گذاری ترسیم می کند. اگر عنصری تغییری بکند نیاز است که مرورگر دوباره آن را ترسیم کند (repainting). مثلا اگر عنصر به هر دلیلی حرکت کند یا تغییر شکل بدهد مرورگر نیاز به ترسیم دوباره آن دارد تا ما بتوانیم تغییر را مشاهده کنیم.
در مثال اول زمانی که از ویژگی left
استفاده کردیم مرورگر در سرتاسر حرکت در هر لحظه در حال ترسیم دوباره مربع بود.
اما در مثال دوم تنها زمانی که عنصر بارگذاری شد مرورگر آن را ترسیم کرد و در زمان حرکت هیچ ترسیم دوباره ای صورت نگرفت.
ترسیم کردن برای مرورگر هزینه دارد و عملیات سنگینی است به همین دلیل تکرار آن در هر لحظه در مثال اول باعث می شد تا حرکت روان و نرم مشاهده نشود.
چرا transform
باعث ترسیم دوباره نمی شود؟
در یک جمله می توان گفت چون transform
مستقیما در GPU اجرا می شود.
در GPU اجرا شدن چه منفعتی دارد؟
ترسیم یک صفحه وب در مرورگر کار دشوار و سنگینی است که بر روی دوش CPU قرار دارد.
در دنیای کامپیوتر مفهومی به نام شتاب سخت افزاری وجود دارد.
شتاب سخت افزاری در اینجا بدین معنی است که GPU با کمک کردن به CPU برای ترسیم عناصر مقداری از دشواری موضوع می کاهد تا این عملیات سریع تر و بهینه تر انجام شود.
شتاب سخت افزاری یعنی آزاد کردن CPU از طریق کمک گرفتن از GPU که این کار باعث نفس کشیدن CPU می شود و وقتی CPU نفس بکشد دستگاه ما سریع تر کار می کند. همچنین GPU مخصوص پردازش و ترسیم عملیات گرافیکی می باشد پس بهتر از پس ترسیمات بر می آید. زمانی که از GPU کمک گرفته شود کار سریع تر انجام شده و CPU آزاد می شود که این موضوع خود را در دستگاهای موبایل واضح تر نشان می دهد.
مرورگرها برای ترسیم عناصر از یک مدل لایه ای استفاده می کنند. وقتی یک عملیات به GPU انتقال داده می شود به آن عملیات و عنصر یک لایه جدا و مستقل تعلق می گیرد که این امر باعث می شود تغییرات آن عنصر سریع تر و روان تر انجام شده و همچنین باعث ترسیم دوباره خود عنصر و عناصر دیگر نشود. ( یادتونه گفتم ترسیم دوباره کار سنگین و پر هزینه ایه؟ )
مثلا فرض کنید عنصری را در یک صفحه جابجا کنیم. قطعا این جابجایی در مکان عناصر دیگر و وضعیت آنها نیز تاثیر خواهد گذاشت و این امر باعث می شود تا مرورگر علاوه بر خود عنصر مجبور به ترسیم دوباره عناصر دیگر نیز شود.
پس وقتی عنصر برای یک عملیات در لایه مخصوص به خودش باشد باعث ترسیم دوباره کل صفحه نمی شود و این باعث بهینه تر شدن عملیات می شود.
با توجه به نکات گفته شده حال متوجه می شویم چرا زمانی که در مثال بالا از transform
استفاده کردیم حرکت بهینه و روان تر انجام شد.
چه موقع یک شی برای ترسیم به GPU انتقال داده می شود؟
انیمیشن ها و ترنزیشن ها به خودی خود به GPU منتقل نمی شوند. تنها تعداد اندکی عملیات وجود دارد که مرورگر از شتاب سخت افزاری برای انجام آنها استفاده می کند.
به عنوان نمونه اگر opacity
را با ترنزیشن و یا انیمیشن تغییر دهیم مرورگر می داند که GPU تغییرات مربوط به opacity
را سریع تر و بسادگی انجام می دهد پس آن را به به عهده GPU می گذارد تا انیمیشن یا ترنزیشن روان تر و بهینه تری داشته باشیم.
ویژگی هایی که انیمیشن و ترنزیشن آنها در GPU انجام می شوند شامل موارد زیر می باشند:
همینطور عناصری مثل <video>
و <canvas>
مستقیما در GPU ترسیم می شوند.
نکته مهمی که باید در نظر داشته باشیم این است که برای transform
از نوع سه بعدی مرورگر از اول برای ترسیم آن یک لایه مستقل در نظر می گیرد. این در حالی است که اگر عنصری transform
دو بعدی داشته باشد مثل دیگر عناصر رسم می شود اما اگر آن عنصر را از طریق انیمیشن یا ترنزیشن متحرک سازی کنیم به محض شروع تغییرات مرورگر آن را به لایه مستقل به خود می برد تا GPU به حسابش برسد :)
پس در زمان متحرک سازی از طریق transform
نوع دو بعدی، یک ترسیم در اول حرکت خواهیم داشت و یک ترسیم در آخر حرکت. ( ترسیم اول مربوط به انتقال به لایه جدید و ترسیم آخر مربوط به بازگشت آن از آن لایه به لایه عناصر فقیر بیچاره می باشد :) )
انتقال ترسیم یک عنصر به GPU
فرض کنید به هر دلیلی نیاز داریم تا ترسیم عنصر را به GPU انتقال دهیم. چگونه این کار را انجام دهیم؟
همانطور که امکان دارد حدس بزنید کافی است کاری کنیم تا مرورگر فکر کند این عنصر قرار است عملیات سه بعدی داشته باشد.
پس می توانیم به سادگی برای آن عنصر بنویسیم:
.element {
transform: translateZ(0);
/* یا */
transform: translate3d(0,0,0);
}
translate3d و translateZ از توابع سه بعدی می باشند که مرورگر به محض دیدن آنها می داند که عنصر باید به GPU برای ترسیم انتقال داده شود.
چون مقادیر این توابع را برابر با صفر قرار دادیم و در واقع هیچگونه عملیات سه بعدی در کار نیست و به مرورگر کلک زدیم به این کار، هک translate3d می گویند.
برای جلوگیری از انجام این کار و دور زدن مرورگر یک ویژگی جدید در دنیای سی اس اس به نام will-change
معرفی شد. که این ویژگی به ما اجازه می دهد تا بتوانیم تغییراتی که قرار است بر روی عنصر اتفاق بیافتد را به مرورگر بگوییم تا شرایط را برای بهینه اجرا کردن آن تغییرات آماده کند.
برای استفاده از این ویژگی می توان به سادگی نوع تغییر را برای آن تنظیم کرد. مثلا اگر عنصر قرار است از طریق ویژگی transform
تغییری داشته باشد می نویسیم:
.element {
will-change: transform;
}
و یا اگر opacity
عنصر قرار است که با ترنزیشن تغییر کند:
.element {
will-change: opacity;
}
همینطور می تواند چند مقدار برای این ویژگی تعریف کرد:
.element {
will-change: opacity, transform;
}
با این کار ما نحوه بهینه سازی را بر عهده خود مرورگر می گذاریم فقط ما زودتر به آن اعلام می کنیم که تغییرات چه خواهند بود تا بتواند بهتر تصمیم گیری کند و به بهترین نحو با تغییرات رو برو شود.
این راه به مراتب بهتر است هکی است که با هم بررسی کردیم. چرا که شاید مرورگر روش دیگری برای بهینه سازی بداند.
اثرات جانبی
شاید بگوییم چرا برای همه عناصر هک بالا را استفاده نکنیم یا چرا برای همه عناصر ویژگی will-change
را معرفی نکنیم؟
* {
will-change: all;
}
استفاده زیاد از این روش ها مشکلاتی برای حافظه (مخصوصا VRAM) مخصوصا در موبایل ها بوجود می آورد. پس حتما با دقت از این روش ها استفاده کنید. و حتما مطمئن شوید که مشکلی در اجرا وجود دارد و این تنها روش حل آن مشکل است.
استفاده از ویژگی will-change: transform
و همینطور توابع سه بعدی باعث می شود تا عنصر به عنوان یک بلاک جدا شناخته شود که این معمولا دو مشکل می تواند بوجود بیاورد.
اول اینکه اگر فرزندی از عناصر آن دارای position از نوع fixed
باشد دیگر آن فرزند ثابت نسبت به viewport نخواهد بود بلکه absolute
به عنصر پدرش خواهد بود یعنی همان عنصری که دارای تابع سه بعدی یا will-change
می باشد.
همینطور z-index فرزندان این عنصر محدود به خود عنصر می شوند.
روش صحیح
برای استفاده صحیح از این ویژگی اول از همه اینکه در استفاده از آن زیاده روی نشود.
دوم اینکه بهتر است این ویژگی کمی قبل از تغییر به عنصر داده شود و پس از تغییرات از عنصر گرفته شود. مثلا فرض کنید عنصری درون یک نگهدارنده وجود دارد و قرار است زمانی که نشانه گر بر روی آن می رود شروع به چرخش کند.
می توانیم به این صورت عمل کنیم که زمانی که نشانه گر بر روی نگهدارنده آن می رود will-change
را به عنصر بدهیم نه اینکه از اول به خود عنصر این ویژگی داده شود:
.element {
transition: transform .3s linear;
}
.yeki-az-babahash:hover .element {
will-change: transform;
}
.element:hover {
transform: rotate(520deg);
}
به اینصورت مرورگر لحظه ای قبل از تغییر می تواند آماده پذیرایی از آن شود.
اگر شرایط طوری بود که قادر به این کار در سی اس اس نبودید می توان این کار را از طریق جاوااسکریپت انجام داد:
var el = document.getElementById('element');
el.addEventListener('mouseenter', hintBrowser);
el.addEventListener('animationEnd', removeHint);
function hintBrowser() {
this.style.willChange = 'transform, opacity';
}
function removeHint() {
this.style.willChange = 'auto';
}
دلیل اینکه توصیه می شود این ویژگی از عنصر بعد از انجام تغییرات گرفته شود به این خاطر است که اگر با این کار مرورگر منابعی مثل حافظه و غیره را درگیر می کند بهتر است بعد از انجام عملیات کاری کنیم تا آن منابع آزاد شوند.
این نکات معمولا وقتی مورد بحث و بررسی خواهد بود که تعداد عناصری زیادی در حال انیمیشن و یا ترنیزشن هستند که این امر باعث می شود تا مرورگر منابع بیشتری را نیاز داشته باشد.
توجه داشته باشید خود اضافه کردن و حذف این ویژگی باعث ترسیم دوباره عنصر می شود. پس باید مراقب بود و همیشه کار را از طریق ابزارهای مرورگر تست کنیم و مطمئن شویم با این روش ها کار را بهینه تر می کنیم نه بالعکس.
مثلا اگر تنها یک sidebar
در سایت وجود دارد که کاربر با کلید بر روی دکمه آن را از صفحه خارج یا به صفحه وارد می کند نیازی نیست تا به روش های بالا عمل کنیم. کافی است به عنصر اصلی از همان اول ویژگی will-change
را اعمال کنیم:
.sidebar {
will-change: transform;
}
مقادیر این ویژگی
auto
auto
مقدار اولیه و پیشفرض ویژگی است که هیچ تاثیری بر روی عنصر نخواهد داشت.
scroll-position
این مقدار باعث می شود تا مرورگر بداند که محتوای عنصر قرار است باعث شود تا عنصر اسکرول شود پس مرورگر می تواند خود را برای ترسیم محتوایی که قابل دیدن نیست آماده کند و اسکرول روان تری را برای عنصر فراهم کند:
.element {
will-change: scroll-position;
}
contents
این مقدار به مرورگر می گوید که محتوای عنصر تغییرات خواهد داشت تا مرورگر بتواند بهتر در مورد این عنصر تصمیم بگیرد:
.element {
will-change: contents;
}
در بیشتر مواقع محتوای یک عنصر تغییر زیادی نمی کند به همین دلیل مرورگر از مکانیزم کش کردن ترسیمات برای عناصر استفاده می کند. اما اگر عنصری وجود داشته باشد که محتوای آن بصورت منظم تغییر کند بهتر است مرورگر را نسبت به این موضوع آگاه کنیم تا از مکانیزم کش برای این عنصر استفاده نکند. استفاده از این ویژگی می تواند سیگنالی برای مرورگر باشد تا بتواند روش بهینه تر را انجام دهد.
<custom-ident>
همچنین همانطور که در مثال های قبل دیده شد می توان ویژگی هایی که قرار هستند در ادامه باعث شوند تا عنصر تغییراتی داشته باشد را می توان برای will-change
تعریف کرد:
.element {
will-change: opacity;
}
.element {
will-change: transform;
}
.element {
will-change: left;
}
پشتیبانی مرورگر ها
جمع بندی
- در حال حاضر پشتیبانی مرورگرها از هک
translate3d
بهتر است اما باز تاکید می کنم که در استفاده از آن مراقب باشید چون ساختن یک لایه جدید هزینه بر است و منابع خاص خود را نیاز دارد. - این هک فقط یک لایه جدید می سازد و ترسیمات را به GPU انتقال می دهد اما
will-change
روش را به عهده مرورگر می گذارد شاید راه بهتری داشته باشد. - هر زمان در دنیای وب حرف از بهینه سازی شد به مقالات و نوشته ها بطور کامل اعتماد نکنید و حتما کار خود را تست کنید و نتیجه را مشاهده کنید چرا که ساختار مرورگرها و پیاده سازی آنها روز به روز تغییر می کند و هر مرورگری هم روش خود را داد.
- برای بررسی مثال هایی برای این ویژگی به این مطلب مراجعه کنید.
خیلی عالی
ممنون از شما
من اصلاً نمیدونستم همچین ویژگی ای وجود داره!
مقالات شما خیلی عالی و جامع هستن
منتظر اون ویدیو هم که قولشو توی این مقاله دادین هستیم
ممنون از پیامتون. این ویژگی تقریبا جدیده یه دو سالی هستش که داره مطرح می شه واسه همون هنوز خیلی شناخته شده نیست.
ویدیو هم بزودی به امیدخدا :)
مثل همیشه عالی،
خداقوت…
عالی بود منم اصلا باهاش آشنایی نداشتم باید یکم باهاش کار کنم که دستم بیاد چجوریه
طبق معمول عالی …
سلام مطلب خیلی خوبی بود
خصوصا خوب و واضح توضیح داده بودید
ممنون
با سلام خدمت آقا مجتبی گل
امیدوارم خدا هر چی آرزو داری بهت بده
البته خدا خانوادت رو نگه داره که همچین پسر خوب و باسوادی تربیت کردن
ما اینهمه با سواد توی کشورمون داریم ولی نمی دونم چرا سایت های خوب فقط به اندازه انگشت های دست داریم
ممنون از سایت خوبتون
سلام، خیلی خیلی ممنون از پیام شیرین و زیباتون :)
خوشحالم که سایت می تونه کمکتون کنه.
با تشکر فراوان از آقا مجتبی گل، لازمه بگم داداش دمت گرم گل کاشتی انشالله که همیشه موفق باشی تو تمام مراحل زندگیت.