پرفورمنت Parallaxing

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

تصویر اختلاف منظر.

TL; DR

  • از رویدادهای اسکرول یا background-position برای ایجاد انیمیشن‌های اختلاف منظر استفاده نکنید.
  • از تبدیل های سه بعدی CSS برای ایجاد جلوه منظر دقیق تر استفاده کنید.
  • position: sticky برای اطمینان از انتشار اثر اختلاف منظر.

اگر راه حل کشویی را می خواهید، به مخزن UI Element Samples GitHub بروید و Parallax helper JS را بگیرید! می‌توانید یک نسخه نمایشی زنده از پیش‌روی اختلاف منظر را در مخزن GitHub ببینید.

اختلاف منظر مشکل

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

بد: استفاده از رویدادهای اسکرول

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

این بخش مهم از اطلاعات به ما می گوید که چرا باید از راه حل مبتنی بر جاوا اسکریپت که عناصر را بر اساس رویدادهای اسکرول جابجا می کند اجتناب کنیم: جاوا اسکریپت تضمین نمی کند که اختلاف منظر مطابق با موقعیت پیمایش صفحه باشد . در نسخه‌های قدیمی‌تر Mobile Safari، رویدادهای اسکرول در واقع در انتهای اسکرول ارائه می‌شدند که ایجاد افکت اسکرول مبتنی بر جاوا اسکریپت را غیرممکن می‌کرد. نسخه‌های جدیدتر رویدادهای اسکرول را در طول انیمیشن ارائه می‌کنند ، اما، مشابه کروم، بر اساس «بهترین تلاش». اگر موضوع اصلی با هر کار دیگری مشغول باشد، رویدادهای اسکرول فورا تحویل داده نمی‌شوند، به این معنی که جلوه اختلاف منظر از بین می‌رود.

بد: به‌روزرسانی background-position

موقعیت دیگری که مایلیم از آن اجتناب کنیم، نقاشی روی هر قاب است. بسیاری از راه‌حل‌ها سعی می‌کنند background-position تغییر دهند تا ظاهر منظر ایجاد کنند، که باعث می‌شود مرورگر قسمت‌های آسیب‌دیده از صفحه را دوباره در اسکرول نقاشی کند، و این می‌تواند به اندازه‌ای گران باشد که انیمیشن را به‌طور قابل‌توجهی جابجا کند.

اگر می‌خواهیم به وعده حرکت اختلاف منظر عمل کنیم، چیزی می‌خواهیم که بتوان آن را به‌عنوان یک ویژگی شتاب‌گرفته (که امروزه به معنای چسبیدن به تبدیل‌ها و کدورت است) اعمال کرد و به رویدادهای اسکرول متکی نباشد.

CSS به صورت سه بعدی

هم اسکات کلوم و هم کیث کلارک کار قابل توجهی در زمینه استفاده از CSS 3D برای دستیابی به حرکت اختلاف منظر انجام داده اند و تکنیکی که آنها استفاده می کنند عملاً این است:

  • یک عنصر حاوی را برای پیمایش با overflow-y: scroll (و احتمالا overflow-x: hidden ) تنظیم کنید.
  • برای همان عنصر، یک مقدار perspective اعمال کنید، و یک perspective-origin در top left یا 0 0 تنظیم کنید.
  • برای فرزندان آن عنصر، ترجمه‌ای را به زبان Z اعمال کنید، و آن‌ها را پشتیبان‌گیری کنید تا حرکت اختلاف منظر را بدون تأثیرگذاری بر اندازه آن‌ها روی صفحه نمایش دهید.

CSS برای این رویکرد به این صورت است:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

که یک قطعه از HTML را مانند زیر فرض می کند:

<div class="container">
    <div class="parallax-child"></div>
</div>

تنظیم مقیاس برای پرسپکتیو

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

در مورد کد بالا، پرسپکتیو 1px است و فاصله Z parallax-child -2px است. این به این معنی است که عنصر باید 3 برابر بزرگ شود، که می توانید ببینید مقدار وصل شده به کد: scale(3) است.

برای هر محتوایی که مقدار translateZ اعمال نشده است، می‌توانید مقدار صفر را جایگزین کنید. این به این معنی است که مقیاس (چشم انداز - 0) / پرسپکتیو است، که در مقدار 1 خالص می شود، به این معنی که مقیاس آن نه بالا و نه پایین است. بسیار مفید، واقعا.

این رویکرد چگونه کار می کند

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

با این حال، اعمال یک مقدار پرسپکتیو برای عنصر اسکرول، این فرآیند را به هم می زند. ماتریس هایی را که زیربنای تبدیل اسکرول هستند را تغییر می دهد. اکنون یک اسکرول 300 پیکسلی بسته به perspective و مقادیر translateZ که انتخاب کرده‌اید، ممکن است فقط 150 پیکسل بچه‌ها را جابه‌جا کند. اگر عنصری دارای مقدار translateZ 0 باشد، در 1:1 پیمایش می‌شود (مانند سابق)، اما فرزندی که در Z از مبدأ پرسپکتیو فاصله می‌گیرد، با سرعت متفاوتی پیمایش می‌شود! نتیجه خالص: حرکت اختلاف منظر. و مهمتر از همه، این به عنوان بخشی از دستگاه اسکرول داخلی مرورگر به صورت خودکار مدیریت می شود، به این معنی که نیازی به گوش دادن به رویدادهای scroll یا تغییر background-position نیست.

مگس در پماد: سافاری موبایل

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

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

در HTML بالا، .parallax-container جدید است و به طور موثر مقدار perspective را صاف می کند و ما جلوه منظر را از دست می دهیم. راه حل، در بیشتر موارد، نسبتاً ساده است: شما transform-style: preserve-3d به عنصر اضافه می‌کنید، و باعث می‌شود که هر اثر سه‌بعدی (مانند مقدار پرسپکتیو ما) که در بالای درخت اعمال شده است را منتشر کند.

.parallax-container {
  transform-style: preserve-3d;
}

با این حال، در مورد Mobile Safari، همه چیز کمی پیچیده تر است. اعمال overflow-y: scroll به عنصر ظرف از نظر فنی کار می کند، اما به قیمت امکان پرت کردن عنصر اسکرول. راه حل این است که -webkit-overflow-scrolling: touch ، اما perspective نیز صاف می کند و ما هیچ اختلاف منظری نخواهیم داشت.

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

position: sticky به نجات!

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

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

با اعمال position: -webkit-sticky به عنصر اختلاف منظر، می‌توانیم به طور موثری اثر مسطح -webkit-overflow-scrolling: touch را معکوس کنیم. این تضمین می کند که عنصر اختلاف منظر به نزدیکترین جد با یک جعبه پیمایش ارجاع می دهد که در این مورد .container است. سپس، مشابه قبل، .parallax-container یک مقدار perspective اعمال می کند، که افست اسکرول محاسبه شده را تغییر می دهد و یک اثر اختلاف منظر ایجاد می کند.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

این اثر اختلاف منظر برای موبایل سافاری را بازیابی می‌کند، که خبر بسیار خوبی در سراسر جهان است!

هشدارهای تعیین موقعیت چسبنده

اما در اینجا یک تفاوت وجود دارد : position: sticky مکانیک اختلاف منظر را تغییر می دهد. موقعیت چسبنده سعی می کند، خوب، عنصر را به محفظه اسکرول بچسباند، در حالی که یک نسخه غیر چسبنده این کار را نمی کند. این بدان معنی است که اختلاف منظر با چسبنده در نهایت معکوس منظر بدون:

  • با position: sticky ، هر چه عنصر به z=0 نزدیک‌تر باشد کمتر حرکت می‌کند.
  • بدون position: sticky ، هر چه عنصر به z=0 نزدیک‌تر باشد، بیشتر حرکت می‌کند.

اگر همه اینها کمی انتزاعی به نظر می رسد، به این دمو توسط رابرت فلک نگاهی بیندازید ، که نشان می دهد چگونه عناصر با و بدون موقعیت چسبنده رفتار متفاوتی دارند. برای مشاهده تفاوت، به Chrome Canary (که در زمان نگارش نسخه 56 است) یا Safari نیاز دارید.

اسکرین شات پرسپکتیو اختلاف منظر

نسخه ی نمایشی توسط رابرت فلک که نشان می دهد چگونه position: sticky بر پیمایش اختلاف منظر تأثیر می گذارد.

اشکالات و راه حل های مختلف

با این حال، مانند هر چیز دیگری، هنوز توده ها و برآمدگی هایی وجود دارد که باید صاف شوند:

  • پشتیبانی چسبنده ناسازگار است. پشتیبانی هنوز در کروم اجرا می شود، Edge به طور کامل فاقد پشتیبانی است، و فایرفاکس باگ های نقاشی را در صورت ترکیب چسبندگی با تبدیل پرسپکتیو دارد. در چنین مواردی، ارزش افزودن کد کمی را دارد تا فقط position: sticky (نسخه پیشوند -webkit- ) در صورت نیاز، که فقط برای Mobile Safari است.
  • این افکت "فقط" در Edge کار نمی کند. Edge سعی می‌کند اسکرول را در سطح سیستم‌عامل انجام دهد، که به طور کلی چیز خوبی است، اما در این مورد از تشخیص تغییرات پرسپکتیو در حین اسکرول جلوگیری می‌کند. برای رفع این مشکل، می‌توانید یک عنصر موقعیت ثابت اضافه کنید، زیرا به نظر می‌رسد که Edge را به یک روش پیمایش غیر سیستم‌عامل تغییر می‌دهد و تضمین می‌کند که تغییرات پرسپکتیو را در نظر می‌گیرد.
  • "محتوای صفحه به تازگی بزرگ شده است!" بسیاری از مرورگرها هنگام تصمیم گیری در مورد حجم محتوای صفحه، مقیاس را در نظر می گیرند، اما متأسفانه کروم و سافاری چشم انداز را در نظر نمی گیرند . بنابراین اگر - مثلاً - مقیاسی 3 برابری برای یک عنصر اعمال شود، ممکن است نوارهای اسکرول و موارد مشابه را ببینید، حتی اگر بعد از اعمال perspective ، عنصر در 1x باشد. می‌توان با مقیاس‌گذاری عناصر از گوشه پایین سمت راست (با transform-origin: bottom right ) این مشکل را حل کرد، که کار می‌کند زیرا باعث می‌شود عناصر بزرگ‌تر به «منطقه منفی» (معمولاً سمت چپ بالا) رشد کنند. منطقه قابل پیمایش؛ مناطق قابل پیمایش هرگز به شما اجازه نمی‌دهند محتوای ناحیه منفی را ببینید یا به آن بروید.

نتیجه گیری

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

یک بازی داشته باشید و به ما اطلاع دهید که چگونه پیش می روید.