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.