{"id":355493,"date":"2024-05-20T23:09:51","date_gmt":"2024-05-20T23:09:51","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=355493"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-05-20T23:09:51","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=355493","title":{"rendered":"<span>Creating a mini-game with drip effect and moving circles. Part 2. Final<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>Hello, dear Habr contributors!<\/p>\n<p>Today let&#8217;s continue creating a mini-game with moving circles and an interesting drip effect.<\/p>\n<p>In <a href=\"https:\/\/habr.com\/ru\/articles\/761744\/\" rel=\"noopener noreferrer nofollow\">the first part<\/a> we made a basic movement of circles on the page. And in today&#8217;s lesson we will make an animation of &#171;explosion&#187; and absorption of circles.<\/p>\n<p><strong>Final demo of the second part of the lesson:<\/strong><\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/650c1a353add78308967b00f\" data-style=\"\" id=\"650c1a353add78308967b00f\" width=\"\"><\/div>\n<p>Let&#8217;s get started!<\/p>\n<h3>Generating circles<\/h3>\n<p>In order to &#171;split&#187; one circle into several, we will add a new <strong>splitting<\/strong> function to the <strong>Substance<\/strong> class, which will create a given number of new circles in a loop. We will also define two new fields as data:<\/p>\n<ul>\n<li>\n<p><strong>minSize<\/strong> &#8212; the minimum size of the circle;<\/p>\n<\/li>\n<li>\n<p><strong>countPieces<\/strong> &#8212; the number of generated circles.<\/p>\n<\/li>\n<\/ul>\n<p>And at the end of the constructor we will call the created function.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     constructor(...) {         this.data = {             minSize: 60,             countPieces: 5,             ...         }         ...          this.splitting();     }      splitting() {         for (let i = 0; i &lt; this.data.countPieces; i++) {             this.data.pieces.push(new Piece(this));         }     } }<\/code><\/pre>\n<p>To keep the circles from flying too fast across the screen, you can adjust the <strong>MIN_SPEED<\/strong> and <strong>MAX_SPEED<\/strong> values of the Piece class to your liking. In the example, I&#8217;ll change the movement speed to make the &#171;merging&#187; effect of the circles more visible as they fly past each other.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     MIN_SPEED = 1;     MAX_SPEED = 3;          ... }<\/code><\/pre>\n<p>As a result, when you click on the page, the number of circles specified in the <strong>countPieces<\/strong> field will appear, which will move chaotically around the page.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/25f\/16a\/f48\/25f16af48fa384146562450c450892da.png\" alt=\"The result is the appearance of many circles\" title=\"The result is the appearance of many circles\" width=\"1600\" height=\"1254\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/25f\/16a\/f48\/25f16af48fa384146562450c450892da.png\"\/><\/p>\n<div><figcaption>The result is the appearance of many circles<\/figcaption><\/div>\n<\/figure>\n<h3>Animation of a circle &#171;exploding&#187;<\/h3>\n<p>Since we will be changing the width and height values of each element while creating the animation, we will use JavaScript&#8217;s built-in <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Element\/animate\" rel=\"noopener noreferrer nofollow\">animate<\/a> function to create a smooth animation.<\/p>\n<blockquote>\n<p>The animate function allows you to describe the animation order for any HTML element, as well as to set other animation characteristics (duration, start delay, etc.). CSS animations using @keyframes work in a similar way.<\/p>\n<\/blockquote>\n<p>Let&#8217;s create <strong>animateCollapse<\/strong> function in the <strong>Piece<\/strong> class.<\/p>\n<p>As the first parameter to the <strong>animate<\/strong> function we pass an object that contains the name of each CSS property that we are going to animate, and as an array &#8212; the sequence of animation frames.<\/p>\n<p>As the second parameter we will set the duration of the animation, as well as the animation start delay.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      animateCollapse() {         const maxSize = this.parent.data.maxSize;         const minSize = this.parent.data.minSize;          this.animate = this.el.animate({             width: [`${maxSize}px`, `${minSize}px`],             height: [`${maxSize}px`, `${minSize}px`]         }, {             duration: 300,             delay: 300         });          this.animate.onfinish = () => {}     } }<\/code><\/pre>\n<p>In this case, we will use JavaScript implementation. We need to define the moment when the animation ends, and we can do it through the <strong>onfinish<\/strong> method. We&#8217;ll leave it empty for now, but we&#8217;ll come back to it soon.<\/p>\n<p>The <strong>animateCollapse<\/strong> function needs to be called to start the animation chain. Let&#8217;s remove the call to the <strong>splitting<\/strong> function, as we&#8217;ll move it to the <strong>onfinish<\/strong> method a little later. And when we create the first <strong>Piece<\/strong> element, we will call the &#171;explosion&#187; animation for it.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     constructor(parent, params) {         this.data = {...}          this.data.pieces.push(new Piece(this));         this.data.pieces[0].animateCollapse();     }          ... }<\/code><\/pre>\n<p>We now have an animation of an element &#171;exploding&#187;. Now we can add the generation of circles.<\/p>\n<h4>Appearance of circles after the end of animation<\/h4>\n<p>In order to generate circles immediately after the end of the animation, let&#8217;s call the <strong>splitting<\/strong> function in <strong>onfinish<\/strong>.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...          animateCollapse() {         ...          this.animate.onfinish = () => {             this.parent.splitting();         }     } }<\/code><\/pre>\n<p>Checking the result in the browser, we can see that a large number of circles appear after the animation is over.<\/p>\n<p>But there are a few flaws:<\/p>\n<ul>\n<li>\n<p>during the animation the element moves;<\/p>\n<\/li>\n<li>\n<p>due to the fact that the element is moving, the generation of circles happens exactly where we clicked earlier.<\/p>\n<\/li>\n<\/ul>\n<p>We will fix it!<\/p>\n<h3>Stopping the movement of the circle during animation<\/h3>\n<p>For the <strong>Substance<\/strong> class, let&#8217;s add a new field <strong>canMove<\/strong>, which will store the movement state &#171;allowed\/forbidden&#187;. It is this field that we will change to &#171;movement allowed&#187; when the animation ends, and vice versa, when the animation starts. And the default value is <strong>false<\/strong>, as our circle will immediately start its animation when it appears on the page.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     constructor(parent, params) {         ...         this.data = {...}                  this.canMove = false;          ...     } }<\/code><\/pre>\n<p>In the <strong>Piece<\/strong> class, let&#8217;s make a few additions:<\/p>\n<ul>\n<li>\n<p>at the start of the <strong>animateCollapse<\/strong> function, change the value of <strong>canMove<\/strong> to &#171;no movement allowed&#187;, because in the future the cycle of &#171;explosion&#187; and &#171;merging&#187; of elements will be infinite;<\/p>\n<\/li>\n<li>\n<p>at the end of the animation in the <strong>onfinish<\/strong> method, change the value of <strong>canMove<\/strong> to &#171;movement allowed&#187;;<\/p>\n<\/li>\n<li>\n<p>at the start of the <strong>update<\/strong> function we check that &#171;movement is allowed&#187;, otherwise we terminate the function and do not update the position of the circle.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      update() {         if (!this.parent.canMove) return;         ...     }          animateCollapse() {         this.parent.canMove = false;         ...                  this.animate.onfinish = () => {             this.parent.canMove = true;             ...         }     } }<\/code><\/pre>\n<p>Now we have achieved the desired effect, and new circles appear exactly where the main one &#171;explodes&#187;.<\/p>\n<h4>Reducing the size of the circles<\/h4>\n<p>In the <strong>Base<\/strong> class, let&#8217;s add an updateSize function to update the sizes of HTML elements.<\/p>\n<pre><code class=\"javascript\">class Base {     ...      updateSize() {         this.el.style.width = `${this.data.size}px`;         this.el.style.height = `${this.data.size}px`;     } }<\/code><\/pre>\n<p>As the default size value when creating an instance of the Piece class, we replace the maximum size with the minimum size, since only the first item will have the maximum size and all others should be generated at the minimum size.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     constructor(parent) {         ...                  this.data = {             size: this.parent.data.minSize,             ...         }         ...     } }<\/code><\/pre>\n<p>And in order to apply a dynamic value as the size of the circle, in the <strong>createElement<\/strong> function we replace the manually set value with the field from <strong>data.size<\/strong>.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      createElement() {         ...         this.el.style.width = `${this.data.size}px`;         this.el.style.height = `${this.data.size}px`;         ...     }      ... }<\/code><\/pre>\n<p>As mentioned earlier, the first circle should have the maximum size to play the animation correctly. Therefore, we will add size updates to the <strong>animateCollapse<\/strong> function.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      animateCollapse() {         ...          this.data.size = this.parent.data.maxSize;         this.updateSize();                  ...                  this.animate.onfinish = () => {             this.data.size = this.parent.data.minSize;             this.updateSize();             ...         }     } }<\/code><\/pre>\n<p>The &#171;explosion&#187; and resizing animations now look correct.<\/p>\n<h3>Defining the intersection of circles<\/h3>\n<p>Let&#8217;s make it so that when two circles intersect, only one circle remains. In the future it will look like simulation of &#171;eating&#187; of a small circle by a larger one.<\/p>\n<p>Let&#8217;s add a static function <strong>checkCollision<\/strong> to the <strong>Base<\/strong> class, in which we will use <a href=\"https:\/\/www.cyberforum.ru\/cpp-beginners\/thread1745707.html\" rel=\"noopener noreferrer nofollow\">the mathematical formula for the contact of circles<\/a> and return the result <strong>true\/false<\/strong>.<\/p>\n<pre><code class=\"javascript\">class Base {     ...      static checkCollision(piece1, piece2) {         const a = piece1.r + piece2.r;         const x = piece1.x - piece2.x;         const y = piece1.y - piece2.y;                  return a > Math.sqrt((x * x) + (y * y));     } }<\/code><\/pre>\n<p>In the Piece class we will create two new functions:<\/p>\n<ul>\n<li>\n<p>checkCollision &#8212; in it we will call the static method <strong>Base.checkCollision<\/strong> and pass the arguments of two circles. If the method returns <strong>true<\/strong>, it means that the circles are in contact, and we remove one of the circles;<\/p>\n<\/li>\n<li>\n<p>removeElement &#8212; will remove from the DOM the element of the circle, in the instance of the class of which the function was called, as well as remove the same circle from the <strong>pieces<\/strong> array.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      checkCollision() {         this.parent.data.pieces.forEach((piece) => {             \/\/ Exit the loop if we are comparing the same circle             if (this === piece) return;              if (Base.checkCollision({                 r: this.data.size \/ 2,                 x: this.data.position.x,                 y: this.data.position.y             }, {                 r: piece.data.size \/ 2,                 x: piece.data.position.x,                 y: piece.data.position.y             })) {                 this.removeElement();             }         });     }      removeElement() {         this.parent.removePieceFromArray(this);         this.el.remove();     } }<\/code><\/pre>\n<p>In the <strong>Substance<\/strong> class, create a function <strong>removePieceFromArray<\/strong>. This function is necessary even though we have removed the object from the DOM, but it remains in JavaScript memory and is still being moved.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     ...      removePieceFromArray(piece) {         this.data.pieces.splice(this.data.pieces.indexOf(piece), 1);     } }<\/code><\/pre>\n<p>At the end of the <strong>update<\/strong> function, we will add a call to the <strong>checkCollision<\/strong> function.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...          update() {         ...          this.checkCollision();     } }<\/code><\/pre>\n<p>Checking the result, we see a strange behaviour. This happens because the elements do not have time to fly away from each other and instantly &#171;eat&#187; all of themselves.<\/p>\n<p>In order to &#171;switch off&#187; the &#171;eating&#187; functionality for some time after the &#171;explosion&#187; occurs, let&#8217;s add a new field <strong>canCheckCollision<\/strong> in the <strong>Substance<\/strong> class.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     constructor(parent, params) {         this.data = {...}         ...          this.canCheckCollision = false;          ...      }          ... }<\/code><\/pre>\n<p>At the start of <strong>animateCollapse<\/strong> we will assign the value <strong>false<\/strong> for the <strong>canCheckCollision<\/strong> field, and at the end of the <strong>onfinish<\/strong> method we will add a timer. Since some time passes after the end of the &#171;explosion&#187; animation and before the circles fly around the page, the timer will allow us to change the value after the time has passed.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      animateCollapse() {         this.parent.canCheckCollision = false;         ...          this.animate.onfinish = () => {             ...             setTimeout(() => {                 this.parent.canCheckCollision = true;             }, 1000);         }     }          ... }<\/code><\/pre>\n<h3>&#171;Absorption&#187; of small circles<\/h3>\n<p>Now we have just deleting one of the circles at intersection. Let&#8217;s make the effect of &#171;eating&#187; and define the largest circle by increasing its size, and delete the smaller one.<\/p>\n<p>In the <strong>Substance<\/strong> class create a new field <strong>difSize<\/strong>, which will be the difference between the values of the maximum and minimum sizes of the circles. We will use it in the formula for calculating the increase of the absorber circle.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     constructor(parent, params) {         ...          this.canCheckCollision = false;         this.difSize = this.data.maxSize - this.data.minSize;          ...     }          ... }<\/code><\/pre>\n<p>And in the <strong>Piece<\/strong> class we will add the <strong>consume<\/strong> function, describing the formula that will be used to calculate the number added to the size when a smaller element is &#171;consumed&#187;. <\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      consume(piece) {         this.data.size += piece.data.size - this.parent.data.minSize + this.parent.difSize \/ this.parent.data.countPieces;         this.updateSize();         piece.removeElement();     } }<\/code><\/pre>\n<p>Call the <strong>consume<\/strong> function when the condition of intersection of circles inside <strong>checkCollision<\/strong> is fulfilled.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      checkCollision() {         if (!this.parent.canCheckCollision) return;          this.parent.data.pieces.forEach((piece) => {             ...              if (Base.checkCollision(...)) {                 if (this.data.size > piece.data.size) this.consume(piece);                 else piece.consume(this);             }         });     } }<\/code><\/pre>\n<p>And to make the animation of the increasing circle look smoother, we still need to add one more additional property to our style.css style file.<\/p>\n<pre><code class=\"css\">.spore {     transition: width .2s, height .2s;     ... }<\/code><\/pre>\n<h3>The repeated &#171;explosion&#187; of the mug<\/h3>\n<p>The condition under which we will re-run the animation for a circle is that all circles from the array are completely consumed. Thus, each time a circle is removed from the array, we will call a function that checks if the animation can be re-run.<\/p>\n<p>In the <strong>checkSplitting<\/strong> function, we will check that the length of the array is exactly one element and overwrite the &#171;generation&#187; position of the circles with the current position of the circle.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     ...          removePieceFromArray(piece) {         this.data.pieces.splice(this.data.pieces.indexOf(piece), 1);         this.checkSplitting();     }          checkSplitting() {         if (this.data.pieces.length === 1) {             const piece = this.data.pieces[0];                          this.data.position.x = piece.data.position.x;             this.data.position.y = piece.data.position.y;              piece.animateCollapse();         }     } }<\/code><\/pre>\n<h3>Adding different colours<\/h3>\n<p>We will add an array of different colours to the COLORS variable as a constant at the beginning of the file.<\/p>\n<pre><code class=\"javascript\">const SCREEN_WIDTH = ... const SCREEN_HEIGHT = ...  const COLORS = ['red', 'cyan', 'yellow', 'green', 'blue', 'black'];<\/code><\/pre>\n<p>To make the circles appear in different colours when clicking on the page, we will use an already implemented function that allows you to get a random number in a given range. Let&#8217;s add one more string as a data field for the <strong>Substance<\/strong> class.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     constructor(parent, params) {         ...          this.data = {             ...             countPieces: 5,             color: COLORS[Game.random(0, COLORS.length - 1)]         }         ...     }          ... }<\/code><\/pre>\n<p>And to apply the colour of all circles &#8212; let&#8217;s add a background to the element in the <strong>createElement<\/strong> function.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     createElement() {         ...         this.el.style.backgroundColor = this.parent.data.color;          ZONE.appendChild(this.el);     }          ... }<\/code><\/pre>\n<h3>Conclusion<\/h3>\n<p>As a result of the work we have a small interactive page with an interesting effect, which you can continue to refine by introducing your ideas!<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/72c\/895\/98c\/72c89598c84caeb64fd6822beed9071b.png\" alt=\"The final result of the lesson\" title=\"The final result of the lesson\" width=\"1600\" height=\"1252\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/72c\/895\/98c\/72c89598c84caeb64fd6822beed9071b.png\"\/><\/p>\n<div><figcaption>The final result of the lesson<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/collision-spore\" rel=\"noopener noreferrer nofollow\">Repository on GitHub<\/a><\/p>\n<p>Thanks for listening, everyone, and see you all back on Habr!<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/762546\/\"> https:\/\/habr.com\/ru\/articles\/762546\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>Hello, dear Habr contributors!<\/p>\n<p>Today let&#8217;s continue creating a mini-game with moving circles and an interesting drip effect.<\/p>\n<p>In <a href=\"https:\/\/habr.com\/ru\/articles\/761744\/\" rel=\"noopener noreferrer nofollow\">the first part<\/a> we made a basic movement of circles on the page. And in today&#8217;s lesson we will make an animation of &#171;explosion&#187; and absorption of circles.<\/p>\n<p><strong>Final demo of the second part of the lesson:<\/strong><\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/650c1a353add78308967b00f\" data-style=\"\" id=\"650c1a353add78308967b00f\" width=\"\"><\/div>\n<p>Let&#8217;s get started!<\/p>\n<h3>Generating circles<\/h3>\n<p>In order to &#171;split&#187; one circle into several, we will add a new <strong>splitting<\/strong> function to the <strong>Substance<\/strong> class, which will create a given number of new circles in a loop. We will also define two new fields as data:<\/p>\n<ul>\n<li>\n<p><strong>minSize<\/strong> &#8212; the minimum size of the circle;<\/p>\n<\/li>\n<li>\n<p><strong>countPieces<\/strong> &#8212; the number of generated circles.<\/p>\n<\/li>\n<\/ul>\n<p>And at the end of the constructor we will call the created function.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     constructor(...) {         this.data = {             minSize: 60,             countPieces: 5,             ...         }         ...          this.splitting();     }      splitting() {         for (let i = 0; i &lt; this.data.countPieces; i++) {             this.data.pieces.push(new Piece(this));         }     } }<\/code><\/pre>\n<p>To keep the circles from flying too fast across the screen, you can adjust the <strong>MIN_SPEED<\/strong> and <strong>MAX_SPEED<\/strong> values of the Piece class to your liking. In the example, I&#8217;ll change the movement speed to make the &#171;merging&#187; effect of the circles more visible as they fly past each other.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     MIN_SPEED = 1;     MAX_SPEED = 3;          ... }<\/code><\/pre>\n<p>As a result, when you click on the page, the number of circles specified in the <strong>countPieces<\/strong> field will appear, which will move chaotically around the page.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>The result is the appearance of many circles<\/figcaption><\/div>\n<\/figure>\n<h3>Animation of a circle &#171;exploding&#187;<\/h3>\n<p>Since we will be changing the width and height values of each element while creating the animation, we will use JavaScript&#8217;s built-in <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Element\/animate\" rel=\"noopener noreferrer nofollow\">animate<\/a> function to create a smooth animation.<\/p>\n<blockquote>\n<p>The animate function allows you to describe the animation order for any HTML element, as well as to set other animation characteristics (duration, start delay, etc.). CSS animations using @keyframes work in a similar way.<\/p>\n<\/blockquote>\n<p>Let&#8217;s create <strong>animateCollapse<\/strong> function in the <strong>Piece<\/strong> class.<\/p>\n<p>As the first parameter to the <strong>animate<\/strong> function we pass an object that contains the name of each CSS property that we are going to animate, and as an array &#8212; the sequence of animation frames.<\/p>\n<p>As the second parameter we will set the duration of the animation, as well as the animation start delay.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      animateCollapse() {         const maxSize = this.parent.data.maxSize;         const minSize = this.parent.data.minSize;          this.animate = this.el.animate({             width: [`${maxSize}px`, `${minSize}px`],             height: [`${maxSize}px`, `${minSize}px`]         }, {             duration: 300,             delay: 300         });          this.animate.onfinish = () => {}     } }<\/code><\/pre>\n<p>In this case, we will use JavaScript implementation. We need to define the moment when the animation ends, and we can do it through the <strong>onfinish<\/strong> method. We&#8217;ll leave it empty for now, but we&#8217;ll come back to it soon.<\/p>\n<p>The <strong>animateCollapse<\/strong> function needs to be called to start the animation chain. Let&#8217;s remove the call to the <strong>splitting<\/strong> function, as we&#8217;ll move it to the <strong>onfinish<\/strong> method a little later. And when we create the first <strong>Piece<\/strong> element, we will call the &#171;explosion&#187; animation for it.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     constructor(parent, params) {         this.data = {...}          this.data.pieces.push(new Piece(this));         this.data.pieces[0].animateCollapse();     }          ... }<\/code><\/pre>\n<p>We now have an animation of an element &#171;exploding&#187;. Now we can add the generation of circles.<\/p>\n<h4>Appearance of circles after the end of animation<\/h4>\n<p>In order to generate circles immediately after the end of the animation, let&#8217;s call the <strong>splitting<\/strong> function in <strong>onfinish<\/strong>.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...          animateCollapse() {         ...          this.animate.onfinish = () => {             this.parent.splitting();         }     } }<\/code><\/pre>\n<p>Checking the result in the browser, we can see that a large number of circles appear after the animation is over.<\/p>\n<p>But there are a few flaws:<\/p>\n<ul>\n<li>\n<p>during the animation the element moves;<\/p>\n<\/li>\n<li>\n<p>due to the fact that the element is moving, the generation of circles happens exactly where we clicked earlier.<\/p>\n<\/li>\n<\/ul>\n<p>We will fix it!<\/p>\n<h3>Stopping the movement of the circle during animation<\/h3>\n<p>For the <strong>Substance<\/strong> class, let&#8217;s add a new field <strong>canMove<\/strong>, which will store the movement state &#171;allowed\/forbidden&#187;. It is this field that we will change to &#171;movement allowed&#187; when the animation ends, and vice versa, when the animation starts. And the default value is <strong>false<\/strong>, as our circle will immediately start its animation when it appears on the page.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     constructor(parent, params) {         ...         this.data = {...}                  this.canMove = false;          ...     } }<\/code><\/pre>\n<p>In the <strong>Piece<\/strong> class, let&#8217;s make a few additions:<\/p>\n<ul>\n<li>\n<p>at the start of the <strong>animateCollapse<\/strong> function, change the value of <strong>canMove<\/strong> to &#171;no movement allowed&#187;, because in the future the cycle of &#171;explosion&#187; and &#171;merging&#187; of elements will be infinite;<\/p>\n<\/li>\n<li>\n<p>at the end of the animation in the <strong>onfinish<\/strong> method, change the value of <strong>canMove<\/strong> to &#171;movement allowed&#187;;<\/p>\n<\/li>\n<li>\n<p>at the start of the <strong>update<\/strong> function we check that &#171;movement is allowed&#187;, otherwise we terminate the function and do not update the position of the circle.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      update() {         if (!this.parent.canMove) return;         ...     }          animateCollapse() {         this.parent.canMove = false;         ...                  this.animate.onfinish = () => {             this.parent.canMove = true;             ...         }     } }<\/code><\/pre>\n<p>Now we have achieved the desired effect, and new circles appear exactly where the main one &#171;explodes&#187;.<\/p>\n<h4>Reducing the size of the circles<\/h4>\n<p>In the <strong>Base<\/strong> class, let&#8217;s add an updateSize function to update the sizes of HTML elements.<\/p>\n<pre><code class=\"javascript\">class Base {     ...      updateSize() {         this.el.style.width = `${this.data.size}px`;         this.el.style.height = `${this.data.size}px`;     } }<\/code><\/pre>\n<p>As the default size value when creating an instance of the Piece class, we replace the maximum size with the minimum size, since only the first item will have the maximum size and all others should be generated at the minimum size.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     constructor(parent) {         ...                  this.data = {             size: this.parent.data.minSize,             ...         }         ...     } }<\/code><\/pre>\n<p>And in order to apply a dynamic value as the size of the circle, in the <strong>createElement<\/strong> function we replace the manually set value with the field from <strong>data.size<\/strong>.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      createElement() {         ...         this.el.style.width = `${this.data.size}px`;         this.el.style.height = `${this.data.size}px`;         ...     }      ... }<\/code><\/pre>\n<p>As mentioned earlier, the first circle should have the maximum size to play the animation correctly. Therefore, we will add size updates to the <strong>animateCollapse<\/strong> function.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      animateCollapse() {         ...          this.data.size = this.parent.data.maxSize;         this.updateSize();                  ...                  this.animate.onfinish = () => {             this.data.size = this.parent.data.minSize;             this.updateSize();             ...         }     } }<\/code><\/pre>\n<p>The &#171;explosion&#187; and resizing animations now look correct.<\/p>\n<h3>Defining the intersection of circles<\/h3>\n<p>Let&#8217;s make it so that when two circles intersect, only one circle remains. In the future it will look like simulation of &#171;eating&#187; of a small circle by a larger one.<\/p>\n<p>Let&#8217;s add a static function <strong>checkCollision<\/strong> to the <strong>Base<\/strong> class, in which we will use <a href=\"https:\/\/www.cyberforum.ru\/cpp-beginners\/thread1745707.html\" rel=\"noopener noreferrer nofollow\">the mathematical formula for the contact of circles<\/a> and return the result <strong>true\/false<\/strong>.<\/p>\n<pre><code class=\"javascript\">class Base {     ...      static checkCollision(piece1, piece2) {         const a = piece1.r + piece2.r;         const x = piece1.x - piece2.x;         const y = piece1.y - piece2.y;                  return a > Math.sqrt((x * x) + (y * y));     } }<\/code><\/pre>\n<p>In the Piece class we will create two new functions:<\/p>\n<ul>\n<li>\n<p>checkCollision &#8212; in it we will call the static method <strong>Base.checkCollision<\/strong> and pass the arguments of two circles. If the method returns <strong>true<\/strong>, it means that the circles are in contact, and we remove one of the circles;<\/p>\n<\/li>\n<li>\n<p>removeElement &#8212; will remove from the DOM the element of the circle, in the instance of the class of which the function was called, as well as remove the same circle from the <strong>pieces<\/strong> array.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"javascript\">class Piece extends Base {     ...      checkCollision() {         this.parent.data.pieces.forEach((piece) => {             \/\/ Exit the loop if we are comparing the same circle             if (this === piece) return;              if (Base.checkCollision({                 r: this.data.size \/ 2,                 x: this.data.position.x,                 y: this.data.position.y             }, {                 r: piece.data.size \/ 2,                 x: piece.data.position.x,                 y: piece.data.position.y             })) {                 this.removeElement();             }         });     }      removeElement() {         this.parent.removePieceFromArray(this);         this.el.remove();     } }<\/code><\/pre>\n<p>In the <strong>Substance<\/strong> class, create a function <strong>removePieceFromArray<\/strong>. This function is necessary even though we have removed the object from the DOM, but it remains in JavaScript memory and is still being moved.<\/p>\n<pre><code class=\"javascript\">class Substance extends Base {     ...      removePieceFromArray(piece) {         this.data.pieces.splice(this.data.pieces.indexOf(piece), 1);     } }<\/code><\/pre>\n<p>At the end of the <strong>update<\/strong> function, we will add a call to the <strong>checkCollision<\/strong> function.<\/p>\n<pre><code class=\"javascript\">class Piece extends Base {     ...          update() {         ...          this.checkCollision();     } }<\/code><\/pre>\n<p>Checking the result, we see a strange behaviour. This happens because the elements do not have time to fly away from each other and instantly &#171;eat&#187; all of themselves.<\/p>\n<p>In order to &#171;switch off&#187; the &#171;eating&#187; functionality for some time after the &#171;explosion&#187; occurs, let&#8217;s add a new field <strong>canCheckColli<\/strong><\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-355493","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/355493","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=355493"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/355493\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=355493"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=355493"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=355493"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}