مقدمه‌ای بر انیمیشن‌های Canvas

گردآوری و تالیف : ارسطو عباسی
تاریخ انتشار : 24 فروردین 1397
دسته بندی ها : جاوا اسکریپت

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

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

برای شروع ابتدا باید یک فایل HTML را با نام index.html ایجاد کنیم و کدهای زیر را در آن قرار دهیم:

<!doctype html>
<html>
  <head>
    <title>Demo</title>
    <style>
      body {
        padding: 0;
        margin: 0;
        background-color: #000;
      }
    </style>
  </head>
  <body>
    <canvas></canvas>
    <script></script>
  </body>
</html>

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

(() => {
  const canvas = document.querySelector('canvas');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
})();

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

(() => {
  const canvas = document.querySelector('canvas');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const context = canvas.getContext('2d'); // Set the context to create a 2D graphic
  context.translate(canvas.width / 2, canvas.height / 2); // The starting point for the graphic to be in the middle

  context.beginPath(); // Start to create a shape
  context.arc(0, 0, 50, 0, 2 * Math.PI, false); // Draw a circle, at point [0, 0] with a radius of 50, and make it 360 degrees (aka 2PI)
  context.fillStyle = '#8ED6FF'; // Set the color of the circle, make it a cool AI blue
  context.fill(); // Actually fill the shape in and close it
})();

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

(() => {
  const canvas = document.querySelector('canvas');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const context = canvas.getContext('2d'); // Set the context to create a 2D graphic
  const color = '#8ED6FF';

  context.translate(canvas.width / 2, canvas.height / 2); // The starting point for the graphic to be in the middle
  context.beginPath(); // Start to create a shape
  context.arc(0, 0, 50, 0, 2 * Math.PI, false); // Draw a circle, at point [0, 0] with a radius of 50, and make it 360 degrees (aka 2PI)
  context.strokeStyle = color; // Set the stroke color
  context.shadowColor = color; // Set the "glow" color
  context.lineWidth = 10; // Set the circle/ring width
  context.shadowBlur = 60; // Set the amount of "glow"
  context.stroke(); // Draw the shape and close it
})();

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

(() => {
  const canvas = document.querySelector('canvas');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const context = canvas.getContext('2d'); // Set the context to create a 2D graphic
  const color = '#8ED6FF';

  const drawCircle = () => {
    context.translate(canvas.width / 2, canvas.height / 2); // The starting point for the graphic to be in the middle
    context.beginPath(); // Start to create a shape
    context.arc(0, 0, 50, 0, 2 * Math.PI, false); // Draw a circle, at point [0, 0] with a radius of 50, and make it 360 degrees (aka 2PI)
    context.strokeStyle = color; // Set the stroke color
    context.shadowColor = color; // Set the "glow" color
    context.lineWidth = 10; // Set the circle/ring width
    context.shadowBlur = 60; // Set the amount of "glow"
    context.stroke(); // Draw the shape and close it
    window.requestAnimationFrame(drawCircle); // Continue drawing circle
  };

  window.requestAnimationFrame(drawCircle); // Start animation
})();

این مورد هنوز هیچ انیمیشنی را ایجاد نکرده است، اما اگر یک console.log در تابع drawCircle قرار دهید متوجه می‌شوید که بعد از تقریبا هر ۶۰ ثانیه یکبار لاگ‌هایی را مشاهده می‌کنید. قدم بعدی آن است که حالت‌هایی را به این تابع اضافه کنید و اندازه‌هایی را تغییر دهید. می‌توانید اینکار با ساخت یک متغیر size از نوع integer بسازیم که هر بار آن را بالا و پایین می‌آوریم. جدای از آن یک متغیر از نوع boolean ایجاد می‌کنیم که می‌توانیم با آن مسیر رشد را بررسی کنیم.

(() => {
  const canvas = document.querySelector('canvas');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const context = canvas.getContext('2d'); // Set the context to create a 2D graphic
  const color = '#8ED6FF';

  let size = 50; // Default size is the minimum size
  let grow = true;

  const drawCircle = () => {
    context.translate(canvas.width / 2, canvas.height / 2); // The starting point for the graphic to be in the middle
    context.beginPath(); // Start to create a shape
    context.arc(0, 0, size, 0, 2 * Math.PI, false); // Draw a circle, at point [0, 0] with a radius of 50, and make it 360 degrees (aka 2PI)
    context.strokeStyle = color; // Set the stroke color
    context.shadowColor = color; // Set the "glow" color
    context.lineWidth = 10; // Set the circle/ring width
    context.shadowBlur = size + 10; // Set the amount of "glow"
    context.stroke(); // Draw the shape and close it

    // Check if the size needs to grow or shrink
    if (size <= 50) { // Minimum size
      grow = true;
    } else if (size >= 75) { // Maximum size
      grow = false;
    }

    // Grow or shrink the size
    size = size + (grow ? 1 : -1);

    window.requestAnimationFrame(drawCircle); // Continue drawing circle
  };

  window.requestAnimationFrame(drawCircle); // Start animation
})();

نوسازی کردن صفحه و مشاهده نکردن هیچ خروجی ممکن است کمی دلهره آور باشد! اما نگران نباشید. Canvasها نیاز دارند برای کارکردن دوباره از ابتدا تمیز شوند. حال باید یک راه حل پیدا کنیم که Canvas بروزرسانی و تغییر پیدا کند اما باید راهی را پیدا کنیم که با استفاده از آن Canvas را به روز اول برگردانیم.

(() => {
  const canvas = document.querySelector('canvas');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const context = canvas.getContext('2d'); // Set the context to create a 2D graphic
  const color = '#8ED6FF';

  let size = 50; // Default size is the minimum size
  let grow = true;

  const drawCircle = () => {
    context.clearRect(0, 0, canvas.width, canvas.height); // Clear the contents of the canvas starting from [0, 0] all the way to the [totalWidth, totalHeight]

    context.translate(canvas.width / 2, canvas.height / 2); // The starting point for the graphic to be in the middle
    context.beginPath(); // Start to create a shape
    context.arc(0, 0, size, 0, 2 * Math.PI, false); // Draw a circle, at point [0, 0] with a radius of 50, and make it 360 degrees (aka 2PI)
    context.strokeStyle = color; // Set the stroke color
    context.shadowColor = color; // Set the "glow" color
    context.lineWidth = 10; // Set the circle/ring width
    context.shadowBlur = size + 10; // Set the amount of "glow"
    context.stroke(); // Draw the shape and close it

    // Check if the size needs to grow or shrink
    if (size <= 50) { // Minimum size
      grow = true;
    } else if (size >= 75) { // Maximum size
      grow = false;
    }

    // Grow or shrink the size
    size = size + (grow ? 1 : -1);

    window.requestAnimationFrame(drawCircle); // Continue drawing circle
  };

  window.requestAnimationFrame(drawCircle); // Start animation
})();

حال به نظر کدها کمی پیچیده شدند. تابع context.translate را به یاد می‌آورید؟ این مورد باعث می‌شود که تمام المان‌ها به قسمت مرکزی canvas منتقل شوند. حال ما نیاز داریم قبل از انجام هر کار، وضعیت Canvas را ذخیره کنیم.

(() => {
  const canvas = document.querySelector('canvas');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const context = canvas.getContext('2d'); // Set the context to create a 2D graphic
  const color = '#8ED6FF';

  let size = 50; // Default size is the minimum size
  let grow = true;

  const drawCircle = () => {
    context.restore(); // Restore previous canvas state, does nothing if it wasn't saved before
    context.save(); // Saves it until the next time `drawCircle` is called

    context.clearRect(0, 0, canvas.width, canvas.height); // Clear the contents of the canvas starting from [0, 0] all the way to the [totalWidth, totalHeight]

    context.translate(canvas.width / 2, canvas.height / 2); // The starting point for the graphic to be in the middle
    context.beginPath(); // Start to create a shape
    context.arc(0, 0, size, 0, 2 * Math.PI, false); // Draw a circle, at point [0, 0] with a radius of 50, and make it 360 degrees (aka 2PI)
    context.strokeStyle = color; // Set the stroke color
    context.shadowColor = color; // Set the "glow" color
    context.lineWidth = 10; // Set the circle/ring width
    context.shadowBlur = size + 10; // Set the amount of "glow"
    context.stroke(); // Draw the shape and close it

    // Check if the size needs to grow or shrink
    if (size <= 50) { // Minimum size
      grow = true;
    } else if (size >= 75) { // Maximum size
      grow = false;
    }

    // Grow or shrink the size
    size = size + (grow ? 1 : -1);

    window.requestAnimationFrame(drawCircle); // Continue drawing circle
  };

  window.requestAnimationFrame(drawCircle); // Start animation
})();

حال صفحه را مشاهده کنید، یکسری انیمیشن‌های بسیار جذاب در اختیار داریم. کد نهایی باید چیزی شبیه به مورد زیر باشد:

<!doctype html>
<html>
  <head>
    <title>Demo</title>
    <style>
      body {
        padding: 0;
        margin: 0;
        background-color: #000;
      }
    </style>
  </head>
  <body>
    <canvas></canvas>
    <script>
      (() => {
        const canvas = document.querySelector('canvas');
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        const context = canvas.getContext('2d'); // Set the context to create a 2D graphic
        const color = '#8ED6FF';

        let size = 50; // Default size is the minimum size
        let grow = true;

        const drawCircle = () => {
          context.restore();
          context.save();
          context.clearRect(0, 0, canvas.width, canvas.height); // Clear the contents of the canvas starting from [0, 0] all the way to the [totalWidth, totalHeight]

          context.translate(canvas.width / 2, canvas.height / 2); // The starting point for the graphic to be in the middle
          context.beginPath(); // Start to create a shape
          context.arc(0, 0, size, 0, 2 * Math.PI, false); // Draw a circle, at point [0, 0] with a radius of 50, and make it 360 degrees (aka 2PI)
          context.strokeStyle = color; // Set the stroke color
          context.shadowColor = color; // Set the "glow" color
          context.lineWidth = 10; // Set the circle/ring width
          context.shadowBlur = size + 10; // Set the amount of "glow"
          context.stroke(); // Draw the shape and close it

          // Check if the size needs to grow or shrink
          if (size <= 50) { // Minimum size
            grow = true;
          } else if (size >= 75) { // Maximum size
            grow = false;
          }

          // Grow or shrink the size
          size = size + (grow ? 1 : -1);

          window.requestAnimationFrame(drawCircle); // Continue drawing circle
        };

        window.requestAnimationFrame(drawCircle); // Start animation
      })();
    </script>
  </body>
</html>

خوشبختانه این مورد باعث می‌شود که درک کلی روی انیمیشن‌های Canvas پیدا کنید و بتوانید از آن الهام بگیرید. قدم بعدی که باید در نظر بگیرید آن است که این کدها را به یک رویداد یا مورد خاصی متصل کنید.

منبع

برچسب :
این مطلب را با دیگران به اشتراک بگذارید :

مقالات پیشنهادی

مقدمه‌ای بر تست پروژه لاراولی بوسیله Laravel Dusk

یکی از بزرگترین مشکلاتی که در PHPUnit وجود داشت این بود که تست نرم‌افزارهای برپایه JavaScript غیرممکن بود. با Dusk میتونید براحتی ویژگی‌های Client-sid...

مقدمه‌ای بر استفاده از Vue.js در لاراول

در این مقاله میخوایم یک پروژه تستی بسازیم و در اون بتونیم یکسری اطلاعات رو به دیتابیس بفرستیم یا از دیتابیس حذف کنیم. این کار معمولی هست اما ما میخوای...

مقدمه‌ای بر رندر پیشرفته تصاویر

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

مقدمه‌ای بر YEOMAN

توسعه دهندگان مختلف همیشه از راه های مختلفی کارهای‌شان را انجام می دهند، اما همیشه یک موضوع وجود دارد و آن این است که صرف نظر از نام و نوع فریمورک یا...