Transcripts
1. Introduction: Great and beautiful
things with code can be easy if you take
it step-by-step, everything makes sense if you break it into individual parts, let me show you how to take
basic programming principles, functions, arrays, and for loops to create complex
animated code basis. Once we fully understand
the techniques, we will take it
to the next level in the advanced section. Today, we will discover
the tools, cool, experiment with code,
and we will use that knowledge to design
our own animations. Let's make art with code and learn programming
at the same time. Have fun.
2. HTML & CSS setup: In this first section, I will create a separate
beginner friendly project. We will learn how to
animate lines and how to turn them into
multiple different effects, such as lightening, spirals, waves, and so on. If you want, you can jump straight into the
flow field section, which starts from less than 17. I included source code downloads during key points
in each project. So it's easier for
you to jump between the projects and
experiment with code. If that's what you want to do, I will guide you through the
entire process step-by-step, and I will explain
every line of code, but some basic
knowledge of HTML, CSS, and JavaScript is needed
to get the maximum value. Here, I'm just setting up a basic generic web page with
HTML canvas element on it. I linked to style.css
and script.js files. And then I declare some CSS
styles for canvas and create global reset rules to make
sure that margins and paddings appear the same
across different browsers. I recommend using
a modern browser for this class,
ideally Google Chrome. You can see I'm using this
common absolute position, center and technique to
make sure that Canvas is in the middle of the web page,
vertically and horizontally.
3. JavaScript setup: Basic setup inside
index.HTML and style CSS, we move into script.js. All of our logic will
be written in here. I set up my HTML canvas element, our drawing board with the
usual two lines of code. I created a custom
variable I call e.g. canvas, and I pointed
towards the canvas element we created in index.HTML
using its ID. I gave it an ID of Canvas one. Then I create a
context variable, CTX. I take that canvas
variable I just created, and I call built-in
getContext method on it. This method creates
so-called draw in context, which is basically a
JavaScript object with all built-in Canvas drawing
methods and properties. We pass it an argument
called contexts type. We want to work with
two-dimensional rendering context. Today, canvas element has
two independent sizes. Element size, the actual
size of the HTML element, and drawing surface size. We wanted to make sure both of these sizes are set
to the same values. I do that by not defining
canvas size in CSS and just certain
width and height attributes on Canvas
to the values. I want like this. If you are also declaring
canvas size in your CSS file, make sure it's set to
the same values to avoid stretched or otherwise
distorted drawings. If I console log c
dx from line two, we can see this canvas rendering context object I talked about. Inside. We can see the default
Canvas settings such as the current fill style font. We can see that the
default stroke style is set to black here, e.g. we can also see all the built-in
Canvas drawing methods. Today, we will focus on
drawing and animated lines. So we will learn everything
you need to know about begin path method
that comes from here. We also have closed
path method here, line to here, and
move to method here. Let's use these
methods so we fully understand what they
do and how they work.
4. How to draw rectangles: We can draw many different
shapes on Canvas, too basic shapes are
circles and rectangles. To draw a rectangle
is very simple. We can take contexts we
defined on line two. We know it holds a reference to the object that contains
all canvas drawing methods. From there we call built-in
fill rectangle method. Fill rectangle
expects coordinates from which we will start
to draw in the rectangle, the top-left corner
of the rectangle. X-coordinate, the distance
of the top-left corner of the rectangle
from the left edge of the canvas will
be 100 pixels, y coordinate, the distance from the top will also be 100 pixels. The width of the
rectangle will be 50 pixels and the
height will be 150. I can change the
width to 200 pixels, vertical y coordinate
250 pixels. I think you get the
idea by default, the fill of the shapes drawn
on Canvas is set to black. I can use the fillStyle
property to override the default value and I can
set it to something else. Let's try red. We can also call built-in
stroke rectangle method, which will just
outline the shape. By default, it will be black
line with one pixel width. I can change that using
line width property. Let's try ten pixels. And I can use a stroke style
to change the color of all lines drawn on
Canvas, e.g. blue.
5. How to draw lines: So these are the very basics
of drawing shapes on Canvas. Today, we will focus
on drawing lines. They work a little bit
different than rectangles. We need to start by colon
built-in begin path method. This method will start a
new path and it will also automatically close all
existing path if there are any. This is important because
this way you can draw each line in a different
color and without begin path, all your lines
will automatically join into one massive shape, which is not what we want today. Right? Now, we just want
to draw a simple line. We start by using
built-in move to method. This method will create a new subpath for our
intents and purposes. Let's think about move to
methods as a way to set the starting x and y
coordinates of our line. Then we will use line
to methods which will create a straight line
from the subpath, from the point defined
in move to two, the coordinates we
pass to line two. Let's think about it for now
that line two methods will represent the ending x and
y coordinates of our line. Move to end line two methods
just define the path. They don't directly render anything to draw
the path onto us, we have to use built-in
stroke method. Now, we are drawing a line
from coordinates 300, 302, coordinates 350, 400. We can change these
values to change where our line starts
and where it ends. I can delete the rectangle. Now, in this area, I wanted to define
global settings. Line width will be
ten pixels or 100. I move the coordinates. We have many other
properties we can use here. You can see all of them
if you console log CTX from line two as
we did before, e.g. look what happens when I said
Line Cap property to round. I set the stroke
style to magenta, line width to ten. We can also chain multiple
line two methods like this. Each new line two methods, we will define another
point in the shape that will be connected
with our subpath. And the dentist wrote, this is the main concept of
animation we are doing today. We will have a line
that starts somewhere. Then we will have
many line two calls to the find points
along that path. And similar to the
classic game of snake, we will animate this
by always adding a new line segment to the start and removing
the last segment, Making it look like the line
is crawling over the screen. I add a new line. I add one more. I remove the oldest segment, I add another new line. We will automate this and we will play with
the coordinates we pass to these lines to create interest in
movement patterns. Once you understand
this concept, we will use it to build beautiful flow fields like
this one in an advanced class. It might be easier
than you think to make the lines flow around
images or letters.
6. Object oriented programming: Now that we understand the
concept, let's scale it up. I wanted to create a
custom JavaScript class that will draw and
animate lines for me, I wanted to animate many
lines at the same time. So using a class is ideal here. Class constructor
is a special method to create an
instance of a class. When we call it with the new keyword later
class constructor, we'll create one new
blank JavaScript object, and it will set
its properties and values based on the
blueprint inside. So let's define that blueprint. I want you to start very simple and we will
optimize the code after to make sure it's
absolutely clear what's going on. Each line object will have
starting x and y coordinates. So a custom property I call e.g. start x will be a
random point between zero and width horizontally. Starting vertical coordinate
of each line will be a random value between zero and kind of as
height vertically. So when we combine these
random x and y coordinates, it will give us a point somewhere
within the canvas area. I want to do the same thing for ending x and y
coordinates like this. So when a new line
object is created, it gets randomly assigned
start point and end point. Let's give it a
custom draw method to draw a line between
these two points. I call begin path first. Move to method to define the starting x and y
coordinates of the line. Line two method to define ending x and y
coordinates of the line. And we call stroke to actually render that
path on Canvas. We have a class, our
blueprint is ready. How do we actually use it
to create one line object? I create a constant variable, I call line one. I set it equal to
new line like this. The new keyword will
look for a class with this name and it will trigger
its class constructor. Class constructor, we'll create one new blank JavaScript object, and it will assign it values and properties based
on the blueprint inside that object will be saved in disgust them
line one variable. Since this object was created
using the line class, it has access to this
public draw method and I can call it like this
line one dot draw, random starting and
ending coordinates were calculated and a line
was drawn perfect. Up on line seven, uncertain linewidth
to ten pixels. But maybe I want
each line created by the class to have a random width within
a predefined range. I created this dot
line width property, and it will be a random
value, 1-16 pixels. I will wrap it in Math.floor so we don't get sub pixel values. It's a good habit to do that for everything we draw on
Canvas whenever possible. I could have also done it
for x and y coordinates. I take CTX from line to line width and I set it to this randomized
line width value. Now, every time I refresh
my browser window, align with randomized position and randomized line
width will be drawn. We are doing something that's considered a bad practice here. I defined canvas
variable on line one, and I'm using that same variable directly inside my class. I do the same with ctx variable. The code will work, but if you are writing
object-oriented code, the goal is to keep your classes independent of their
lexical environment, independent of the code that's
outside the class itself. Right? Now, this class
will only work if Canvas and CTX variables
are defined up here. The right way to do
this is to convert this outside variables
into class properties. That way, there
will be passed to the class constructor here
when the class is created. And they can come from
anywhere in our code base, making our class more
self-contained and reusable. You often ask me about why
I do this in the comments, so I hope this explains it. Line class constructor will expect cannabis as an argument. Inside. I convert that argument
into a class property. I'm saying take canvas variable that was passed
here and convert it into Canvas property on this instance of line class
you are creating right now. Then we can use this dot
Canvas from line 12 here, instead of directly pulling
the variable from line one. To make all this work, I know that line
class constructor expects us as an argument. So I take canvas
variable from line one and I pass it as
an argument here. When we create an
instance of that class with the new keyword perfect, We are also directly pulling CTX variable from land
into our class here. So our public draw
method will expect context as an argument
and it will be used here. Then we take CT
x-variable from line two and we pass it to
draw method down here. When we call it that, CTX will get converted
into context here. So this is how you make
classes more self-enclosed and less dependent on
the surrounding cold outside that class.
7. Dynamic colors with HSL: We are defining stroke
style up on line eight, but maybe I want the color of the lines to be
randomized as well. In that case, I have to define stroke style inside
the draw method here. I said it's to write
it just to test it. If it's working. I wanted to assign
a random color every time we create a new line. The easiest way to
do that is by using the HSL color
declaration on the web, we can define colors in
many different ways. You have your standard RGB
and hex color declarations that everyone is familiar with. We can also use HSL
color declaration, hue, saturation, lightness. The first argument here, you will represent a degree
on the color wheel 0-360. Saturation will be 100 per cent. We want full colors. Lightness will be 50 per cent. We want a color not
affected by light or dark. Zero degrees on the color wheel
is right, as you can see, 120 is green, 240 is blue, 360. We cycled back to red. Again. As we increase the view, we travel over all the
colors in between. If we go any higher than 360, we just cycled
from the beginning from the red color again. Hsl color declaration is structured in a way
that makes it very easy to randomize colors or to dynamically cycle through
the color spectrum. I create a custom
class property. I call this dot view. It will be a random value 0-360, and I want only integers. So Math.floor, we will make
stroke style dynamic by replacing hard-coded hue value with this randomized
variable like this. And that's it. Every time I refresh the page, we randomly calculate
a different color.
8. Randomized line art: As we said, JavaScript
class is a blueprint. We can use it to create
many similar objects. So why are we creating
only one randomized line? Let's create many more. At this point, it's very easy. First I create a variable
called lions array. This will be an array to hold all airline objects
in one place. Now I can create a for-loop. That for loop will run e.g. ten times. Every time it runs, i take lines array and I call
built-in array push method. Push method. We'll take
whatever we pass to it as an argument and it will push
it to the end of the array. I will pass it a new line. And online 11, I can see that line class constructor expects Canvas as an argument. So I pass it canvas
variable from line one. I delete this code, and I console log lines array. As expected, it contains ten line objects created
using our custom line class. I open one and I inspected to make sure all the
properties have values. I want you to check that
nothing is undefined, which would be a signal of a
problem. All is good here. I can also create
a number of lines, a variable, and I set it to 50. I use it here in the for-loop. In the console, I can see
that lines array now contains 50 line objects with randomized positions,
linewidth, and colors. How do we draw them? Since we are holding all align objects inside lines array, we can simply call built-in
array for each method on it, for each method will call a provided callback function
once for each array element. So inside, for each line
object in the array, I want to call its
associated draw method we defined on line 20. If you are wondering where this word object is coming from, it's just a random
word I chose to use. We are basically a sign-in
each object in the array, a variable name that
will be used to refer to that object inside
this for each method, that variable name can be anything as long as
we keep referring to it that way everywhere
inside this callback method, I can also call it line e.g. so for each line object inside lines array called
its draw method. On line 20, I can see that draw method expects
context as an argument. So I pass it CTX variable. We defined on line two. We are drawing 50
lines on Canvas. Each one has randomized starting
and ending coordinates, randomized width, and color. You can refresh the page
to generate a new set of random values and
draw them on Canvas. Congratulations if this is your first creative
coding project, this is a very simple form of procedurally generated art,
so-called generative art. Generative art is
a collaboration between a human and a machine. Rules and scope of this artwork are decided by us, the human. But some of the features, in this case colors
and positions, are randomly decided
by a computer. There are of course,
much more advanced forms of generative art. And I made many classes where we explore that in different ways. Let's see where we
can take this one. Let's animate it.
9. Drawing multi-segmented lines: For clarity, I will go back to one line so we can better
see what's happening. As we said in the beginning, I wanted to have a line made
out of multiple segments. We will be adding new segments and remove an old ones to make it look as if it's crawling around like the
classic snake game. I need to make some changes in the line class to achieve that, we will need only one set of x and y coordinates, like this. These coordinates will
update for every tick of animation loop
and we will add and hold a certain number
of positions in a history array
that I define here. First, it will be a
simple object with x and y property equal to this
dot x and this dot y. We just randomized here. This node history
is an object now, but it will be an array
where each element in that array is an object like this with x and y coordinate. Move to method will define the starting x and y
coordinate of our path. And it will be this dot
history array index zero, which will be object like this. And we access its x-coordinate
and also its y-coordinate. Let's say I will keep ten
objects inside history array. Each one has its x
and y position value. Here I want to cycle through however many positions we
hold in history array, and I want to connect
them with a line. So I create a for loop which
will run as many times depending on the length of this dot history
array from line 15. And each time it runs, it will take object, which will look like this
at that index in the array, and it will pass its x and y coordinates to line two method. So we have moved
to method to set the starting x and y
position of the line. And then we cycle through all other positions and we
connect them with a line. Then we call stroke to render that multi segmented
path on Canvas. We don't have
animation loop yet, so I will create
another for loop that we'll just simply run three times and create three
randomized x and y positions. I need to make sure
the stone history up here is actually an array. At first, it will contain just one element with
an index of zero. It will be this object that
has x and y properties set to this dot x
and y coordinates. Here I run a for-loop
three times. Each time it runs, i create new random
x and y position. I take distort history array and I push another object with these new x and y properties in their careful about
the brackets here. Nice. Every time I refresh
the browser window, we draw a new multi-segment line starting x and y coordinates. And for loop that runs 123 times each time creating a
line segment, a subpath. I can make the for-loop
run 30 times if I want to, which will create much
more complex shape. I hope it's clear now how the concept of
history array works. Now we take it one step
further to animate it.
10. Animating lines: I create a custom
function I call e.g. animate. Inside, we
will draw a line. We will update it. And then we call built-in
Request Animation Frame, which tells the browser we
want to perform an animation. This will make the browser
call a function we specify before the next repaint. In practice, this means I simply pass it the name of
its parent function, animate runs, it does something and then
request animation frame. We'll call animate again, creating an endless
repeating animation loop. Request animation frame method
fits on the window object, but it can be called
directly like this. So here we define animate.
I need to call it. To start the first
animation loop. I put a console log inside
that says animating, just to check the loop
is actually running. Yes, I deleted. I want to put our draw
code inside like this. This is just creating an
endlessly grow in line. We can temporarily comment
outline for the seven. If your computer
struggles with this, we will fix it in a second. I want to delete old paint between every animation frames, so I call built-in
clear rectangle method. I want to clear the entire
canvas from coordinate zero-zero to canvas
width, canvas height. Okay, I comment this out
because we are adding 30 line segments
over and over into history array here on line 20 for creating an
endlessly grow in array, I create an update method on the line class for
every animation frame, every time update method runs, I want to add one new
segments to our line. I will delete all of this. I uncomment line 47. And for each tick of animation, I want you to call
draw method on every object inside
the lines array. Currently, we have just
one object in there. I also wanted to call the
update method we just defined. Now we can see line gets
drawn segment by segment. It doesn't make sense to cycle through the same
array twice here. So instead, I call draw and update in the same
callback function. Careful about the brackets here. Is it to make a mistake
and get an error? Nice. I don't want it to be
drawing a path with an endlessly increasing
amount of points. I want to have a
certain max length. And when that is reached, we will remove the oldest
segment and we add a new one. Let's say I want the path
to be ten segments long. Inside update method. As I add a new segment, I check if the length of history array is more
than max length. If it is more than ten, we will use built-in
array shift method. Array shift method removes
the first element from the array because we are using push method to add
a new segment. Push method adds a new element
to the end of the array. Shift method will remove the oldest segment for us from the beginning
of the array. And here we reach the second step in our
procedural line art. What do you think about this? It scales, but with rules, we can add as many
lines as we want. They all have
randomized width and color because of the logic
we wrote in the beginning, we're just randomly picking
points inside cannabis. What if we want some sense
of continuous motion? We want the updated position
of the next segment to have some relation to the position of the
previous segment. This is getting us one step closer to an actual flow field. I hope you're
getting some value. Let's explore motion and now I will think of our
line as a particle. Its first segment
is moving along the canvas in a certain
speed and direction. The other segments follow. I give each line speed X, horizontal speed and speed. Why vertical speed? We will start from
a random point somewhere within canvas area. And every time we
call update method, we increase the current
exposition by the value of speed x and vertical y position
by the value of speed. Why? Insight animation
loop I will uncomment, draw and update to
see what we just did. Okay. I create 20 lines. Yes. This is what's happening.
11. Rainbow lightning storm effect: I'm going for lightening effect. I want a rainbow
lightning storm. Every time we update
horizontal position by speed x of ten pixels, we will, on top of that, add a random value 0-10 and 50. What about a random value
between -25 and plus 25 horizontally and the same
vertically interests. Then when I refresh my page, I get a set of very
short lightning bolts. If I want them to go more
from the top to bottom, maybe horizontal speed
will be only plus u2 and vertical speed 15 or 46. I would like the animation
to play out and then reset. I could e.g. check if all
segments left the canvas area. Or I can give each line a timer. Let's say each line
has a lifespan of maxlength from
line 18 times ten, each line will move around
for 100 animation frames. Inside update method, I will increase the timer by one for every animation frame.
How do I do this? If timer is less than lifespan? We animated the line. I also need to define this dot timer in the
class constructor up here. And I initially set it to zero. When the timer is
more than lifespan, we are still drawing the lines, but we're no longer adding
and removing segments, so it looks like
they froze in space. At that point, I want
to start removing segments one by one until
the line disappears. So else, and here I will again call shift on
the history array. Bear with me a second. I need to work out
the logic here. We need to make
sure that there is always at least one
element in the array. Else if this dot
history dot length is less or equal than one,
called reset method. We will define reset method. Now, will this work? Timer is increasing as long as the timer is less
than than lifespan, add and remove positions, making the line crawl around. If the length is less or
equal to one, reset it. Else, meaning the timer
is more than lifespan, but length is not yet
less or equal to one. Keep removing one
segment at a time. I think this should work. Now I need to define the reset
method that we'll just set the line back to its initial state so
that we can animate it. Again. We will need to
reset x and y position, and we will add that
starting position inside history array. We will also reset timer back to zero so that it can count again. I'm getting a console error. Probably you notice
this before I need to call a shift to remove the first segment
from the array on the actual history
array. Like this. Yes, this is what I wanted. Lines move around
400 animation frames and then they reset. They all reset at the
same time because we hard-coded maxlength to be the same value for all of them. I set it to a random
value, 10-160 segments. Now we're getting some
real sense of motion. I can also randomize speed x, let's say a random value between
-2.5 and plus 2.5 speed, y will be six. Speed. Why ten? Maybe here I should do random value
between -0.5 and plus 0.5. Speed Y5 seven. I can also play with the
random range here and here. Generating random
numbers like this for each animation frame is very performance expensive,
just so you know. But since we don't have
thousands of lines, most computers
probably won't even notice reducing how many
times your codebase calls mastered random
pair animation frame will massively increase
performance of your effects. I said lifespan to max
length times three. You can see that we have a line that starts from one segment. It grows until it
reaches max length, then it calls around to a
maximum of three terms. It's maxlength. At that point, it no longer grows. It shrinks from the last
segment from the end, and then it disappears
and resets. This type of line
motion is exactly what you need to
create flow fields. Now, one way how
to implement it. I create 100 lines. This is my version of procedurally generated
rainbow lightning storm. If you are a beginner and you
followed all the way here, you are now a creative corridor with a knowledge of HTML canvas. Congratulations, feel free to add this project,
your online resume.
12. Linear gradients: Html canvas offers so many
different built-in tools that allow us to
modify our animations. We just built the rainbow
lightning storm effect. The lines we are drawing
on Canvas are just shapes. Let me show you four
very different ways how you can enhance the fill and stroke of your shapes using gradients, images, and shadows. Html canvas offers two
methods to draw gradients. We can use create
linear gradient method. It's very simple to
use. Let me show you. It creates a gradient
along a line connecting two given
set of coordinates. First two arguments,
we pass to it our x and y coordinates
of the start point. I want my gradient to start
at coordinate zero-zero. And I wanted the end point to be at coordinates canvas
width, height. We drew an invisible
line from here to here. Gradient will be drawn
along this line. To work with gradients, it helps to save
it in a variable. I call it e.g. gradient one. Then I take that
variable and I call built-in add color
stop method on it. Here we define offset and color. So at offset zero in the
beginning of the gradient, I wanted to start from pink. I copy this line a few times. Let's start at offset 0.2. Actually, 0.3 will be read. 0.4 is orange, 0.5
is the yellow. 0.6 will be green. 0.7, turquoise, 0.8 is
violet. That's enough. I think I'd take this stroke style
declaration and I put it here after we
define the gradient. And I will set it to
the variable that holds our newly defined
linear gradient. Notice there are no quotes, just the variable
name like this. I also need to comment out line 33 so that our
gradient gets applied. As you can see, we are drawing
a linear gradient from coordinate zero-zero to
canvas width, canvas height. I create 200 lines. This is a very different version of rainbow lightning
storm, isn't it?
13. Radial gradients: I copy this line of code. I will call it gradient to. We also have create a
radial gradient method. This one expects at
least six arguments, x and y of the center point of the inner start circle
and its radius. I want the circle center point to be in the middle of Canvas. So Canvas width times 0.5, canvas height times 0.5, and radius will be 30 pixels. The end circle will
be also starting in the middle of canvas
with a radius of 200. I give it to color stops just to show you what
that looks like. I think you already understand
this concept anyway. At 0.4 offset, we will have green from 0.6, it will be blue. Then I set the stroke
style to Gradient tool. Here. I can change
the radius value. I can add color stop here. So this is how you use
linear and radial gradient.
14. How to fill shapes with a pattern: As we are filling our
shapes with gradients, we can fill them with a pattern. That pattern can be something we draw on an off-screen canvas, but we can also use an image. In index.HTML. I create an IMG element. I give it an ID
of pattern image. As a source, I
would normally use a path towards that image file, but that would give
us problems if we run our code locally
just from a folder without using a server to avoid any cross-origin
resource sharing issues, I will turn the actual
image into base 64 code, and I will make that code
part of our index HTML file. When I do that, the
image will be considered the same origin
in all scenarios. So I don't have to deal
with anything server site. In this course, I want to focus on front-end
web development. I prepared an image that you can download in the
resources section below, I made it the same
size as our canvas. We can also use your
own image if you want. I can use JavaScript
to turn image into base-64 data string by drawing it on Canvas and using two data URL built-in method. But for simplicity, I will use a website to do that for me, there are many
websites that can turn images into code
in seconds for us, I just go to Google
and I searched for PNG to base 64 phrase. I will choose this link
online PNG tools.com. I drag and drop the
image I want to convert here and it generates
a data string for me. We have to make sure that the
string starts with base 64. If it doesn't fall, you click this checkbox to get
the correct format. I copy this entire code block. It's a lot of code depending on how big the image
you are using. S. I paste it here inside the
source argument, inside as RC. In Visual Studio Code, I can go to View
where drop to put this extremely long line
of code onto one line, making it easier to
navigate in this code file. I go to style CSS. Actually I saved the changes in index.HTML and you
can see that we are drawing the image
on the web page without needing the
actual image file. The code we just generated
replaces the image file completely and it contains all the pixel information
about that image. I hide the image with CSS
using its ID display none. Okay, we have an
image we can use. So in this area, we define the gradients. Here, we will create a pattern. It's also very easy. I will need a reference
to that IMG element we just created in index.HTML. I create a variable I
call pattern image, and I pointed JavaScript towards the IMG element using its ID. Another variable I call pattern
one is equal to ctx dot. And here we use built-in
create pattern method. This method works
very similar to the two methods we used
to create gradients. It takes two arguments,
image and repetition, and we can then use
it as fillStyle or stroke style to fill our
shapes with that image. Let me show you what
that looks like. Image to draw will be pattern
image we just created. And repetition rule
will be no repeat. Now I simply set a stroke
style to button one. And here we go. We can see that our lines are now
filled with an image. This technique can
be used as a way to reveal an image, e.g. depending on what
kind of application or effect you are building, we can also use a seamless tile as an image to create
a repeating pattern, which is probably what this
method was designed to do. As the main purpose, I can use wider lines to
see more of the image e.g.
15. Improve your effects with shadows: I like to use Canvas
shadows to get my shapes a subtle 3D effect. I said shadow offset
to two pixels. This will define the
distance between shadow and the
shape horizontally. Shadow offset y,
vertical distance will also be two pixels. This can be positive or
negative values, by the way, depending on which side, relative to the shape you
want the shadow to be. Shadow color will be white. This will give us this
interesting cracks affect or reduce the
number of lines. Shadow color block. I can go back to gradient
one, the linear gradient. Now we can better see
what the shadow is doing and how it
highlights the edges. If you have any performance
issues and FPS drops, the quickest way to fix it is to reduce the number of
lines you are animating. And you can also
disable these shadows. I supply and width
to a smaller value. Our rainbow lightning
storm can take many forms, and I think you have
plenty of tools now to adjust it and
make it your own. Let's do something completely
different with this. Now. How about instead of
lightning, I want spirals. Let's talk about trigonometry. As always, you might
want to create a copy of your code to save this effect
for a future reference, we will make radical changes
to the code now and we will transform it to a completely different
generative art piece.
16. Trigonometry: Trigonometry is
all about angles. I want each line object to
have its own angle property. So I define it here. For every animation frame, I will be endlessly increasing
that angle value by 0.1. This value will
represent radians. So instead of doing this
lightening effect here, I will increase x by Masdar sign and I pass it this
ever-increasing angle value. This will create left and right horizontal motion like this. Parsing and every increase
in value to methoxide will just map these
values along a sine wave. What if I want the waves to
go further left and right? I create a property called this dotted curve
and I set it to 30. By default, method sine is cycling between minus
one and plus one. If I multiply that value
by this dotted curve, we increase that range. And this is the result. I want smooth lines, so I remove this
part of code here. Try to use different
value for the curve. If you need more clarity on how this value affects the
motion of our lines. What if I wanted to
curve to start at a very small value and
gradually increase, starting curve will be 0.1. And Vc, the speed at which it will be increasingly
will be 0.25. Inside update method for
every animation frame, I wanted to increase
curve by VC like this. And when lines restart, I want the angle and the
curve to go back to zero. Interesting effect. We can do so many different
things with it now.
17. Chaos scribbles effect: What if I also increased
vertical y coordinate by sine of the
angle times curve, we get this strange
back-and-forth motion. But if I use cosine instead
of sine and cosine, we'll work together to map a path along a
radius of a circle. And because curve is
increasing as animation runs, we don't get Circle. We actually get spirals. I said VC, velocity of curve
to a much smaller value. Now we are drawing
spirals like this. I can also make it a random
value between -0.2 and plus 0.2 minus values will move in the
opposite direction. Maybe I also want to randomize how fast the angle increases. Va, Velocity of angle
will be a random value between -0.25 and plus 0.25. Then I use it here instead
of the hard-coded value. The speed at which the
angle is increasing will affect how
compact the curve is, how fast it spreads out. This is symmetry. I want to add some chaos now
for this simple art project, or imagine someone is drawing these beautiful
symmetrical spirals. But at a certain point
they get angry and they start scribbling randomly and they break the spiral shape. Let's say if the timer is more
than half of the lifespan, the second half of lines life cycle start multiplying
va by minus one, switching it to the
opposite value each time a new animation
frame takes, each time a new
segment is drawn. This will break the
spiral shaped like this. We have a path that starts as a spiral and then it
turns into a broken line. I wanted the line to be
more and more broken. We need more chaos
at the end. Yes. Let's change this to 0.9. Most of the time it
will be symmetry. Last 10% will be chaos
because we are modifying va value and we are not bringing it back to
its original range. Lines stop making spirals
the way they should. As the lines reset, we need to be also
reset and va value. I copy line 48 and I paste it down here inside
the reset method. Nice. We started with
symmetry and we end in chaos. Exactly like me on
a Saturday night. We are constantly
calculating lifespan times 0.9 because
this calculation happens so often to
make it easier for JavaScript to run this code and help the performance
a little bit. We can precalculate this just once in the
class constructor, and we will then use that
pre calculated value here. I will call it e.g. this dot breakpoint and it
will be lifespan times 0.85. Then I can use this
dot breakpoint here. And instead of
calculating this 60 times per second for every
line object, now, we are just calculating it once for each line at the point where each line object gets created using the
class constructor. I make the lines wider. I said canvas
background to black. Yes, this is the
effect I wanted. I comment out stroke
style on line 54. Now the stroke is black
so we don't see anything. I can give it to white color. Now we can see again
how the shadows give it a very subtle 3D effect. I said number of lines, 250. Be careful here. It might be too much for older computers and you
might get lower FPS. I do 100, I'm just
experimenting now. I can set the stroke style
to linear gradient we defined on line 15 just to
see what that looks like. How about I use radial gradient? For this one, I should
definitely choose different colors to
make it look nice. I can also use the image pattern
here again. Interesting. Have you seen this great
pattern method being used before or is
this your first time? Now you have all the
tools you need to create fully animated,
beautiful flow fields, check out the links in the
description and join me in an advanced class where
we use the techniques we learned today to create
more generative art.
18. What is a flow field: Here is a grid of angles. We can also call it a
vector field if we add particle system on top of it and make the particles
flow over the grid. Their motion is influenced
by the field vector. The direction and
speed of movement will be influenced
by the angle value stored in the cell each particle is
currently moving over. This class is for creative
people who wants to understand how the code
works under the hood. Don't forget to check
the resources section to download source code if you
want to compare it with mine. I included multiple
source code downloads at key points in
the project as we progress with the class
in case you want to inspect my code and
compare it with yours. I created a generic web page with HTML canvas element on it. I linked to style.css
and script.js files. I did a global CSS reset and I gave canvas element
a black background.
19. HTML canvas setup: After a simple HTML
and CSS set up, everything else in the
project will be handled by JavaScript here inside
script.js file. As always, I need to set up my Canvas custom variable called cannabis
will be document, get element by ID and ID
I gave it was canvas one. Ctx context is that canvas
variable dot getContext. And I pass it to the to create an instance of canvas
rendering context object. Now, the CTX contains
all built-in to the drawing methods
and properties that any web browser
can understand. I want you to have a
full-screen effect today. So width will be window in a rave and canvas height
is window in her height. I'm not sure if you can see it, but doing this gave my
web page scroll bars. I don't want them so easy
fix is to give canvas element position absolute,
like this, nice. So this entire black area is our Canvas, our drawing board. I can use ctx variable from
line tool to draw something. Now, if I console gate, we can see the default settings. E.g. I. Can see that the default kind
of as fillStyle is black to make sure the shapes we draw our visible against the
black background. I said fillStyle to white.
20. Drawing shapes on canvas: Down here we can see all built-in Canvas
drawing methods, e.g. the arc method,
which can be used to create an arc or a full circle. If we had more shapes on Canvas, I will have to call begin path. But now I can skip it. And let's say I want a
circle with a center point at coordinates 100 horizontally from the left edge of Canvas, 100 vertically
from the top edge. Radius of the circle
will be 50 pixels. Start angle of the
arc will be 0 rad. And since I want to
draw a full circle, the end angle will be
math.pi times two, which is approximately
6.28 rad or 360 degrees. Full circle. Arc method
works with a path, so it doesn't directly
render anything on cannabis. It just positioned
an invisible path. We can choose to stroke that path or to fill
that shape with color. So I call built-in
fill method like this. I can move the central
point of the circle around. I can change the radius. I can even change the end angle or start angle to get
a different shape. Okay, so our Canvas is set up and we tested it
by drawing a shape. In this area, I will keep my global Canvas settings
to keep our code organized. Today, we are building
animated flow field. It will be made out
of individual lines. So how do we draw a
simple line on Canvas? We start by Colin, built-in and begin path method. This will start a new path as well as clothes all
previous paths. If there are any, we
call moved to method to set the starting x and y
coordinates of the line. We call a line to method to
set the end in coordinates. Or we can chain
multiple line to, to create a more complex shape, which is what we
will be doing today. Same as the arc method. Move to end line two
methods just said above. They don't directly render
anything on Canvas. Now we have to choose
if we want to stroke the path or if we want to
fill that shape with color, I will call built-in stroke
method to draw the path. Default stroke style is black, so it's not visible against
the black background. I go up here and I said
stroke style to white. Now we can see we are drawing a line from coordinates 100, 202, coordinates 400, 500. By default, the line
is one pixel wide. I can use line width
property to set it to ten pixels or 100. I can also use line gap
and set it to round e.g.
21. Object oriented programming in JavaScript: Okay, So this is how we draw and style lines on HTML canvas. This is all we need to know to create a wide
variety of effects. I wanted to create
many animated lines that will flow
around our Canvas. I will use the JavaScript
class called particle. Like this. Each particle will
be moving around and we will connect its
positions with a line. Javascript classes
are blueprints to create many similar objects. And we are about to build a so-called particle system
made out of many particles. I will also have a
class called effect. This could have been just
a simple object because we will only have one
instance of this class. It will be the main
brain of our code base. So we have particle class, a blueprint we will
use whenever we need to create a new
particle object. And we will have affect class that will manage
the entire effect, all the particles at once. I'm using classes and object-oriented programming
to keep our code clean, well organized,
and easy to read. This is a tutorial
for beginners, so I want everything
to be cleaned and as easy to
understand as possible. It's my main goal. Affect class will need to be aware of available
cannabis space. So constructor will
expect width and height to come as arguments from the outside when an
instance of this class is created using the
new keyword inside, I converted these arguments
into class properties. Take with passed as an argument here and convert it to width property on this instance of affect class, same for height. Affect class will have
initialized method. This method will run just
once to set everything up. We will have this dog
particles property. This will be an array that holds all currently active
particle objects. So when we initialize
this effect, we want to take
this load particles array and we want to use built-in array push
method to push one new particle into the array. That particle will be created using the class we
defined on line 11. So let's write that
particle class right now. Every particle will
also need to be aware of canvas width
and height because it needs to know when it moves
outside of the canvas area to keep our code modular instead of boolean canvas size
from lines 3.4, I will take that value from
the main effect class. I wanted to access these
properties from lines 920. I will give particle class access to the entire
effect class. Particle class constructor
will expect effect as an argument and inside we convert it into a
class property. So here we are pointing towards
the main effect object. Keep in mind that objects in JavaScript are so-called to
reference the datatypes. We are not creating a
copy of effect object. Every time we create
a new particle, we are just pointing to that
same space in memory that holds the effect class from
all the particle objects. That way particles
have access to all methods and properties
on the main effect glass. And also if any of
these properties update because we are just
pointing to that object, any changes on the
effect object will be immediately visible from
the particle objects, which will be very
useful for us today. As I said before, we will have one effect
object to manage everything. And we will have many particles, hundreds or maybe thousands. Let's see how it goes. Each particle will have
starting x and y coordinate. Horizontal x coordinate will
be a random value between zero and this dot effect
from line 13 dot width, we are accessing this
property from line 20. Vertical y coordinate
will be a random value between zero and this
dot effect dot height. To avoid sub pixel values, it's a good practice
to use integers, whole numbers without
decimal points by wrapping these in Math.floor, this will round the value down to the nearest lower integer. Each particle will have
a public draw method. This method will expect
contexts as an argument to specify which kind of
us we wanted to draw on. I pass it as an argument like
this to keep our code more modular and less dependent
on the outside environment. Then we take that context and we call built-in fill
rectangle method. Fill rectangle method
is very simple. It will just take
these randomize X and Y coordinates
we just defined, and it will draw a
rectangle there. Its width and
height will be e.g. ten times ten pixels. We have our particle class
and we have affect class. I wanted to create an
instance of effect class. I create a custom
variable I call e.g. effect, and it will be equal
to new effect like this. The new keyword will look
for class with that name. It finds it up here and it will trigger its class
constructor online 23. We can see that effect expects width and
height as arguments. So we pass it canvas
width from line three. And kind of as height from
line four, like this. Now, I can take that
affect the variable and call init method
we defined on line 28. Init method will take
particles array and it will push one new particle
object into the array. We get an error because
here on line 12, you can see particle
class constructor expects a reference to the main
effect class as an argument, then uses that
reference to access width and height
properties of Canvas. Since we are creating
that particle inside the effect class itself, I pass it this keyword, which in this area refers
to the entire effect class. Nice. So after we created an
instance of effect glass, and after we call
this init method, I expect that particles
array from line 26 contains one particle object. I console log the
effect glass to check. I can see the effect object. It has width and
height properties. And if I open this
particles array, I can see there is one
particle object inside. I check if all
properties have values. If I see undefined
or non somewhere, it would indicate a
problem. All is good here. Now, I need a way to actually
draw my effect on Canvas. I will give it another
method I call e.g. render inside our cycle over all particle objects
inside particles array using array
for each method. For now we know there is only
one particle object inside. For each particle object
inside particles array, I call its associated
withdrawal method. We defined that draw
method on line 17 and we can see it expects
context as an argument. So I take render method and I call it we need this context. So I take CTX from line two. I pass it to render. Inside render it will
be expected as context. And we pass that context
reference along to draw method because we know
it's expected on line 17. From there, fill
rectangle method is called and our particle
is drawn. Nice. I can change the width and height when I refresh
the browser window, x and y coordinates from lines 14.15 get randomized
and particle is drawn.
22. Drawing particle systems: The main effect class I create a property I call
number of particles. Initialized method will run, just wants to set our
effect up inside. I will create these
50 particles. I can do that by
using a for loop. It will run 50 times, and every time it runs, it will take particles array, and it will push one
new particle with randomized x and y
coordinates inside. And this is how you create
a simple particle system. If you want, you can save
this code as a boilerplate in a different folder
because you can use it for so many
different experiments, affects, and
generative art pieces. I'm calling init method from
here on the first page load. I can also remove
this code and I call this.in it from inside the
effect class constructor. Because when we create an
instance of a class using the new keyword
constructor will run all the code in the
blueprint line by line. We can take advantage of that behavior by putting
any code in here that we want to run at the
same point when an instance of this
class is created.
23. Animating particle systems: We are building a flow field. I want these particles
to move around and leave trails
lines behind them. Let's animate this speed X
horizontal speed will be one. Particles will
move one pixel per animation frame in the
positive direction. On the horizontal x-axis, they will move to the right. Speed. Y will be one pixel
per frame as well. Positive direction on the
vertical y-axis means down. Since both of these
forces will be applied to the particle
at the same time, I expected the
particles to be moving towards the bottom
right corner of Canvas. Let's see. Motion will be handled
inside update method. This method will define a
single-step in the animation. And when we call this update
method 62 times per second, it will create an
illusion of motion. For every animation frame, I want to increase horizontal
x position of this particle from line 14 by speed
x value from line 16. I also want to increase the vertical position
by speed. Why? Here inside render, as we
cycle through all particles, we trigger there
draw method and we also call the update
method we just wrote. This gets called, just wants
so there is no motion. And he's to call this render
method over and over. I wanted to draw particles,
update their positions, draw them again at those
updated positions, update them again, draw
them again, and so on. Calling this over and over, we'll create animation,
an illusion of movement. We need animation loop. I create a custom
function I call e.g. animate from inside our call render to draw and
update all particles. And then I call built-in Request Animation
Frame and method. I pass it, animate the name
of its parent function. So animate runs, render is
called particles are drawn and updated and then request animation frame
and method calls animate. Again, we have a loop request
animation frame method was designed for this purpose, and it has special features such as it all to generate
the timestamps for us. And it also adjust itself
to screen refresh rate. I have a regular screen, so my animations run at
60 frames per second. If you have a high refresh
rate game and screen, your animations will run faster. You can easily use request
animation frame and it's auto-generated timestamp to set any animation speed you want. Now everything is
ready and I can call animate our expected all particles to
move one pixel to the right and one pixel
down there animation frame. They are leaving trails
because we can see old paint, their
previous positions. If I wanted to see only the
current animation frame, I need to delete Canvas between each frame using clear
rectangle method. I want to clear the entire
canvas from coordinates zero-zero to canvas
width, canvas height. Nice. We are animating. I change speeds. The relationship between
speed x and speed. Why? We'll define the direction
and speed of motion. We can also randomize the
speed of each particle. How about a random
value between -2.5 and plus 2.5 minus value
will move to the left, plus values to the right. On the vertical axis, negative values will
move particles up. Positive values will
move particles down. The ratio between randomized
speed x and speed. Why will determine the final
direction of movement? And now particles move in
all possible directions. I can make the
particles smaller.
24. Drawing lines and trails: I actually want to draw lines. I call Big Bath method to tell JavaScript I wanted
to start a new path. As the particle moves around Canvas for each animation frame, it will update its
x and y position. And I will keep track
of those positions as simple objects with x and y properties inside
the history array. At first, each particle
will be assigned a random x and y position
somewhere within canvas area. And we use these values
as the first object, the first element
with an index of zero inside the history array. The start point of the line will be saved by move to method. We want to start from x and y coordinates of
the first object inside the history array
object with an index of zero. Then we create a for loop. It will run as long as there are any more elements in
the history array. For every object, all these
objects look like this. For each position object
inside history array, we will take x and y coordinates of an
object at that index. As the for-loop cycles
over the array, we pass its x and y coordinates
to line two methods. So start point of the line. Then we run through
all positions inside history array and we
connect them with a line. Then we stroke that path to
actually draw it on Canvas. Now I need to make sure
positions are being added to the history array as the
particle travels around. Whenever we update
particle position, we take this dot history
array and we push an object like this with these updated
x and y values inside. Nice, It looks like it's
just one straight line, but it's actually a shape made out of many smaller
line segments. I can e.g. make it
wiggle by adding a random value between
-1.5 and plus 1.5. This should make it
more visually clear that these lines are
made out of segments. I can do the same
thing vertically. I can increase the
random range may be from -2.5 to plus 2.5. I can increase the
range even more. A random value between
-7.5 and plus 7.5. Finally, this is starting
to look interesting. Right? Now. These are just
endlessly grow in lines. What if I want to define
max length of a line? In this case, this will mean
max number of segments. Inside the update method. I say if the length of history array is
more than maxlength, call built-in array
shift method. So we have two
array methods here. Push method adds a new element
to the end of an array. Shift method removes one
element from the beginning. Because we are
adding new elements to the end of the array. Shift method removes
the oldest segment. So airlines crawl
around like this. They can look like small
creatures under microscope. I could just give them some
AI and make this into a game. I can increase max length of
the line to 100s segments. Or I can do a random
value 10-110. Now, some of them have short and some have very long tails.
25. Motion patterns with trigonometry: I can also apply trigonometry, which particle will have angled property which will
initially start at zero. For every animation frame, I will increase angle
by some small value. Angle value is in radians. Then I can pass that ever increasing
angle to Martha sign, which will automatically map these values along a sine wave. This will give the horizontal coordinate a irregular
wave emotion. Look what happens
when I do this for the vertical position as well. Instead of adding here, I try and multiplication. I do it for both combination
of sine and cosine. We'll map the positions along
the radius of a circle, multiplying it by a value like this will increase the radius. You can try to use different
combinations of values and apply different operations
if you want to experiment, there are many things
that can be done here. Basically, we built
a particle system where each particle
draws a line behind it. I comment out a line
69 for a while.
26. Creating a vector field: Now my goal is to make these lines travel
around the canvas following a specific order to
create a flow field effect. Flow field is based
around a grid of angles. Each cell in the grid
stores and angle value. And each cell in this grid in the flow field has a relationship with
its neighboring cells. They are not random. They increase or
decrease gradually. And as the particle travels
over the grid, cell by cell, particle speed and direction of movement is influenced
by this angle values, creating a motion pattern. Very common algorithm
to generate angle values for a flow
field is Perlin noise. It creates a gradient noise that gradually changes
values in a random way. The resulting effect looks very natural and can be
used to generate textures for procedurally
generated serine or many other things. Today we will generate
a spiral flow field where we use sine and
cosine values and we attach them to vertical and
horizontal coordinate of each point of each cell. This creates a beautiful
symmetrical repeating pattern. We will be able to
zoom in and zoom out of the pattern for very
different and results. Let's take it
step-by-step and it will become clear what I'm talking
about as we build it. Our goal now is to
create a grid that splits the entire canvas
into individual cells. Let's say I want to sell size each cell to be 20
times 20 pixels. This will split the canvas
into rows and columns. The number of rows and columns
will depend on the width and height of Canvas
and on this cell size. I could calculate the number
of rows and columns here. But because I want this
effect to be responsive, I will calculate that
inside init method. We can then call that init
method every time resize event happens to correctly recalculate the entire flow field grid. This load flow field will
be an array that holds angle values for individual
cells in the grid. The number of rows
will be the height of canvas area divided
by cell size. The number of columns will be
wave divided by cell size. We will also set flow
field to an empty array in case this init method is
called while resizing Canvas. That way, all the
old values will be deleted and we can
recalculate the new values. Now, I want to travel over that grid whenever there
is a great involved, the easiest way to handle it is with two nested for loops, the outer for loop, the cycle over the grid, row by row from row zero, as long as there
are any more rows. The inner for loop will travel over each row from
left to right. So we enter the outer loop, we are on the row zero. We enter the inner loop
and we cycled through the cells on that
row one-by-one. We reach the end, we exited the inner loop. We enter the outer loop, Y increases, and we
enter the next row. Again. We cycled through all the positions on
that row one-by-one. We repeat this
process row by row until we cover the
entire Canvas. Each time we enter a new cell, we create an angle value. We can do different things here. I will simply tie x
and y coordinate to sine and cosine to
create a spiral pattern. We know that if we
pass an increase in angle value to
sine or cosine, these methods will map this increase in
values along a wave. In this case, those
increasing values are x and y positions of
the cells in the grid. So as we move along the grid, we create spirals and curves. Every time we generate
a new angle value, we push it into
flow field array. Keep in mind that
we are cycling, integrate row by row,
generating these values, but we are storing
them in an array as a single continuous
sequence of values. We need to keep this in
mind when we need to extract this angle values again, to correctly match them to particles current
x and y position. So at this point,
flow field array should be filled
with angle values. So let's console log it. I'm actually console log
in it after every row, I should have put
the console log here after it's all done,
but it doesn't matter. We can see that angle
values are being generated. Perfect. So we cycled
over the canvas, integrate, and for each
cell in that grid, we generated angle value and we start all these angle values
inside flow field array. Because we passed this
gradual increase in x and y values to
sine and cosine, we created beautiful
symmetrical pattern. The pattern will be
revealed when we draw the flow field.
So let's do it. We already have
particles that move over Canvas and they drag
lines behind them. Currently, we are using
this logic to move them. Let's move all this away
and completely changed this code in the update
method here I want particles to move around and the direction of
their movement will depend on the position
of the particle as it travels over different
cells in the flow field. The angle values in the flow
field change cell by cell. And this angle will
determine the motion of the particle that's currently
flowing over that cell. There are many different
ways to do this. I will create a helper
temporary x variable, and it will be
equal to particles current exposition
divided by cell size. Basically, I'm saying
how many cells in our grid from the
left edge of Canvas, this particle currently is, in which column
we currently are. Remember that cell
size was set to 20. I want integers here, so I will round the value down. Helper y-variable
will be particles current y position
divided by cell size. How many cells? From the top we
are on which row. Now I can use these values
to map x and y coordinates of a particle to an index
in the flow field array. Basically I have a particle
with certain x and y position in a
two-dimensional grid. And I have an array which
represents that grid, but it's just one
long set of numbers. So what I'm trying
to do now is to map that x and y position to that index so I can extract
the correct angle value. The grid nanoparticles current vertical and
horizontal position. And I know over
which column and row in the flow field grid
it is currently moving. Now I need to use this
column and row value to get the angle value of the cell
from flow field array. This is a bit complicated to visualize if you are a beginner, so don't worry about
it too much to get index of disposition
from fluid-filled array, I take the row number
we calculated here, and I multiply it
by the number of columns that calculates
this block rows, terms, cells per row basically
needs to know which cell we are currently in
because we added one angle value per cell
into flow field array. Then we add however
many cells we are into the current row
in the grid like this. This gives us index inside
flow field array that holds angle value of the cell we are
currently moving over. Then we set angle value
of this particle to the index inside flow field
array we just calculated.
27. How to create a flow field: Now we have multiple
different options how to use it to achieve
different patterns. I will set speed x two cosine
of the angle at first. Speed y will be the sign of the same value we just
extracted online 35. Then I take particle screened
horizontal position, and I increase it by
that new speed value. And I do the same for
the vertical position, increasing it by vertical speed. I delete this code. So we sliced us into
an invisible grid. Each cell is 20 times 20 pixels and it has a specific
angle value. We calculate which
column and which row of this grid the particle is
currently moving over. We map that column and
row value to index in flow field array to extract angle value that belongs
to the cell we need, we increase speed x by
cosine of that angle speed. Why by sign? This will give us a direction of movement depending
on that angle. Then we increased particles
X and Y position. By these speeds. We push this new
particle position inside history arrays so that
we can animate its tail, the line that each
particle drugs alone. If length of the line
is more than maxlength, we remove the oldest segment. I uncommented render to
see what motion we get. It doesn't look like it, but we already have
a working flow field with sine and cosine
spiral pattern. We are just zoomed out
really far away from it. It will make more sense as
we play with the code now. So the base idea is that parsing an ever increasing angle
value to sine and cosine, we'll make the values cycle periodically between
minus one and plus one. We use X and Y position of each particle as these
increase in angle values. So the pattern will be tied
to coordinates in that way. I close this in brackets and
I multiply it times two. This will increase the
radius of the curve. I remove the rectangle. I only wanted lines. I make max length of the
line to be a random value, 10-210 segments. I can use different values here. Let's increase the number of particles so we can
better see the pattern. Look what happens if I slowly
increase this value, 0.5. I can go much higher than this. I think around ten. Let's put this value
into variable, into a class property. I call this the dotted curve. I will show you why I call it curve in a minute.
It will be clear. I use that property here. Okay? I can also multiply
x and y by a value. If the values the same, we will get a symmetrical
zoom into that pattern. It's not yet fully obvious, but a clear pattern will emerge
from this. Bear with me. I want these values to be
identical for symmetry, and I will put them
into a class property. I call this Zoom. I said this the
Zoom to zero point. To keep in mind that curve Zoom is also connected
to cell size because of how we tied the sine
and cosine curve to a position in a great change in cell size will
affect the pattern. It will affect the Zoom. I think at this point we can already call it a flow field. Slowly increase the
zoom to show you how that value
affects the pattern. I can go really
into the pattern, zoom in close and slowly, I zoom out of it. Still. I don't think it's
obvious what the pattern is. Let's go up here to
particle class constructor. I can remove these initial
values of speed x and speed. Why? Because they
get immediately overwritten here inside
of the update method. I want each line to move
at a different speed. So I will give each particle
a speed modifier property, and it will be a
random integer 1-3. Down here, I multiply
speed x and speed. Why by that speed modifier. Now some lines move
slower or faster. Let's increase the speed. Modify arrange. To show you clearly how curve and zoom
effect the pattern. We need lines to reset when they finish their animation sequence. I create a method I call e.g. reset. When the
reset I said x and y coordinate to a new
random position somewhere around the canvas. And I also need to
set history array to these new values as the initial position
with an index of zero. Right now, the largest moves endless list somewhere
far away off screen. I will create a
property I call e.g. timer. It will be max length
of the line times two. For every tick of animation, I will decrease
the timer by one. Timer starts as a value
equal to maxlength. Terms to it decreases by one
for every animation frame. As long as the timer is
more or equal to one, we will animate the line and make it crawl
around like a dust. Already. When timer reaches one, the lines will freeze
in place like this. At this point, we want an else statement
to run which will start to remove an old segments from each line one-by-one, making the line
disappear from the end. I do that by calling shift
on the history array. This will give us
an error when we remove everything from
the history array, but we still try to draw it. So I will only run
this else statement. If history array contains
more than one element, only then remove all the
segments from the line. Lines flow around. When the timer reaches zero, we start removing all segments until there are no
more segments left. Perfect. At that point, I want the line to reset and repeat this animation
cycle again. So else call this
dot reset method we just defined on line 56. Okay, it's not working because I also need to reset the timer
back to its original value. Congratulations, you
build a flow field. This is a very solid code base and we can do so
many things with it. It's finally time to
properly use Curve and zoom to show you the
pattern I was talking about.
28. Flow field experiments: As we said, Berlin
noise will create a natural look in
gradient noise pattern. We are calculating
angle values for our flow field online
85 using trigonometry. So even though it doesn't
look like it, now, this is a very symmetrical
mathematical pattern that repeats endlessly
in every direction. Let me show you what I mean. Look what happens when I
increase the value of curve. I really enjoy
watching this effect. There is something very
relaxed and about this, I think now you can see a little bit that we have
a repeating pattern. I change the zoom
and I zoom away from it to clearly
see the repetition. I can also zoom
really close into the pattern and we just look
at the small part of it. Kinda looks like the
flow is actually random, but we know it's not. I'll increase the
number of particles. My computer can handle this, but you should
adjust the number of particles to make sure
your computer can animate this smoothly so you don't have to use
2000 particles here. Try different values and see. I don't recommend
going much over 2000 unless you have a
very powerful computer. Flow fields are such
a beautiful effect. And now you know how
to build one yourself. We're just playing with sine
and cosine pattern here, but we can use this
code to change the flow and make it follow
many different patterns. Look what happens when I
change the curve of value. As the value gets higher, a spiral pattern
will start emerging. That knee go a bit higher. It will start curving
in a spiral here. And as I increase the value, it spirals more and more. It will kind of create
a spiral snail shape if we go high enough. Now it's becoming more obvious. And even more, the curve
just spirals in on itself. Interesting, isn't it? So now I think it's very
obvious how the curve affects the spiral shape
of the flow field. This pattern is
endlessly repeating in every direction
and we can zoom in and out of it by
changing the values we use as this dot
zoom on line 75, I can zoom really close
into the pattern. And as I increase this value, we are slowly zooming out. At this point we can see the repeated nature
of this pattern. As we assumed far enough. I go even further. I can make it spiral more. You see how the pattern is
jagged and not very smooth. This is exposing the
grid-like nature of the flow field as the angle
only changes every 20 pixels, I can reduce the cell size to make the flow more smoothly. And basically slicing
cannabis into smaller pieces, five times five pixels. As we can fit more columns now into the available canvas area, change in cell size also zoomed really far away
from the pattern. Now we can clearly see how
it's endlessly repeating. It's not random
like Perlin noise. It's very mathematical
and symmetrical. I zoom in a bit further. I think it's clear
how this works. Now, feel free to play with it and do your own experiments.
29. Grid and debug mode: Both node is basically
a grid of angles. Let's visualize
that grid to make it clear how exactly it works. Let's create a debug mode
on the main effect class, I create a method I
call e.g. draw a grid. It will expect contexts as an argument inside I
create a for-loop. First, let's draw
vertical lines, columns. This dot calls
property already holds a value representing the
number of columns in the grid. So we use it here. Every time we enter
a new column, we call begin path. I wanted to draw
a vertical line. Move to method to define the starting x and y
coordinates of the line. Each line will start at
dynamic horizontal position, cell size, the width of a single cell times the number of the column
we are currently in. All vertical lines will start at the top from y coordinate zero. Line two will
determine the ending x and y coordinates
of our grid lines. Horizontal position
will again be cell size times column number. And I want the lines to end all the way at the bottom
of the canvas area. So this dot height. Then we stroked lines. Let's see what we have
so far by Colin draw grid method from inside
the render method. Nice. We see vertical lines representing columns
in our flow field. Cell size is only five, so we see line
every five pixels, I change it to a 30. As you can see, this also affected the zoom
into the pattern. As the cell size increased, we can fit less cells per row, meaning that the spirals
created by sine and cosine online 85 have
less time to develop. It's all because we
tight angle value, we pass to sine and cosine
to column and row numbers, less columns, lower
range of values passed as angle to sine
and cosine online 85, which affects the pattern. I can always make up for that by adjusting this zoom
property on line 75. Okay, we are drawing
vertical lines. Let's also draw
horizontal lines, the rows, to complete our grid. Again, we start by begin
path starting x and y coordinates of each
horizontal line will be zero horizontally. So from the left edge of
Canvas and vertically it will be cell size
times the row number. Endpoint of each line will
be the right edge of Canvas. So this dot width horizontally, vertical endpoint will be again cell size times row number. I stroke the lines. Perfect. Now we can see the full grid. Look closely at the lines. Do you see how the angle value, the direction of
the line changes at the points as lines enter
new cells in the grid. Each cell in the flow field has different angle value
assigned to it, and lines follow those angles to give them direction
of movement. This visual should help you understand how flow
fields rather work. And if you are coding along, you can inspect it
from up-close on your computer and really
see what's happening. Change the value
of cell size, two, even larger number, and the mystery of making flow
fields should be solved. I wanted to make it the
lines of the grid thinner, but I don't want this change to affect the line width
of the flowing lines. For that reason, I will wrap this entire code block
between safe and restore. These two built-in
Canvas methods, make sure that any
changes we make to a state will stay limited
to a specific area. Those changes can include
any kind of settings such as fillStyle,
stroke style, linewidth, opacity, global alpha, as well as things like
rotation and scaling, which we are not using today. If you wanted to know how
to use rotate and scale, you can check out my
class on fructose. If I set the stroke
style to write, it will only affect the grade, not the flowing lines, not the particle system. Save method creates
a safe point, like in a video game, we can make any changes
we want in here, e.g. linewidth, only 0.3 pixels. These changes will affect this drawing code
and then restore. We'll reset all the
changes made back here to the point it was
when we call it safe. Because of that, when we
later draw the flowing lines, the particle system using draw method on the
particle class, they will not be affected by this stroke style and
this line width at all. I wanted to keep the
grid in our project, but I want a way
for the user to be able to hide it and
show it easily. I create a debug
mode property called this dot d back will be
set to true initially, same as we did with
Colin in it here. When affect class
gets instantiated, all the code inside the
constructor will run. I can actually put any
code I want in here, even an event listener, when we create an instance of effect class using
the new keyword, at that point, I want an event listener for key
down event to be applied. I need to make sure
I still have access to properties on
class constructor, specifically this debug property from inside of this
event listener. If you use a regular
callback function, you will have to bind it. Alternatively, I can
use ES6 arrow function. These functions
automatically inherit the skewered from
their parent scope. So even when this event
listener runs later, it will still be
able to remember it was originally defined here, and it will be able to control this dot debug property for us. If you use regular
callback function here, you will get an error because this keyword will
become undefined. When key down event happens, we console log this
auto-generated event object. I decided to call it
by a variable name E, but you can call it
whatever you want. If I open this event
object down here, I can see it contains
key property which tells us what
key was pressed. That's exactly what I need. If E dot key is triple equals
comparison operator to D, set this to debug
value to its opposite. If it's currently false, set it to true. If it's currently true, set it to false. This is a very useful
line of code that allows us to toggle between
true and false easily. I only want this code to
run if debug mode is true, I only wanted to
see this flow field degrade when debug
mode is active. Now, if you click on Canvas and press letter D on your
keyboard over and over, you are enabling and
disabling debug mode. We will use this debug mode
again in the advanced class where I show you how to trace
letter shapes and images. Now that the individual cells in the flow field are
relatively large, we can clearly see that
the lines break and change direction at the
boundaries of each cell. Because each cell contains
a different angle value, influencing the motion
of these lines. Feel free to play with the code, give different value to
cell size online 70. And then you can
adjust for that change by tweaking this Zoom online 75, you can come up with some
interesting combinations. Like here where I have a pretty interesting pattern
that is clearly affected by the fact that
individual cells in the flow field are
50 times 50 pixels. I will gradually
increase the curve, the inner spiral of our
sine cosine flow field. Beauty of math. Did you know that you could
make art with math like this?
30. Randomized colors: I open another browser window and I will Google Color Picker. I'm looking for something
like this where I can click anywhere I want and it will
give me that color value. You can go into any
color range you want. I take this color by
copying it's hex value. I assign it as this dot color
property on particle class. Inside the draw method
on particle class, I said stroke style
to this dot color. If the entire flow field was just a single color like this, there is no point Colin
stroke style over and over. I would declare it somewhere, so it only runs once on the first page load to
improve performance. But here I'm trying to give our particles a randomized
color range. I create an array, I call this dot colors. I put this value
inside like this. I go back to my color picker. I started from a dark color, so I go a bit lighter
in this direction, e.g. and again, I copy
this hex value. I put that similar
but lighter color in the array as well. This dot color will
be a color picked randomly from this
dot colors array. I can access them
through their indexes. This dot color index
zero is this one. This node color index
one is this one. Let's take another color
and add it into the array. I choose one more color, go in even lighter. I choose one more. This time, it will
be very light. You can choose as many
colors as you want. It doesn't matter
how many colors you add into this array. I want each particle to
be randomly assigned one index from this
array. How do I do that? Of course, by using
Math.random between zero and the length of
this dot colors array. However, Math.random, it gives us a floating
point values. There is no index
1.5 in this array, so I need integers. I wrap all of this
in Math.floor. And this is how you can
randomly assign one value from an array of available values to each object created by a class. You can see that each line
has a random value assigned, and our effect looks
more interesting. Feel free to go back to the color picker and choose
your own color range. It would probably be interesting
to create sliders for this dot zoom so that users
can easily control it. You can do that as a challenge
in the end if you want.
31. Responsive design: If I resize the browser window, as with every Canvas project, canvas element will not
resize automatically. In the same way we are
applying EventListener for keydown event here in side
effects class constructor. I can also create resize
windows event listener. We are inside a class, so I don't want to be pulling width and height
from the outside. So let's console log this
auto-generated event object. I remove this console log. And I also remove this one. So we are Console
login event object that was auto-generated when we resize the browser window and we triggered resize
event listener. Inside I can see
target of the event, in this case browser
window object. And inside of that, we can see the new updated
in your height and width. So we will just
use these values. I console log e dot
target dot inner width, and E dot target
dot inner height. Now I can resize the browser window and I can see the new
width and height. Perfect. So when we resize, I wanted to set width and height of the effect to these values. The problem is that
we also need to resize canvas element itself. And I would again have to reach outside my class to grab it. I wanted to keep my
classes modular. I need a reference to the
canvas element inside my class, I refactor this code effect will expect only Canvas as
an argument inside, we convert it to
a class property. And from this dot canvas, now we can take width
and height like this. I'm 69, now gives us access
to the canvas element itself. I close the console. I need to take this canvas
variable from line one, and I need to pass it to affect class constructor at
the point when we create an instance of
affect class using the new keyword
here on line 135. Inside, it gets converted to
this dot Canvas property. And from their width and
height is extracted. So now when resize event fires, I will call this dot resize
method, which we will write. Next. I pass it the new
width and height of the resize the
browser window. We get these values from this auto-generated
event object. I will write this
method down here. Inside the effect class. We know it expects the new resized width
and height as arguments. We have a reference to the actual canvas
element here on line 69. So I take it, and I also take
width and height. I copy these three lines
and I paste them down here. When we resize browser window, we get a new width and height. We set width of canvas
element to the new width. We said height of canvas
element to the new height. Then we set width and
height properties of the effect to this
new updated values. If I resize the browser
window, it looks interesting, but I'm not sure if we are
not wasting memory like this. If I go back to a smaller size, lines should
eventually move back to the smaller available area. And then when they
get more space, the pattern should expand
to the larger space. Again, I think the way I prefer to do this is
to call init method. Again, after the
window was resized. Don't do what I'm doing now. Don't run this code because
if I call this.in it from resize method and I
re-size browser window. Something very bad happens. We are pushing more
and more particles into this dot particles array, and the effect slows
down considerably. If I go up to init method, you can see that every
time init method runs, we delete the flow
field array and we refill it with
new grid of angles. I need to do the
same with particles. We set this dot particles
to an empty array, delete an old particle
objects inside, and we create a new
set of particles. Performance wise, this is
not the best solution. We don't need new particles. I could have just created a reset method on each particle. And I could have reset
particle positions here rather than deleting all of them and creating new ones
to replace them. We could also use object
pooling technique here if we need to
increase and decrease the number of
particles depending on canvas size that
I'm doing now works because I'm calling
this dot init method from inside resize on line 126. When we resize, we
delete old flow field, degrade and replace
it with a new set of angles now adjusted to the
new canvas width and height. And we also delete
all particles. And we create a new set with random positions spread over the new available canvas area. Of course, for this to work, we need to make sure
we call this.in it from resize method here, I assume most users will not be resized and the
page over and over. So the solution is
perfectly fine.
32. Experimenting with flow field patterns: The shape of the
flow field is what I call Sine and Cosine of
spiral curve pattern, bit of a tongue twister for people with mild
language background. I'm not sure if this shape
has an official name. If it does, let me know, please. It's the pattern
you get when you use increase in positions in a two-dimensional grid as angle values passed
to sine and cosine. We see the formula here. This line of code
that determines the shape of the pattern. We can play with it in many different ways and get
very different results. Look what happens if I replace multiplication with
addition here. Or what about if I do that here? Or I try this? I adjust the zoom. Interesting. I can
also adjust the curve. You can just randomly change
the operators here and maybe you will stumble on something interesting
that I haven't tried. Again, this is how I can slowly
zoom out of the pattern. I can increase curve. And because of the changes to operator we made when calculate an angle grid now changes the direction
of the entire pattern. This looks nice.
I could maybe use this effect for space background in the upcoming game
development class. I could even make this
react to some objects. We will learn more about
that in the next part, where I show you how to make
the lines flow around text. Surprisingly, doing less equals here will also
change the pattern. It will become more
apparent when I zoom out. I think this is
because we change at which point the cells in the
grid break to a new row. If I go back to
multiplication here we are back to the
usual spiral pattern. I'm just playing with it now. I try change in Zoom. I change curve. I can also do both sign here. Or I can swap sine and cosine. I think it's clear
how this works. Now, feel free to play with it and do your own experiments.
33. Drawing text: Down here on the
main effect glass, we have the init method. It splits into a
grid and creates angle values using this
trigonometric formula. I still wanted to
split into grid first. Then instead of this algorithm, I want to draw
text on Canvas and calculate angle values of the vector field
grid from there. Let's draw that text first. For clarity, I will put that
code in a separate method. I will call it draw text. I will need access to
CTX context object from here so that I can sit
Canvas properties from here. At first, I will just pull CTX variable directly
from line two. We will re-factor
this in a minute. From there, I access
font property and I set it to
500 pixels impact. I want to large bold letters. Then I call built-in
filtText method, which expects at least
three arguments, the text to draw and x and y coordinates
where to draw it. The text I want to
draw is the JS, because we are learning
about JavaScript today. Horizontal coordinate
will be this dot width, the width of Canvas times 0.5. The middle of canvas
horizontally, vertical y coordinate will be
this dot height times 0.5, the middle of canvas vertically. As you can see, I'm
pulling the CTX variable from line to directly
inside my class. I want to follow good practices and keep our classes
self-contained. So I will re-factor this. I will need access to CTX from Draw text
method as well as from init method because that's where I draw text method
will be called from. I also need access to that same context from
draw a grid method. Since we need CTX in so many different places
all over the effect class, I will choose this
following solution. We have ctx here on line two. I will make sure affect
class constructor expected as an argument. And inside I will convert it to a class property called
this dot context. Like this. We will use this dot context here and here inside the
draw text method. Inside the drug rate as well, it will no longer be expected as an argument coming
from the outside. Instead, in all of these places, I will use this dot
context class property we just created. We also use context
inside render. It will no longer expect
context as an argument. I will also remove contexts
argument passed to draw method on particles and
here past to draw a grid. I think I made a
few mistakes here. Bear with me a second, please. I actually have to
pass this law context to draw method on
particles like this. Okay, I run the code, I get an error. I open the browser
console effect class constructor expect CTX and converts it into this
dot contexts property. I need to make sure I pass
it CTX down here on line hundred and 51 when I create
an instance of that class, also, I no longer needs to
pass CTX to render method. Here. I take the
draw text method and I call it from inside
the render like this. When we pass x and y
coordinates to filtText method, these coordinates
set anchor point of that text where the text is drawn in relation to that anchor point depends
on the value of texts, line and text baseline
Canvas properties. Text align handles
horizontal alignment. We set the anchor point of the text to the
middle of cannabis. So look what happens when I
set the text line to center. I can also use text
baseline property to align the text vertically in relation to those anchor
point coordinates. I set it to middle. One more time. When you
draw text on cannabis, the values you pass to filtText x and y
coordinates defined the anchor point where the
text sits in relation to that anchor point depends on which values you give to text, line and text
baseline properties. By default, the text
is left aligned horizontally and alphabetic
baseline vertically. We set them to center, middle to get centered
text like this. This is very important to
understand if you ever need to work with
text on HTML canvas, I will set the debug to
true to enable debug mode, I want this text to be
visible when in debug mode. Oh, I have an error. Console is telling me it's here. I need to use this dot context here as well to draw my grid. Probably already
noticed that before. So when we are in debug mode, I want to see the
vector field degrade. And I want you to see this big white letters that will determine the shape
of the flow field. Being able to show
and hide this text easily by pressing
letter D on keyboard, by entering the
debug mode will make the effect easier to
understand and modify. Inside render. I say if
this is the debug is true, only then draw a
grid and draw text. Now, you can press
letter D over and over to toggle debug
mode on and off.
34. Understanding pixel data: Inside init method, I want
to draw to x at this point. Remember that init method
will run a just once on the first page load
and it will create a vector field and
particle system for us. After I draw these big
white letters on Canvas, I want you to scan
Canvas for pixel data. My ultimate goal is to know which cells in the grid
are overdue letter shapes, and which grid cells contain just the black empty background. I do it by scanning cannabis for pixel data after I
drew that text on it. And I will analyse that pixel data and I will
map it on the grid. I will call built-in
get imageData method. This method returns an
auto-generated image data object representing the
underlying pixel data for a specified
portion of cannabis. It expects four
arguments to specify which portion of cannabis
we want to analyze. I want to pixel data from
the entire canvas element. So from coordinate zero-zero to this dot width,
this dot height. In other words, get imageData. I will scan a specific portion of cannabis for pixel data, we can extract colors and coordinates of
each pixel from it. Do that, I need to
understand how is the data in this auto-generated image
data object organized? I will assign this to a
constant variable I call e.g. pixels, and our console log it. I can see the image data
object here in the console. I open it and I can see it has width and height of
the scanned area. This is important. We need to know these
values so that we can extract x and y coordinates
of each pixel from this. If we know the width, how many pixels we have, very row of data, we know after how many pixels this array breaks
to another line. Canvas is scanned. And in the same order we created our angle grid row by
row from top to bottom, going left to right on each row. If I open this data array, this is where all the pixel
information is stored. It's actually very simple. It just holds a long
sequence of integers. My browser console splits this extremely long
array into blocks, but it's actually one
long line of numbers. How does that
translate to pixels? I can see that this is
a special type of array called UI and T8 clamped array. It can only contain unsigned eight bit integers
clamped to arrange 0-255. We know from RGBA color
declaration in CSS that each color can be represented by a combination of red,
green, and blue. Each color has a value 0-255. And different combinations
of these allow us to create any color in CSS RGBA
color declaration. The Alpha opacity value is 0-1. Here in this pixel array, alpha is also represented
by a value 0-255. We need to remember that
pixel data array is an extremely long
line of numbers where each four values represent red, green, blue, and Alpha over
a pixel, a single pixel. So this is pixel one. The next four values, our pixel two, and so on. I can see the width of
the scanned area is 528 pixels and the
height is 464 pixels. 528 times 464 is 244,992
total pixels scanned. And we know that
each of these pixels is represented by four
values in pixels. Array. Value for red, green, blue, and Alpha. 244,992 times four is 979,968. Here we can see that
the pixel data array has exactly 979,968 elements. So that checks out. If I look somewhere in
the middle of the array, I should be able to find these white pixels that
make up the letter shapes. Here I can see that we
have to 55 to 55 to 55, which is white, and 2554
alpha fully visible. We know that these
four pixels with index 533,600 in pixels data array represent pixels somewhere
in this white area. If I change the fill
style too, right? I opened the array. Mostly I see black
pixels are zeros, zeros 00 opacity because the background of our
canvas is transparent. Here I can see those red
pixels to 55 read zero, green, zero blue to 55 Alpha. These values represent a
fully visible red pixel, somewhere in the red area, we know these pixels are
the text drawn on Canvas.
35. Handling pixel data: Okay, so init method will run all the ones on the
first page load and inside we are
trying to create a grid of angles,
a vector field. We drew text and
we scanned us with that text drawn on it with
built-in get imageData method. Now, we are holding all pixel values inside
this pixels variable. I will comment out this code which calculated spiral angles for each cell in the grid using our special sine
and cosine formula. Instead, I will
make the shape of the text of the letters to determine angles held in each
cell of the vector field. To do that, we need to
use nested for loops. Again, the outer for loop will cycle over Canvas row by row, jumping by cell size. Each time we enter a new row, we set the cell size
to be 20 pixels. Here on line 75, I can show
and hide this grid made out of 20 times 20 cells by pressing letter
D on my keyboard. Inside, the inner
for loop will cycle through each row horizontally
from left to right. Also jumping from cell to
cell by cell size value. Hopefully now we understand
how these two nested for loop cycle over the canvas row
by row from top to bottom. Every time we enter a
new cell in the grid, we need to calculate an index. We already calculated
index up here on line 40. This formula helps us
to convert x and y coordinates to a single value representing index in an array. As we cycle over the grid
cell by cell, row by row, I want to match
that cell position with an index corresponding
to these coordinates in pixel array so
that I know what pixel data was scanned
for this particular cell. A match in x and y
coordinates of the cell. I am currently cycling over with these
nested for loops to a specific index
in pixel array to extract pixel data
for this canvas area. Again, this is that
formula where we multiply the row number
times the width. So we get pixels per row plus x, which represents
however many pixels we are currently into new row. This time, we are
trying to match index in pixel data array. We know that each particle is represented by four
elements, four integers, 0-255, represented in red, green, blue, and Alpha. So to get that correct index, I multiply this times four. Now I know the index in pixels array that corresponds to the x and y coordinates of this particular cell in the
vector field, the grade. I want the color I
start with, right? Red will be pixels
array at that index. Green will be the one
index that comes after. So pixels index plus one. Blue is index plus two. Then we have Alpha,
index plus three. Then we get red value
of the next pixel, green, blue, alpha,
and so on and so on. So we have red, green, and blue of each pixel. Combination of these
values will give us the color of the
underlying pixel. Right now, all the
text pixels are red, but we will change that soon.
36. Converting colors into angles: My goal now is to
make the color of the letters affect the
direction of the flow, the angle in the grid of
the vector field, e.g. red pixels push the
flow to the right. Blue color flows down, green color flows
to the left, e.g. and all the color values
in-between give us angle somewhere in-between
these directions. There are many ways I can
convert colors into angles, and we can achieve many
different effects here. The easiest way
would be to somehow converted the three
values of red, green, and blue into a
single number. I can e.g. generate a grayscale value from this RGB colors
are calibrated in a way that when all
three colors for red, green, and blue are equal, the resulting color
is a shade of gray. The color is somewhere on the gray-scale spectrum
without a bias towards red, green, or blue view. We can convert to grayscale by getting the
average of all three, red plus green plus
blue divided by three. Now, I could create a grayscale effect
from this by taking that average value
and applying it back to red, green, and blue. But I actually just
wanted to take that single average value and I want to convert it
to an angle value. We have a range of grayscale
color which goes 0-255. And I want to map these
values two angles, 0-6, 0.28 rad, 6.28 rad convert to
360 degrees, a full circle. So colors can flow
in any direction depending on the gray
scale value of that area, since we are only operating
in positive values. So the formula is very simple. Take the ratio between
the current gray scale of this cell in the vector
field and the max value. And apply that ratio
to that angle value. Let's say if grayscale is 127.5, that's halfway 50
per cent in between the minimum value of zero and
the maximum value of 250. 527 point 5/255 is 0.5. We have the ratio, and now I multiply it times the maximum angular
range of 6.28 rad, which will give us half
of that value, 3.14. 3.14 is pi half circle. By the way, if
gray scale is e.g. 63.75, 63.75 divided by the maximum 255 value
gives us a ratio of 0.25, 25 per cent towards the
max value, one-quarter. We apply the same ratio to
max angle value 0.25 times 6.28 gives us 1.57 rad
half a by quarter circle. Basically I'm taking the ratio, the relationship between
the current grayscale value and the maximum possible
color value of 255. And I'm applying that ratio
to angle range 0-6, 0.28 rad. Because we use gray
scale like this, the resulting flow will be
influenced by how light or dark the colors are on
the gray-scale spectrum. I will show you what I mean when we get the animation going. I want to limit how many
decimal points we get here as there is no need to
calculate tiny angle values. Two decimal points is fine. I put the entire
expression in brackets, so I can call built-in JavaScript
to fixed method on it. I pass it to, I want
only two decimal points. Okay, So as the
init method runs, we set flow field
to an empty array. Down here, I will fill
the array with a grid of angle values built-in
array push method. And we will push an object with x property set to x-coordinate, horizontal coordinate from
the inner for loop as we jump by cell size within
each row cell buy-sell, y will be y index from the
outer for loop as we jump by cell size value over cannabis row by row
from top to bottom. Color angle value is this grayscale color range converted to an angle
value in radians, which will make sure
that particles flow in a different direction
over different colors. In console, we can see
that image data object. I actually don't want it to be pointing to the entire object, but only to the pixel array, which is held here inside
the data property. Pixels will be imageData
object returned by get imageData built-in
method dot data. We are pointing directly
to this array now. Now we see it in
the console here. Flow field array now
contains objects with x, y, and color angle properties for each cell in the
vector field grid. We need to make sure we account for this change when updating particle positions appear inside update method on particle class. Here we are pulling angle
value from flow field array dependent on particles current X and Y
position on Canvas. Now, there is an object with three properties at each index, so I need to access its
color angled property here. Like this, nice,
something is happening. We have two problems to solve. I only want to run this
code block that matches particle position to an object representing a cell
in the flow field. If there is an object at that index on the Dan
pulled the color angle, that will fix the issue
where particles move outside the bounds of
Canvas between recites. The second issue, if
you are coding along, you can zoom out and you
can see we are already draw in a flow field
shaped as letters JS, but for some reason it's
skewed to the right. Why is that? I will make the font
smaller so we can see we have the shape, but why is it
distorted like this? I'm making the cell smaller. Now we can clearly see the flow field is
shaped as letters JS, but it's skewed
in a strange way. It actually took
me a few minutes to realize what's going on, but let's fast-forward
to the solution. If you know why it's doing
that before I reveal it, pause the video and type
it in the comments. Right now, you see up
here we are setting canvas width and height to the current width and height
of the browser window. The problem is that when
we create the flow field, if cell size value on
the main effect class, the width and height of each
cell in the grid is not a number that canvas
width can be divided by. Without any remainder, we will get some leftover
pixels when we break line as we scan Canvas row by row
from top to bottom. As we go down row by row, those leftover
pixels at the end of each row accumulate to
the flow field degrade. And the flow field degrade. The number of elements
in there doesn't exactly match the number of cells
we can fit on Canvas. So the rows get pushed more
and more to the right. There are multiple
ways to solve this. We just need to make sure
that canvas width is divisible by cell size
without any remainder, without any leftover values. I will solve it by setting canvas width to a specific size. But you can also use a dynamic formula here using
the remainder operator. If you wanted to
keep it full screen, if you want to keep the
responsive functionality. I said canvas width
to 600 pixels. And because the cell size on
line 75 is set to 20 pixels, 600 is divisible by 20
without any remainder, so we are getting
the correct shape. I will also set canvas
height to 600 pixels, or maybe 500 times 500. When I resize Canvas, our customer resize method gets triggered and it breaks
the shape again because it allows canvas width to
be something that is not directly divisible by cell
size without an remainder, I can either make
sure it only research kinda size if the new width
is divisible by cell size. Or I can just comment
it out for now, down here on line and 92. And because Canvas is
500 times 500 pixels, I want it to be centered exactly in the
middle of the page. I can do that with CSS. It has positioned
absolute already, so I give it a top 50 per cent, left 50 per cent, and transform translate
-50% -50 per cent. Of course, you can
position it in a different way depending
on where you want it. I will now just
set the background on the body element to black. I think this looks
alright for now. I increase the font size. 450 maybe. Yeah, we are converting
color to an angle. So look where the particles are flowing when the text is right. And look what happens to
their direction of movement. When I change the
color to green. Blue angle is actually
similar to read it because we are not spreading
the color range itself, but we are converting
it to grayscale first, and then we are
converting that to angle. It seems like blue and red have a very similar grayscale value. You will see the biggest
difference in angles when using colors that
are light or dark. Colors that are far apart on the gray-scale spectrum. Brown. Let's try something
much lighter, yellow. We can clearly see
the direction of the flow is influenced
by the color, by the text color, perfect.
37. Flow and gradients: I could leave it here, but
I was playing with it and I came up with more really cool things I want
to share with you. This is always my favorite part where we have all
the logic in place. The effect is running
and I can experiment. We know that the
different colors push the particle flow in
different directions. So let's see what
happens when the fill of the text is not
just one color, but a gradient that transition
from color to color. I wonder what that
will look like. A custom variable called
the gradient one, and I call built-in Canvas
create linear gradient method. It expects four arguments, x and y of the start point, and x and y of the end point. Gradient will be drawn along a line between these two points. I want a gradient from
top-left corner coordinates, zero-zero towards the
bottom right corner coordinates effect width
and affect height, which are the same as width
and height of the canvas. Now I can take that
gradient one variable and I call built-in add color
stop method from it. At offset 0.2, the color
will be white, e.g. 0.8 offset. I try full blue. Now I can just set fillStyle to this gradient one variable. Awesome, this is so cool, like how the flow curves as
we go from blue to white. Now that we understand this, we can experiment with different color ranges
and different gradients. I add another color stop at the zero-point for yellow color. 0.6 will be e.g. green. I can just play with the color values
to see what flow I get. It's interesting how the
flow direction changes as the colors transition
following the gradient. I forgot to mention one
important feature of filtText and stroke text
built-in Canvas methods, we can pass them optional fifth argument to define the max
width of the text. I will say that max width of the text is the
weight of canvas. Now, if the text is longer, it will be squashed
to fit into the area. If I want some of margins, I can say this dot
width times 0.8. And the text will take the maximum of 80
per cent of Canvas. It will take 80 per
cent if we give it more space by setting canvas
width to a larger value. I go back to Canvas size
500 times 500 pixels. I changed the word to flow. We can change font
to Helvetica, e.g. back to impact. And texts will be JS. I want it to flow to
curve more to the right. So I play with the
color values to get the gradient influence the
direction of the flow field. This looks nice. I copy this code block and I rename
all of these gradient too. And I use that as
fillStyle online 112th. I play with the values
again to see what happens. I roughly know which
direction each color flows, but it's mostly trial and error. This looks interesting. I create a gradient three
by copying this code block and using that as fillStyle
online hundred and 18. This time it will be
a radial gradient, so we need to pass
it six arguments, x and y of the central
point of the inner circle. It's radius. So I wanted
the inner circle to start exactly in the middle of canvas horizontally
and vertically. And radius will be ten pixels. The outer edge of the radial gradient
circle will be again from the middle of Canvas and radius will be
a desktop width. Interesting. I adjust the
colors to see what shape I get. You can see how the
radial gradient affects the flow in
a different way. I really like this one. I will keep all three
gradients I defined so I can swap between them
later if I want to.
38. Tips, tricks & experiments: When angles change direction, it's a sharp turn. It might work for you, but maybe we want the
lines to turn more gradually when the angle in
the vector field changes. We are holding the
current angle value for each particle here on
the main particle class. We are changing this value
inside update method to match the angle value of
the grid in the flow field. I will create another property
here called new angle. And I will check the
difference between the current angle
and the new angle. And instead of changing to
that new angle instantly, when particles enter a new
cell in the flow field grid, we will just push
particles towards that new angle by a certain
fraction of their difference. Or to reduce the
calculations needed, rather than calculating
the fraction of the difference between existing
angle and the new angle, I can just have a set value
called angle character. It will be a small
value in radians. Down here in the update method, I said new angle two, an angle stored in the
cell of the vector field. And I say if the
current angle of the particle is more
than new angle, reduce the angle
value of the particle by the angle character
value we defined, move the existing angle one
step closer to the new angle, I choose to do it this
way to save performance. You can also say
change angle value by one tenth of the difference
between angle and new angle, which will give you
more gradual curves at the expense of performance because more calculations
are required. Else if angle is
less than u angle, increase angle value
by angle character. One more elsewhere we
set angle to new angle. Now you can see the sharp
turns are gone and we're just gently pushing the particles towards certain angle values. I adjust the angle
character value to push them a bit harder. Or we can do a random
value here between some small range so that each particle
curves differently. I can adjust maxlength. I can change the speed modifier. We have all the logic in place. If you want, you can
play with it to see what variations of this
effect you can come up with. So this is my very basic
performance efficient way, how to make particles
change directions more gradually as the flow over the grid of cells
in the flow field. I would also like to be able to control how many
particles flow inside the letter shapes
and how many flow in the area outside
of the letters. Now, it's completely random, but it doesn't have to be
when the particles reset. I wanted to check
if it's resetting within the letter shapes
or outside of it, somewhere in the transparent
area around the letter. If it's outside the letters, I wanted to try again. I will have a variable
called attempt. It will start at zero. At first. I will have a
flag called reset success. Initially it will be false. When the particles reset
inside the letter shapes, we will switch this to true. I will have a while loop. When the particles reset, I wanted to try five times. It will make five attempts at
randomly picking a position somewhere in Canvas to try to reset within the letter shapes. As long as attempts
are less than five and as long as
reset success is false, I will create a test index. It will be a random cell somewhere in the
flow field array. I check if this test index
has alpha more than zero, which would mean that
it's the cell in the flow field that has some visible color
in its pixel data, which would mean it's
one of the letters. Alternatively, it
will be transparent, which would mean it's the empty background around the letters. If it has Alpha more than zero, it means we found one
of the letters cells. So we set x and y
positions of the particle to x and y positions of the cell in the flow
field. The grid. Down here I can see that
fluid-filled cells, objects inside flow
field array contain x, y, and color angle values. So this X and Y will
be coming from there. I said that those x
and y coordinates as the first point in the path by adding it to
the history array. And I reset the timer back
to maxlength terms two. I will also set reset
success to true. Importantly, I need to make sure every time we
try to do this, we increase attempt by one
so that when we reach five, this while loop, and we don't want to create
an endless while loop. It will continue only as long as both of these
conditions are true. So only when attempts are less than five and
at the same time, as long as the reset
success is false. If I run the code, it
doesn't reset at all. So that indicates this
condition on line 78 is always returning false. It's because we are not pushing this Alpha value into our
flow field cell objects. I changed attempts
up here to 50. So down here I'm calculating
alpha online 160. I need to make sure
that the value actually ends up inside
flow field array. Like this. Perfect lines are resetting
inside the letters. I said max, attempt back to four after the while
loop has finished. If after trying to
reset four times, we still haven't
found a cell with alpha more than zero and reset, success is still false. We will restart x
and y coordinates of that particle somewhere
randomly around the canvas. It's still not resetting
particles outside letter shapes. Even when I go down
to just two attempt. How about only one attempt? I will figure this out. Just give me a second, right? I have to put these new
positions inside history array. And most importantly, I have
to reset the timer back to maxlength times two for
the particles to animate. Nice. So now when particles reset, this code will attempt
a certain amount of times to reset the particles
inside the letter shapes. And if it doesn't find that the cell in a certain
amount of attempts, it will just reset the particle somewhere randomly
around Canvas. We can tweak how
many particles we want to reset inside and outside letter shapes by changing the number of
attempts on line 75. The last attempt, the
less likely particles are to find the letter
shapes in time. So they might reset outside. I can increase the
number of particles and I increase the cell size. We can see most particles now
reset within letter shapes, but occasionally we get one
reset in somewhere else, somewhere outside, you can adjust the number
of particles and cell size to whatever you want and whatever your
computer can handle here. I can remove this code. I think we all now understand how these values
affect the animation. Maybe 2000 particles. What if I add white color to the colors array for
some highlights? Interesting, I remove it again. I try gradient one to change the direction
of the flow field. Gradient too. I think my favorite
is gradient three. Which one do you prefer?
I increase maxlength, which does have effect
on performance. Longer lines take more
computing power to draw. So we create a grid, we draw text on Canvas. We analyse that kind of us
with texts drawn on it. For pixel data, we cycle over
that pixel data in a grid to extract color values and
coordinates of each pixel. And we convert these result
in colors into angles. From that information,
we create a flow field, a grid of angles, where the direction of angles is influenced by the text
that was drawn on Canvas. I can remove this code block. Then we create a
particle system here. Maybe I wanted the particles
to start flowing from the letter shapes even
on the first page load. So I will manually trigger reset method on each
particle as I create them, which will force them to make a few attempts to find cells in the flow field that
contains some color values, cells that are over
the letter shapes. I changed the text to flow. You can see that it messes with the radial gradient,
which is expected. Increase canvas width
to 1500s pixels. I can create max-width
of the text down here. I can use gradients to, to change the flow direction. This looks interesting. How about gradient one? You probably realized
that this would work on images as
well as I'm just using get imageData to get shapes of letters that
were drawn on Canvas. Do you want to learn how
to use this on images? I will show you exactly
how to do that in the next part. I hope
you're having fun.
39. Image flow fields explained: So we built a text flow field and we experimented with it. I put the text
back to capital js so it fits into the screen
and we can see everything. You can download
images I will be using in the resources
section below, or you can use your own. I will be using
small images, 200, 200 pixels with a
size around 10 kb. Size of the images
matters for performance. So if you are using
your own images, think about that and optimize the images before using
them for this project, if you want your animation
to run smoothly, this effect looks great
with some specific images. From my experience,
the best result is with simple shapes that
have gradient colors, as this will make the flow
curve around it nicely. In index.HTML, I create
an IMG element with an ID of star and source will
point towards that image. If you remember from
the previous part, the trick was to draw
text on Canvas and then analyse that pixel data
and create a vector field, a grid of angles
from that shape. I want to do the same
thing here with an image. Now, I wanted to draw this
image on Canvas and then I want to scan that kinda us
with this image drawn on it. The problem is that there
are some limitations to this Cross-Origin
Resource Sharing is a security measure that it
doesn't allow us to draw any random image on Canvas and then to scan that
Canvas for pixel data. If the image is not
from the same origin, that kind of us is considered tainted by cross origin data. And your code will end
with a console error to draw image on Canvas and the
scan it with get imageData, that image needs to be
considered same origin. If you are running
your code from a server and image source here points to an image stored at the same
server address. Then it will work. I want this codebase to be flexible and to
easily work locally. I don't want to go over setting up a local
server right now. So one possible work-around
is to convert the image itself into a so-called
base 64 data string. To do that, we can use to data URL built in
JavaScript method, or there are many websites that will do that for us in seconds. To sum up, to avoid any cross-origin
resource sharing issues, I am converting image file
itself into a data string, and then I will use that
data string as image source. I do that by Google and
P and G, two base-64. Note that the image
doesn't have to be PNG. It will work with
other formats as well. I will choose e.g. this website from online PNG tools.com. But the other sites will
work in a similar way. I drag and drop the
image I want to convert from my computer
into this field. And it will instantly convert the entire image file into
a very long line of code. Use a small image as possible in terms of
width and height, as well as kilobytes. Otherwise, this code will
be too long and it will cause performance issues when
we animate it in a minute. Ideally, use the
images and providing. And then once you have
a working effect, you can swap to a
different image file. This base-64 data string needs to start like this
data colon image. If it doesn't for you, click this checkbox
to convert it to a specific format
that we need. I copy this extremely
long line of code and I paste it as a
source attribute here. In Visual Studio Code, I can click View and word wrap to make the code
file more manageable. As you can see, we are
drawing that star image, but we never use the
actual image file. This long string
of code completely replaces the image file itself. And since all that code is
inside our index HTML file, it is considered the same
origin in all circumstances. And therefore we can do advanced pixel
operations on it with HTML canvas without triggering
tainted Canvas error. Let me show you. It's easy to get
this to work now, I don't really want to draw
the image element itself, so I take its id and
I hide it with CSS. Instead, I want to draw it on Canvas where the JavaScript, I go to the main
effect of class and I create a new property
called this dot image. I pointed this variable to that image element
using its ID as usual. So we have a variable pointing to the star image
here on line 110. Let's draw it on Canvas. Up here I said canvas
width to 500 pixels. Same as I have this
draw text method. To keep our code organized, I will create a separate
method to draw the image. I don't want to call it
a draw image because that name already exists for
a built-in Canvas method, I will call this
custom method e.g. draw a flow field
image for clarity. Inside, I take this dot contexts and I call built-in
draw image method. It needs at least
three arguments, the image to draw and x and y coordinates
where to draw it. To test it, I will call draw
flow field image from inside render because I am not a defined in width and
height online 148, the image is being drawn
at its original size. We can resize it or stretch it by passing in values
for width and height, 300 with 100 height. How about full-width and
full height of canvas? Nice. What if I wanted the image to be centered exactly in the
middle of cannabis, regardless of which
size I choose. That's also very easy. I create a helper variable
called image size. Let's say I want width
and height of the image to be 70% of canvas width. I use it here as
width and height. Yes. Now I said x-coordinate to
the middle of cannabis. So this dot width times 0.5 and I do minus half
of image size. The image is centered
horizontally. For vertical y coordinate, I do height of Canvas times 0.5. The middle of kind
of as vertically minus Image Size times 0.5. Perfect. This is a very simple
formula to center image in the middle
of HTML canvas. It accounts for the fact that images on Canvas
are drawn same as rectangles from the top-left
corner going right and down, depending on their
width and height. If I resize it now to any size, it will always be drawn
in the middle of Canvas. I will do 80 per cent
of canvas width, e.g. as I explained before, here we create a
flow field degrade, we draw something on Canvas. Then we analyze that
kind of us with that text or that shape
or image drawn on it for pixel data to create
a vector field grid from it by converting
colors into angles. We already have all this logic from the previous part in place. So all I have to do
here is to replace this part where we draw text with code that draws the image. I call draw flow field
image here and here we go. That was easy. I
don't really want to draw the actual star
image over it like this. Instead, I take it from
here and I want to draw it behind the particles only
when debug mode is active. So from within this code block, now we can toggle debug
mode on and off by pressing letter D on the keyboard to show and hide
the vector field, the grid, and the source image. I can change the image size. We can see it always stay centered and that
it all works nice. If I resize Canvas, it will resize the
image with it. Because I said that the size of the star image to be 80
per cent of canvas width.
40. Color manipulation tricks: I couldn't leave this and let you do your own experiments. But again, I was playing with the code a lot and
I was wondering, how can I preserve and use the original colors
of the source image. Right? Now, we are using colors from this dot colors
array on line 25, the colors of the source
image itself are not visible. Other than using them to determine angles
of the flow field. We are scanning that image
for pixel data already, so why not try to use it? I wrote multiple different
solutions to this end. I decided to show
you this one for the reasons I will explain
as we implemented, if you have some ideas how to improve this code base,
please let me know. On each particle object, I will store a separate property for red, green, and blue. This is not needed
if I was just taken the colors as they are
from the original image. But when you see what
that looks like, you will probably agree
that we need a way to dynamically and
gradually transition between colors as
the particles and lines travel from cell to
cell over the flow field. That's why I will
need all three colors separately like this. Let me show you why. I will concatenate RGB color
declaration here. Feel free to use a template
literals here instead, if that's your preferred syntax, I will simply do RGB
plus, plus coma, plus green plus coma, plus bu plus closing bracket. Now, we can change the
color of particles in there by setting these
two different values. Okay, that works well. I go down here inside
init method where we drew that original image file on Canvas and we scanned
it for pixel data. I will need three values
for red, green, and blue. So I make them available
by pushing them as additional properties
inside flow field array. So now it's kind of
as a scanned for pixel data cell by
cell, row by row. Each time we enter a new cell
in the flow field degrade, we will store all this
data for each cell in the grid inside our
flow field array. Now I know what is red, green, and blue value of each
individual cell in the grid. So I can match particle color to these values as particles
flow over Canvas. Inside the update method
on particle class. In this area, I extract the element at this
particular index inside flow field array into a
variable just because I wanted the syntax in this code block to
be easier to read. And also for performance, I pulled that index from
the array only once, and then I use that existing reference
in multiple places. Now I can replace
it here and here. This is the same code. We just refactored it. So in this area, we extract angle value from fulfilled array to
handle particle motion. In this area, I will extract color values of that
cell in the flow field. I only wanted the
cells that contain pixel data of the image if they have alpha
more than zero, because I know the
transparent cells with zero alpha or the background
around the image. We don't care about those here. So if this cell is
not transparent, take this dot read property
on this particle object. We defined on line 25 and set it to read property we
just extracted from flow field read
property contained in this particular cell in the flow field that the particle is
currently moving over. We will do the
same for this dark green and this dark blue. Now, I want to use this updated color values to construct RGB color
string again. So I copy line 28 and I use
it down here on line 62. Nice. We are seeing the actual
colors of the image. Doing this as a lot of extra calculations per
animation frame to our effect. But it's up to you to decide
if it's worth it or if you prefer to use predefined
colors like we did before, I'm just showing you
what's possible. As usual, all of this can be optimized in many
different ways. Advanced optimization
techniques are outside the scope of this class, but we can talk about it
later in some other course. You can see the
lines are blinking. Do you know why that is? It's because when
the particle travels from blue cell to
white cell, e.g. the color of the entire long
line that the particle is dragging behind it will change to that new
color instantly. It will instantly
change color of the entire line because each
line is just one draw call. So we can just
change the color of a part of that line with
the code we wrote so far. Again, multiple solutions here. I could adjust our code to
check if color changed. And then use that updated color just for some line segments. Or what I will do here, I will change the color
of the entire line. But instead of that color
change happening instantly, I will gradually calculate
the difference between the two colors over
multiple animation steps. So the colors will not
just swap instantly, they will transition slowly. How do we do that with code? Since we are in the
advanced section, I will use more advanced
simplified syntax. This is called a
ternary operator, the only JavaScript operator
with three operands. We will use it here as a simple one-line
if else statement. Define a condition, an expression to evaluate.
If it's true. Question mark, do this. Else, if it's false,
colon, do this. So as the particles flow over the flow field grid and they extract color values
from those cells. I check if red value of the
particle is the same as the red value of the cell in the flow field at this index. If it is, just don't do
anything and leave it alone. If the red value
is not the same, instead of instantly reassigning it to the new updated red value, I will just gradually
increase or decrease red value by a fraction
of their difference. Let's say by one tenth
of the difference. We could have done the same
for angle values here, but I decided it's more
performance efficient to use a set value stored
inside angle character, although using gradual
angle differences would create more natural curves
in particle motion. You can try that
later if you want. Anyway, I check if red
is the same as new red. If it is, leave it alone. If it isn't, bring them
one step closer together, but not all the way at once
to get a gradual transition. I can't see if this
is working or not. So let's do the same thing
for green. Like this. I have to be careful
about the syntax here. I think it's working. Let's also do it for blue. Like this. I'm just checking my code
to see if everything is okay after all that
copying and pasting. Yeah, this is good. I'm using 0.1 here. This value will
determine how sharp or how gradual the
color change will be, how fast the colors
will transition from the existing color to
the new target color. Before the transition of
color happened instantly for the entire multi
segmented particle line. Now they happened gradually
and the effect looks nice. I think this is really special. I can't believe we
went from drawing a single animated line to bend in flow field
shapes around images. If you followed
all the way here, this is a great achievement, well done, and congratulations on your dedication to learning. You are a creative
coder for sure. Here on line 86, I can control how many particles restart
within the shape and how many restart outside of it by changing how many attempts we give to each particle to try and find that shape between resets. To make sure this works, we can use a different image. Again, keep in mind too
small images if possible. Simple shapes like stars
work great for this effect. You can download
the other image I provided in the
resources section, or you can use your own image. Now, I copy this
extremely long line of code and I go to index HTML. We need lines to be breaking
to replace the source. So if it's all on
one line for you, you can go to View
word wrap to make VS Code Editor brake
lines like this. To replace the data string. In Visual Studio Code, I can just select the start
of this very long string. I scroll to the end. I press Shift key
on my keyboard. And while I'm holding
Shift key down, I click where I want
the selection to end. It will highlight all the texts in-between and I
can press Delete. Nice. Now that I know I
deleted the old image, I paste the new base 64 data string from the new
image. I save it. And here we go, we are drawing a different image and we can see the colors are being correctly extracted from the
source pixel data. You can check out
the source code in the resources section below. I will also play with
the code now and I will include some of my
other bonus experiments for you to download and
inspect if you want inspiration on where to
take this effect further. I like creative coding projects because we are learning about animation as well as the
general program and principles. We did a lot of array
manipulation functions and for loops today, e.g. these are all very important
in every branch of coding, not just for animation. Thank you so much for
spending your time with me. I hope you got a
lot of value today. I'll see you later.