Learn Creative Coding with Vanilla JavaScript | Frank Dvorak | Skillshare
Search

Playback Speed


1.0x


  • 0.5x
  • 0.75x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 1.75x
  • 2x

Learn Creative Coding with Vanilla JavaScript

teacher avatar Frank Dvorak, Creative Coding

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Lessons in This Class

    • 1.

      Introduction & Project previews

      1:01

    • 2.

      Project setup

      4:05

    • 3.

      Fractals and JavaScript classes

      2:36

    • 4.

      Drawing basic shapes

      4:53

    • 5.

      Modifying shapes

      2:10

    • 6.

      Transforming shapes

      6:04

    • 7.

      Simple geometric patterns

      7:43

    • 8.

      How to draw fractals

      6:46

    • 9.

      Snowflake fractal

      6:20

    • 10.

      Making a wide range or random shapes

      5:08

    • 11.

      Particle systems and animation

      8:55

    • 12.

      Drawing on multiple canvas elements

      2:55

    • 13.

      Converting canvas drawings into pixels

      5:29

    • 14.

      Experiments

      5:58

    • 15.

      What's next?

      0:29

  • --
  • Beginner level
  • Intermediate level
  • Advanced level
  • All levels

Community Generated

The level is determined by a majority opinion of students who have reviewed this class. The teacher's recommendation is shown until at least 5 student responses are collected.

229

Students

5

Projects

About This Class

Make art with code and learn front-end web development in the process. Discover creative coding and explore what can be done with just plain vanilla JavaScript, no frameworks and no libraries.

Meet Your Teacher

Teacher Profile Image

Frank Dvorak

Creative Coding

Teacher

Hello, I'm Frank. I'm a front-end web developer, owner of Frank's Laboratory YouTube channel. Come explore creative coding, game development and generative art with me.

See full profile

Level: Intermediate

Class Ratings

Expectations Met?
    Exceeded!
  • 0%
  • Yes
  • 0%
  • Somewhat
  • 0%
  • Not really
  • 0%

Why Join Skillshare?

Take award-winning Skillshare Original Classes

Each class has short lessons, hands-on projects

Your membership supports Skillshare teachers

Learn From Anywhere

Take classes on the go with the Skillshare app. Stream or download to watch on the plane, the subway, or wherever you learn best.

Transcripts

1. Introduction & Project previews: In Generative Art the artist creates a process which is then set into motion with some degree of autonomy, resulting in a completed artwork. I like to think about it as a human machine collaboration. Today we are the artists and HTML, CSS, and JavaScript is the machine. Make your unique piece of art with me and practice your front end web development skills in the process. In this class, we will go from drawing a single line to this and this, and this. I don't want you to just copy my code. I want you to really understand what's going on so today we will write everything completely from scratch, line by line, using just plain vanilla JavaScript. No frameworks and no libraries. I will explain every line of code in detail as we go along. This class is not for complete beginners, but if you know at least the basics of HTML, CSS, and JavaScript, come join me and let me show you how to make the machine help us to create something unique and beautiful. Have fun. 2. Project setup: All we need today to start working is a code editor and an internet browser. Our entire project will be these three files, index.html, style.css, and script.js. I create a basic web page. I give my project some title and I link style.css file in document head. Inside the body of my web page I create a canvas element with an ID of canvas 1. canvas 1. Canvas is a special HTML element that allows us to draw on it with code. We can use it to draw and animate shapes, images. We can create anything from simple to very complex web animations HTML canvas element is well supported in all modern browsers and learning the basics will unlock so many possibilities with visuals and animation. In my opinion, every front end web developer should know at least the basics of canvas. Let me show you how easy it is to draw something on it. Before we start drawing our interactive shapes with JavaScript, we have to link script.js file down here. In style.css I take my canvas element using its ID as a selector, and I give it a blue background just so that I can see where it is. You can see some margins here, even though I didn't declare any. That's because every browser has default margins and paddings that get automatically applied to each element. To make sure our project looks the same across all browsers, I use asterisk selector to target all elements on my page and I set margin on everything to 0, padding to 0 as well. This is so-called global reset. We can also use more recent CSS property called box-sizing and set it to border-box. This will make elements border and padding included in its total width and height. This is one of my favorite settings because it makes creating layouts so much easier. I give canvas position absolute. and I set it's top position to 50% and it's left position also to 50%. Then I use transform translate minus 50% horizontally and minus 50% vertically. This is how you can center absolutely positioned element exactly in the middle of the page. I give our canvas a border, five pixels solid black, and I remove blue background. In my JavaScript file I start by creating an event listener for load event. This event is called on built-in window object which represents the browser window. Load event is fired when the whole page has been loaded with all its HTML elements, as well as all other dependent resources such as stylesheets and images. Inside the event listener once the page has been fully loaded, I set up my canvas. It's easy. I create a constant variable. I call for example canvas, and I set it equal to document dot getElementByID and I pass it ID, I gave my canvas, canvas 1. To point this variable towards the correct element you can also use document dot querySelector here. Keep in mind that getElementByID is slightly faster and more efficient because it only has to look for IDs. It doesn't have to check all possible selectors on the page to find the right element like querySelector has to do. The difference will not be noticeable unless you have a massive project. I create an additional custom variable. I call, for example ctx, shortcut for context, and I set it to canvas from line 2 dot getContext. getContext is a special built-in method that can only be called on a variable that holds a reference to a canvas element. We can pass it 2D or WebGL. Today we will work with 2D drawings. Now if your console log this ctx variable, you will see it holds an instance of a built-in canvas 2D API with all its properties and a drawing methods. Whenever we want to draw something on canvas from now on, we can use ctx dot and call any of these methods to draw something or change values of canvas properties such as lineWidth, fill color and so on. I will show you, I set canvas width to 600 pixels and canvas height to 600 pixels as well. 3. Fractals and JavaScript classes: What we will draw today is called a fractal. Fractals are infinitely complex patterns that are self-similar across different scales. They are created by repeating a simple process over and over in an ongoing feedback loop, let's say I draw a line, I rotate canvas, I scale it down and I repeat the process of drawing a line. I do this over and over, drawing a line, rotating, scaling and drawing the same line again. That would be a process to create a very simple fractal shape. I can write my code in so-called procedural way, which simply means we are declaring variables and functions line by line as we create our project, It's the most basic way to write code. Today I will use object oriented programming to write the code. That means I will wrap variables and functions in objects. I will create a class. Classes are templates for creating objects. Javascript class contains a set of instructions in a form of values and methods that will serve as a blueprint. And whenever I call this class using the new keyword, it will create one new instance of fractal object based on that blueprint we defined, I will also need a custom Particle class and one more class I call, for example, Rain. Today I want to make this fractal rain effect. If you ever drew any of these more complex shapes before, you will know that if you want your fractals to be detailed, sometimes it can take a couple of milliseconds to render. With canvas, we usually animate things by drawing them first, then we updated their coordinates to move them around. We delete the old drawing and we draw the same thing, but with updated coordinates. We do it very fast over and over, which gives us an illusion of motion. For that movement to be smooth, we need it to run at around 60 frames per second. Since today's effect will be made out of complex shapes, we can't just delete, recalculate and redraw these fractals 60 times per second. I don't think even the strongest computers could do that, but we will still get smooth animation because we will do it differently. We will use Fractal class to render a complex mathematical shape for us. Then we will take that shape and use JavaScript to convert it into pixels. Then we will use Particle class to assign these pixels as a particle shape. Here inside Particle class, we will also deal with motion. We can do floating, raining or spinning particles if we want to. Particle class will contain a blueprint to define properties and behaviors of individual particles. And Rain class will handle the entire effect. It will define things like number of particles and so on. 4. Drawing basic shapes: So because we chose to use object-oriented programming, we are splitting our code into classes. I like this because it makes the code more modular and easier to navigate in. This is a good example of one of the four principles of object oriented programming called encapsulation. Encapsulation means we are bundling data and methods that operate on that data in objects. Today we will encapsulate our data into three classes, Fractal, Particle and Rain. Constructor is a special type of method on JavaScript class. Constructor contains a blueprint, and when we call the class using the new keyword, constructor will create one new blank JavaScript object, and it will assign that blank object values and properties based on the blueprint inside. Data in constructor can be generated inside the method itself, and it can also come from the outside. It can be passed to the constructor as an argument when we call it using the new keyword, I will show you in a minute. Fractal class constructor will expect two arguments to be passed from the outside, one for canvas width and one for canvas height. Inside of the constructor. I convert these arguments into class properties. That way they become part of the blueprint. Fractal will need access to canvas width and canvas height so that it knows where the boundaries of canvas are. We can use this to make sure that the fractal is centered and it doesn't branch out outside the canvas area. Any object created by fractal class will also have access to this custom method I call, for example, draw. Function that sits on an object is called a method. The job of this draw method will be to take some values from the constructor and to draw a fractal shape based on these values, it will expect one argument I call, for example, context. This argument will be passed to it later on when we call it. And it will specify which canvas element we want to draw on. We can draw any shape on canvas we want. To draw a line is easy. We said before that ctx variable on line or 3 contains an instance of built-in canvas 2D API. So if I want to call any of these built-in drawing methods, I call them from there, this context variable will represent the ctx when I call draw method a bit later, I will show you. We start drawing a shape by calling a built-in beginPath method. Think about it as if we are putting a pen on paper. We are telling JavaScript we want to start drawing a new shape. beginPath will also close any previous shapes if there are any. Then we call built-in moveTo method to set the starting x and y coordinates for that line, I just pass it to 0 for horizontal coordinate. And 0 for the vertical. Point 0 0 on canvas is the top-left corner here. Coordinates on canvas are very simple. We have horizontal x-axis that goes from 0 here on the left side and increases as we travel to the right. Vertical y-coordinate starts at position 0 here on top, and it increases as we travel down. So for example, point at coordinates, 100, 200 will be somewhere around here, 100 pixels from the left edge on horizontal x-axis, and 200 pixels from the top on the vertical y-axis. So moveTo method sets the starting x and y coordinates for the line, lineTo method sets ending x and y coordinates. If I pass it canvas width from line 4 as x and canvas height from line 5 as y. That'll be coordinates 600, 600 so this corner, then we can call built-in stroke method to actually draw that line. This line should go from coordinates 0 0 here, to coordinates 600 600 here. Let's call our draw method and actually draw the line to see if it works. So here we declared Fractal class. It has a constructor method. When we create an instance of this class, constructor will automatically create one new object with this class is a blueprint. I do that by creating a variable I call, for example fractal 1. I set it to new fractal. The new keyword is a special command in JavaScript. It will make JavaScript look for a class with this name, and it will trigger its constructor. On line 8 I can see that my fractal class expects canvas width and canvas height as arguments. So I pass it canvas width from line 4 and canvas height from line 5 We created fractal 1. Since it was created using this class, it has access to its public draw method. I call it simply by saying fractal 1 dot draw. On line 12. I can see that the draw method expects context as an argument to specify which canvas we want to draw on. So far we only have one canvas element so not much to choose from. I pass it ctx from line 3. That worked. As you can see, we have a line coming from coordinates 0 0 as defined on line 14 and it goes all the way to coordinates 600 600 as defined on line 15 5. Modifying shapes: Ctx object on line 3 holds a reference to all canvas properties and methods. These properties are basically canvas state or you can call it canvas settings. Once you change any of them, they stay that way until we reset them or until we change them again. If I console.log ctx, we can see the properties here. This is the current default state of canvas. You can see, for example, that fillStyle is by default set to black. lineWidth is set to one pixel. Down here we also have all kinds of canvas 2D drawing methods such as arc method to draw a circle, beginPath method, which we already used on line 15 and many more. What if I want to make my line thicker? I need to take this line width property and I need to update it with a different value. I do it by saying ctx.lineWidth equals 5 pixels, or 20 pixels. I can also move the line around by changing its starting coordinates or its ending coordinates. Let's make the line 50 pixels wide. There's also a property called lineCap. And look what happens when I set it to round, we get round edges. When you understand how these coordinates work, you can draw a line between any two points on canvas, right now, the line starts at a 150 pixels from the left and a 150 pixels from the top. And it ends 200 pixels from the left and 300 pixels from the top. I could also go to minus values, or I can align the line on the top edge, making it go from coordinates 0 0 to coordinates 500 0. Now this single value of 500 actually determines the length of my line. Let's put it into a variable. I create a class property called this.size. Now I can change the length of my line by changing this.size property like this. I can also make the size of the line relative to the size of canvas. If I want to change the color of my line, I access stroke style property and I override it from the default black color to, for example, green. 6. Transforming shapes: Canvas has other built-in methods to draw shapes, for example, fill rectangle method that takes four arguments, x and y position and width and height. I want my rectangle at coordinates 50 50, and I want it to be 200 pixels wide and 200 pixels tall. I can change width and height like this. I can also make this rectangle cover the entire canvas by making it start from the top left corner. So coordinates 0 0, and I make its width the same as canvas width and height will be canvas height. I can change its color by accessing fillStyle property on canvas object, and I change it from the default black to blue, we are going to be rotating now so I want you to see exactly what's going on. In style.css, I give my canvas element red background. If I push the rectangle 50 pixels to the right, we can see we have red canvas element in the back. We have blue rectangle drawn on canvas on line 20, and we have green line drawn on top coming from lines 21 to 24, I put the blue rectangle back, so now it starts from coordinates 0 0. Keep that in mind. This is important. To get full control over our canvas drawings it's important to understand how we can transform the state of canvas using built-in translate, scale and the rotate methods. It's easy when explained properly, but it's not very intuitive. Let me show you exactly how it works. We can transform canvas state using built-in rotate method. This method takes one argument, an angle by which we want to rotate the entire canvas and all drawings that are on it. It takes that angle value in radians, not in degrees. If I rotate it by 0.8 radians, that's approximately 45 degrees. You can see as I rotate in positive and negative angles, the rotation center point is the top-left corner of canvas, coordinates 0 0, all the drawings on canvas that come after this rotate call so rectangle and the line rotate around coordinates 0 0. Translate method takes two parameters. The first one is x, the distance we want to move in horizontal direction. Positive values are to the right. And negative values go to the left. The second parameter, y defines how far to move in vertical direction. Positive values go down, negative values go up. If I pass it 50 50, we move all canvas drawings, 50 pixels to the right and 50 pixels down. It will affect everything that comes after. If I draw another shape or before this, it won't be translated or rotated. Here we translate and after that we rotate. Look at what happens if I pass it a different angle value. We are rotating and we can see that translate method on line 20 not only pushed all drawings to the right and down, it also changed rotation center point to coordinates 50 50. Rotate method will actually always rotate around coordinates 0 0. That's the part that is not very intuitive. What actually happened is that translate method moved what is considered the coordinates 0 0 from its default position in the top-left corner on canvas to coordinates 50 50. You can see that online 20, we are drawing the blue rectangle from coordinates 0 0, but it's actually drawn from coordinates 50 50. It's being pushed by translate method. This is because of translate method changes the state of canvas and it changed position that is considered coordinates 0 0. If you still don't fully understand this, don't worry, it will become more clear as we continue building our effect further. So all that translate method does, it moves what is considered coordinates 0 0 on canvas to the coordinates we pass to it. As a result, that offsets all canvas drawings by these pixel values. And it also moves rotation center point to that position. You saw the fractals we will be building today so for our effect. I want the rotation center point to be exactly in the middle of canvas. To do that, I pass canvas width and divide it by two as x parameter and canvas height and divide it by two as the y parameter, like this. Now when we rotate by different angle values, we can see the rotation center point is exactly in the middle of the page. Perfect. As we said, canvas rotate method takes one argument - angle by which we want to rotate and it's takes this value in radians. If I want to rotate by exactly half circle, so 180 degrees, that value converts to a 3.14 radians. It's actually exactly the value of pi. So if I rotate by Math.PI, it rotated by half circle. If I rotate something by Math.PI * 2, I rotated exactly by 360 degrees. We rotated everything around the whole circle and it ended up a back in its starting position. Thing to remember, rotating by Math.PI is rotating by half circle. Rotating by Math.PI * 2 will rotate your canvas drawings around in a full circle. I set it back to starting position at 0 radians. One last method that transforms canvas state we will cover today is scale. It takes two parameters. We can call them x and y. X is the scaling factor in horizontal direction. Y is the scaling factor in the vertical direction. Base values are 1. If we scale it by one, it will stay at the same size. Values lower than that will scale down. Values higher than that will scale up. If I pass it as 0.5 as x and is 0.5 as y we scaled the blue rectangle and the green line down by 50% to half of their original size. The important thing to know here is that it will shrink down towards coordinates 0 0. Since translate moved these coordinates to the middle of canvas, the scaling center point is there as well. So translate method changes both rotation and scaling center points. Keep that in mind. This behavior is perfect for the effect we are going to build today. 7. Simple geometric patterns: Sometimes you don't want it to translate, scale, and rotate to the entire canvas and all drawings that are on it. Sometimes we only want to rotate something and then we want to reset state back to what it was before. For that we have two special built-in methods called save and restore. Save method will create a snapshot of current state, all of its current properties you can see when you console log ctx, so translate, rotate scale, but also things like fillStyle, lineWidth, basically the entire state of canvas object as it is at this point in our code, then we can change anything we want. We can translate, scale, rotate, redraw the shapes we want to be affected by this translation and rotation. And then we call built-in restore methods that will look for its associated save call and it will reset canvas state back to what it was at the point save was called. In this case, it will undo rotate, scale, and translate so any shapes we draw after restore will not be affected by rotate or scale. As I said, save and restore reset not only transformation, but also all canvas settings including the fillStyle, strokeStyle and so on. This can be useful in some effects we are building. Here you can see I saved canvas state, I translate, rotate and scale. I draw my blue rectangle and I restore little rectangle is translated and scale down because originally it was supposed to cover the entire canvas from coordinates 0 0 to canvas width canvas height. Since we are drawing the green line after canvas state has been restored, it's not being affected by translate and scale at all. For the blue rectangle coordinates 0 0 are in the middle of canvas as defined by translate method for the green line coordinates 0 0, are at the default position in the top-left corner of canvas. I set scale to 1 1 so original width times one, and original height times one. If I rotate now you can see I'm rotating the blue rectangle, but green line is not being affected at all. Let's swap this around. I put green line inside this save and restore area and I put blue rectangle after restore. Now, the blue rectangle is on top of everything, so we can't really see the line. Shapes on canvas are drawn from top to bottom as JavaScript reads the file. So I will take this rectangle code and I will put it before save. Now as I rotate green line is being affected, but we'll rectangle is not being transformed at all. I can push the rectangle a bit so we can see we have red canvas, blue rectangle and the green line is the only thing that rotates. If you understand the things we just covered, you can do really cool things with canvas now. Let me show you some. I delete the blue rectangle and I make canvas background transparent. I go back inside my fractal class and I create a private class method called #drawLine. What I just did here is called a private method. This is a new feature in JavaScript that was introduced a couple of years ago. Private methods are declared with hash names, identifiers pre-fixed with hash symbol. Private methods are only accessible from within the class they are declared in. We can't call them from the outside. I will use this private method to handle some internal functionality of our class. Hiding internal functionality from the outside is a good example of the second principle of object oriented programming called abstraction. Abstraction means we are hiding unnecessary details of inner workings of our objects from the outside. And only exposing the essentials. User doesn't need to know how exactly is our fractal constructed user just needs to know there is a public draw method that they can call that will construct the entire fractal shape. Of course today we are the ones building this effect so we will understand everything. What I'm trying to achieve here is I want to have a private helper method on my class that handles drawing of a single line or a fractal branch part of the fractal. And I want to have a public draw method that can be called from the outside that will draw the complete fractal shape. Let's put this together. Draw line will expect context as an argument to specify which canvas element we want to draw on. Inside I put all this code that draws a line. So beginPath to start drawing, moveTo, to set the starting x and y coordinates lineTo, to set ending x and y coordinates and stroke to actually draw that line we defined. Then inside public draw method, I call this private drawLine method using this keyword, I can see that the drawLine expects context as an argument. I pass it contexts that will be passed as an argument to public draw method here I will show you later. You can see that when I rotate it still works. We just restructured the code so it's a bit more modular and it's going to be easier to keep track of what we're about to do next. Look what happens if I copy these two lines and the second rotate call has a different angle value. I copy them again and again and again. What if I also translate canvas by 50 50 every time we rotate, you can see right here that canvas transformations are additive, cumulative. Translates and rotates add up to one another until they are reset by restore, we have some code repetition here. When you see that in your JavaScript code is usually a good idea to use a loop. I delete all these repeated calls and I create a for-loop. For let index starts at 0, as long as i is less than five, increase i by one. Every time for-loop runs we will call drawLine method from line 31 to draw one new line segment. And we will rotate, canvas by further 0.5 radians. So the first line will be rotated by 0.5, second line by 1, third line by 1.5, and so on. Rotations are additive unless we restore in-between. As the for-loop runs, lines are being drawn and rotations are adding up. I can create more lines like this. I delete this translate call. What if I want the lines to be evenly split in a circular area? Let me show you what I mean. I need to somehow match the number of times this for-loop runs and how much it rotates each time. Currently we run this for loop three times creating three lines. We know that Math.PI converts to 180 degrees or 3.14 radians. So half circle. If I divide half circle, Math.PI divided by three, we get this shape. If I take the whole circle, so Math.PI * 2 and I divide it by the number of times this for-loop runs, we get three branches evenly split in a circular area. Here we can see that the number of loops needs to be the same as the number we divide the whole circle by, to get our branches evenly distributed over a circular area. Let's put this into a variable. I will call it this dot sides because it will determine how many symmetrical sides our final fractal shape will get. I set it to five, for example, and I replace it here and here. Now when I change this value, we change the shape of our soon to be fractal. We have this public draw method on line 20 and private drawLine method on line 31. As we said, private methods can't be called from the outside. If I try to call it, I will get an error that says private field drawLine must be declared in an enclosing class. We can see that this is a built-in functionality and the privacy encapsulation of these class features that start with a hash symbol is enforced by JavaScript itself. On the other hand, draw is a public method and it can be called from the outside like this. 8. How to draw fractals: We are drawing here is not a fractal yet. It's just a line that has been translated and rotated around to create a star shape. Mathematical fractals are infinitely complex patterns that are self-similar across different scales. It's a repeating pattern of similar shapes that you can zoom into closer and closer to get more and more details. In creative coding, we can't have our fractals to be infinitely complex because then it would take JavaScript infinite time to draw them. We can give them layers of complexity until we scaled to a certain small value beyond which we can't really see any difference with naked eye anyway, because individual lines are so small, the fractal shape we will draw today will be made out of lines. And what I'm going for is something inspired by H-tree fractal, where you have three lines that make the base h letter shape that branches into four smaller h letter shapes at each ending and at each ending of these smaller ones, there are even smaller. In mathematical fractal, we can keep zooming closer and closer and see more detail as this pattern repeats itself on infinitely smaller and smaller scales. For our effect, we will define some depth. Let's say we want each line to be split into smaller lines five times, and then our fractal shape is complete. Let me show you what I mean. We will draw our fractal, making this private drawLine method call itself over and over. In programming, this is called recursion. Recursion is a process in which a function calls itself directly or indirectly. Each time draw line calls itself, it will draw a line. It will transform, rotate, and scale canvas and it will call itself to draw that same line, but this time transformed, scaled, and rotated, and then it will call itself again. We will add a condition to define how many times this process will repeat itself, depending on how much detail, how much depth we want in our final fractal shape. I will show you exactly how that works and what it looks like now. I start by isolating any changes I make to canvas state. So I will wrap my code in save and restore methods. Inside our rotate canvas by one radian. And I will make drawLine call itself. I need to pass it context argument. Be careful here, if you run this code, it will create an infinite loop. Most modern browsers could deal with it these days and they will not crash. We need to create a condition that will end recursion at some point that will prevent a drawLine from colon itself and if further, when a certain limit is reached, I do it by passing it a second argument called level. I create an if statement and I check if this level variable that will be passed as an argument is more than this dot max level. If it is, use return statement like this. When a return statement is used in a function body, the execution of the function is stopped. I need to create max level property up here. Let's say I want drawLine to call itself two times. Our fractal will have a depth of two levels. When drawLine calls itself, I can see up here that it expects two arguments, contexts and level it every time drawLine calls itself, I increase that level argument by plus one. I run the code and we still get an infinite loop. It's because that level variable is undefined. I need to give it an initial value when I call drawLine for the very first time, I set the initial level to 0. So we call drawLine private method and we pass it context to specify which canvas we wanted to draw on and we pass it initial level 0, this code runs to draw a line. And then through a programming principle called recursion, the function calls itself passing along that same context variable and increasing level variable by plus one. So at this point, level is one and max level is two, as we defined it up online 19, this check will be false and it will rotate canvas and draw another line, increasing level by one. This loop will be repeating itself, drawing more and more lines until level reaches max level. Every time we draw another line, we can also scale canvas down. I scale it down times 0.9 horizontally and vertically. And now each line is 90% size of the previous one. If I want to create a branch shape, I also want to move the point from which the new line grows from its parent line. I will do it by translating canvas state. As we explained earlier, this will push coordinates 0 0 by that value, and it will also move rotation and scale in center point with it as a side effect, for example, look at what happens if between each line I translate by 50 pixels horizontally and 0 vertically, I can push the new line along its parent line using translate. What if I want the new line to grow from the end of its parent line, I can do that by translating horizontally by the entire length of that line. So here I used this.size property from line 17. I created kind of a hook shape. I can scale it down a bit. I can change the size of individual segments here on line 17. Now if I increase max level, I allow drawLine to call itself more times increasing in the amount of lines that make up our final fractal shape. You can see that with these settings, we quickly reach a point where new lines are scaled down to a point where they become barely visible. With mathematical fractals, this will be infinite and we would be able to zoom closer and closer and see more and more lines. In creative coding, we only care about the part we can see, also we don't have unlimited computing power to create infinite shapes like that. So at any point in this tutorial, if the shapes become too complex and rendering time takes too long on your computer, the easiest way to make them render faster is to come here to max level property and set it to a lower value decreasing the depth of our fractal. I will set it to five and I set the scale to 0.7. If I change this angle value in radians, I pass rotate method. I change the angle between new branch and its parent branch. This can create really cool shapes and movement I showed in the intro. So this is our very simple fractal shape. We made a fractal hook. I can now use the code we wrote here on line 26 to turn it into a shape with radial symmetry. This for-loop draws a fractal branch, each time it runs and it distributes these branches evenly in a circular area. The number of branches depends on this.size variable from line 18, Let's increase it to 2 3 4 5 6. I hope you can see we are getting closer to something interesting. I go back to one branch for now so that we can clearly see what we are doing. I will also set max level to 1 for now. 9. Snowflake fractal: The way our code works right now, it draws a line, rotates in a positive direction, and draws another line again. This way we draw these curved hooks. What if I want more symmetry? Let's say I want one branch to grow to the left and one to the right. I want to create kind of a fork that can give them interesting snowflake, tree branch or crystal shapes. Let me show you. We will have two branches growing out from the main parent branch. One will be splitting the left, one to the right. Both of these branches will be the same scale, so I move, translate and scale outside this save call here. And I wrap this entire block in another pair of save and restore to make sure our translates, scales and rotates don't overflow outside this loop of drawLine. I hope you don't find it confusing that we have multiple save and restore calls here. Think of the save and restore in pairs. They are kind of nested in each other. So each restore knows which one is its associated save point. So it knows what state of canvas to restore back to. This restore will work with this save. This restore is paired with this save. I hope it makes sense. Save pushes the current state on top of the stack, restore pops the last added state of the top of the stack. It will become more intuitive if you used it for awhile. I copy this code block one more time. And the only change will be that we pass rotate method and negative angle. I put some spaces here for clarity. When we call draw line, we draw a line. We translate the position 0 0 along the branch. So it's on the other end. We rotate canvas by a positive angle value and call drawLine to draw this branch. And here we rotate it by a negative value to draw this one. Right now, I'm hard-coding scale 0.7. Let's make it into a class property. I say this.scale is 0.7 and I replace it here and here. Now I can change scaling of our fractal levels here on line 20. It will do the same thing for angle value. We pass to rotate method. It will affect how spread out the final fractal shape is. So I will call it this.spread. You can also call it angle or something if you want. Initially I set it to one radian, which is around 57 degrees. I replace the hard-coded angle value here on line 46 and on line 51, I will use the same value but negative to make sure these two smaller branches split out in both directions away from the parent branch. If I change the angle value here you can see exactly how it affects the relationship between child and parent branch. I add more sides. I can change any value here to change what the final shape looks like. Here on line 35, we limit the number of drawLine method calls itself. In this case, each time it runs, it calls itself twice to create two smaller versions of the same line growing from the tips. Look what happens if I increase max level by one, we will add one more level of recursion and each smaller branch will have two even smaller branches growing from it. If I add one more, the smallest branches will each have two even smaller ones. I hope you can see what is happening. I will change the angle value and it will become more obvious that we have our base level shape and three more fractal levels branching from it. We have a branch that splits into two smaller branches. And each time we add max level, this process is repeated on smaller and smaller scale. Basic principle of fractals. I make the entire shape smaller by setting the initial segment size to 20% of canvas width. I set line width to 30 pixels. Let's take it one step further. Let's say this is the main branch and it has two smaller branches coming out from it here and here. What if I want to have more branches? For example, I want another pair of coming from somewhere here and here. Let's turn to a number of branches into a class property. Initially I set it to one. I go inside our private drawLine method. And after I draw the main initial line I create a for loop. i is 0, as long as i is less than this dot branches increase i by one. Now, I take this entire code and I put it inside that for loop. This code translates coordinates 0 0 along the parent branch to its end and scales canvas down a bit. Then it draws this line here and this line here. I need to move this save outside the for-loop because it's associated restore is also outside the for-loop here. I want one set of branches somewhere here, and the other set here, the code that determines where the next branches grow out of is this translate. Instead of translating all the way to the end of parent branch, I will translate to this dot size divided by branches and this whole thing in brackets times i. That way if we have, let's say two sets of branches, the first set will be size divided by two times a 0 here, and the second pair will be size divided by two times one. So here we just have one set now because this dot branches is set to 1. If I want to move it to the other side of the main branch, I set this value to this dot size minus like this. I appreciate that this code in line 44 could be a little hard to understand at first. It will become more clear as we play with the code and see how the final fractal shape reacts. I set branches to two and I can see it did something I did not expect. Let's see what that looks like in a more complex shape by adding more sides. I actually like this. The reason the branches are not spread out along the main branch but are pushed kind of outside is because I'm calling this save and restore only ones outside of the for-loop. I actually want to save and restore each time we draw a pair of branches so that this translate online 44 can correctly climb along the parent branch. I move save and restore inside the for-loop. And now it's doing what I intended. If I scale it down, you can see we have two sets of branches. One set growing from this point and another set growing from this point, I adjust some of these values to get a better size. Now we are making really nice fractal snowflakes. 10. Making a wide range or random shapes: I create a class property called this.color. Instead of RGB color declaration, I will use HSL color model, hue, saturation, lightness. Hsl color model was designed in 1970s to more closely align with how human vision perceives colors. It's really useful for creative coding because you can cycle through the entire color spectrum by changing just the first value. That first value stands for hue. And it represents a degree on the color wheel from 0 to 360 degrees, I cut strokeStyle and I paste it inside the draw method here. Context dot stroke style is equal to this dot color from line 22. 0 degrees is red. 120 degrees is green, 240 is blue. You can even go to values over 360, and it will just be spinning around the wheel again. So 360 is red again, 480 is green, and so on. Saturation is a percentage value, 0 means a shade of gray, 100% is full-color. Lightness is also a percentage. 0 is black, 100% is white, 50% is full-color, not affected by light or dark at all. I can replace the hue part with a randomized value like this. Let's say on the first page load I roll a random number between 0 and 360. So now our fractal can be any random color. I could also give it some shadow. I take CTX variable from line three and I set canvas shadow color property to black, shadow offset y so vertical shadow will be ten pixels down. Shadow offset X horizontal shadow will be five pixels to the right from its parent shape. Shadow blur will be, for example, 10. We are making it look really good. You can see how spread value, so the angle we pass to rotate method changes the final shape of the fractal. I started from 0.1 radians and I will go all the way to 2.9 to get the full range I want. Yeah, I think these values are good. I will make spread a random value between 0.1 and 2.9 like this. Now every time I refresh page, I get a random color and random angle between branch levels. So we are getting a nice random range of shapes. I can set lineWidth to ten pixels to be able to see more details. If I set max level to one, we just get the main branch on each side that splits into four smaller branches. If I set it to 2, each of the small branches will have four smaller ones. If I increase max level to 4, each of those small branches will have four even smaller ones. At this point, we are asking JavaScript to rotate, scale, and translate canvas many, many times in order to draw this shape. So as we increase max level further, the render time will be increasing depending on the power of our computer. It's not necessarily a bad thing. Let's say we want to use this code to render a complex fractal shape and we want to save it as an image. There's no problem if we get a couple of seconds render time. It depends on what you want to use this code for. If at any point today you like the fractal shape you got. If you are using a modern browser, you should be able to right-click canvas and select 'Save Image As'. That should give you your fractal shape as a PNG image with a transparent background. If your render time takes too long you are asking JavaScript to draw too much detail in your fractal. You can fix it by going to line 22 here and set max level property to a lower value if you are playing with your fractal shape and at some point it goes over the edges of canvas. You can make it smaller by changing this.size property on line 20. This is our fractal class. We used it to render or randomized fractal shape on canvas. I will show you more fractal shapes in experiments section later. But now, let's take this fractal and turn it into a particle system. With particle systems, we have many individual particle objects that together create different effects, such as smoke, some kind of flow or rain effect, for example. Today, each of our particles will be one of these complex fractals and I will make them reign over canvas. Like the examples I showed you in the beginning of this course. On canvas, we usually animate something by drawing a static shape, deleting it, updated its position, and drawing it at this updated position. We do that very fast, usually 60 times per second. And as particles are being drawn, deleted, updated and drawn again, we get an illusion of movement with simple shapes like circles or rectangles it's easy, but with a complex fractal shape like this, we have a problem. What if I want the shapes in my particle system to be complex? For example, I will draw a fractal that takes 1 second to render. There is no way I can draw multiple of these fractals on canvas, delete them, updated them, and redraw them over and over 60 times per second. With a shape that takes 1 second to render it's simply impossible to draw and animate multiple ones 60 times per second. But if you saw the examples at the beginning of the video, you could see I did that. I rendered many complex randomized fractal shapes and I made them rain over canvas at 60 frames per second. How did I do that? That's what we are about to learn right now. 11. Particle systems and animation: Here we have our custom particle class to draw individual particle and define its behavior and movement. And we will have this rain class that will handle the entire effect, all the particles at once. Let's write and explain it line by line so we know exactly what's going on. This class will need a constructor. It will expect arguments for width and height of canvas, mainly because one of these classes responsibilities will be to spread our fractal particles over the available canvas area. I convert these arguments into class properties as usual, canvas width property on this rain object we are creating right now is equal to canvas width variable that was passed as an argument to the class constructor. Canvas height property on this instance of rain class is equal to canvas height variable that was passed as an argument. This dot number of particles will determine maximum number of active particles on screen. This.particles will be an array and it will hold all currently active particle objects created using our Particle class. I create a private helper method called initialize. The job of this private method will be to fill particles array from line 79 with 20 particle objects. So for-loop, it starts at 0, as long as i is less than number of particles from line 78, i++. Every time this for-loop runs, in our case, it will run 20 times. It will take this.particlesArray from line 70, and it will use built-in array push method. In JavaScript array push method adds one or more elements we pass to it, to the end of the array it is called on. It also returns the new length of the array, which might be useful sometimes I pass it new Particle so one instance of our custom Particle class from line 70, the new keyword will look for a class called Particle, and it will create one new blank JavaScript object. It will then run code inside constructor of this Particle class to assign it values and properties based on the blueprint inside. Let's write our Particle class and define that blueprint. Each object created by this class will also have to be aware of available canvas width and height because I want these objects to reset when they fall off screen. Initial starting x-position horizontal coordinate will be a random number between 0 and canvas width. Vertical y-coordinate will be a random value between 0 and canvas height. Width of each particle will be, for example, 50 pixels and height will be 50 pixels as well. Each particle object created by this class will have access to update method. Inside update method we will define behavior and movement. Update method will be called over and over 60 times per second and each time it runs, we will push it one pixel on horizontal x-axis to the right, and one pixel on the vertical y-axis so downwards, these two actions combined will result in bottom-right movement. Each particle will also have its associated draw method where we define some code that will draw this particle 60 times per second. After each position update. Draw method, will need one argument to specify which canvas we want to draw on. This effect will use two canvas elements. I will explain it as we go along. Let's start simple by calling built-in fill rectangle method. This method takes x, y, width, and height, and it will draw a rectangle at that position of that size. Perfect, so we have a blueprint that can be used to create 20 particle objects. On line 71, I can see my Particle class needs arguments for canvas width and canvas height. I need to remember that when I trigger this class constructor using the new keyword on line 97, I pass it this,canvasWidth from line 90 and this.canvasHeight from line 91. Initialize is a private method. It can't be called from outside of this class. It can only be called from somewhere within this class. Class constructor in JavaScript has one main purpose. It creates one new blank object and assigns it values and properties it contains to create one instance of that class. While doing that, it also runs all the code it contains. So we can take advantage of that and we can run our initialize method from inside of the constructor. I do it by calling this dot #initialize like this. Structuring our code like this will cause the code inside initialize method to be automatically executed when the constructor is triggered. When we create an instance of this rain class using the new keyword, we will test it in a minute. Rain class will also need a public method that will run 60 times per second. It will cycle over all currently active particle objects from line 93 and it will call their associated update method from line 79 and draw method from line 83 on each of these 20 particles one-by-one. And it will do it 60 times per second. So all our active particles will be constantly updating and redrawing at new positions, creating an illusion of movement. I do that by taking this dot particles array from line 93, which will contain 20 particle objects, thanks to the initialize method we just wrote and I call built-in for each array method on it. For each method executes a provided function once for each array element, so I assign each element a temporary variable name. Just for the inner purposes of this for loop, I call each one, for example, particle, and provided function that will be executed for each one of the 20 elements will just take that particle and call its draw method from line 83. I can see that method expects argument for context to specify which canvas to draw on. Run method is public so it will take that context argument from the outside. I will show you in a minute when I call it, and it will pass that context along to draw method on each particle. For each array method, will also call update method from line 79 on each particle object. Our Rain class is ready so let's try to run the code to see what we get. I create a custom variable I call for example rain effect, and I make it an instance of Rain class from line 88 using the new keyword. As we said, the new keyword will look for class with this name and it will run its constructor. On line 89. I can see that rain class expects canvas width and canvas height as arguments. So I pass it canvas width from line four and canvas height from line five. Because we call this.#initialize on line 94 from inside the class constructor this code should get automatically triggered just by creating an instance of the Rain class. On line 97, we can see that this code should have filled this.particles array from line 93 with 20 particle objects. Let's check if it worked by console logging rainEffect from line 108. I open my console and I can see that we have an instance of Rain class with properties like canvas width, canvas height and number of particles exactly as we defined in the blueprint. And we can also see that particles is an array with 20 objects created using Particle class. This is looking really good. Let's test if run method works. Since this is a public method, I can call it from the outside from the instance of this class. So rain effect dot run. I actually want this method to run over and over 60 times per second, updating and drawing all 20 particles inside particles array. To do that, I will need to put it inside animation loop. I create a custom function. I call, for example, animate. Inside. I call rain effect from line 188 dot run the method we defined on line 101. Then I call request animation frame method. This built-in method tells the browser that we want to perform an animation and it will request that the browser to call a specific function to update the animation between the next repaint, that function to be invoked before repaint is passed as an argument. And I will pass it animate the name of its parent function. So I will create an infinite loop. Animate will start, it will call run method from line 101 and then request animation frame we'll call animate again. This will repeat endlessly. When we call request animation frame like this, it will usually try to adjust itself to screen refresh rate. So in most cases it will run at 60 frames per second for smooth animation, here we declared animate. I also need to call it for the first time to start animation loop. It's not working yet because here on line 101, I can see that run method expects context as an argument. Without it, JavaScript doesn't know which canvas element we are trying to draw on. I pass it ctx from line 3. And we are animating if this is your first canvas animation, I want to say congratulations, with the knowledge, we covered today you can do thousands of different effects. Let's finish and polish this one. 12. Drawing on multiple canvas elements: First of all, I don't want my particle rain to be limited to the small canvas area in the middle of the page. I want it to cover the entire browser window. To do that, I go back to index.html and I create another canvas element. We can have as many canvas elements as we want in our project. And we can specify which shapes we want to draw on which canvas. Today I want to render large fractal shape on the smaller canvas 1 in the middle of the page. I will somehow convert it to particles and I want those particles to fall on canvas 2, which will cover the entire browser window. Right now, these particles are just represented by blue rectangles, but we will fix that in a minute. We created another canvas element with an ID of canvas2. In style.css I give it a red background, position absolute, top 0 and left 0. In script.js up here between lines 3 and 5, we have some code we wrote to set up canvas 1. Let's do something similar for canvas 2. Constant variable called canvas2. is a document dot get element by ID canvas2 ctx2 is canvas2.getContext 2D. canvas2.width is window.innerWidth. So the full width of the browser window, canvas2.height is window.innerHeight. Nice. It index.html I position canvas2 behind canvas1 like this. Now we know that canvas 2 covers the entire browser window so I can remove the red color. Canvas 2 is set up. And if I want to draw something on it, I can use this ctx2 variable from line 17. Inside animation loop I pass it as an argument to run method like this. Now, the rectangles are being drawn on that canvas. As you can see, they turned from blue to black. It's because that blue fillStyle was set up on ctx1, on the other canvas, ctx2 is still set to its default black fill color. I have to pass rain effect width and height of canvas 2, so that it can correctly spread the particles across the newly available area. I'm trying to draw black rectangles, but they are leaving long trails. That's because we can see old frames, old paint. I can use built-in clear rectangle method which will delete a rectangular area on canvas that we defined and it will make it transparent. In this case, I want to delete the entire canvas 60 times per second between each animation frame. I wanted to clear old paint on canvas 2 from coordinates 0 0 to canvas2.width and canvas2.height. So this is how you animate movement on canvas, you delete everything, you update and redraw all elements, and you loop it so it's deleting, updating and redrawing over and over, we have a smaller canvas 1, where we draw our detailed fractal shape and we have canvas 2 positioned behind it that animates falling particles shaped as black squares. 13. Converting canvas drawings into pixels: The effect we want to build today is "fractal rain" so I actually want to replace these black squares with smaller versions of the main fractal we drew on canvas 1. As we said before, it's not really possible to draw 20 of these fractals on canvas at the same time, keep deleting them, updating their positions and redrawing them over and over 60 times per second. It takes much less performance to do that with black rectangles. With complex shapes like these fractals we need to do differently. Drawing complex randomized fractal shapes takes a lot of computing power. Drawing simple black rectangles is cheap and quick and so is drawing images. Canvas is very good at drawing images very fast. What I will do today, I will take the complex fractal we generated, and I will use JavaScript to save it as an image. We will then replace black rectangles that represent our particles with these images. It's actually very easy to do that. Here on line 72, we are creating an instance of fractal class we wrote earlier, and we are calling its draw method to render it on canvas we specified here, I create a new variable called for example, fractal image and I set it to new image like this. This is a built-in JavaScript class that will simply create one new blank img element, image element. It's the same as if I created <img> tag in my HTML file, but this image is not yet included anywhere on the web page. Unless we put it there with JavaScript, I will then access its source property and I will set that fractal. we drew on canvas 1 as image source. We can do that using built-in toDataURL method. Be careful here, URL is spelled with capital letters. If you just do capital U and lowercase r, l, you will get an error. toDataURL method converts the entire content of canvas into an image in base64 format. It's a format that every browser can understand and it's basically a very long string of text. We can then treat it as a normal image and we can do anything with it we can with images in JavaScript, HTML, and CSS. It has an optional arguments to specify file format. If we don't declare it, it will default to PNG which is ideal because I want to preserve the transparent background. So I'm creating one new blank image JavaScript object and I'm basically taking a screenshot of the entire canvas 1. So this area and saving it as this new fractal image variable. If I console.log fractal image, we can see the long base64 data string representing all color and position values of pixels that make up the image. Since we gave our image element a source attribute, we can now listen for load event on it. I cut all this code and I paste it down here online a 111. I delete the console log. The onload event is triggered when an object has been fully loaded. In this case, when the image has been created and the fractal shape we generated with JavaScript was successfully converted to base64 format and assigned as that image's source. I put all this code inside that callback function because we only want to run this after the image has been made available. I pass the image itself as the third argument to the rain class constructor. Up here on line 90, I make sure rain class actually expects this image and I convert it into a class property. So this.image equals image. I will pass that image along to particle class constructor here as the third argument. On line 73, I make sure it is expected and I convert it into a class property on particle class. Now, all our particles have access to the image of fractal we just generated. I will use built-in draw image method to draw it on canvas. We need to pass it at least three arguments. Image we want to draw this.mage from line 76 and x and y coordinates where to draw it on canvas. So I pass it to this.x and this.y. We can also pass it width and height to shrink or stretch that image. Let's try 200 times 200 pixels. I remove the black rectangle. What if I want each particle to be a random size? I create a property called this.sizeModifier. It will be a random number between 0.1 and 0.3. Width will be set to the actual width of the image. So this.image.width, if you control this dot image from line 76 here you will see it has width and height attributes. I multiply that times this.sizeModifier, which will be random and slightly different for each particle. I do the same thing for height. Nice, we have randomized particles. I can increase the range of sizes. I create a property called this dot speed and I set it to one. I replace it down here inside update method. Each particle will have random speed between 0.2 and 1.2 pixels per frame. I want those shapes that fell off screen to reset on the opposite side so that they can animate across the screen again. If this.x is more than canvas width plus its own with meaning they're completely hidden behind the right edge of canvas set their x position just behind the left edge. So 0 minus this.width. I can also write it like this. Yes, that works. They are resetting horizontally. If they fall completely off screen vertically, reset their vertical y-coordinate just above the top edge of canvas. Now we have a pool of 20 particles that will endlessly flow over canvas. You can adjust their sizes and speeds here. 14. Experiments: I can increase the number of particles here. What if I want them to rotate as they fall? I create a property called angle. Starting angle is 0 radians. I also create va, velocity of angle. For every frame, angle will increase by 0.01 radians. We will be rotating and I want each particle to rotate by its own va value. I don't want rotates to flow over to other shapes, so I wrap it in a save and restore like we did before. Now I translate the rotation center point over the fractal shape. So this dot x dot y, I want to rotate and I rotate by this.angle property from line 83, since we moved coordinates 0 0 from the top-left corner over the fractal shape, I need to change this.x and this.y here, to 0 0. For every animation frame we increase angle by this.va from line 84. When we are drawing images on canvas, It's basically a rectangle and by translating to this.x, this.y, I brought rotation center point to the top-left corner of rectangular area that contains each snowflake shape. Now, all the shapes are rotating around that top-left corner. If I want them to rotate around their own center, I need to draw them from a minus half their own width and minus half their own height like this. This part might be a bit confusing, but when you use translate and rotate for a while, it becomes clear and intuitive. Don't worry about it right now too much. I made other classes where I show this on different set of examples. So seeing visuals eventually makes it very clear how canvas rotation works. If I set va to a negative number, they rotate in the opposite direction. What if I want some to rotate to the left and some to the right, I can set va to a random number between minus 0.005 and plus 0.005, like this. Nice, this looks pretty organic. We can increase the range if you want more spinning. So we have one canvas where generate fractal shape, we screenshot it with JavaScript and use that image as particles drawn on the second larger canvas behind it. These techniques can be used for prerendering assets, canvas can be off screen. It could be invisible canvas and I can, for example, a render 20 of these fractals and make each particular choose one randomly. If you want me to show some of these more advanced techniques, let me know. If I remove border around canvas 1 and I set background on canvas 2 to black. Every time I refresh page, we get a different color and a different shape. I cover more about how to create experimental fractal shapes in another class, all of those other shapes could be used in this effect. Let me try some experiments now to see what we come up with. A reduced the number of particles to ten. To make fundamental changes to the core fractal shape, we need to change the code inside the drawLine method here. Right now our base fractal logic is this. We draw the main line, we scale canvas down and we draw smaller line to the left and another smaller line branching to the right. It's kind of a simple fork that repeats itself over and over to finer and finer detail. It doesn't have to be like this. We can use circles, rectangles or Bezier curves here, and the shapes can get really wild. We can also use images to make up our fractal for example. Look what happens if I move, save and restore outside this for-loop. So I'm not reset and translate and scale between each branch, but rather only after all sets of branches have been drawn, we immediately get a different set of shapes. Try to refresh your browser over and over to get an idea what we have here now. If I change this minus to plus we will get spaces between lines. The lines will no longer directly connect. I think this also looks very interesting. Try to refresh your browser a couple of times to see if you get different shapes than what I'm getting. I put this save and restore back inside the for-loop. Now we get shapes that get clipped because canvas 1 is only 600 times 600 pixels. If you want, you can make it larger. It will still work if you make it cover the entire window width and height, same as we do with canvas 2. I remove the second branching line. So base of our fractal is no longer a fork. It's a small hook. I can go up here and increase max level to 8 to get more finer details. Maybe max level 5 is fine. I can add more branches on line 30. If my shape is too big for canvas I can also reduce the base size here. Minus here will make the branches connect again, so I can make it larger. Does anyone else like creative coding experiments like this, or is it just me? I set branches to 2. What if I want to add some circles in the mix. To draw a circle on canvas, we have to start with beginPath. Built-in arc method takes at least five arguments, starting x and y coordinates of center point of the circle, radius, start angle and end angle. Now I call fill to fill the circular path with color. Up online 35 I set fillStyle to match the stroke style. I can also stroke these circles instead. I increased the size of canvas 1 so we have more space available to render our shapes. I change horizontal x-coordinate of circle's center point. I make the base size smaller. Let's try fill. How about we add some rectangles. Starting x will be this.size * 1.5. Y will be 0. Width and height 50 50, width 150. Adjust x-coordinate. Stroke it. Width 10 height 150, width 30. I will be playing with this for a while now. 15. What's next?: Now that you understand some canvas basics, you can try to use curved lines and replace lineTo and moveTo methods with bezierCurveTo and quadraticCurveTo methods. You can also replace lines with images. This effect can be combined with many different canvas drawing methods and properties to get a wide variety of shapes. Experimenting with a code-base like this is always my favorite part. If you want to learn more about HTML canvas, you can check out my YouTube channel. I hope you found some value today. I'll see you later.