6 сентябр 2025

JavaScript animatsiyalari

JavaScript animatsiyalari CSS bilan amalga oshirib bo’lmaydigan narsalarni boshqarishi mumkin.

Masalan, murakkab yo’l bo’ylab harakat qilish, Bezier egri chiziqlaridan farqli timing funksiyasi bilan yoki canvas ustida animatsiya.

setInterval dan foydalanish

Animatsiyani kadrlar ketma-ketligi sifatida amalga oshirish mumkin – odatda HTML/CSS xususiyatlarining kichik o’zgarishlari.

Masalan, style.leftni 0pxdan 100pxgacha o’zgartirish elementni harakatga keltiradi. Va agar biz uni setIntervalda oshirsak, sekundiga taxminan 50 marta 2px ga kichik kechikish bilan o’zgartirsak, u silliq ko’rinadi. Bu kinematografiya bilan bir xil printsip: sekundiga 24 kadr silliq ko’rinish uchun etarli.

Psevdo-kod quyidagicha ko’rinishi mumkin:

let timer = setInterval(function() {
  if (animation complete) clearInterval(timer);
  else increase style.left by 2px
}, 20); // har 20ms da 2px ga o'zgartirish, sekundiga taxminan 50 kadr

Animatsiyaning to’liqroq misoli:

let start = Date.now(); // boshlanish vaqtini eslab qolish

let timer = setInterval(function() {
  // boshlanishdan qancha vaqt o'tdi?
  let timePassed = Date.now() - start;

  if (timePassed >= 2000) {
    clearInterval(timer); // 2 soniyadan keyin animatsiyani tugatish
    return;
  }

  // timePassed momentida animatsiyani chizish
  draw(timePassed);

}, 20);

// timePassed 0 dan 2000 gacha o'tganda
// left qiymatlari 0px dan 400px gacha oladi
function draw(timePassed) {
  train.style.left = timePassed / 5 + 'px';
}

Demo uchun bosing:

Natija
index.html
<!DOCTYPE html>
<html>
  <head>
    <style>
      #train {
        position: relative;
        cursor: pointer;
      }
    </style>
  </head>

  <body>
    <img id="train" src="https://js.cx/clipart/train.gif" />

    <script>
      train.onclick = function () {
        let start = Date.now();

        let timer = setInterval(function () {
          let timePassed = Date.now() - start;

          train.style.left = timePassed / 5 + "px";

          if (timePassed > 2000) clearInterval(timer);
        }, 20);
      };
    </script>
  </body>
</html>

requestAnimationFrame dan foydalanish

Faraz qilaylik, bizda bir vaqtning o’zida bir nechta animatsiyalar ishlayapti.

Agar biz ularni alohida ishga tushirsak, har birida setInterval(..., 20) bo’lsa ham, brauzer har 20msdan ko’ra tez-tez qayta chizishga majbur bo’ladi.

Buning sababi ular turli boshlanish vaqtiga ega, shuning uchun “har 20ms” turli animatsiyalar o’rtasida farq qiladi. Intervallar bir xil emas. Shunday qilib, 20ms ichida bir nechta mustaqil ishga tushirish bo’ladi.

Boshqacha qilib aytganda, bu:

setInterval(function() {
  animate1();
  animate2();
  animate3();
}, 20)

…Uchta mustaqil chaqiruvdan yengilroq:

setInterval(animate1, 20); // mustaqil animatsiyalar
setInterval(animate2, 20); // skriptning turli joylarida
setInterval(animate3, 20);

Bu bir nechta mustaqil qayta chizishlar birlashtirilishi kerak, brauzer uchun qayta chizishni osonlashtirish va shu bilan CPU yukini kamaytirish va silliqroq ko’rinish uchun.

Yodda tutish kerak bo’lgan yana bir narsa bor. Ba’zida CPU yuklanadi yoki kamroq tez qayta chizish uchun boshqa sabablar bor (masalan, brauzer yorlig’i yashiringanda), shuning uchun biz uni har 20msda ishlatmasligimiz kerak.

Lekin buni JavaScript-da qanday bilamiz? Animation timing spetsifikatsiyasi mavjud bo’lib, u requestAnimationFrame funksiyasini taqdim etadi. U bu barcha masalalarni va undan ham ko’pini hal qiladi.

Sintaksis:

let requestId = requestAnimationFrame(callback)

Bu brauzer animatsiya qilmoqchi bo’lgan eng yaqin vaqtda callback funksiyasini ishga tushirishni rejalashtiradi.

Agar biz callbackda elementlarda o’zgarishlar qilsak, ular boshqa requestAnimationFrame callback-lari va CSS animatsiyalari bilan birlashtiriladi. Shunday qilib, ko’p marta emas, balki bitta geometriya qayta hisoblanishi va qayta chizilishi bo’ladi.

Qaytarilgan qiymat requestId chaqiruvni bekor qilish uchun ishlatilishi mumkin:

// callback-ning rejalashtirilgan bajarilishini bekor qilish
cancelAnimationFrame(requestId);

callback bitta argumentni oladi – sahifa yuklanishining boshlanishidan o’tgan vaqt mikrosoniyalarda. Bu vaqtni performance.now() ni chaqirish orqali ham olish mumkin.

Odatda callback juda tez ishlaydi, CPU yuklanmagan yoki noutbukning batareyasi deyarli tugamagan yoki boshqa sabab bo’lmasa.

Quyidagi kod requestAnimationFrame uchun dastlabki 10 ta ishga tushirish orasidagi vaqtni ko’rsatadi. Odatda bu 10-20ms:

<script>
  let prev = performance.now();
  let times = 0;

  requestAnimationFrame(function measure(time) {
    document.body.insertAdjacentHTML("beforeEnd", Math.floor(time - prev) + " ");
    prev = time;

    if (times++ < 10) requestAnimationFrame(measure);
  })
</script>

Tuzilgan animatsiya

Endi biz requestAnimationFrame asosida universal animatsiya funksiyasini yaratishimiz mumkin:

function animate({timing, draw, duration}) {

  let start = performance.now();

  requestAnimationFrame(function animate(time) {
    // timeFraction 0 dan 1 gacha
    let timeFraction = (time - start) / duration;
    if (timeFraction > 1) timeFraction = 1;

    // joriy animatsiya holatini hisoblab chiqish
    let progress = timing(timeFraction)

    draw(progress); // uni chizish

    if (timeFraction < 1) {
      requestAnimationFrame(animate);
    }

  });
}

animate funksiyasi animatsiyani asosan tavsiflayotgan 3ta parametrni qabul qiladi:

duration

Animatsiyaning umumiy vaqti. Masalan, 1000.

timing(timeFraction)

Timing funksiyasi, CSS-xususiyati transition-timing-function kabi, o’tgan vaqtning qismini (boshlanishda 0, oxirida 1) oladi va animatsiya tugallanishini qaytaradi (Bezier egri chizig’idagi y kabi).

Masalan, chiziqli funksiya animatsiya bir xil tezlik bilan bir tekis davom etishini bildiradi:

function linear(timeFraction) {
  return timeFraction;
}

Uning grafigi:

Bu transition-timing-function: linear kabi. Quyida ko’rsatilgan qiziqarli variantlar bor.

draw(progress)

Animatsiya tugallanish holatini oladigan va uni chizadigan funksiya. progress=0 qiymati boshlang’ich animatsiya holatini, progress=1 esa oxirgi holatni bildiradi.

Bu animatsiyani haqiqatan ham chizadigan funksiya.

U elementni harakatga keltirishi mumkin:

function draw(progress) {
  train.style.left = progress + 'px';
}

…Yoki boshqa istalgan narsani qilishi mumkin, biz har qanday narsani, har qanday usulda animatsiya qilishimiz mumkin.

Keling, funksiyamizdan foydalanib element widthini 0dan 100%gacha animatsiya qilaylik.

Demo uchun elementga bosing:

Natija
animate.js
index.html
function animate({ duration, draw, timing }) {
  let start = performance.now();

  requestAnimationFrame(function animate(time) {
    let timeFraction = (time - start) / duration;
    if (timeFraction > 1) timeFraction = 1;

    let progress = timing(timeFraction);

    draw(progress);

    if (timeFraction < 1) {
      requestAnimationFrame(animate);
    }
  });
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <style>
      progress {
        width: 5%;
      }
    </style>
    <script src="animate.js"></script>
  </head>

  <body>
    <progress id="elem"></progress>

    <script>
      elem.onclick = function () {
        animate({
          duration: 1000,
          timing: function (timeFraction) {
            return timeFraction;
          },
          draw: function (progress) {
            elem.style.width = progress * 100 + "%";
          },
        });
      };
    </script>
  </body>
</html>

Uning kodi:

animate({
  duration: 1000,
  timing(timeFraction) {
    return timeFraction;
  },
  draw(progress) {
    elem.style.width = progress * 100 + '%';
  }
});

CSS animatsiyasidan farqli o’laroq, biz bu yerda har qanday timing funksiyasi va har qanday chizish funksiyasini yaratishimiz mumkin. Timing funksiyasi Bezier egri chiziqlari bilan cheklanmagan. Va draw xususiyatlardan tashqariga chiqishi mumkin, masalan, otashin animatsiyasi yoki shunga o’xshash narsalar uchun yangi elementlar yaratishi mumkin.

Timing funksiyalari

Biz yuqorida eng oddiy, chiziqli timing funksiyasini ko’rdik.

Keling, ulardan ko’proqini ko’raylik. Biz ular qanday ishlashini ko’rish uchun turli timing funksiyalari bilan harakat animatsiyalarini sinab ko’ramiz.

n darajasi

Agar biz animatsiyani tezlashtirmoqchi bo’lsak, progressni n darajasida ishlatishimiz mumkin.

Masalan, parabola egri chizig’i:

function quad(timeFraction) {
  return Math.pow(timeFraction, 2)
}

Grafik:

Harakatda ko’ring (faollashtirish uchun bosing):

…Yoki kub egri chizig’i yoki undan ham katta n. Darajani oshirish tezroq harakat qilishga olib keladi.

Mana progressning 5 darajasidagi grafigi:

Harakatda:

Yoy

Funksiya:

function circ(timeFraction) {
  return 1 - Math.sin(Math.acos(timeFraction));
}

Grafik:

Orqaga: kamon otish

Bu funksiya "kamon otish"ni amalga oshiradi. Avval biz “kamon torini tortamiz”, keyin “otamiz”.

Oldingi funksiyalardan farqli o’laroq, u qo’shimcha parametr x – "elastiklik koeffitsienti"ga bog’liq. “Kamon torini tortish” masofasi uni belgilaydi.

Kod:

function back(x, timeFraction) {
  return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x)
}

x = 1.5 uchun grafik:

Animatsiya uchun biz uni xning aniq qiymati bilan ishlatamiz. x = 1.5 uchun misol:

Sakrash

Tasavvur qiling, biz to’pni tashlaymiz. U pastga tushadi, keyin bir necha marta sakrab qaytadi va to’xtaydi.

bounce funksiyasi xuddi shunday qiladi, lekin teskari tartibda: “sakrash” darhol boshlanadi. U buning uchun bir nechta maxsus koeffitsientlardan foydalanadi:

function bounce(timeFraction) {
  for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
    if (timeFraction >= (7 - 4 * a) / 11) {
      return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
    }
  }
}

Harakatda:

Elastik animatsiya

“Boshlang’ich diapazon” uchun qo’shimcha parametr xni qabul qiladigan yana bitta “elastik” funksiya.

function elastic(x, timeFraction) {
  return Math.pow(2, 10 * (timeFraction - 1)) * Math.cos(20 * Math.PI * x / 3 * timeFraction)
}

x=1.5 uchun grafik:

x=1.5 uchun harakatda:

Teskari: ease*

Shunday qilib, bizda timing funksiyalari to’plami bor. Ularning to’g’ridan-to’g’ri qo’llanilishi “easeIn” deb ataladi.

Ba’zan biz animatsiyani teskari tartibda ko’rsatishimiz kerak. Bu “easeOut” transformatsiyasi bilan amalga oshiriladi.

easeOut

“easeOut” rejimida timing funksiyasi timingEaseOut wrapper-ga o’raladi:

timingEaseOut(timeFraction) = 1 - timing(1 - timeFraction)

Boshqacha qilib aytganda, bizda “oddiy” timing funksiyasini oladigan va uning atrofida wrapper qaytaradigan “transform” funksiyasi makeEaseOut bor:

// timing funksiyasini qabul qiladi, o'zgartirilgan variantni qaytaradi
function makeEaseOut(timing) {
  return function(timeFraction) {
    return 1 - timing(1 - timeFraction);
  }
}

Masalan, biz yuqorida tasvirlangan bounce funksiyasini olib uni qo’llashimiz mumkin:

let bounceEaseOut = makeEaseOut(bounce);

Keyin sakrash animatsiyaning boshida emas, balki oxirida bo’ladi. Yanada yaxshi ko’rinadi:

Natija
style.css
index.html
#brick {
  width: 40px;
  height: 20px;
  background: #ee6b47;
  position: relative;
  cursor: pointer;
}

#path {
  outline: 1px solid #e8c48e;
  width: 540px;
  height: 20px;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="style.css" />
    <script src="https://js.cx/libs/animate.js"></script>
  </head>

  <body>
    <div id="path">
      <div id="brick"></div>
    </div>

    <script>
      function makeEaseOut(timing) {
        return function (timeFraction) {
          return 1 - timing(1 - timeFraction);
        };
      }

      function bounce(timeFraction) {
        for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
          if (timeFraction >= (7 - 4 * a) / 11) {
            return (
              -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) +
              Math.pow(b, 2)
            );
          }
        }
      }

      let bounceEaseOut = makeEaseOut(bounce);

      brick.onclick = function () {
        animate({
          duration: 3000,
          timing: bounceEaseOut,
          draw: function (progress) {
            brick.style.left = progress * 500 + "px";
          },
        });
      };
    </script>
  </body>
</html>

Bu yerda biz transformatsiya funksiya harakatini qanday o’zgartirishini ko’rishimiz mumkin:

Agar boshida animatsiya effekti bo’lsa, masalan, sakrash – u oxirida ko’rsatiladi.

Yuqoridagi grafikda oddiy bounce qizil rang bilan, easeOut bounce esa ko’k rang bilan.

  • Oddiy bounce – ob’ekt pastda sakraydi, keyin oxirida keskin tepaga sakraydi.
  • easeOut dan keyin – u avval tepaga sakraydi, keyin u yerda sakraydi.

easeInOut

Shuningdek, biz effektni animatsiyaning boshida ham, oxirida ham ko’rsatishimiz mumkin. Bu transformatsiya “easeInOut” deb ataladi.

Timing funksiyasi berilgan holda, biz animatsiya holatini quyidagicha hisoblaymiz:

if (timeFraction <= 0.5) { // animatsiyaning birinchi yarmi
  return timing(2 * timeFraction) / 2;
} else { // animatsiyaning ikkinchi yarmi
  return (2 - timing(2 * (1 - timeFraction))) / 2;
}

Wrapper kodi:

function makeEaseInOut(timing) {
  return function(timeFraction) {
    if (timeFraction < .5)
      return timing(2 * timeFraction) / 2;
    else
      return (2 - timing(2 * (1 - timeFraction))) / 2;
  }
}

bounceEaseInOut = makeEaseInOut(bounce);

Harakatda, bounceEaseInOut:

Natija
style.css
index.html
#brick {
  width: 40px;
  height: 20px;
  background: #ee6b47;
  position: relative;
  cursor: pointer;
}

#path {
  outline: 1px solid #e8c48e;
  width: 540px;
  height: 20px;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="style.css" />
    <script src="https://js.cx/libs/animate.js"></script>
  </head>

  <body>
    <div id="path">
      <div id="brick"></div>
    </div>

    <script>
      function makeEaseInOut(timing) {
        return function (timeFraction) {
          if (timeFraction < 0.5) return timing(2 * timeFraction) / 2;
          else return (2 - timing(2 * (1 - timeFraction))) / 2;
        };
      }

      function bounce(timeFraction) {
        for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
          if (timeFraction >= (7 - 4 * a) / 11) {
            return (
              -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) +
              Math.pow(b, 2)
            );
          }
        }
      }

      let bounceEaseInOut = makeEaseInOut(bounce);

      brick.onclick = function () {
        animate({
          duration: 3000,
          timing: bounceEaseInOut,
          draw: function (progress) {
            brick.style.left = progress * 500 + "px";
          },
        });
      };
    </script>
  </body>
</html>

“easeInOut” transformatsiyasi ikkita grafikni birlashtiradi: animatsiyaning birinchi yarmi uchun easeIn (oddiy) va ikkinchi qism uchun easeOut (teskari).

Agar biz circ timing funksiyasining easeIn, easeOut va easeInOut grafiklarini solishtirsak, effekt aniq ko’rinadi:

  • Qizilcirc (easeIn) ning oddiy varianti.
  • YashileaseOut.
  • Ko’keaseInOut.

Ko’rib turganingizdek, animatsiyaning birinchi yarmining grafigi kichraytirilgan easeIn, ikkinchi yarmi esa kichraytirilgan easeOut. Natijada, animatsiya bir xil effekt bilan boshlanadi va tugaydi.

Qiziqarli “draw”

Elementni harakatga keltirish o’rniga boshqa narsani qilishimiz mumkin. Bizga kerak bo’lgan narsa – to’g’ri draw yozish.

Mana animatsiyalangan “sakrash” matn yozishi:

Natija
style.css
index.html
textarea {
  display: block;
  border: 1px solid #bbb;
  color: #444;
  font-size: 110%;
}

button {
  margin-top: 10px;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="style.css" />
    <script src="https://js.cx/libs/animate.js"></script>
  </head>

  <body>
    <textarea id="textExample" rows="5" cols="60">
      U vorpal qilichini qo'liga oldi:
      Uzoq vaqt davomida u manxome dushmanini qidirdi -
      Shunday qilib, u Tumtum daraxti yonida dam oldi,
      Va bir muddat o'ylanib qoldi.
  </textarea
    >

    <button onclick="animateText(textExample)">
      Animatsion yozishni ishga tushiring!
    </button>

    <script>
      function animateText(textArea) {
        let text = textArea.value;
        let to = text.length,
          from = 0;

        animate({
          duration: 5000,
          timing: bounce,
          draw: function (progress) {
            let result = (to - from) * progress + from;
            textArea.value = text.substr(0, Math.ceil(result));
          },
        });
      }

      function bounce(timeFraction) {
        for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
          if (timeFraction >= (7 - 4 * a) / 11) {
            return (
              -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) +
              Math.pow(b, 2)
            );
          }
        }
      }
    </script>
  </body>
</html>

Xulosa

CSS yaxshi boshqara olmaydigan animatsiyalar yoki qat’iy nazorat kerak bo’lganlar uchun JavaScript yordam berishi mumkin. JavaScript animatsiyalari requestAnimationFrame orqali amalga oshirilishi kerak. Bu o’rnatilgan metod brauzer qayta chizishga tayyorlanayotganda callback funksiyasini ishga tushirish imkonini beradi. Odatda bu juda tez, lekin aniq vaqt brauzerga bog’liq.

Sahifa fonda bo’lganda, qayta chizishlar umuman bo’lmaydi, shuning uchun callback ishlamaydi: animatsiya to’xtatiladi va resurslarni iste’mol qilmaydi. Bu ajoyib.

Mana ko’pchilik animatsiyalarni sozlash uchun yordamchi animate funksiyasi:

function animate({timing, draw, duration}) {

  let start = performance.now();

  requestAnimationFrame(function animate(time) {
    // timeFraction 0 dan 1 gacha
    let timeFraction = (time - start) / duration;
    if (timeFraction > 1) timeFraction = 1;

    // joriy animatsiya holatini hisoblash
    let progress = timing(timeFraction);

    draw(progress); // uni chizish

    if (timeFraction < 1) {
      requestAnimationFrame(animate);
    }

  });
}

Opsiyalar:

  • duration – ms da umumiy animatsiya vaqti.
  • timing – animatsiya jarayonini hisoblash funksiyasi. 0 dan 1 gacha vaqt qismini oladi, animatsiya jarayonini qaytaradi, odatda 0 dan 1 gacha.
  • draw – animatsiyani chizish funksiyasi.

Albatta biz uni yaxshilashimiz, ko’proq xususiyatlar qo’shishimiz mumkin, lekin JavaScript animatsiyalari har kuni qo’llanilmaydi. Ular qiziqarli va nostandart narsalar qilish uchun ishlatiladi. Shuning uchun siz kerak bo’lganda kerakli xususiyatlarni qo’shmoqchi bo’lasiz.

JavaScript animatsiyalari har qanday timing funksiyasidan foydalanishi mumkin. Biz ularni yanada ko’p qirrali qilish uchun ko’plab misollar va transformatsiyalarni ko’rib chiqdik. CSS dan farqli o’laroq, biz bu yerda Bezier egri chiziqlari bilan cheklanmaymiz.

draw haqida ham xuddi shunday: biz CSS xususiyatlari emas, balki har qanday narsani animatsiya qilishimiz mumkin.

Vazifalar

Tebranuvchi to’p hosil qiling. Qanday ko’rinishini ko’rish uchun bosing:

Vazifa uchun sandbox-ni oching.

Qaytish uchun maydon ichidagi to’p uchun top va position: absolute CSS xususiyatidan foydalanishimiz mumkin.

Maydonning pastki koordinatasi field.clientHeight. CSS top xususiyati to’pning yuqori chetiga ishora qiladi. Demak, u 0 dan field.clientHeight - ball.clientHeight gacha borishi kerak, bu to’pning yuqori chetining oxirgi eng past holati.

O'tish effektini olish uchun biz easeOut rejimida bounce vaqt funksiyasidan foydalanishimiz mumkin.

Mana animatsiyaning yakuniy kodi:

let to = field.clientHeight - ball.clientHeight;

animate({
  duration: 2000,
  timing: makeEaseOut(bounce),
  draw(progress) {
    ball.style.top = to * progress + "px";
  },
});

Yechimni sandbox-da oching.

To’pni o’ngga sakrashga harakat qiling. Shunga o’xshash:

Animatsiya kodini yozing. Chapdagi masofa 100px.

Manba sifatida oldingi Saqlayotgan to'pni jonlantiring topshirig’ining yechimini oling.

Saqlayotgan to'pni jonlantiring topshirig’ida bizda jonlantirish uchun faqat bitta xususiyat bor edi. Endi bizga yana bitta kerak: elem.style.left.

Gorizontal koordinata boshqa qonun bilan o’zgaradi: u sakrash emas, balki to’pni o’ngga siljitishni asta-sekin oshiradi.

Buning uchun yana bitta animate yozishimiz mumkin.

Vaqt funksiyasi sifatida biz linear dan foydalanishimiz mumkin, ammo makeEaseOut(quad) kabi narsa ancha yaxshi ko’rinadi.

Kod:

let height = field.clientHeight - ball.clientHeight;
let width = 100;

// tepaga jonlantirish (sakrab)
animate({
  duration: 2000,
  timing: makeEaseOut(bounce),
  draw: function (progress) {
    ball.style.top = height * progress + "px";
  },
});

// chapga jonlantirish (o'ngga harakatlanish)
animate({
  duration: 2000,
  timing: makeEaseOut(quad),
  draw: function (progress) {
    ball.style.left = width * progress + "px";
  },
});

Yechimni sandbox-da oching.

O'quv qo'llanma xaritasi

Izohlar

izoh berishdan oldin buni o'qing…
  • Agar sizda nimani yaxshilash kerakligi haqida takliflaringiz bo'lsa - iltimos, GitHub muammosini yuboring yoki izoh berish o'rniga so'rov yuboring.
  • Agar siz maqolada biror narsani tushunolmasangiz - iltimos, batafsilroq ma'lumot bering.
  • Bir nechta so'z so'zlarini kiritish uchun <code> yorlig'ini ishlating, bir nechta satrlar uchun - ularni <pre> yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)