Document Object Model

译 文 : The Document Object Model

Translator: Flying Dragon

Protocol: CC BY-NC-SA 4.0

Proudly using Google Translate

Part of reference to “JavaScript Programming Essentials (2nd edition)”

Too bad! Same old story! Once you’ve finished building your house you notice you’ve accidentally learned something that you really should have Known — before you started.

Friedrich Nietzsche, Beyond Good and Evil

When you open a web page in a browser, the browser receives the HTML text of the page and parses it, much like the parser you saw in Chapter 11. The browser builds a model of the document structure and uses that model to draw pages on the screen.

JavaScript provides the ability to turn text into a document object model in its sandbox. It’s a data structure that you can read or modify. A model is a WYSIWYG data structure, and changing the model causes the page on the screen to change accordingly.

The document structure

You can think of HTML files as a series of nested boxes. Tags such as and surround other tags, and enclosed tags can also contain other tags and text. Here are the sample files covered in the previous chapter.

<! doctype html> <html> <head> <title>My home page</title> </head> <body> <h1>My home page</h1> <p>Hello, I am Marijn and this is my home page.</p> <p>I also wrote a book! Read it <a href="http://eloquentjavascript.net">here</a>.</p> </body> </html>Copy the code

The page structure is as follows.

The browser uses a data structure corresponding to this shape to represent the document. Each box is an object, and we can interact with these objects to find boxes and text contained within them. We call this representation the Document Object Model, or DOM for short.

We can access these objects through the global binding Document. The documentElement attribute of this object refers to the < HTML > tag object. Since every HTML document has a header and a body, it also has head and body attributes that point to these elements.

The tree

Recall the syntax tree mentioned in Chapter 12. The structure is very similar to that of a browser document. Each node uses children to refer to other nodes, and each child node has its own children. The shape is a typical nested structure, where each element can contain child elements similar to itself.

We call a data structure a tree if it has a branching structure without any loops (a node cannot directly or indirectly contain itself) and has a single, well-defined “root node.” In the case of DOM, the document.documentElement is the root node.

Trees are widely used in computer science. In addition to representing recursive structures such as HTML documents or programs, trees can also be used to maintain ordered collections of data, because it is often more efficient to find or insert a node in a tree than in an array.

A typical tree has different types of nodes. The syntax tree of the Egg language has identifiers, values, and application nodes. Application nodes often contain child nodes, and identifiers and values are leaf nodes, that is, nodes with no child nodes.

The same thing is true in DOM. The node of the element, which represents the HTML tag, is used to determine the document structure. These nodes can contain child nodes. An example of such a node is document.body. Some of these child nodes can be leaf nodes, such as text snippets or comments.

Each DOM node object contains the nodeType property, which contains a code (number) identifying the nodeType. The element has a value of 1, which DOM also defines as a constant attribute document.element_node. The text node, which represents a piece of text in a document, has code 3 (document.text_node). The code for the comment is 8 (document.COMMENT_NODE).

So we can use another way to represent the document tree:

Leaf nodes are text nodes, and arrows indicate parent-child relationships between nodes.

standard

It’s not just JavaScript that uses numeric code to represent node types. Later in this chapter, we’ll show you other DOM interfaces that you might find a little strange. This is because DOM was not designed for JavaScript, and it tries to be a language-neutral set of interfaces, ensuring that it can be used in other systems, not just HTML, but XML as well. XML is a general-purpose data format with a similar syntax to HTML.

That’s bad. Standards are generally very easy to use. But here the advantage (consistency across languages) is not obvious. Having interfaces properly integrated with a developer’s language can save developers a lot of time, rather than providing similar interfaces for different languages.

Let’s illustrate the integration problem with an example. For example, every element in the DOM has a childNodes attribute. The property is an array-like object that has a length property and can also access the corresponding child node using a number label. But this property is an instance of the NodeList type, not a true array, so there are no methods for this type such as slice and map.

Some problems are caused by bad design. For example, we cannot create a new node and immediately add child nodes and attributes to it. Instead, you first create nodes and then, using side effects, add child nodes and attributes to each node. Code that makes heavy use of the DOM is often long, repetitive, and ugly.

But these problems are not insurmountable. Because JavaScript allows us to build our own abstractions, we can design improvements to express what you’re doing. Many libraries for browser programming ship with these tools.

Move along the tree

DOM nodes contain many links to neighboring nodes. The chart below illustrates this.

Although only one link is shown for each type of node in the diagram, each node has a parentNode attribute that points to a node that is part of that node. Similarly, each element node (node type 1) contains the childNodes attribute, which points to an array-like object that holds its children.

In theory, you can move anywhere in the tree through a link between your father and child. But JavaScript also provides additional links that are more convenient. The firstChild and lastChild attributes point to the first and lastChild, respectively, or null if there are no children. Similarly, previousSibling and nextSibling point to adjacent nodes, to the previous node and the next node that have the same father, respectively. PreviousSibling is null for the first child, and nextSibling for the last child is null.

There is also the children attribute, which is like childNodes, but contains only childNodes of the element (type 1) and no children of other types. This can be useful when you are not interested in text nodes.

Recursive functions are often useful when dealing with nested data structures like this. The following functions scan the document for text nodes containing the given string and return true if one is found:

function talksAbout(node, string) { if (node.nodeType == document.ELEMENT_NODE) { for (let i = 0; i < node.childNodes.length; i++) { if (talksAbout(node.childNodes[i], string)) { return true; } } return false; } else if (node.nodeType == document.TEXT_NODE) { return node.nodeValue.indexOf(string) > -1; } } console.log(talksAbout(document.body, "book")); / / to trueCopy the code

Since childNodes is not a true array, we cannot iterate over it with for/of, and must use a normal for loop to iterate over the index range.

The nodeValue property of a text node holds the text string it represents.

Look for the element

It’s really useful to traverse nodes using connections between parent, child, and sibling nodes. But if we only want to find specific nodes in a document, blindly following a hard-coded link path starting with Document.body is not a good idea. If the program locates nodes through a tree structure, it relies on the specific structure of the document, which can then change. Another complicating factor is that DOM creates text nodes for whitespace characters between nodes. For example, the body tag in the sample document contains not only three child nodes (

and two

elements), but actually seven child nodes: these three nodes, the Spaces around them, and the Spaces between the elements.

Therefore, if you want to fetch the href attribute of a link in a document, it is better to fetch the first link in the document rather than the second child of the sixth child in the body element, and this can be done.

let link = document.body.getElementsByTagName("a")[0];
console.log(link.href);Copy the code

All element nodes contain the getElementsByTagName method, which is used to search from all descendant nodes (direct or indirect child nodes) for the node containing the given tag name and return an array of objects.

You can also use document.getelementByID to find a node that contains a specific ID attribute.

<p>My ostrich Gertrude:</p>
<p><img id="gertrude" src="img/ostrich.png"></p>

<script>
  let ostrich = document.getElementById("gertrude");
  console.log(ostrich.src);
</script>Copy the code

A third, similar method is getElementsByClassName, which, like getElementsByTagName, searches the contents of element nodes and retrieves all elements that contain a specific class attribute.

Modify the document

Almost any element in a DOM data structure can be modified. The shape of the document tree can be modified by changing the parent-child relationship. The node’s remove method removes them from the current parent node. The appendChild method adds child nodes and places them at the end of the list of child nodes, while insertBefore inserts the node represented by the first argument before the node represented by the second argument.

<p>One</p>
<p>Two</p>
<p>Three</p>

<script>
  let paragraphs = document.body.getElementsByTagName("p");
  document.body.insertBefore(paragraphs[2], paragraphs[0]);
</script>Copy the code

Each node can only exist in one place in the document. Therefore, if you insert paragraph Three before paragraph One, you remove the node from the end of the document and insert it in front of the document, resulting in Three/One/Two. All methods of inserting a node somewhere have the side effect of removing it from its current location (if it exists).

The replaceChild method is used to replace one child node with another. The method takes two parameters, the first is the new node and the second is the node to be replaced. The node to be replaced must be a child node of the method caller. Note that both replaceChild and insertBefore take the new node as their first argument.

Create a node

Suppose we want to write a script that replaces all images in a document (the tag) with text in its Alt attribute that specifies a literal alternative representation of the image.

This involves not only removing the image, but also adding a new text node and replacing the original image node. For this we use the document.createTextNode method.

<p>The <img src="img/cat.png" alt="Cat"> in the
  <img src="img/hat.png" alt="Hat">.</p>

<p><button onclick="replaceImages()">Replace</button></p>

<script>
  function replaceImages() {
    let images = document.body.getElementsByTagName("img");
    for (let i = images.length - 1; i >= 0; i--) {
      let image = images[i];
      var image = images[i];
      if (image.alt) {
        let text = document.createTextNode(image.alt);
        image.parentNode.replaceChild(text, image);
      }
    }
  }
</script>Copy the code

Given a string, createTextNode gives us a text node that we can insert into the document to make it appear on the screen.

This loop iterates through the image starting at the end of the list. We have to traverse the list in reverse this way because the list of nodes returned by methods like getElementsByTagName changes dynamically. The list changes as the document changes. If we start at the head of the list, removing the first image will cause the list to lose its first element. On the second loop, since the length of the set is now 1 and I is also 1, the loop will stop.

If you want to get a fixed set of nodes, you can use the array. from method of an Array to convert it to the actual Array.

let arrayish = {0: "one", 1: "two", length: 2}; let array = Array.from(arrayish); console.log(array.map(s => s.toUpperCase())); / / - > [" ONE ", "TWO"]Copy the code

You can create an element node using the document.createElement method. This method takes a label name and returns a new empty node of the type specified by the label name.

The following example defines an ELT tool for creating a new element node and treating its remaining parameters as children of that node. This function is then used to add the source information for the reference.

<blockquote id="quote"> No book can ever be finished. While working on it we learn just enough to find it immature the moment we turn away from it. </blockquote> <script> function elt(type, ... children) { let node = document.createElement(type); for (let child of children) { if (typeof child ! = "string") node.appendChild(child); else node.appendChild(document.createTextNode(child)); } return node; } document.getelementById ("quote"). AppendChild (elt("footer", "-- ", elt("strong", "Karl Popper"), preface to the second editon of ", elt("em", "The Open Society and Its Enemies"), ", 1950")); </script>Copy the code

attribute

We can access some attributes of the element, such as the href attribute of the link, through the same attribute of the element’s DOM object. This is limited to the most commonly used standard attributes.

HTML allows you to set any attribute on a node. This feature is useful because it allows you to store additional information in the document. The attributes you create do not appear in the attributes of the element node. You must use the getAttribute and setAttribute methods to access these attributes.

<p data-classified="secret">The launch code is 00000000.</p>
<p data-classified="unclassified">I have two feet.</p>

<script>
  let paras = document.body.getElementsByTagName("p");
  for (let para of Array.from(paras)) {
    if (para.getAttribute("data-classified") == "secret") {
      para.remove();
    }
  }
</script>Copy the code

It is recommended to prefix the names of these composite properties with data- to ensure that they do not conflict with any other properties.

There is one commonly used attribute: class. This property is a reserved word in JavaScript. For some historical reason (some older JavaScript implementations can’t handle attributes with the same name as a keyword or reserved word), the attribute that accesses the class is called className. You can also use the getAttribute and setAttribute methods to access the attribute using its actual name class.

layout

You may have noticed that different types of elements have different layouts. Certain elements, such as paragraphs (

) and headings (

), take up the entire width of the document and are rendered on separate lines. These elements are called Block elements. Other elements, such as links ( or elements, are rendered on the same line as the surrounding text. Such elements are called Inline elements.

For any particular document, the browser can calculate layout information such as size and location for each element based on its type and content. The layout is then used to draw the document.

The size and position of elements are accessible in JavaScript.

The offsetWidth and offsetHeight attributes give the starting position of the element in pixels. Pixels are the basic unit of measurement in the browser. It usually corresponds to the smallest point that can be drawn on the screen, but on modern displays, it is possible to draw very small points, which may no longer be appropriate, and browser pixels may span multiple display points.

Similarly, clientWidth and clientHeight give you the size of the space within the element, ignoring the border width.

<p style="border: 3px solid red">
  I'm boxed in
</p>

<script>
  let para = document.body.getElementsByTagName("p")[0];
  console.log("clientHeight:", para.clientHeight);
  console.log("offsetHeight:", para.offsetHeight);
</script>Copy the code

The getBoundingClientRect method is the most efficient way to get the exact location of an element on the screen. This method returns an object with attributes top, bottom, left, and right that represent the element’s position in pixels relative to the upper-left corner of the screen. If you want to know its position relative to the entire document, you must add its scroll position, which you can find in the pageXOffset and pageYOffset bindings.

We still need some work to finish the layout of the document. To speed things up, instead of redrawing the entire document immediately every time you change it, the browser engine waits and delays redrawing as much as possible. When a JavaScript program that modifies a document ends, the browser calculates the new layout and displays the modified document on the screen. When a program gets the position or size of some element by reading attributes like offsetHeight and getBoundingClientRect, the browser also needs to calculate the layout in order to provide the correct information.

If your program repeatedly reads the DOM layout information or modifs the DOM, it forces a lot of layout calculations, resulting in very slow execution. The following code shows an example. The example contains two different programs that use the X character to build a line that is 2000 pixels long and time each task.

<p><span id="one"></span></p> <p><span id="two"></span></p> <script> function time(name, action) { let start = Date.now(); // Current time in milliseconds action(); console.log(name, "took", Date.now() - start, "ms"); } time("naive", () => { let target = document.getElementById("one"); while (target.offsetWidth < 2000) { target.appendChild(document.createTextNode("X")); }}); // → naive took 32 ms time("clever", function() {let target = document.getelementbyid ("two"); target.appendChild(document.createTextNode("XXXXX")); let total = Math.ceil(2000 / (target.offsetWidth / 5)); target.firstChild.nodeValue = "X".repeat(total); }); // → clever took 1 ms </script>Copy the code

style

We see that different HTML elements are drawn differently. Some elements are displayed as blocks and some are displayed inline. We can also add styles, such as bold content with , or blue content with and underline.

The way the tag displays the image or the link that jumps to when the tag is clicked depends on the element type. But the default style of the element, such as the color of the text and whether it is underlined, can be changed. Here is an example of using the style attribute.

<p><a href=".">Normal link</a></p>
<p><a href="." style="color: green">Green link</a></p>Copy the code

A style attribute can contain one or more declarations in the form of an attribute (such as color) followed by a colon and a value (such as green). When more declarations are included, different attributes must be separated by semicolons, such as color:red; Border: none.

Many aspects of a document are influenced by styles. For example, the display attribute controls whether an element is displayed as a block element or an inline element.

This text is displayed <strong>inline</strong>,
<strong style="display: block">as a block</strong>, and
<strong style="display: none">not at all</strong>.Copy the code

The block tag closes the line because block elements are not displayed inline with the surrounding text. The last TAB won’t be displayed at all, because display: None prevents an element from being displayed on the screen. This is a way of hiding elements. It is better to remove it completely from the document, as it is a simple matter to put it back later.

JavaScript code can manipulate the style of an element through its style attribute. This property holds an object that stores all the possible style properties, and the values of these properties are strings, and we can write strings to properties to change the element style of certain aspects.

<p id="para" style="color: purple">
  Nice text
</p>

<script>
  let para = document.getElementById("para");
  console.log(para.style.color);
  para.style.color = "magenta";
</script>Copy the code

Some style attribute names contain dashes, such as font-family. Since these attribute names are not appropriate for JavaScript use (you have to write style[“font-family”]), in JavaScript, dashes are removed from attribute names in style objects. And capitalize the letter after the dash (style.fontfamily).

Cascading style

We call the styling system for HTML CSS, or Cascading Style Sheets. A style sheet is a set of rules that indicate how to style elements in a document. You can write CSS in the

<style>
  strong {
    font-style: italic;
    color: gray;
  }
</style>
<p>Now <strong>strong text</strong> is italic and gray.</p>Copy the code

Cascading refers to combining multiple rules to produce the final style of an element. In the example, the default style of the tag, font-weight:bold, is overridden by the rules in the

When multiple rules repeatedly define the same attribute, the latest rule has the highest priority. So if the rule in the

We can use tag names in CSS rules to locate tags. Rule. ABC refers to all elements that contain ABC in the class attribute. Rule #xyz applies to elements whose ID attribute is xyz (which should be unique in the document).

.subtle {
  color: gray;
  font-size: 80%;
}
#header {
  background: blue;
  color: white;
}
/* p elements with id main and with classes a and b */
p#main.a.b {
  margin-bottom: 20px;
}Copy the code

Priority rules favor the most recently defined rules and apply only when the rule particularity is the same. The specificity of a rule is used to measure the accuracy of the rule in describing matched elements. Specificity depends on the number and type (tag, class, or ID) of elements in the rule. For example, target rule P.A is more specific and therefore has higher priority than target rule P or.a.

The p>a notation applies the style to the immediate child of the

tag. Similarly, pa applies to the tag in all

tags, whether or not it is a direct descendant node.

Query selector

There are not many stylesheets in this book. Although understanding stylesheets is critical to browser programming, it might take two or three books to properly explain all the browser-supported properties and how they are used.

My main reason for introducing selector syntax (used in style sheets to determine which elements a style applies to) is that this microlanguage is also an efficient way to find DOM elements.

The querySelectorAll method is defined in both the Document object and the element node, which takes a selector string and returns a class array object containing all matching elements.

<p>And if you go chasing <span class="animal">rabbits</span></p> <p>And you know you're going to fall</p> <p>Tell 'em a <span class="character">hookah smoking <span class="animal">caterpillar</span></span></p> <p>Has given you the call</p> <script> function count(selector) { return document.querySelectorAll(selector).length; } console.log(count("p")); // All <p> elements // → 4 console.log(count(".animal")); // Class animal // → 2 console.log(count("p ")); <p> // → 2 console.log(count("p >.animal")); // Direct child of <p> // → 1Copy the code

Unlike methods such as getElementsByTagName, the object returned by querySelectorAll is not dynamically changed. When you modify a document, its contents will not be modified. But it’s still not a real Array, so if you want to treat it as a real Array, you still need to call array.from.

The querySelector method (without All) is similar to the querySelectorAll method. This method is useful if you only want to find a particular element. This method returns only the first matched element, or null if there is no matched element.

Position and Animation

The Position style attribute is a powerful layout method. By default, the value of this property is static, indicating that the element is in the default position in the document. If the attribute is set to relative, the element still occupies space in the document, but its top and left style attributes are offset from the normal position. If position is set to Absolute, the element is removed from the default document flow and no longer occupies space but overlaps with other elements. The top and left attributes are offsets from their nearest closed element, where the value of the position attribute is not static. If no closing element exists, it is an offset from the entire document.

We can use this property to create an animation. The document below shows a picture of a cat that moves along an elliptical trajectory.

<p style="text-align: center">
  <img src="img/cat.png" style="position: relative">
</p>
<script>
  let cat = document.querySelector("img");
  let angle = Math.PI / 2;
  function animate(time, lastTime) {
    if (lastTime != null) {
      angle += (time - lastTime) * 0.001;
    }
    lastTime = time;
    cat.style.top = (Math.sin(angle) * 20) + "px";
    cat.style.left = (Math.cos(angle) * 200) + "px";
    requestAnimationFrame(newTime => animate(newTime, time));
  }
  requestAnimationFrame(animate);
</script>Copy the code

Our image is in the center of the page and position is relative. To move the cat, we need to keep updating the top and left styles of the image.

The script uses requestAnimationFrame to call the animate function every time the browser is ready to redraw the screen. The animate function calls requestAnimationFrame again to prepare for the next update. When the browser window (or TAB) is active, the update rate is about 60 times per second, which can produce beautiful animations.

If we just update the DOM in a loop, the page will stand still and nothing will display on the page. The browser does not refresh the display while executing the JavaScript program, nor does it allow any interaction on the page. That’s why we need requestAnimationFrame, a function that tells the browser that the JavaScript program has done its job so that the browser can continue to perform other tasks, such as refreshing the screen and responding to user actions.

We pass the animator function as a parameter to the requestAnimationFrame. To ensure that every millisecond of the cat’s movement is stable and the animation is smooth, it is based on a speed at which the Angle changes the difference between this and the last function run. If you take only a few steps at a time, the cat’s movements may be slightly slower; for example, another heavy task on the same computer may cause the function to run only a few tenths of a second later.

We use the trigonometric functions math. cos and math. sin to move the cat along the arc. You may not be familiar with these calculations, but I’ll cover them briefly here, because you’ll come across them occasionally in this book.

Math.cos and math. sin are very useful, and we can use a 1 radian to calculate the position of a particular point on a circle centered at the point (0,0). Both functions interpret the argument as a position on the circle, with 0 representing the right-most point on the circle, increasing counterclockwise until 2π (about 6.28), exactly across the circle. Math.cos computes the x coordinates of a point on the circle, while Math.sin computes the y coordinates. Any position (or Angle) greater than 2π or less than 0 is legal. Because radians are cyclic, a plus 2 PI is the same Angle as a.

The units used to measure angles are called radians – a complete arc is 2π radians, similar to 360 degrees when measured in angles. The constant π is math.pi in JavaScript.

The cat animation code saves a counter called Angle, which records the Angle of the cat on the circle and increases the value of this counter each time the animate function is called. We then use this Angle to calculate the current position of the image element. The top style is the result of math. sin multiplied by 20 to represent the vertical radians in a circle. The left style is the result of math. cosine multiplied by 200, so that the circle is wider than its height, resulting in the cat moving along the elliptical trajectory.

The important thing to note here is that the style values generally need to be specified in units. In this case, we add px after the number to tell the browser to measure in pixels (not centimeters, EMS, or whatever). It’s easy to miss this unit. If we don’t add units to the number in the style, the browser will eventually ignore the style, unless the number is 0, in which case the result is the same regardless of the unit used.

The summary of this chapter

JavaScript programs can view and modify documents displayed in the browser through a data structure called DOM. This data structure describes the browser document model, and JavaScript programs can modify this data structure to modify the document presented by the browser.

The DOM is organized like a tree, laying out elements hierarchically according to the structure of the document. Objects that describe elements have many attributes, such as parentNode and childNodes, which can be used to traverse the DOM tree.

Styles can be used to change how documents are displayed, either by attaching styles directly to nodes, or by writing rules that match nodes. Styles contain many different properties, such as color and display. JavaScript code can manipulate the style of elements directly through the node’s style attribute.

Problem sets

Create a table

The HTML table is constructed using the following tag structure:

<table>
  <tr>
    <th>name</th>
    <th>height</th>
    <th>place</th>
  </tr>
  <tr>
    <td>Kilimanjaro</td>
    <td>5895</td>
    <td>Tanzania</td>
  </tr>
</table>Copy the code

In the

tag, each row contains a

tag. Inside the
tag are cell elements, divided into header (

) and regular cell (< TD >).

Given a mountain dataset, an array of objects containing the name, height, and place attributes, generate a DOM structure for a table of enumerated objects. There should be one column for each key, one row for each object, plus a header row with a element at the top listing the column names.

Write this program to automatically generate columns from an object by getting the property name of the first object in the data.

Add the resulting table to the element with an ID attribute of “mountains” so that it is visible in the document.

When you’re done, set the style.textalign property of the element to right and right-align the cells that contain the values.

<h1>Mountains</h1>

<div id="mountains"></div>

<script>
  const MOUNTAINS = [
    {name: "Kilimanjaro", height: 5895, place: "Tanzania"},
    {name: "Everest", height: 8848, place: "Nepal"},
    {name: "Mount Fuji", height: 3776, place: "Japan"},
    {name: "Vaalserberg", height: 323, place: "Netherlands"},
    {name: "Denali", height: 6168, place: "United States"},
    {name: "Popocatepetl", height: 5465, place: "Mexico"},
    {name: "Mont Blanc", height: 4808, place: "Italy/France"}
  ];

  // Your code here
</script>Copy the code

Gets the element by tag name

Document. The getElementsByTagName () method returns all child elements with specific tag name. Implement the function, notice that it’s a function, not a method. This function takes a node and a string (label name) and returns an array containing all descendant element nodes with a particular label name.

You can use the nodeName attribute to get the tag name from the DOM element. Note, however, that the tagName obtained using tagName is all uppercase. You can use toLowerCase or toUpperCase for strings to solve this problem.

<h1>Heading with a <span>span</span> element.</h1> <p>A paragraph with <span>one</span>, <span>two</span> spans.</p> <script> function byTagName(node, tagName) { // Your code here. } console.log(byTagName(document.body, "h1").length); // → 1 console.log(byTagName(document.body, "span").length); // → 3 let para = document.querySelector("p"); console.log(byTagName(para, "span").length); / / - > 2 < / script >Copy the code

The cat’s hat

Extend the previously defined animation function for drawing the cat to move the cat and its hat along the oval track edge (the hat is always opposite the cat).

You can also try to make the hat move around the cat, or modify it into some other fun animation.

To make it easy to locate multiple objects, a good approach is to use absolute positioning. This means that the top and left attributes are coordinates relative to the upper-left corner of the document. You can simply add a fixed number to the coordinates to avoid negative coordinates, which move the image off the visible page.

<style>body { min-height: 200px }</style> <img src="img/cat.png" id="cat" style="position: absolute"> <img src="img/hat.png" id="hat" style="position: absolute"> <script> let cat = document.querySelector("#cat"); let hat = document.querySelector("#hat"); let angle = 0; let lastTime = null; function animate(time) { if (lastTime ! Angle += (time - lastTime) * 0.001; lastTime = time; cat.style.top = (Math.sin(angle) * 40 + 40) + "px"; cat.style.left = (Math.cos(angle) * 200 + 230) + "px"; // Your extensions here. requestAnimationFrame(animate); } requestAnimationFrame(animate); </script>Copy the code