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

استفان مک گروئر
Stephen McGruer

TL; DR

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

قبلاً به‌روزرسانی‌هایی درباره نحوه ایجاد جلوه‌های منظر عملکردی و اسکرول‌های بی‌نهایت ارسال کرده‌ایم. در این پست، اگر می‌خواهید انیمیشن‌های کلیپ پرفورمنس را داشته باشید، به بررسی این موضوع می‌پردازیم. اگر می خواهید یک نسخه نمایشی ببینید، مخزن Sample UI Elements GitHub را بررسی کنید.

به عنوان مثال، یک منوی در حال گسترش را در نظر بگیرید:

برخی از گزینه ها برای ساخت این کارایی بیشتری نسبت به سایرین دارند.

بد: متحرک کردن عرض و ارتفاع در یک عنصر ظرف

می توانید تصور کنید که از کمی CSS برای متحرک سازی عرض و ارتفاع عنصر ظرف استفاده کنید.

.menu {
  overflow: hidden;
  width: 350px;
  height: 600px;
  transition: width 600ms ease-out, height 600ms ease-out;
}

.menu--collapsed {
  width: 200px;
  height: 60px;
}

مشکل فوری این رویکرد این است که به متحرک سازی width و height نیاز دارد. این ویژگی ها نیاز به محاسبه چیدمان دارند و نتایج را روی هر فریم از انیمیشن نقاشی می کنند که می تواند بسیار گران باشد و معمولاً باعث می شود تا 60 فریم در ثانیه را از دست بدهید. اگر این برای شما خبری است، راهنمای عملکرد رندر ما را بخوانید، جایی که می توانید اطلاعات بیشتری در مورد نحوه عملکرد فرآیند رندر دریافت کنید.

بد: از ویژگی های CSS clip یا clip-path استفاده کنید

یک جایگزین برای متحرک سازی width و height ممکن است استفاده از ویژگی clip (اکنون منسوخ شده) برای متحرک سازی افکت بسط و فروپاشی باشد. یا اگر ترجیح می دهید، می توانید به جای آن از clip-path استفاده کنید. با این حال، استفاده از clip-path کمتر از clip پشتیبانی می شود. اما clip منسوخ شده است. درسته اما ناامید نباشید، به هر حال این راه حلی نیست که شما می خواستید!

.menu {
  position: absolute;
  clip: rect(0px 112px 175px 0px);
  transition: clip 600ms ease-out;
}

.menu--collapsed {
  clip: rect(0px 70px 34px 0px);
}

اگرچه بهتر از متحرک سازی width و height عنصر منو است، اما نقطه ضعف این رویکرد این است که همچنان رنگ را فعال می کند. همچنین ویژگی clip ، اگر آن مسیر را طی کنید، مستلزم این است که عنصری که روی آن کار می‌کند، کاملاً یا ثابت باشد، که می‌تواند به کمی کشمکش اضافی نیاز داشته باشد.

خوب: مقیاس های متحرک

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

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

مرحله 1: حالت شروع و پایان را محاسبه کنید

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

function calculateCollapsedScale () {
    // The menu title can act as the marker for the collapsed state.
    const collapsed = menuTitle.getBoundingClientRect();

    // Whereas the menu as a whole (title plus items) can act as
    // a proxy for the expanded state.
    const expanded = menu.getBoundingClientRect();
    return {
    x: collapsed.width / expanded.width,
    y: collapsed.height / expanded.height
    };
}

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

اما صبر کن! مطمئناً با این کار محتویات منو نیز افزایش می یابد، اینطور نیست؟ خوب، همانطور که در زیر می بینید، بله.

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

  1. تبدیل متقابل نیز یک عملیات مقیاس است . این خوب است زیرا می توان آن را نیز شتاب داد، درست مانند انیمیشن روی ظرف. ممکن است لازم باشد که عناصری که متحرک می شوند، لایه ترکیبی خود را داشته باشند (که GPU را قادر می سازد کمک کند)، و برای این کار می توانید will-change: transform به عنصر یا، اگر نیاز به پشتیبانی از مرورگرهای قدیمی دارید، backface-visiblity: hidden

  2. تبدیل متقابل باید در هر فریم محاسبه شود. اینجاست که همه چیز می‌تواند کمی پیچیده‌تر شود، زیرا با فرض اینکه انیمیشن در CSS است و از یک تابع easing استفاده می‌کند، خود easing باید هنگام متحرک سازی متحرک سازی متحرک مقابله شود. با این حال، محاسبه منحنی معکوس برای - مثلاً - cubic-bezier(0, 0, 0.3, 1) چندان واضح نیست.

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

مرحله 2: انیمیشن های CSS را در لحظه بسازید

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

برای ساخت انیمیشن فریم کلیدی، از 0 تا 100 گام می گذاریم و محاسبه می کنیم که چه مقادیر مقیاس برای عنصر و محتویات آن مورد نیاز است. سپس می‌توان آن‌ها را به یک رشته تبدیل کرد که می‌تواند به عنوان عنصر سبک به صفحه تزریق شود. تزریق استایل‌ها باعث می‌شود که Recalculate Styles Pass در صفحه ایجاد شود، این کار اضافی است که مرورگر باید انجام دهد، اما زمانی که کامپوننت در حال بوت شدن است، این کار را فقط یک بار انجام می‌دهد.

function createKeyframeAnimation () {
    // Figure out the size of the element when collapsed.
    let {x, y} = calculateCollapsedScale();
    let animation = '';
    let inverseAnimation = '';

    for (let step = 0; step <= 100; step++) {
    // Remap the step value to an eased one.
    let easedStep = ease(step / 100);

    // Calculate the scale of the element.
    const xScale = x + (1 - x) * easedStep;
    const yScale = y + (1 - y) * easedStep;

    animation += `${step}% {
        transform: scale(${xScale}, ${yScale});
    }`;

    // And now the inverse for the contents.
    const invXScale = 1 / xScale;
    const invYScale = 1 / yScale;
    inverseAnimation += `${step}% {
        transform: scale(${invXScale}, ${invYScale});
    }`;

    }

    return `
    @keyframes menuAnimation {
    ${animation}
    }

    @keyframes menuContentsAnimation {
    ${inverseAnimation}
    }`;
}

کنجکاوهای بی پایان ممکن است در مورد تابع ease() در داخل حلقه for تعجب کنند. می توانید از چیزی شبیه به این برای نگاشت مقادیر از 0 تا 1 به یک معادل ساده استفاده کنید.

function ease (v, pow=4) {
  return 1 - Math.pow(1 - v, pow);
}

می توانید از جستجوی گوگل برای ترسیم ظاهر آن نیز استفاده کنید. دستی! اگر به معادلات تسهیل‌کننده دیگری نیاز دارید، Tween.js توسط Soledad Penadés را بررسی کنید، که حاوی انبوهی از آنهاست.

مرحله 3: انیمیشن های CSS را فعال کنید

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

.menu--expanded {
  animation-name: menuAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

.menu__contents--expanded {
  animation-name: menuContentsAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

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

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

const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;

نسخه پیشرفته تر: دایره ای نشان می دهد

همچنین می توان از این تکنیک برای ساخت انیمیشن های دایره ای بسط و جمع کرد استفاده کرد.

اصول تا حد زیادی مانند نسخه قبلی است، جایی که شما یک عنصر را مقیاس می‌دهید و فرزندان مستقیم آن را ضد مقیاس می‌کنید. در این مورد، عنصری که بزرگ‌تر می‌شود، border-radius 50% دارد و آن را دایره‌ای می‌کند، و توسط عنصر دیگری که overflow: hidden ، به این معنی که دایره را خارج از محدوده عنصر نمی‌بینید.

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

کد افکت گسترش دایره ای را می توان در مخزن GitHub یافت.

نتیجه گیری

بنابراین شما آن را دارید، راهی برای انجام انیمیشن های کلیپ با استفاده از تبدیل مقیاس. در یک دنیای عالی، دیدن انیمیشن‌های کلیپ بسیار عالی خواهد بود ( یک باگ Chromium برای آن ساخته شده توسط جیک آرچیبالد وجود دارد)، اما تا زمانی که به آنجا برسیم، باید در انیمیشن‌سازی clip یا clip-path محتاط باشید و قطعا از متحرک کردن اجتناب کنید. width یا height .

استفاده از انیمیشن‌های وب برای افکت‌هایی مانند این نیز مفید خواهد بود، زیرا آنها یک API جاوا اسکریپت دارند اما اگر فقط transform و opacity متحرک کنید، می‌توانند روی رشته کامپوزیتور اجرا شوند. متأسفانه، پشتیبانی از Web Animations عالی نیست ، اگرچه می‌توانید در صورت در دسترس بودن، از بهبودهای پیشرونده برای استفاده از آنها استفاده کنید.

if ('animate' in HTMLElement.prototype) {
    // Animate with Web Animations.
} else {
    // Fall back to generated CSS Animations or JS.
}

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

اگر می‌خواهید از طریق کد این افکت نگاهی بیندازید، نگاهی به مخزن UI Element Samples GitHub بیندازید و مانند همیشه، نحوه عملکرد خود را در نظرات زیر به ما اطلاع دهید.