Physics Demos!
Making physics demos can be fun! Let's learn how to do this. You may already be familliar with much of the content of this document. If so, feel free to skip or skim large portions of it!
If you find yourself confused, this document can probably be improved! Ask questions, suggest improvements, or point out a confusing part of the document here. If interested in contributing, please fork the project and submit a pull request! Thank you for reading!
- An Introduction to HTML
- An Introduction to JavaScript
- Changing HTML With JavaScript
- Functions in JavaScript
- Loops
Math.
CSS
- The HTML5 Canvas
- Embedding Demos in a Discussion
What is HTML?
Hypertext Markup Language (HTML) is a powerful tool and is used by practically every site on the internet. Because of its ubiquity and power, we will be building our demos with it!
Tags in HTML
HTML uses tags to format text.
For example,
<b>
this text is in bold,</b>
but the following text is not.
Tags tell browsers what to do with the text inside of them.
<tag_name_here>
starts a tag named "tag_name_here"
and </tag_name_here>
ends the tag! Let's see
some examples!
<b>
This text is in bold!</b>
<i>
This text is in italics!</i>
<u>
This text has been underlined!</u>
<p>
This text is considered a separate paragraph! It gets its own line!
</p>
Try using them in the editor below!
Notice the text surrounded by <!--
and -->
above, when in the Editor tab. That text is in a comment! It will not be displayed as a part
of the website.
Setting up the Document
<!DOCTYPE html>
starts
our documents — it tells the web browser that
we are, in fact, using HTML (instead of something like XHTML).
Even though we tell web browsers that we intend to use
HTML by starting our documents with
<!DOCTYPE html>
, we can put things that
are not HTML in an HTML document (like JavaScript). As such,
we need the <html> </html>
tag.
The <html> </html>
tag
surrounds the contents of the document -- it surrounds
all of our HTML. The <body> </body>
tag is used to mark the website's content.
The <head> </head>
tag
surrounds information related to parts of the website
outside the page. For example,
<title> sets the page's title
to this text! </title>
Browsers use the <title> </title>
to show people using the website which page is which by,
for example, putting this text at the top of the screen,
in a tab label.
This is an example:
More HTML Tags
Before continuing, let's look at several additional elements!
-
<div>
This text is in a divider! Think of it like a paragraph for things that aren't actually paragraphs! It puts its content in a group, but you don't have to think of it as a paragraph.
For example, I might want a picture of a dog to have its own line. I could do this by putting it in a div.
</div>
-
<span>
This text is is in a different type of divider! A “span” acts like a “div”, but does not put its contents on a new line!</span>
Introduction to JavaScript
We will be using JavaScript to make our simulations
act as they should. To run JavaScript, we can put it
inside a <script></script>
tag!
- Math! JavaScript can evaluate expressions like
5*43 + 2.6
. - Variables! We can make new variables using
let varname = initialValue
and change the value of an existing variable like this:varname = newValue
. For example,varname = (varname + 1) / 2
sets a variable namedvarname
to the average of its old value and1
. It is important to note that we are settingvarname
to the result of an expression on the old value of varname. -
IDs in HTML! We can give HTML tags identifires so that we
can access their contents through JavaScript. For example,
we could associate “anId” with a paragraph like
this:
<p id="anId">Contents of the paragraph... </p>
Note that anId is in quotation marks. This keeps the contents ofid
separate from any other properties we might want to give the paragraph (we'll see more of these later). We can use single (') or double (") quotes.
Read about them in the comments below!
Changing Text!
To change text, we need to be able to let JavaScript know
what we want to change. To do this, we give JavaScript a
selector. This might be the name of a tag (like the
"p" in a <p> </p>
).
Things we can change are part of the HTML document
.
To query and use them, we give JavaScript a selector. We can specify it
like this: document.querySelector("p")
queries a <p> </p>
from the document.
Notice that we put "p" in quotation marks in the selector --
this tells JavaScript that "p" isn't the name of a variable, it
represents the text, p
.
We write let aParagraph = document.querySelector("p")
to
get the first <p> </p>
paragraph and store it
in the new variable aParagraph.
More generally, document.querySelector("p")
gets the paragraph
and let aParagraph =
makes the new variable, aParagraph
,
and sets it.
Notice that we set the textContent
of
the paragraph just like we set a variable! We can
use textContent
just like other variables!
Text in JavaScript
Put text in quotation marks to mark text as text! If we don't do this, JavaScript assumes we are are refering to a variable.
For example, to set the text of aParagraph
to the text "some text", we can do this:
We can join two strings together with the +
operator. For example, "some text" + " joined with even more"
is equivalent to "some text joined with even more"
.
From here, I will call HTML tags and their contents "elements" — "tag"
is specific to the text between <these brackets>
.
As such, we can add additional text to an element by setting
adding text to its textContent
. For example,
aParagraph.textContent = aParagraph.textContent
+ " with new text";
adds the text " with new text" to the end of the contents of
aParagraph
.
Adding text on to the end of existing text (appending it)
is so common that JavaScript lets us write
aParagraph.textContent += " with new text"
.
This shorthand is equivalent to writing out aParagraph.textContent = aParagraph.textContent
+ " with new text"
, but it is easier to read and type.
Let's see an example!
Changing HTML with JavaScript
Although we can use element.textContent
to change the text of an element, we can't use it to
make new elements.
Let's see what happens when we try!
We can, instead, change element.innerHTML
!
What?! Only part of the text was italicized!
Only the text we added with
aParagraph.innerHTML = "<i>Will this text "
was put in italics!
This is because each time we set element.innerHTML
,
the browser expects the new innerHTML
to only contain
complete elements. It updates the page just after you set the HTML
(but it might take some time for you to be able to see it).
We can do it like this instead:
f\left(\textbf{unctions}\right) in JavaScript!
In mathematics, we use functions to organize expressions. In JavaScript, we do the same!
Let's start by translating the function, f(t) = -9.81 \frac{m}{s^2} \cdot t^2, into JavaScript. See below!
let f = (t) => -9.81 * t * t
is one of several ways to make a function!
We can evaluate f(t) at some t
just by typing something like f(12)
.
f(12)
evaluates f at t = 12 s.
While let f = (t) => -9.81 * t * t
-style notation is useful, it only lets us evaluate a single
line of code! If we want f
to do more than evaluate a single expression, we can surround its contents
with curly-braces. For example,
let f = (t) => { let displacementFromAcceleration = -9.81 * t * t; return displacementFromAcceleration; };evaluates
f
with two sub-expressions — it acts like let f = (t) => -9.81 * t * t
,
but allows us to divide f
into multiple expressions!
If we didn't use a function to represent f
, our program might look like the below:
By moving -9.81 * t * t
into a function made our code much easier to read!
Note: Writing f(something)
is called
“calling f
”. For example, if I wrote
doSomething(32)
, I would be calling
doSomething
with 32 as an argument.
I wonder why this is the convention...
Repeating Things!
JavaScript supports running code in loops! There are several ways to write loops. Let's look at two of them!
while
Loops
while (true) {
repeats code }
between the “{”, “}” braces while true
is true.
Of course, true
is always true,
so this loop repeats the code inside the
{ }
braces
until we stop running the program.
More useful would be to replace true
with something else...
Similarly, we can write: while (false) { x = 7; }
. false
is not true, so x
is never set to 7
.
Let's see another type of loop!
Above, we wrote output += "<p>" + f(8) + " m</p>";
many more times than we needed to! Let's see how to
simplify this!
for
Loops
// A for loop for (let x = 0; // Start with x = 0. x < 10; // Loop while x < 10. x++) // Increase x by one each time we loop! { // Do something with x. }
This is equivalent to:
// A while loop let x = 0; while (x < 10) { // Do something with x. x++; // This line is very easy to forget! // x++ is a more concise way of writing x = x + 1. }
Let's see how this can simplify our earlier example.
\MaTh in JavaScript!
Like a calculator, JavaScript supports arithmetic! Let's see some examples:
-
(5 + 4) * 23 / 6
is equivalent to (5 + 4) \cdot \frac{23}{6}. -
5 == 4
isfalse
— checks whether 5 is equal to 4. -
!false
istrue
— it's equivalent to notfalse
, or \neg \textsf{false} \equiv \textsf{true} -
!true
istrue
-
a % b
, where we have defined integersa
andb
, is equivalent to a\mmod b. In other words, it computes a - b \cdot k, where k = \left\lfloor\frac{a}{b}\right\rfloor. -
5 % 4
is equivalent to 5\mmod 4 = 1. -
3^2
is not equivalent to 3^2. It instead computes the exclusive or (XOR) of the binary data that represents the numbers 3 and 2. If we want to compute 3^2, we writeMath.pow(3, 2)
, thereby raising 3 to the power of 2.
Mathematical Constants
Several mathemtaical constants can be accessed in JavaScript
through the Math
variable. Like document
,
we can write Math.something
to get some entity associated
with Math
.
-
Math.PI
is a pre-defined variable roughly equivalent to \pi. -
Math.E
\approx e = 2.718281828459045... -
Math.LN10
\approx \log_e(10) = 2.302585092994046... -
Infinity
and-Infinity
are constants (not prefixed withMath.
) that appear when we attempt to divide by zero (or negative zero). JavaScript also hasNaN
to represent the concept of a value that is “not a number”. For example,"some text!" * 3
evaluates toNaN
.
Trigonometry
Trigonometric functions accept radians in JavaScript and
can be accessed via Math.functionNameHere(amount)
.
For example,
Math.sin(3.14)
computes the sine of3.14 radians
.Math.sin(45 * Math.PI / 180)
computes the sine of \Big(45^\circ \cdot {\large\frac{\pi \cdot \text{radians}}{180^\circ}}\Big) — it computes the sine of 45 degrees!Math.cos(Math.PI)
\approx \cos(\pi) = -1Math.tan(0)
\approx \tan(0) = 0Math.atan(1)
\approx \Arctan(1) = \large\frac{\pi}{4}Math.atan2(1, 2) == Math.atan(1/2)
\approx \Arctan(1/2)Math.atan2(a, b) == Math.atan(a/b)
\approx \Arctan(a/b)\qquad \forall\;\; b \not= 0Math.atan2(1, 0)
\approx {\large \frac{\pi}{2}} to make it easy to avoid division by zero!
Other Useful Functions
Math.pow(x, y)
computes x^y.Math.exp(x)
computes e^x.Math.log(x)
computes \text{ln} (x) — the log base e of x.
Operations With Booleans
Named after the mathematician, George Bool[ Better Citation Needed 🙂 ], Booleans are values that are either true or false.
Booleans are represented by true
and false
in JavaScript.
This type of variable is useful for determining which pieces of code should run when.
// Say we have defined, let T = true; let F = false;
T == true
evaluates totrue
.F == true
evaluates tofalse
.T == F
evaluates tofalse
.-
T != F
evaluates totrue
, becuase it is true that \textsf{T} \not= \textsf{F}. -
true || false
is read “true or false”. This example evaluates totrue
. In general, though,a || b
for somea, b
,a || b
is true if eithera, b,
or both aretrue
. F || F
evaluates tofalse
.T || T
evaluates totrue
.T && F
is read “true and false” and evaluates tofalse
because it is not the case thatF == true
.T && T
evaluates totrue
becauseT
andT
are bothtrue
.
Practice!
Try these concepts out in the editor below!
let F = false; let T = true; F == false true == false F != true Math.sin(Math.PI)
What is the result of running F == 0
?
What if happens if you run F === 0
?
What about true == 1
? What is the
difference between ==
and ===
?
CSS — Cascading Style Sheets
CSS lets us change how parts of our demo will look.
Just as JavaScript is run when within a <script>
</script>
block, we write CSS within a
<style> </style>
block. Let's see
an example!
Okay! Now, let's just make one of the paragraphs red by using an id
!
Notice that we used document.querySelector
to access
the paragraph by its ID in a way similar to that done in CSS:
#styleThisOne
.
Filling the Screen!
Now, let's see how to fill the screen with an element.
Say, a <div></div>
.
We'll want to set its width and height:
<style> div { width: 100vw; /* 100% of the screen's width (vw). */ height: 100vh; /* 100% of the screen's height (vh). */ } </style>
To see it, let's give it a border.
<style> div { width: 100vw; height: 100vh; border: 1px solid black; /* ^ ^ ^ . |_____ | The border is | . The border will be black! 1-pixel thick. . It will be a solid line (as opposed to dashed or dotted). */ } </style>
All together,
Oh no! The <div></div>
is big enough, but it isn't at the top-left of the screen.
Let's remove the extra space:
Notice that we selected both body
and html
with
body,html
and used this to set their margin to zero.
We still, however, have a scrollbar. This is because, currently,
the border adds padding to the outside of the <div>
.
Let's fix that:
It works! By adding box-sizing: border-box
, we moved the
moved the div's border inside the box div.
A Fun Example!
You can skip this. The below example demonstrates some of the fun things that can be done with just CSS!
Drawing!
We can draw on a <canvas></canvas>
element
with JavaScript. Let's see how.
Setting it Up
Like other elements, we can access a canvas element using
document.querySelector
. Unlike other elements,
we will be using getContext
to get a variable
we can use to draw on the canvas. Here's an example:
As we see above, the canvas, by default, does not fill the entire screen. As we saw earlier, we can resize elements. Let's make the canvas' size fit that of the screen!
Changing the Number of PixelsAt present, as the screen shrinks and grows, our square is stretched into a rectangle!
We want to make the drawing surface
the same size as the <canvas>
</canvas>
.
We can use the following to get a canvas' display size and its drawing surface size:
-
canvas.clientWidth
is the current width of the canvas on the screen in pixels (px
). -
canvas.clientHeight
is the current height of the canvas on the screen in pixels (px
). -
canvas.width
is the width of the canvas' drawing surface in pixels. It won't change unless we change it. -
canvas.height
is the height of the canvas' drawing surface in pixels. It, too, won't change unless we change it.
Setting canvas.width
and
canvas.height
set the size of the
drawing surface!
To make the drawing surface fit the canvas, we can do this:
canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight;
Let's see an example:
Drawing with a ctx
To draw shapes on a canvas, in general,
-
We start a path. This can be done
through
ctx.beginPath()
. -
We add onto the path. This can be done
through
ctx.lineTo(x, y)
,ctx.rect(x, y, width, height)
, and more! -
We do something with the path.
To fill it, we can use
ctx.fill()
, or, we can trace it withctx.stroke()
.
let ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(50, 50); ctx.lineTo(50, 0); ctx.stroke();
Notice that, above, we also included
code to set drawing settings (e.g. rotation,
scaling, translation), and ctx.restore
them!
For a full tutorial on the HTML5 Canvas,
try
this tutorial on MDN. If the link is broken,
search for the MDN HTML5 Canvas Tutorial
.
Animations!
If we use a while
loop to do this, the browser will
wait indefinitely for our script to stop running before showing
any changes we've made to the canvas.
Instead, we use requestAnimationFrame
to
tell the browser to call a function after it has had
time to update the contents of the page.
let f = () => // f is a function of nothing. { // Move things. ctx.translate(1, 1); // Draw things. ctx.fillRect(0, 0, 100, 100); // Draw again! requestAnimationFrame(f); }; requestAnimationFrame(f);
Note that we can get the current
time in milliseconds (relative to some
time years in the past) with
(new Date()).getTime()
.
(new Date())
accesses
the current time. getTime
gives us this time in milliseconds.
Using this, we can make something like this:
Notice that, above, we only update canvas.width
and canvas.height
if
the
drawing surface and the canvas' sizes are different. This
is because setting canvas.width
and canvas.height
has side-effects,
most notably, clearing the screen
(I find it makes much more sense to clear the screen using ctx.clearRect
).
Note that we clear the canvas with ctx.clearRect
. Notice how similar this is to
our use of ctx.fillRect
and ctx.strokeRect
!
Creating a Wave-Superposition Demo!
Let's make a physics demo that demonstrates wave superposition!
This example will walk you through the process of creating and sharing a demo! We will be using GitHub for hosting. If you don't have a GitHub account, please create one now.
Setting up GitHub Hosting
- Create a new GitHub repository by clicking the “new” button (on GitHub).
-
This can be either public or private —
it can be changed later under the “Settings”
tab of your project.
Note: This has only been tested with a public repository.
-
Select
Add file
, thenCreate new file
. -
Name the file
tutorial.html
. - As demonstrated below (and above, and in almost all examples provided in this document), set up the HTML document.
- Your document should now look somewhat like this:
-
Scroll down. Making sure
commit directly ...
is selected, click . -
Select the settings tab (it may be hidden within the
overflow menu,
The overflow menu should look something like this.
Settings, if not one of the tabs, should be one of the options in the overflow menu on GitHub. -
Scroll down to
...
GitHub Pages
Some text telling you to select a branch and save will be here.
... -
Select the
main
branch of your project. - Click .
-
Select the
Code
tab at the top of the screen. - Click on the github-pages link under Environments (open this in a new tab).
- Click .
-
You should see a
404 Error
page! By default, requests forhttps://your-username.github.io/your-repository-name/
are re-directed tohttps://your-username.github.io/your-repository-name/index.html
. We haven't made that page yet. -
On the main page for your project (the page you used to add
tutorial.html
), selectAdd File
, thenNew File
. -
Name the file
index.html
so thathttps://your-username.github.io/your-repository-name/index.html
displays a page. - Set its contents to something similar to this (note the link included to tutorial.html!):
- this new file directly to the main branch of your repository.
- Click github-pages under environments and test the link!
-
Back on the main page of your project (where you created
index.html
andtutorial.html
), clicktutorial.html
, then click the edit (✎
) button. - This is where we will be creating the tutorial!
Making the Demo
To start, let's replace the contents of <body>...</body>
in tutorial.html
with a canvas
and make
it fill the screen.
We'll probably want the waves to move,
so we'll add animation as we did above
— with requestAnimationFrame
and a function.
This is very similar to what we did before!
The biggest difference is the drawWaves
function.
It seems easiest to graph waves with
x
and y
in a fixed range.
Say, from negative one to one.
To do this, we'll want to change our
drawing settings. To keep our old settings
(which draw things normally, without
scaling, rotating, etc.), we use
ctx.save()
.
We restore the saved settings with
ctx.restore()
. We've
seen this before!
To do this transformation, we first want
to center everything — we translate
by (canvas.width / 2, canvas.height / 2)
.
This causes an object that would be drawn at
(0, 0) to be drawn
at
(\frac{1}{2} \cdot \texttt{canvas.width}, \frac{1}{2} \cdot \texttt{canvas.height}).
Now, (0, 0) is at the center of the screen! Let's make (1, 1) be at the top-right of the screen!
How, then should we scale it? Here are some of our options:
ctx.scale(1/canvas.width, 1/canvas.height);
ctx.scale(canvas.width, canvas.height);
ctx.scale(canvas.width / 2, -canvas.height / 2);
ctx.scale(canvas.width / 2, canvas.height / 2);
Looking at these, we can see:
-
ctx.scale(1/canvas.width, 1/canvas.height)
won't work because it would cause a point, P = (1, 1), to be drawn at P' = (1 / \texttt{canvas.width}, 1 / \texttt{canvas.height}).
By default,canvas.width
andcanvas.height
are in the hundreds of pixels. This makes1/canvas.width
and1/canvas.height
very small. As such, this does not achieve our goal of mapping (1, 1) to the top-right corner of the screen. -
ctx.scale(canvas.width, canvas.height)
maps the point (1, 1) to the point (\texttt{canvas.width}, \texttt{canvas.height}).
This might seem like it would work, but remember that we have shifted the point (0, 0) to the center of the screen!
The points (\texttt{canvas.width} / 2, \texttt{canvas.height} / 2), (0, 0), and (-\texttt{canvas.width} / 2, -\texttt{canvas.height} / 2) may be on the screen before scaling, but (\texttt{canvas.width}, \texttt{canvas.height}) isn't!
Because (1, 1) maps to (\texttt{canvas.width}, \texttt{canvas.height}), we know (1, 1) will be off-screen. We need to map it to (\pm\texttt{canvas.width} / 2, \pm\texttt{canvas.height} / 2) instead!
ctx.scale(canvas.width / 2, canvas.height / 2)
might
seem like it should work. Unfortunately, this causes
(1, 1) to be in the bottom-right
corner of the screen.
This is because, without scaling/translation, (0, 0) is at the top-left corner of an HTML5 canvas!
While this doesn't match our conventional placement of the origin when graphing functions, it does match how we write English — starting at the top-left of a page and moving to the right and sometimes down.
To make (1, 1) be at the top-right of
the display, we need to reflect over the y-axis!
In other words, we need to ctx.scale(1, -1)
.
ctx.scale(canvas.width / 2, -canvas.height / 2)
both stretches and reflects — it maps (1, 1) to the
top-right corner of the screen, as desired.
// To summarize, ctx.translate(canvas.width / 2, canvas.height / 2); ctx.scale(canvas.width / 2, -canvas.height / 2); // moves the origin (0, 0) to the center of the canvas // and treats the distance from the origin to the top // of the canvas be 1 (and to the right of the screen // and to the left, ...).
To summarize, we first ctx.translate
by (canvas.width / 2, canvas.height / 2)
to move (0, 0)
to the center of the screen.
We then ctx.scale(canvas.width / 2, -canvas.height / 2
.
Don't forget to your changes! After doing this, re-open the file and start editing again.
Drawing the Waves
Let's start drawing!
As a quick review:
(new Date()).getTime()
gives us the current time in milliseconds since some fixed point in time.- Paths
ctx.beginPath()
starts a path.-
ctx.moveTo(x, y)
adds a point to the path. ctx.lineTo(x, y)
draws a line from the previous point on the path to (x, y)ctx.stroke()
draws/outlines the path on the screen.ctx.fill()
fills the region that the path contains. The color of this fill is set throughctx.fillStyle
.-
ctx.strokeStyle="red";
sets the color of the line drawn withstroke()
to red. -
ctx.lineWidth=5;
sets the width of the stroked line to5
pixel-units. -
ctx.save()
saves all drawing settings. -
ctx.restore()
restores all drawing settings. -
We set
canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight;
to make the size of the drawing-region match the size of where it will be displayed.
Debugging Tip! Most browsers can provide a summary of detected errors in JavaScript code.
Often, this list can be accessed by pressing the F12
key, then clicking on the
Console
tab in the window that appears.
Why is the line so thick?
By scal
ing the canvas, we changed the size of the line!
We can fix this by moving ctx.stroke()
to just after we restore
the canvas' settings. The width of the line is relative to the (transformed) size of the canvas.
As such, restoring before drawing makes the line a more-reasonable width.
Okay. The demo works. Somewhat.
We probably want to choose different functions. While the sine function is a good model for a wave, pulses do a better job of demonstrating the principle of superposition.
Let's change our function!
One option is to make f(t) have a graph similar to that of a normal distribution:
f(t)'s graph then looks like this:
Graphing f
as a function of a sine wave (so that we have repeated pulses)...
Making it Better!
Now that it works, let's make it better!
Let's add a checkbox that hides/shows f(t2)
!
We can create a checkbox using the <input type="checkbox"></input>
HTML element, which can also be written as <input type="checkbox"/>
because we won't be putting anything inside of it.
After we store the input in a variable, say checkbox
, we
can determine whether it is checked by accessing checkbox.checked
!
Embedding a Demo in a Discussion
For course discussion services like Canvas, to embed a demo in a discussion, you'll want a link to the demo.
One option is to use GitHub. See GitHub's documentation for more information.
After you have a link to the demo, create an <iframe></iframe>
element in your discussion post. Many platforms include a “edit as HTML”
button that allows you to do this.
Set its src
attribute to the URL for your demo.
For example,
In the below editor, you might click
and add <iframe src="https://your-username.github.io/your-repository-name/tutorial.html">
</iframe>
to your post.
Some information here.
Add the demo here?
Above, notice that I added style="box-sizing: border-box; width: 100%; height: 500px;"
Although generally considered bad programming style, setting styles through attributes can be done.
Here, it is especially useful because many discussion-post editors do not allow <style></style>
blocks!
As such, we need to make any stylistic changes in-line!
The default size of an <iframe src="..."></iframe>
is rather small:
As such, we set the iframe
's width to as large as its parent (100%
),
and height to a fixed amount (500 px
). <iframe>
s are often given
borders, so we also set its box-sizing
to border-box
to prevent the
iframe
's box from being larger than its container!
Our resized iframe
looks more like this:
This iframe
should be large enough to fit our demo!