In vanilla Javascript, why does the DOM element update only after the event listener has finished executing?

I'm currently working on a JavaScript program that aims to change the background color of an element after a delay, similar to how a traffic light functions.

However, I've encountered an issue. When the button is clicked, the function associated with the onclick listener runs. Within this function, there are for loops intended to iterate through each cell and update its color. Each iteration through the loop should ideally change the color of one cell, but instead, all cells are updated once the onclick function completes.

You can view a simplified version of the problem I'm facing here.

let main = document.getElementById('main')

// create 'cells' which are just empty divs (refer to css styles above)
let cells = []
for (let x = 0, numCells = 15; x < numCells; x++) {
  let cell = document.createElement("DIV")
  cell.setAttribute('class', 'cell')
  cells.push(cell)
  main.appendChild(cell)
}


// create button to run main logic
const run = document.createElement("BUTTON")
run.innerHTML = 'change colors'
run.addEventListener('click', function() {
  console.log('starting program');
  // reset all cell colors
  for (let cell of cells) {
    cell.style.background = 'white'
  }
  for (let cell of cells) {
    // change color of cell
    cell.style.background = `hsl(${cells.indexOf(cell) * (360 / cells.length)}, 100%, 50%)`
    // halt program for 500ms
    sleep(100)
  }
  console.log('done');
})
main.appendChild(run)

// sleep function halts entire program during period of ms
function sleep(milliseconds) {
  console.log(`waiting ${milliseconds} milliseconds`);
  const start = Date.now();
  let current = null;
  do {
    current = Date.now();
  } while (current - start < milliseconds);
}
.main {
  display: flex;
}

.cell {
  width: 20px;
  height: 20px;
  border: 1px solid black;
  margin: 1px;
}
<div id="main" class="main"></div>

The same issue arises when introducing new elements, modifying innerHTML, or any other DOM changes.

I also believe that the 'sleep' function isn't causing the problem, as it simply pauses the entire program in the browser until a specified number of milliseconds have passed (it continuously calls Date.now() until the time delta surpasses the specified duration).

If anyone has any suggestions or solutions, I would greatly appreciate it. Thank you!

Answer №1

The issue lies within your sleep function. It is causing a problem by continuously running a blocking loop, preventing the event loop from rendering until it's finished. This delay can be detrimental as it stops the browser from updating the display for 10 seconds if the nested loop doesn't resolve quickly enough.

To address this issue, consider using a Promise with setTimeout instead of a looping mechanism. By utilizing setTimeout, processing and rendering will not be blocked, unlike in a situation where a loop goes through numerous iterations:

const sleep = ms => new Promise(res => setTimeout(res, ms));
const main = document.getElementById('main')
const cells = []
for (let x = 0, numCells = 20; x < numCells; x++) {
  const cell = document.createElement("DIV")
  cell.setAttribute('class', 'cell')
  cells.push(cell)
  main.appendChild(cell)
}
const run = document.createElement("BUTTON")
run.innerHTML = 'change colors'
run.addEventListener('click', async function() {
  for (let cell of cells) {
    cell.style.background = `hsl(${cells.indexOf(cell) * (360 / cells.length)}, 100%, 50%)`
    await sleep(500)
  }
  console.log('done');
})
main.appendChild(run)
.main {
  display: flex;
}
.cell {
  width: 20px;
  height: 20px;
  border: 1px solid black;
  margin: 1px;
}
<div id="main" class="main"></div>

Using a while loop to wait for a specific Date.now() threshold is generally discouraged due to its computational cost, user-unfriendliness, and potential unexpected outcomes like the issues you are currently facing.

Answer №2

UniqueResponder was quick to react before me as I was diligently working on my own side of the response.

I have just implemented some enhancements to the code,
I am of the opinion that putting the sleep function before applying colors is a better approach...

const main     = document.getElementById('main')
  ,   cells    = []
  ,   numCells = 16
  ,   run      = document.createElement('button')
  ,   sleep    = ms => new Promise(res => setTimeout(res, ms))
  ;
let InOut = 0  // used for reversing colors order
  ;
run.textContent = 'set colors'
  ;
for (let x=0; x < numCells; x++)    // create 'cells' which are simply empty divs (refer to css above)
  {
  cells[x] = main.appendChild(document.createElement('div'))
  }
main.appendChild(run)  // button to execute main logic
  ;    
run.onclick = async _=>
  {
  for (let cell of cells)
    { cell.style.backgroundColor = null }
  for (let x in cells)
    {
    await sleep(200)
    cells[x].style.backgroundColor = `hsl(${(Math.abs(InOut-x)) *(360 /numCells)}, 100%, 50%)`
    }
  InOut = InOut ? 0 : (numCells-1)
  }
#main {
  display: flex;
}
#main div   {
  display: inline-block;
  width: 1.2em;
  height: 1.2em;
  border: 1px solid black;
  margin: .1em;
  background-color: white;
}
#main button   {
  margin-left:.3em
}
<div id="main"></div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible for me to return a function reference as a response to an API call?

Is it possible to return a function reference or function as a response to an API call from an Express server when using AngularJS as the front-end framework? I attempted to send the response object like this: {per: true, listEvnts: events} where events i ...

Ways to determine if a user is able to receive direct messages

I am facing an issue with a DM command. It seems to work properly, but when I try to DM a user who has disabled their DMs, user.send('test') triggers an error: UnhandledPromiseRejectionWarning: DiscordAPIError: Cannot send messages to this user ...

Module Ionic not found

When I attempt to run the command "ionic info", an error is displayed: [ERROR] Error loading @ionic/react package.json: Error: Cannot find module '@ionic/react/package' Below is the output of my ionic info: C:\Users\MyPC>ionic i ...

HTML5: Implementing shared web workers for efficient communication across multiple connections

As far as I know, one of the main advantages of HTML5's shared web workers is their ability to handle multiple connections within a single separate thread of execution. I'm curious if anyone has managed to achieve multiple connections working as ...

What are the steps to save an XML file into a MySQL database and later access it?

An XML file has been automatically generated from the code snippet provided below. if (isset($_POST["song"]) && $_POST['song'] != "") { $song = $_POST["song"]; } else { $song = array(); } $dom = new DOMDocument("1.0"); $root = $dom-> ...

The Navbar-Burger in Bulma is not linking to menu items properly in Vue.js 2

I have been working on implementing a navbar for my application using Vue 2.0 and Bulma. Everything seems to be working fine on desktop screens, but on smaller screens, the burger icon appears without any elements displayed. It's just there. <temp ...

Error encountered: AngularJS routes causing 500 internal server error

I am struggling with organizing my directory structure. Here is how it currently looks - -project -public -app -app.js ( angular app module ) -server -server.js ( node root js file ) -includes -layout.jade - ...

Identifying the presence of node.js on your system

After installing node.js, I found myself at a loss on how to run applications. Despite the lack of instructions, I was determined to test if it was working by executing a script named hello.js: console.log('hello world'); I couldn't help b ...

Utilize Bootstrap for EF Core pages with pre-designed scaffolds - text will remain neatly displayed within textbox without the need for inline CSS word-wrap styling

After generating CRUD pages using EF core, I am now attempting to make edits to one of the cshtml files. The default Bootstrap CSS file that I am using includes various CSS class definitions: .row { display: -ms-flexbox; display: flex; -ms-flex-wrap: ...

Dimensions of CSS Buttons

On my screen, I have 3 buttons with varying sizes determined by their padding. As the text wraps within each button, they adjust in height accordingly. However, the issue arises when the buttons end up being different heights. Instead of setting a specific ...

How to reset or clear the RangePicker in Ant Design for React

I am working with a component similar to this one and I am looking for a way to make it automatically reset after the user selects a date. Currently, once a date is selected, it remains as is until manually cleared. Current behavior: https://i.sstatic.ne ...

Angularjs fails to refresh $scope changes

I'm facing an issue with my controller where the Gui $scope is not updating properly. After conducting some research, I discovered that using $timeout could potentially help. The service's processing time varies on different units and setting a ...

How can I style radio input elements with CSS when using Material UI within a component?

When styling in CSS, I typically use input[type="radio"]:checked { color: #000}, however this expression is not valid in Material UI. In Material UI, I create rules using makeStyles like so: const useStyles = makeStyles(() => ({ text: { colo ...

Is there a way to implement an onclick event for every iframe within a document using jquery?

I have a webpage containing two iframes that can be switched using a selector. My goal is to implement an onclick event that will trigger a URL for specific <rect> elements within the iframes. After reading a helpful post on accessing iframe childr ...

JavaScript Update Function for Pointers in Parse.com - Modify a Row with a Pointer

I am currently working with Parse and JavaScript. While I know the ObjectId from the Status_id-class, I am facing a challenge in updating the column in the Question_status-class due to it being of Pointer-type. Can anyone guide me on how to update a Poin ...

Not able to drag or drop list items using Jquery Sortable in Django

I am having trouble with implementing a Jquery sortable drag and drop feature from one list to another in my code. The specific error message I am encountering is: Uncaught TypeError: $(...).sortable is not a function I would greatly appreciate any assis ...

Tips for extracting innerHTML or sub-string from all elements that have a specific class name using JavaScript

When it comes to shortening the innerHTML of an element by its Id, I have found success using either slice or substring. While I'm not entirely clear on the differences between the two methods, both accomplish what I need them to do. The code snippet ...

How to apply CSS styles to a variable containing an element in React

I'm having some trouble styling this element I created. So, I made a variable called test and assigned it to an element. var test = <Button className="testButton" /> Then in my return statement, I am using the test variable like so: render () ...

Move the contents of a div to the right side

One of the issues I am facing is with aligning replies to comments correctly in my comment section. The replies are not aligning properly with the parent comment and it's causing a display problem. Link to JSFiddle for reference Here is the CSS code ...

The usage of line breaks within the <textarea> and <pre> elements is crucial for maintaining the formatting of text

In this box, you can enter text. If you reach the end of the line with letters, it will automatically continue on a new line. Once you click on 'update details,' the letters will be saved in a database. https://i.sstatic.net/q2Y1Z.jpg On the &a ...