Functions Built-in to Your Browser: The Web Api
FUNCTIONS BUILT-IN TO YOUR BROWSER: THE WEB API
Your web browser comes with a lot of extra functions built that we can use in our JavaScript application, hundreds, in fact. We’ll only need to use a couple of them though. Let’s take a brief look at the ones we’ll be using now.
While working through this chapter, if you like, you can open up your Browser Console and try typing the commands in there to verify that they work as expected.
The Document Object Model (DOM)
The Document Object Model decribes the way in which a HTML document is modelled as a JavaScript object.
Given a simplifed webpage that looks like this:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
</body>
</html>
This is represented as a series of nested JavaScript objects that looks like this:
window = {
document: {
head: {
},
body: {
},
}
}
This is a highly simplified examples - there are lots of properties and methods attached to the
window
and document
objects, as well as to every sub object. Let’s examine the parts of this that we will be using now.The global object: Window
The top-level object in the DOM is called the
window
. In fact, every other object in a JavaScript application that’s running in a web browser gets attached to the window
object, and, while we are running our JavaScript code in a web browser, we can access the window
object at any time.
This top-level object is referred to as the global object, meaning that it contains the entire world of your application.
For example, if we forget to write
const
or let
when creating a variable, it will get attached to the window object:x = 5;
Since we didn’t use
let
or const
, this is actually equivalent to:window.x = 5;
Go ahead and open up your browser console and test it out!
Whenever we include the three.js script, it creates a keyword
THREE
that we can use to access all the features of three.js. For example, to create a spotlight, we will type:const spotlight = new THREE.SpotLight();
The
THREE
object is also attached to the window
object, so these two statements are also identical:window.THREE
THREE
Whenever JavaScript compiler finds a word it doesn’t know, it automatically searches for it on the
window
object.
In practice, we’ll always just type
THREE
to keep things short, and only include window.
when we want to make it clear in the code that we’re using a built-in browser method.
All of the rest of the functions that we’ll be using will be attached either to the
window
object or to the document
object.
The name
window
is specific to browsers - other JavaScript environments will also have a global object, but it may be called something else. For example, in Node.js it’s called global
(it also behaves a little differently).Document
Next level down is the
document
object. This represents the actual HTML document that is loaded.
Since this attached to the
window
, we should actually be typing window.document
to access this. However, since everything is attached to the window, JavaScript allows us to avoid typing it every time, meaning that the following are equivalent:document
window.document
document.head and document.body
Inside the
document
, the document.head
and document.body
refer the <head>
and <body>
elements in your HTML document.The Elements that Make Up Your Page: Nodes
We’ve been referring to the pieces that make up our HTML as elements, which is common practice. Technically though, they are known as nodes. This includes the
<head>
, <body>
, <h1>
, <title>
, any <div>
elements, and so on.Accessing Nodes: document.querySelector
One of the most common needs in a JavaScript application is accessing and manipulating the HTML elements in the page, and, as you would expect, there are lots of functions for doing this. We’ll restrain ourselves to using just one selector function which is attached to the
document
object, called querySelector
. Supposing we have HTML like this:<body>
<h1>Title</h1>
<div id="scene-container"></div>
</body>
Then we can access the elements using
querySelector
like so:const bodyElem = document.querySelector( 'body' );
const headingElem = document.querySelector( 'h1' );
const sceneContainerElem = document.querySelector( '#scene-container' );
Note that we’re using the
#
symbol to refer to an element by ID, just as we do in CSS.
If
querySelector
finds more than one matching node, it will just return the first node. If we want all matching nodes, then we can use querySelectorAll
instead.Node.textContent
Our
headingElem
just says Title
. That’s a little generic, don’t you think? We can change the text inside elements in a number of ways, but we’ll be using node.textContent
.const headingElem = document.querySelector( 'h1' );
headElem.textContent = "The Secret and Amazing Life of Bumblebees"
Much better!
Getting the Window’s Dimensions: window.innerWidth and window.innerHeight
At any time, we can access the width and height of the browser windows using
window.innerWidth
and window.innerHeight
:const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
Getting a Node’s Dimensions: node.clientWidth and node.clientHeight
Similarly, we can access the width and height of a node using
node.clientWidth
and node.clientHeight
:const headingWidth = headingElem.clientWidth;
const headingHeight = headingElem.clientHeight;
There are other ways of getting information about a nodes dimensions, such as node.offsetWidth which takes into account any borders and padding that may have been set in the CSS, and node.scrollWidth, which also includes parts of the element that may be offscreen. We’ll only use
clientWidth
in this book, however.Creating Nodes with document.createElement
We can also create nodes directly:
const headingElem = document.createElement( 'h1' );
headElem.textContent = "Section Two: Waggle Dancing for Children and Teenagers"
Adding a Node as a Child of an Existing Node: node.appendChild
However, the node that we just created exists in limbo - well, in our computer’s memory. We’ll need to add it to our page to be able to see it. Here’s how to add it as a child of the
<body>
element.document.body.appendChild( headingElem );
Listening for Events with eventTarget.addEventListener
We can use the
addEventListener
API to listen for changes. The eventTarget
can be many different objects, such as:window.addEventListener
document.addEventListener
node.addEventListener
,
And we can listen for all kinds of different events, such as clicks and keypresses from the user, or the browser’s window changing sizes, or the user scrolling, and so on.
The one that we’ll be using in every example past the third chapter is the
resize
event, which must be attached to the window
.const onResizeCallback = () {
console.log( 'The new width is:', window.innerWidth );
console.log( 'The new height is:', window.innerHeight );
}
window.addEventListener( 'resize', onResizeCallback );
Note that the
resize
event won’t work when attached to anything other than the window
!document.addEventListener( 'resize', onResizeCallback ); // this won't work!
We can listen for clicks on a button like this:
<button id="click-me">Click Me!</button>
const button = document.querySelector( '#click-me' );
button.addEventListener( 'click', ( e ) => {
console.log( 'You clicked the button!' );
} );
Note that we’re using an inline callback here, rather than defining it first, and that the callback gets passed a parameter which we’re calling
e
, for event.
This parameter will hold quite a bit of information related to the event and will be different for each event type.
We can listen for key presses with
keydown
and keyup
:window.addEventListener( 'keydown', ( e ) => {
if ( e.key === 'ArrowUp' ) {
console.log( 'You pressed the Up Arrow Key' );
e.preventDefault();
}
} );
window.addEventListener( 'keyup', ( e ) => {
if ( e.key === 'ArrowUp' ) {
console.log( 'You released the Up Arrow Key' );
}
} );
Here, we’re calling the
preventDefault()
method, which is telling the browser not to follow through with its default behavior in the event of the up arrow key being pressed (normally it would scroll up).
We’ll listen for some other events over the course of this book, and we’ll explain those as we go.
window.devicePixelRatio
As we mentioned in the HTML and CSS Intro chapter, mobile devices render your webpage in a virtual viewport, and then afterward scale it and draw it onto the physical device screen. The virtual pixels (CSS pixels) may not be the same size as the devices physical pixels! In fact, they commonly differ by a factor of between two and five.
We’ll need to take this into account when rendering our scenes to make sure that things are not blurry. Fortunately, three.js does all the hard work for us, and we just need to make sure that we tell it what the ratio between the real and CSS pixels is, which we can access using
window.devicePixelRatio
:const pixelRatio = window.devicePixelRatio;
window.requestAnimationFrame
window.requestAnimationFrame( callback )
is very important to us, as three.js developers, because it allows us to set up a simple and performant animation loop. We’ll cover this function in detail in Ch 1.2: Lights! Color! ActionsDebugging Your Code with Your Browser’s Developer Tools
Press
CTRL + SHIFT + J
, or CMD + Shift + J
on a Mac, and take a look at the window that opens up. Looks familiar? Great. If not take a moment now to become familiar with it, especially the tab called Elements
which holds data about your HTML structure and the CSS applied to it, and the tab called Console
where any messages, warnings or errors related to your app will show up. You’ll be using this a lot throughout your career as a three.js developer - or indeed while doing any kind of web development.
The most basic requirement for getting a three.js scene running is to load the three.js script, so let’s take a few moments to see how we can use the browser console to test whether this has been done successfully.
First of all, open up the browsers console for this page. Type
THREE
(all capitals) into the console and press enter. You will see something like this (the exact message will vary between browsers):Uncaught ReferenceError: THREE is not defined
at <anonymous>1
OK, so three.js has not been loaded on this page.
Next, open up this page (it’s the empty template that we’ll be building on in Chapter 1.1).
Repeat the process by opening up the console and typing
THREE
. This time you should see something similar to this:Object {
WebGLRenderTargetCube: WebGLRenderTargetCube(),
WebGLRenderTarget: WebGLRenderTarget(),
WebGLRenderer: WebGLRenderer(),
ShaderLib: Object,
UniformsLib: Object,
UniformsUtils: Object,
ShaderChunk: Object,
FogExp2: FogExp2(),
Fog: Fog(),
Scene: Scene(),
367 more…
}
Great! The empty template is all set up and ready to go.
Uncaught ReferenceError: SomeObject is not defined
is a common error and means that you are referencing an object that does not exist (in the current scope). There can be many reasons for this, such as a script that hasn’t loaded correctly, or calling an object before it was defined, or somewhere that it is not accessible… or even just misspelling the object’s name! There’s nothing more annoying than spending an hour trying to track down a bug only to find that you had typed Someobject
instead of SomeObject
- so always double check your spelling first!
Logging to the Console with console.log
The main debugging technique we’ll be using in this book is logging information to the console using
console.log()
. It’s a simple but powerful technique, and until you start to create more complex apps it’s likely to be the only one that you need.
Here’s how to check the value of a variable called
x
using the browser console:let x = 'something';
// Do some stuff with x, so that now you expect that x = 'something else'.
// But how do you test this? Simple! Just use:
console.log( x );
// output: "something else"
There’s a range of other console methods that we can use, such as
console.warn
, console.error
. For our methods, console.log
will be enough, but you it’s worth taking a few moments to familiarise yourself with all the console methods. console.table
in particular can be very handy, while console.time
is very useful for testing how long things like loading models takes.
Comments
Post a Comment