کارنامه انیمیشن هودینی

انیمیشن های برنامه وب خود را شارژ کنید

TL;DR: Animation Worklet به شما امکان می‌دهد تا انیمیشن‌های ضروری را بنویسید که با نرخ فریم اصلی دستگاه اجرا می‌شوند تا نرم‌افزاری بدون ژانک را داشته باشید، انیمیشن‌های شما را در برابر انحراف موضوع اصلی انعطاف‌پذیرتر می‌کند و به جای زمان، می‌تواند به اسکرول متصل شود. Animation Worklet در Chrome Canary است (در پشت پرچم "ویژگی‌های پلتفرم وب آزمایشی") و ما در حال برنامه‌ریزی نسخه آزمایشی اولیه برای Chrome 71 هستیم. می‌توانید از امروز به عنوان یک پیشرفت تدریجی استفاده از آن را شروع کنید.

API انیمیشن دیگری؟

در واقع نه، این گسترش چیزی است که ما در حال حاضر داریم، و دلیل خوبی هم دارد! بیایید از ابتدا شروع کنیم. اگر امروز می‌خواهید هر عنصر DOM را در وب متحرک کنید، 2 ½ انتخاب دارید: انتقال CSS برای انتقال ساده A به B، انیمیشن‌های CSS برای انیمیشن‌های بالقوه چرخه‌ای و پیچیده‌تر مبتنی بر زمان و Web Animations API (WAAPI) برای تقریباً دلخواه. انیمیشن های پیچیده ماتریس پشتیبانی WAAPI بسیار بد به نظر می رسد، اما در راه است. تا آن زمان، پلی پر وجود دارد.

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

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

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

نکته این است که همه این چیزها ناخوشایند هستند و اجرای کارآمد آنها سخت تا غیرممکن است. اکثر آنها به رویدادها و/یا requestAnimationFrame متکی هستند، که ممکن است شما را در سرعت 60 فریم در ثانیه نگه دارد، حتی زمانی که صفحه نمایش شما قادر است با سرعت 90 فریم در ثانیه، 120 فریم در ثانیه یا بالاتر اجرا شود و از کسری از بودجه گرانبهای فریم رشته اصلی شما استفاده کند.

Animation Worklet قابلیت‌های پشته انیمیشن‌های وب را برای آسان‌تر کردن این نوع جلوه‌ها گسترش می‌دهد. قبل از غواصی، بیایید مطمئن شویم که در مورد اصول اولیه انیمیشن ها به روز هستیم.

آغازگر انیمیشن ها و جدول زمانی

WAAPI و Animation Worklet به طور گسترده از جدول زمانی استفاده می کنند تا به شما امکان می دهند انیمیشن ها و افکت ها را به روشی که می خواهید هماهنگ کنید. این بخش یک تجدید یا مقدمه سریع برای خطوط زمانی و نحوه کار آنها با انیمیشن ها است.

هر سند دارای document.timeline است. هنگامی که سند ایجاد می شود از 0 شروع می شود و از زمانی که سند شروع به کار کرده است، میلی ثانیه ها را می شمارد. همه انیمیشن های یک سند نسبت به این جدول زمانی کار می کنند.

برای اینکه همه چیز کمی دقیق تر شود، بیایید نگاهی به این قطعه WAAPI بیندازیم

const animation = new Animation(
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
      {
        transform: 'translateY(500px)',
      },
    ],
    {
      delay: 3000,
      duration: 2000,
      iterations: 3,
    }
  ),
  document.timeline
);

animation.play();

هنگامی که ما animation.play() فراخوانی می کنیم، انیمیشن از currentTime خط زمانی به عنوان زمان شروع استفاده می کند. انیمیشن ما 3000 میلی‌ثانیه تأخیر دارد، به این معنی که وقتی خط زمانی به «startTime» برسد، انیمیشن شروع می‌شود (یا «فعال» می‌شود.

  • 3000 . After that time, the animation engine will animate the given element from the first keyframe ( translateX(0) ), through all intermediate keyframes ( translateX(500px) ) all the way to the last keyframe ( translateY(500px) ) in exactly 2000ms, as prescribed by the options. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline's از آنجایی که مدت زمان ما 2000 میلی‌ثانیه است، زمانی که زمان فعلی خط زمانی startTime + 3000 + 1000 and the last keyframe at startTime + 3000 + 2000 is options. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline's . نکته اینجاست که تایم لاین مکان ما را در انیمیشن خود کنترل می کند!

هنگامی که انیمیشن به آخرین فریم کلیدی رسید، به اولین فریم کلیدی برمی گردد و تکرار بعدی انیمیشن را شروع می کند. از زمانی که iterations: 3 . اگر می خواستیم انیمیشن هرگز متوقف نشود، iterations: Number.POSITIVE_INFINITY . در اینجا نتیجه کد بالا آمده است.

WAAPI فوق‌العاده قدرتمند است و ویژگی‌های بسیار بیشتری در این API وجود دارد، مانند آسان‌سازی، شروع افست، وزن‌دهی فریم‌های کلیدی و رفتار پر کردن که دامنه این مقاله را تحت تأثیر قرار می‌دهد. اگر دوست دارید بیشتر بدانید، خواندن این مقاله در مورد انیمیشن های CSS در ترفندهای CSS را توصیه می کنم.

نوشتن کارنامه انیمیشن

اکنون که مفهوم خطوط زمانی را پایین آورده ایم، می توانیم شروع به بررسی انیمیشن Worklet کنیم و اینکه چگونه به شما اجازه می دهد تا جدول های زمانی را به هم بزنید! Animation Worklet API نه تنها بر اساس WAAPI است، بلکه - به معنای وب توسعه پذیر - یک ابتدایی سطح پایین تر است که نحوه عملکرد WAAPI را توضیح می دهد. از نظر نحو، آنها به طرز باورنکردنی شبیه هستند:

کارنامه انیمیشن WAAPI
new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)'
      },
      {
        transform: 'translateX(500px)'
      }
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY
    }
  ),
  document.timeline
).play();
      
        new Animation(

        new KeyframeEffect(
        document.querySelector('#a'),
        [
        {
        transform: 'translateX(0)'
        },
        {
        transform: 'translateX(500px)'
        }
        ],
        {
        duration: 2000,
        iterations: Number.POSITIVE_INFINITY
        }
        ),
        document.timeline
        ).play();
        

تفاوت در پارامتر اول است که نام Worklet است که این انیمیشن را هدایت می کند.

تشخیص ویژگی

کروم اولین مرورگری است که این ویژگی را ارائه می‌کند، بنابراین باید مطمئن شوید که کد شما فقط انتظار ندارد AnimationWorklet در آنجا باشد. بنابراین قبل از بارگیری worklet، باید با یک بررسی ساده تشخیص دهیم که آیا مرورگر کاربر از AnimationWorklet پشتیبانی می کند یا خیر:

if ('animationWorklet' in CSS) {
  // AnimationWorklet is supported!
}

در حال بارگیری کارنامه

Worklet ها مفهوم جدیدی هستند که توسط کارگروه هودینی معرفی شده اند تا ساخت و مقیاس بندی بسیاری از API های جدید را آسان تر کنند. ما جزئیات کارکشت ها را کمی بعدتر پوشش خواهیم داد، اما برای سادگی می توانید در حال حاضر آنها را به عنوان نخ های ارزان و سبک وزن (مانند کارگران) در نظر بگیرید.

قبل از اعلام انیمیشن، باید مطمئن شویم که یک Worklet با نام "passthrough" بارگذاری کرده ایم:

// index.html
await CSS.animationWorklet.addModule('passthrough-aw.js');
// ... WorkletAnimation initialization from above ...

// passthrough-aw.js
registerAnimator(
  'passthrough',
  class {
    animate(currentTime, effect) {
      effect.localTime = currentTime;
    }
  }
);

اینجا چه خبر است؟ ما در حال ثبت یک کلاس به عنوان انیماتور با استفاده از فراخوانی AnimationWorklet's registerAnimator() هستیم و نام آن را "passthrough" می گذاریم. این همان نامی است که در سازنده WorkletAnimation() در بالا استفاده کردیم. هنگامی که ثبت نام کامل شد، وعده ای که توسط addModule() برگردانده شده است حل می شود و ما می توانیم با استفاده از آن Worklet شروع به ساخت انیمیشن کنیم.

متد animate() نمونه ما برای هر فریمی که مرورگر می‌خواهد رندر کند فراخوانی می‌شود، و currentTime از جدول زمانی انیمیشن و همچنین افکتی که در حال حاضر در حال پردازش است، عبور می‌کند. ما فقط یک افکت داریم، KeyframeEffect و currentTime برای تنظیم localTime افکت استفاده می کنیم، از این رو به این انیماتور "passthrough" می گویند. با این کد برای Worklet، WAAPI و AnimationWorklet فوق دقیقاً یکسان رفتار می کنند، همانطور که در نسخه نمایشی مشاهده می کنید.

زمان

پارامتر currentTime متد animate() ما currentTime خط زمانی است که به سازنده WorkletAnimation() ارسال کردیم. در مثال قبلی، ما فقط آن زمان را به اثر گذراندیم. اما از آنجایی که این کد جاوا اسکریپت است و می توانیم زمان را تحریف کنیم

function remap(minIn, maxIn, minOut, maxOut, v) {
  return ((v - minIn) / (maxIn - minIn)) * (maxOut - minOut) + minOut;
}
registerAnimator(
  'sin',
  class {
    animate(currentTime, effect) {
      effect.localTime = remap(
        -1,
        1,
        0,
        2000,
        Math.sin((currentTime * 2 * Math.PI) / 2000)
      );
    }
  }
);

ما Math.sin() currentTime را می گیریم و آن مقدار را به محدوده [0; 2000]، که محدوده زمانی است که اثر ما برای آن تعریف شده است. اکنون انیمیشن بسیار متفاوت به نظر می رسد ، بدون اینکه فریم های کلیدی یا گزینه های انیمیشن را تغییر داده باشید. کد کار می‌تواند به‌طور دلخواه پیچیده باشد و به شما اجازه می‌دهد تا به‌طور برنامه‌نویسی تعریف کنید که کدام افکت‌ها به ترتیب و به چه میزان پخش می‌شوند.

گزینه ها روی گزینه ها

ممکن است بخواهید دوباره از یک Worklet استفاده کنید و شماره آن را تغییر دهید. به همین دلیل سازنده WorkletAnimation به شما اجازه می دهد یک شی گزینه را به worklet ارسال کنید:

registerAnimator(
  'factor',
  class {
    constructor(options = {}) {
      this.factor = options.factor || 1;
    }
    animate(currentTime, effect) {
      effect.localTime = currentTime * this.factor;
    }
  }
);

new WorkletAnimation(
  'factor',
  new KeyframeEffect(
    document.querySelector('#b'),
    [
      /* ... same keyframes as before ... */
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY,
    }
  ),
  document.timeline,
  {factor: 0.5}
).play();

در این مثال ، هر دو انیمیشن با یک کد، اما با گزینه‌های متفاوت هدایت می‌شوند.

ایالت محلی خود را بدهید!

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

registerAnimator(
  'randomspin',
  class {
    constructor(options = {}, state = {}) {
      this.direction = state.direction || (Math.random() > 0.5 ? 1 : -1);
    }
    animate(currentTime, effect) {
      // Some math to make sure that `localTime` is always > 0.
      effect.localTime = 2000 + this.direction * (currentTime % 2000);
    }
    destroy() {
      return {
        direction: this.direction,
      };
    }
  }
);

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

قلاب زدن به پیوستار فضا-زمان: ScrollTimeline

همانطور که در بخش قبل نشان داده شد، AnimationWorklet به ما اجازه می دهد تا به طور برنامه نویسی تعریف کنیم که چگونه پیشبرد جدول زمانی بر اثرات انیمیشن تاثیر می گذارد. اما تا کنون، جدول زمانی ما همیشه document.timeline بوده است که زمان را ردیابی می کند.

ScrollTimeline امکانات جدیدی را باز می کند و به شما امکان می دهد به جای زمان، انیمیشن ها را با پیمایش رانندگی کنید. ما می‌خواهیم از اولین ورکلت "عبور" خود برای این دمو دوباره استفاده کنیم:

new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
    ],
    {
      duration: 2000,
      fill: 'both',
    }
  ),
  new ScrollTimeline({
    scrollSource: document.querySelector('main'),
    orientation: 'vertical', // "horizontal" or "vertical".
    timeRange: 2000,
  })
).play();

به جای عبور document.timeline ، یک ScrollTimeline جدید ایجاد می کنیم. شاید حدس زده باشید، ScrollTimeline از زمان استفاده نمی کند، بلکه از موقعیت اسکرول scrollSource برای تنظیم currentTime در Worklet استفاده می کند. اسکرول شدن تا انتها به سمت بالا (یا چپ) به معنی currentTime = 0 است، در حالی که پیمایش تا آخر به پایین (یا راست) currentTime روی timeRange تنظیم می کند. اگر کادر را در این نسخه نمایشی اسکرول کنید، می توانید موقعیت کادر قرمز را کنترل کنید.

اگر یک ScrollTimeline با عنصری ایجاد کنید که پیمایش نمی کند، currentTime خط زمانی NaN خواهد بود. بنابراین، به‌ویژه با در نظر گرفتن طراحی واکنش‌گرا، همیشه باید برای NaN به عنوان currentTime خود آماده باشید. اغلب معقول است که مقدار پیش فرض 0 باشد.

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

زیر کاپوت

ورکلت ها

Worklet ها زمینه های جاوا اسکریپت با یک محدوده ایزوله و یک سطح API بسیار کوچک هستند. سطح کوچک API امکان بهینه سازی تهاجمی تر از مرورگر را به خصوص در دستگاه های ارزان قیمت فراهم می کند. علاوه بر این، Worklet ها به یک حلقه رویداد خاص محدود نمی شوند، اما می توانند در صورت لزوم بین رشته ها حرکت کنند. این امر به ویژه برای AnimationWorklet بسیار مهم است.

Compositor NSync

ممکن است بدانید که برخی از ویژگی‌های CSS سریع متحرک می‌شوند، در حالی که برخی دیگر اینطور نیستند. برخی از ویژگی‌ها برای متحرک شدن فقط به مقداری کار روی GPU نیاز دارند، در حالی که برخی دیگر مرورگر را مجبور می‌کنند تا کل سند را دوباره طرح‌بندی کند.

در کروم (مانند بسیاری از مرورگرهای دیگر) فرآیندی به نام compositor داریم که وظیفه آن است – و من در اینجا بسیار ساده می‌کنم – مرتب کردن لایه‌ها و بافت‌ها و سپس استفاده از GPU برای به‌روزرسانی صفحه‌نمایش تا حد امکان منظم. ایده آل به همان سرعتی که صفحه می تواند به روز شود (معمولا 60 هرتز). بسته به اینکه کدام ویژگی های CSS متحرک می شوند، ممکن است مرورگر فقط نیاز داشته باشد که ترکیب کننده کار خود را انجام دهد، در حالی که ویژگی های دیگر باید طرح بندی را اجرا کنند، که عملیاتی است که فقط رشته اصلی می تواند انجام دهد. بسته به ویژگی هایی که قصد دارید متحرک سازی کنید، Worklet انیمیشن شما یا به رشته اصلی متصل می شود یا در یک رشته جداگانه و همگام با کامپوزیتور اجرا می شود.

به مچ دست بزنید

معمولاً فقط یک پردازش ترکیبی وجود دارد که به طور بالقوه در چندین تب به اشتراک گذاشته می شود، زیرا GPU یک منبع بسیار مورد بحث است. اگر کامپوزیتور به نحوی مسدود شود، کل مرورگر متوقف می شود و به ورودی کاربر پاسخ نمی دهد. باید به هر قیمتی از این امر اجتناب کرد. بنابراین چه اتفاقی می‌افتد اگر Worklet شما نتواند داده‌های مورد نیاز compositor را به موقع برای رندر شدن فریم ارائه دهد؟

اگر این اتفاق بیفتد، Worklet مجاز است - بر اساس مشخصات - "لغزش" کند. از کامپوزیتور عقب می ماند و کامپوزیتور مجاز است از داده های آخرین فریم مجددا استفاده کند تا نرخ فریم را بالا نگه دارد. از نظر بصری، این مانند jank به نظر می رسد، اما تفاوت بزرگ این است که مرورگر هنوز به ورودی کاربر پاسخ می دهد.

نتیجه گیری

جنبه های زیادی برای AnimationWorklet و مزایای آن برای وب وجود دارد. مزایای آشکار آن کنترل بیشتر بر روی انیمیشن ها و روش های جدید برای هدایت انیمیشن ها برای آوردن سطح جدیدی از وفاداری بصری به وب است. اما طراحی APIها همچنین به شما این امکان را می‌دهد که برنامه خود را در برابر jank انعطاف‌پذیرتر کنید و در عین حال به همه خوبی‌های جدید دسترسی داشته باشید.

Animation Worklet در Canary است و ما به دنبال نسخه آزمایشی Origin با Chrome 71 هستیم. مشتاقانه منتظر تجربیات جدید وب عالی شما و شنیدن چیزهایی هستیم که می توانیم بهبود دهیم. همچنین یک polyfill وجود دارد که همان API را به شما می‌دهد، اما انزوای عملکرد را ارائه نمی‌کند.

به خاطر داشته باشید که CSS Transitions و CSS Animations هنوز گزینه های معتبری هستند و می توانند برای انیمیشن های پایه بسیار ساده تر باشند. اما اگر نیاز دارید که به کارهای فانتزی بروید، AnimationWorklet پشت شماست!