پارالاکسین'

معرفی

سایت های Parallax اخیراً مورد توجه قرار گرفته اند، فقط به این موارد نگاهی بیندازید:

اگر با آنها آشنایی ندارید، آنها سایت هایی هستند که ساختار بصری صفحه با اسکرول کردن تغییر می کند. معمولاً عناصر در مقیاس صفحه، متناسب با موقعیت اسکرول در صفحه می چرخند یا حرکت می کنند.

یک صفحه اختلاف منظر دمو
صفحه نمایشی ما با جلوه اختلاف منظر کامل شده است

اینکه آیا شما سایت های اختلاف منظر را دوست دارید یا نه یک چیز است، اما چیزی که می توانید با اطمینان بگویید این است که آنها سیاه چاله ای از عملکرد هستند. دلیل این امر این است که مرورگرها تمایل دارند برای مواردی که محتوای جدید در بالا یا پایین صفحه هنگام اسکرول ظاهر می شود (بسته به جهت پیمایش شما) بهینه می شوند و به طور کلی، مرورگرها زمانی که تغییرات بسیار کمی تغییر می کند بهترین عملکرد را دارند. به صورت بصری در طول یک اسکرول برای یک سایت اختلاف منظر که به ندرت اتفاق می افتد زیرا چندین بار عناصر بصری بزرگ در سراسر صفحه تغییر می کنند و باعث می شوند مرورگر کل صفحه را دوباره رنگ آمیزی کند.

معقول است که یک سایت اختلاف منظر را مانند این تعمیم دهیم:

  • عناصر پس زمینه که با حرکت به سمت بالا و پایین، موقعیت، چرخش و مقیاس خود را تغییر می دهند.
  • محتوای صفحه، مانند متن یا تصاویر کوچکتر، که به شکل معمولی از بالا به پایین اسکرول می شود.

ما قبلاً عملکرد اسکرول و راه‌هایی را که می‌توانید برای بهبود پاسخ‌گویی برنامه‌تان جستجو کنید، توضیح دادیم، و این مقاله بر اساس این پایه است، بنابراین اگر قبلاً این کار را نکرده‌اید، ممکن است ارزش خواندن آن را داشته باشد.

بنابراین سوال این است که اگر در حال ساخت یک سایت پیمایش اختلاف منظر هستید، آیا در رنگ آمیزی های گران قیمت قفل شده اید یا آیا روش های جایگزینی برای به حداکثر رساندن عملکرد وجود دارد؟ بیایید نگاهی به گزینه های خود بیندازیم.

گزینه 1: از عناصر DOM و موقعیت های مطلق استفاده کنید

به نظر می رسد این رویکرد پیش فرضی است که اکثر مردم از آن استفاده می کنند. دسته ای از عناصر در داخل صفحه وجود دارد، و هر زمان که یک رویداد اسکرول فعال می شود، یکسری به روز رسانی های بصری برای تغییر آنها انجام می شود.

اگر DevTools Timeline را در حالت فریم راه اندازی کنید و در اطراف حرکت کنید، متوجه خواهید شد که عملیات رنگ آمیزی تمام صفحه گران قیمت وجود دارد، و اگر زیاد اسکرول کنید ممکن است چندین رویداد اسکرول را در یک فریم مشاهده کنید که هر یک از آنها باعث می شود. کار چیدمان

Chrome DevTools بدون رویدادهای اسکرول بازگردانده شده.
ابزارهای توسعه‌دهنده که رنگ‌های بزرگ و طرح‌بندی‌های متعدد با رویداد را در یک فریم نشان می‌دهند.

نکته مهمی که باید در نظر داشت این است که برای رسیدن به سرعت 60 فریم در ثانیه (مطابق با نرخ تازه سازی معمولی مانیتور 60 هرتز) ما فقط بیش از 16 میلی ثانیه زمان داریم تا همه کارها را انجام دهیم. در این نسخه اول، ما هر بار که یک رویداد اسکرول دریافت می‌کنیم، به‌روزرسانی‌های بصری خود را انجام می‌دهیم، اما همانطور که در مقاله‌های قبلی در مورد انیمیشن‌های ضعیف‌تر و ضعیف‌تر با requestAnimationFrame و عملکرد پیمایش بحث کردیم، این با برنامه به‌روزرسانی مرورگر مطابقت ندارد. و بنابراین ما یا فریم‌ها را از دست می‌دهیم یا کارهای زیادی در داخل هر یک انجام می‌دهیم. این به راحتی می تواند باعث ایجاد یک احساس بد و غیر طبیعی در سایت شما شود که منجر به ناامیدی کاربران و بچه گربه های ناراضی می شود.

بیایید کد به روز رسانی را از رویداد اسکرول به یک درخواست پاسخ به تماس requestAnimationFrame منتقل کنیم و به سادگی مقدار اسکرول را در پاسخ به تماس رویداد پیمایش بگیریم.

اگر تست اسکرول را تکرار کنید، احتمالاً بهبود جزئی را مشاهده خواهید کرد، البته نه چندان. دلیل آن این است که عملیات طرح‌بندی که با پیمایش راه‌اندازی می‌کنیم آنقدرها هم گران نیست، اما در موارد استفاده دیگر واقعاً ممکن است باشد. در حال حاضر حداقل ما فقط یک عملیات چیدمان را در هر فریم انجام می دهیم.

Chrome DevTools با رویدادهای اسکرول بازگردانده شده.
ابزارهای توسعه‌دهنده که رنگ‌های بزرگ و طرح‌بندی‌های متعدد با رویداد را در یک فریم نشان می‌دهند.

ما اکنون می‌توانیم یک یا صد رویداد اسکرول را در هر فریم مدیریت کنیم، اما مهم‌تر از همه، ما فقط آخرین مقدار را برای استفاده در هر زمان که requestAnimationFrame اجرا می‌شود و به‌روزرسانی‌های بصری ما را انجام می‌دهد، ذخیره می‌کنیم. نکته اینجاست که شما از تلاش برای اجبار به‌روزرسانی‌های بصری هر بار که یک رویداد اسکرول را دریافت می‌کنید، به درخواست مرورگر برای ارائه پنجره‌ای مناسب برای انجام آنها تغییر کرده‌اید. شیرین نیستی؟

مشکل اصلی این رویکرد، requestAnimationFrame یا نه، این است که ما اساساً یک لایه برای کل صفحه داریم و با جابجایی این عناصر بصری به اطراف، نیاز به رنگ آمیزی مجدد بزرگ (و گران قیمت) داریم. به طور معمول، نقاشی یک عملیات مسدود کردن است (اگرچه در حال تغییر است)، به این معنی که مرورگر نمی‌تواند کار دیگری انجام دهد و ما اغلب از بودجه 16 میلی‌ثانیه فریم خود فراتر می‌رویم و همه چیز بی‌معنا باقی می‌ماند.

گزینه 2: از عناصر DOM و تبدیل های سه بعدی استفاده کنید

به جای استفاده از موقعیت‌های مطلق، رویکرد دیگری که می‌توانیم اتخاذ کنیم، اعمال تبدیل‌های سه بعدی به عناصر است. در این وضعیت می بینیم که عناصر با تبدیل های سه بعدی اعمال شده یک لایه جدید به ازای هر عنصر داده می شوند و در مرورگرهای WebKit، اغلب باعث سوئیچ به ترکیب کننده سخت افزار نیز می شود. در گزینه 1، در مقابل، ما یک لایه بزرگ برای صفحه داشتیم که وقتی چیزی تغییر می کرد و تمام نقاشی ها و ترکیب ها توسط CPU انجام می شد، نیاز به رنگ آمیزی مجدد داشت.

این بدان معناست که با این گزینه، همه چیز متفاوت است: ما به طور بالقوه یک لایه برای هر عنصری داریم که یک تبدیل سه بعدی را روی آن اعمال می کنیم. اگر تنها کاری که از این نقطه انجام می‌دهیم، تغییر شکل‌های بیشتر بر روی عناصر باشد، دیگر نیازی به رنگ آمیزی مجدد لایه نخواهیم داشت، و GPU می‌تواند با حرکت دادن عناصر به اطراف و ترکیب صفحه نهایی با هم مقابله کند.

بسیاری از اوقات مردم فقط از -webkit-transform: translateZ(0); هک کنید و بهبودهای عملکرد جادویی را ببینید، و در حالی که امروز کار می کند، مشکلاتی وجود دارد:

  1. این با مرورگر سازگار نیست.
  2. با ایجاد یک لایه جدید برای هر عنصر تبدیل شده، دست مرورگر را مجبور می کند. بسیاری از لایه‌ها می‌توانند گلوگاه‌های عملکردی دیگری را به همراه داشته باشند، بنابراین از آن استفاده کنید!
  3. برای برخی از پورت های WebKit غیرفعال شده است (گلوله چهارم از پایین!).

اگر مسیر ترجمه سه بعدی را پایین می آورید، محتاط باشید، این یک راه حل موقت برای مشکل شما است! در حالت ایده‌آل، می‌توانیم ویژگی‌های رندر مشابهی را از تبدیل‌های دوبعدی ببینیم که در مورد سه بعدی مشاهده می‌کنیم. مرورگرها با سرعت فوق العاده ای در حال پیشرفت هستند، بنابراین امیدواریم قبل از آن چیزی را ببینیم.

در نهایت، باید هدف خود را از رنگ‌ها در هر کجا که می‌توانید اجتناب کنید و به سادگی عناصر موجود را در صفحه جابجا کنید. به عنوان مثال، این یک رویکرد معمولی در سایت های اختلاف منظر استفاده از div های ارتفاع ثابت و تغییر موقعیت پس زمینه آنها برای ارائه افکت است. متأسفانه این بدان معنی است که این عنصر باید در هر پاس دوباره رنگ شود، که می تواند از نظر عملکرد برای شما هزینه داشته باشد. در عوض، اگر می توانید، باید عنصر را ایجاد کنید (آن را در یک div با overflow: hidden ) و به سادگی آن را ترجمه کنید.

گزینه 3: از بوم موقعیت ثابت یا WebGL استفاده کنید

آخرین گزینه ای که می خواهیم در نظر بگیریم استفاده از یک بوم موقعیت ثابت در پشت صفحه است که تصاویر تبدیل شده خود را در آن ترسیم می کنیم. در نگاه اول ممکن است کارآمدترین راه حل به نظر نرسد، اما در واقع چند مزیت برای این روش وجود دارد:

  • به دلیل داشتن یک عنصر، بوم، دیگر نیازی به کار آهنگسازی نداریم.
  • ما به طور موثر با یک بیت مپ تسریع شده سخت افزاری سر و کار داریم.
  • Canvas2D API برای نوع تحولاتی که ما به دنبال انجام آن هستیم مناسب است، به این معنی که توسعه و نگهداری قابل مدیریت تر است.

استفاده از یک عنصر canvas یک لایه جدید به ما می دهد، اما این فقط یک لایه است، در حالی که در گزینه 2 ما در واقع یک لایه جدید برای هر عنصر با یک تبدیل سه بعدی اعمال می شود، بنابراین حجم کاری افزایش یافته ای داریم که همه آن لایه ها را با هم ترکیب می کند. این نیز سازگارترین راه حل امروزی با توجه به پیاده سازی های متقابل مرورگرهای مختلف تبدیل ها است.


/**
 * Updates and draws in the underlying visual elements to the canvas.
 */
function updateElements () {

  var relativeY = lastScrollY / h;

  // Fill the canvas up
  context.fillStyle = "#1e2124";
  context.fillRect(0, 0, canvas.width, canvas.height);

  // Draw the background
  context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));

  // Draw each of the blobs in turn
  context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
  context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
  context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
  context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
  context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
  context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
  context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
  context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
  context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));

  // Allow another rAF call to be scheduled
  ticking = false;
}

/**
 * Calculates a relative disposition given the page's scroll
 * range normalized from 0 to 1
 * @param {number} base The starting value.
 * @param {number} range The amount of pixels it can move.
 * @param {number} relY The normalized scroll value.
 * @param {number} offset A base normalized value from which to start the scroll behavior.
 * @returns {number} The updated position value.
 */
function pos(base, range, relY, offset) {
  return base + limit(0, 1, relY - offset) * range;
}

/**
 * Clamps a number to a range.
 * @param {number} min The minimum value.
 * @param {number} max The maximum value.
 * @param {number} value The value to limit.
 * @returns {number} The clamped value.
 */
function limit(min, max, value) {
  return Math.max(min, Math.min(max, value));
}

این رویکرد واقعاً در جایی کار می‌کند که با تصاویر بزرگ (یا عناصر دیگری که می‌توان به راحتی در یک بوم نوشت) سر و کار دارید، و مطمئناً برخورد با بلوک‌های بزرگ متن چالش‌برانگیزتر خواهد بود، اما بسته به سایت شما ممکن است ثابت شود. مناسب ترین راه حل اگر مجبورید با متن در بوم سر و کار داشته باشید، باید از متد fillText API استفاده کنید، اما این به قیمت دسترسی است (شما فقط متن را به صورت بیت مپ شطرنجی کردید!) و اکنون باید با بسته بندی خطوط و ... مقابله کنید. انبوهی از مسائل دیگر اگر می‌توانید از آن اجتناب کنید، واقعاً باید، و احتمالاً با استفاده از رویکرد تبدیل‌های بالا، بهتر می‌شوید.

با توجه به اینکه ما تا آنجایی که ممکن است این کار را انجام می دهیم، دلیلی وجود ندارد که فرض کنیم کار اختلاف منظر باید در داخل یک عنصر بوم انجام شود. اگر مرورگر آن را پشتیبانی می کند، می توانیم از WebGL استفاده کنیم. نکته کلیدی در اینجا این است که WebGL مستقیم ترین مسیر را در بین تمام APIها به کارت گرافیک دارد و به این ترتیب، محتمل ترین نامزد شما برای دستیابی به سرعت 60 فریم در ثانیه است، به خصوص اگر جلوه های سایت پیچیده باشد.

واکنش فوری شما ممکن است این باشد که WebGL بیش از حد زیاد است، یا اینکه از نظر پشتیبانی در همه جا حاضر نیست، اما اگر از چیزی مانند Three.js استفاده می کنید، همیشه می توانید به استفاده از یک عنصر بوم برگردید و کد شما به صورت یکنواخت انتزاع می شود. و رفتار دوستانه تنها کاری که باید انجام دهیم این است که از Modernizr برای بررسی پشتیبانی API مناسب استفاده کنیم:

// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
  renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
  renderer = new THREE.CanvasRenderer();
}

به عنوان آخرین فکر در مورد این رویکرد، اگر طرفدار زیادی برای افزودن عناصر اضافی به صفحه نیستید، می‌توانید همیشه از یک بوم به عنوان عنصر پس‌زمینه در مرورگرهای فایرفاکس و WebKit استفاده کنید . بدیهی است که همه جا حاضر نیست، بنابراین طبق معمول باید با احتیاط با آن رفتار کنید.

انتخاب باشماست

دلیل اصلی که توسعه‌دهندگان به‌جای هر یک از گزینه‌های دیگر، عناصر کاملاً موقعیت‌یافته را پیش‌فرض می‌کنند، ممکن است به سادگی فراگیر بودن پشتیبانی باشد. این تا حدی توهم است، زیرا مرورگرهای قدیمی‌تری که مورد هدف قرار می‌گیرند احتمالاً تجربه رندر بسیار ضعیفی را ارائه می‌دهند. حتی در مرورگرهای مدرن امروزی، استفاده از عناصر کاملاً دارای موقعیت، لزوماً به عملکرد خوبی منجر نمی شود!

Transforms، مطمئناً نوع سه بعدی، به شما امکان کار مستقیم با عناصر DOM و دستیابی به نرخ فریم ثابت را می دهد. کلید موفقیت در اینجا این است که از نقاشی کردن در هر کجا که می توانید اجتناب کنید و به سادگی سعی کنید عناصر را به اطراف منتقل کنید. به خاطر داشته باشید که روشی که مرورگرهای WebKit لایه‌ها را ایجاد می‌کنند لزوماً با سایر موتورهای مرورگر مرتبط نیست، بنابراین قبل از متعهد شدن به آن راه‌حل، حتماً آن را آزمایش کنید.

اگر هدف شما فقط ردیف بالای مرورگرها است و می‌توانید سایت را با استفاده از بوم‌های نقاشی رندر کنید، این بهترین گزینه برای شماست. مطمئناً اگر می‌خواهید از Three.js استفاده کنید، باید بتوانید به راحتی بسته به پشتیبانی مورد نیاز خود، بین رندرها تعویض و تغییر دهید.

نتیجه

ما چند رویکرد را برای برخورد با سایت‌های اختلاف منظر، از عناصر کاملاً دارای موقعیت تا استفاده از بوم موقعیت ثابت، ارزیابی کرده‌ایم. البته پیاده‌سازی‌ای که انجام می‌دهید بستگی به هدفی که می‌خواهید به دست آورید و طراحی خاصی که با آن کار می‌کنید بستگی دارد، اما همیشه خوب است بدانید که گزینه‌هایی دارید.

و مثل همیشه، هر رویکردی را که امتحان کردید: آن را حدس نزنید، آن را امتحان کنید .