Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Page is freezing with tabs when having a list with too many dom elements

I have a web page with two columns, a header with a navigation bar and a footer.

The left column is used to listing items in three different tabs. Each tab contains its items' type.

The right side contains one or more maps to display the items.

The problem occurs when selecting a tab that contains too many elements.

It freezes the interaction (highlights, adding/removing DOM and animations), breaking its responsiveness.

Even when it isn't an interaction with the selected tab (i.e. mouse hover on a navigation bar link).

But the page's responsiveness is fine when the selected tab has fewer items.

I created a spike solution to show you what I'm saying.

Please, you should keep in mind that this is a much simpler version of my problem. It's just a dummy example to present my case.

$('#nav-tabs a').click(function (e) {
  e.preventDefault()
  $(this).tab('show')
});

$('#addBox').on('click', function () {
  $("#content").append("<div class='box pull-left'></div>");
});

$('#newPapper').on('click', function () {
  $("#content").empty();
});


$('#addOne').on('click', function () {
  $("#home div.panel-default").append(createContactDom());
});

$('#addThousand').on('click', function () {
  var dom = "";
  for(var i = 5000; i > 0; i--){
    dom+=createContactDom();
  }
  $("#home div.panel-default").append(dom);
});

$('#clean').on('click', function () {
  $("#home div.panel-default").empty();
});

function createContactDom(){

  var age = Math.round(Math.random()*100);
  var birthday = moment().subtract(age, 'years');
  var isFemale = Math.random() > 0.4;
  var nameIndex = Math.floor(Math.random() * names[isFemale ? "female" : "male"].length);
  var surnameIndex = Math.floor(Math.random() * surnames.length)
  var name = names[isFemale ? "female" : "male"][nameIndex] + " " + surnames[surnameIndex];
  
  var html = '<div id="p' + birthday.format("X") + '" class="panel-heading">';
  html += '<span class="fa-stack fa-lg custom-stack pull-left font-grey-gallery" >';
  html += '<i class="fa fa-square-o fa-stack-2x"></i>';
  html += '<i class="fa fa-user fa-stack-1x"></i>';
  html += '</span>';
  html += '<div class="pull-left">';
  html += '<div class="title">';
  html += '<span >' + name + '</span> ';
  html += '</div>';
  html += '<div class="sub-title">';
  html += '<span title="' + (isFemale ? 'She': 'He') + ' was born on a ' + birthday.format('dddd') + ' at ' + birthday.format('HH:MM a') + '" class="badge pull-left" >' + birthday.format("YYYY/MM/DD") +'</span>';
  html += '<span title="It is a ' + (isFemale ? 'female' : 'male') + '" class="badge pull-left ' + (isFemale ? 'female' : 'male') + '" >' + (isFemale ? 'Female' : 'Male') + '</span>';
  html += '<div class="clearfix"></div>';
  html += '</div>';
  html += '</div>';
  html += '<div class="pull-right actions">';
  html += '<a id="d' + birthday.format("X") + '" title="Delete contact" class="fa fa-times fa-times-close"  style="color: rgb(87, 142, 190);" onclick="deleteContact(this)"></a>';
  html += '</div>';
  html += '<div class="clearfix"></div>';
  html += '</div>';
  
	return html;                      
}


deleteContact = function(e){
  $("#" + e.id.replace("d", "p")).remove();
}

var names = {
  female: ["Maria","Leonor","Matilde","Beatriz","Carolina","Mariana","Ana","Inês","Margarida","Sofia"],
  male: ["João", "Martim", "Rodrigo", "Santiago", "Francisco", "Afonso", "Tomás", "Miguel", "Guilherme", "Gabriel"]
}
var surnames = ["Silva", "Santos", "Ferreira", "Pereira", "Oliveira", "Costa", "Rodrigues", "Martins", "Jesus", "Sousa", "Fernandes", "Gonçalves", "Gomes", "Lopes", "Marques", "Alves", "Almeida", "Ribeiro", "Pinto", "Carvalho"]
.row{
  height: 600px;
}

.col-sm-5,
.col-sm-7{
  height: inherit;
}

.tab-content{
  height: inherit;
  overflow-y: auto;  
}

button{
  margin-top: 5px;
}
span.badge{
  margin-right: 5px;
}

.badge{
   background-color: #999 !important;
}
.badge.female{
   background-color: pink !important;
}
.badge.male{
   background-color: #1c90f3 !important;
}

#content{
  height: inherit;
  overflow-y: auto;  
  padding-top: 5px;
}

#content .box{
  width: 100px;
  height: 100px;
  background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="row">
  <div class="col-sm-5">
    <ul class="nav nav-tabs" id="myTabs" role="tablist"> 
      <li role="presentation" class="active">
        <a href="#home" id="home-tab" role="tab" data-toggle="tab" aria-controls="home" aria-expanded="true">Contacts</a>
      </li> 
      <li role="presentation" class="">
        <a href="#profile" role="tab" id="profile-tab" data-toggle="tab" aria-controls="profile" aria-expanded="false">Problem?</a>
      </li> 
      <li>
        <button type="button" id="addOne" class="btn btn-sm">+</button>
        <button type="button" id="addThousand" class="btn btn-sm btn-primary">+5k</button>
        <button type="button" id="clean" class="btn btn-sm">clean</button>
      </li>
    </ul>

    <div class="tab-content" id="myTabContent"> 
      <div class="tab-pane fade active in" role="tabpanel" id="home" aria-labelledby="home-tab">
        <div class="panel panel-default" style="border-left: 3px solid rgb(87, 142, 190);" >
        </div> 
      </div> 
         
      <div class="tab-pane fade" role="tabpanel" id="profile" aria-labelledby="profile-tab"> 
        <h1>Problems to solve:</h1>
        <ul>
          <li>Adding 5k new contacts blocks the browser's page</li>
          <li>Switching between the two tabs blocks the browser's page</li>
          <li>Painting freezes when adding 5k new contacts</li>
        </ul> 
        <p>Note: The problems get worse as may contacts you add...</p>
      </div>
    </div>
  </div>

  <div class="col-sm-7">
    <button tyoe="button" id="addBox" class="btn btn-sm btn-primary">Paint</button>
    <button tyoe="button" id="newPapper" class="btn btn-sm">New papper</button>
    <div id="content"></div>
  </div>
  
</div>

I'm already using the chrome developer tools to capture the timeline's actions. As far I can see, the problem is some reflows in the layout rendering from jquery.

Here is a screenshot from my timeline: enter image description here

How can I optimize my DOM manipulation in order to avoid page freezing? Can I tell Browser to not redraw left column until I say so?

like image 659
jbatista Avatar asked Jan 27 '17 15:01

jbatista


People also ask

How many DOM elements is too many?

While browsers can handle larger DOM trees, they are optimized for a maximum of 32 elements deep. A large DOM tree can harm your page performance in multiple ways: Network efficiency and load performance.

How does a large DOM tree affect page performance?

A large DOM tree often includes many nodes that aren't visible when the user first loads the page, which unnecessarily increases data costs for your users and slows down load time. As users and scripts interact with your page, the browser must constantly recompute the position and styling of nodes.

What is a good DOM size?

As covered by Google, an excessive DOM (Document Object Model AKA web page) can harm your web page performance. It is recommended that your web page have no more than 900 elements, be no more than 32 nested levels deep, or have any parent node that has more than 60 child nodes.


1 Answers

Why don't you use virtual scroll or infinite scroll or pagination for such a big table, instead of creating it on startup?

There are many example: https://www.sitepoint.com/jquery-infinite-scrolling-demos/

Putting 5k objects in one line is too much, imo.

In your case, you will need to split/interrupt dom manipulation loop for other stuff in stack/event loop.

$('#addThousand').on('click', function () {
  for(var i = 5000; i > 0; i--){
  setTimeout( function() {
    var dom=createContactDom();
    $("#home div.panel-default").append(dom);
    }, 0);
  }

});

Or event that:

$('#addThousand').on('click', function () {
  for(var i = 5000; i > 0; i--){
  setTimeout( function() {
    var dom=createContactDom();
    $("#home div.panel-default").append(dom);
    }, i);
  }

});
like image 168
Andrey L. Avatar answered Nov 15 '22 21:11

Andrey L.