Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dom iterating causing half webview rendering

im using android to open a local web file and then iterate the dom and apply some changes BUT while its iterating the webview stop to render the page at some random part, look the gif:

what i already did

var elements = document.querySelectorAll("P");
for(var i = 0; i < elements.length;i++) {
//just iterating at the gif im chaging 
//but i tried without changing, just iterating and the result still the same 
} 

and now to avoid the rendering issues at each dom change, i created a documentFragment and change the dom in it(or just iterate) and than return its html to the main document

var x = document.getElementById('contentRoot'); //getting the element root from the DOCUMENT (not the fragment)
      var frag = document.createDocumentFragment(); //creating fragment
      var contentRoot = document.createElement("DIV"); // creating a new contentRoot html element
      contentRoot.id = 'contentRoot';
      contentRoot.innerHTML = x.innerHTML;
      frag.appendChild( contentRoot );
    var elements = frag.querySelectorAll("P");
    for(var i = 0; i < elements.length;i++) {
    //just iterating at the gif im chaging 
    //but i tried without changing, just iterating and the result still the same 
    } 

document.body.innerHTML = frag.getElementById('contentRoot').innerHTML; //returning the edited html from the fragment so the webview will render/reflow just once everything not for each change

is there a way to keep the rendering normal without "cutting it"?

PS: im using the function to iterate on the window.onload, so it just starts AFTER all the dom is loaded into the browser, why its keep cutting it? and just render again at the end of the iteration?

enter image description here

like image 752
user2582318 Avatar asked Oct 16 '16 03:10

user2582318


2 Answers

The issue with JavaScript is that while JavaScript is running, the entire page is frozen and will not render anything. If you have a very large for loop running, this explains your issue.

Most desktop computer browsers will render scrolling at the same time as rendering the page (I bet that if you run this page on a desktop browser, it would not even let you scroll).

However, most mobile devices, in an effort to look fast and slick, will render scrolling separate from the page itself. The issue with this is that if JavaScript is stuck running a very long loop, the user will be able to scroll, but the page won't render ahead of them, eventually getting to this blank white area where nothing has rendered yet.

I bet that if you tried zooming in on the page while the function is running, it would look very blurry, because no rendering is taking place.

One way to fix this issue is by using timers or the requestAnimationFrame API.

requestAnimationFrame is supported by almost all modern browsers. What it does is it puts off code until the next frame is rendered and is commonly used to make smooth animations.

Using this API, I have created the following code...

var elements = document.querySelectorAll("P");
var i = 0;
var j = elements.length;
function iterate(){
    // this acts as the body of your for loop
    // do stuff with the current selected DOM element here...
    // select dom element with elements[i]

    i++;
    if(i < j){
        requestAnimationFrame(iterate);
    }
}
iterate();

Now, the code above runs an iteration of your for loop every animation frame. Assuming your device runs at 60fps, this makes a max of 60 iterations per second.

To combat this and make it even faster, we can replace requestAnimationFrame(iterate) with alternate code window.setTimeout(iterate, 0), which essentially tells the browser to do an iteration every millisecond it can, while still being able to render the page. This method, however, can bring down the framerate. (for a mobile browser that handles separated scrolling like yours, however, framerate should not be an issue)

Edit:

When I ran a fairly simple, but long, loop in my desktop browser using my above method, I achieved a JavaScript-measured 60fps, with about 150 - 200 iterations per second. If you're on mobile, your results will probably be slower.

Alternate Suggestion:

If you want to use a prebuilt API for this kind of thing, there is one out there that looks pretty cool. The OP found one called Turboid, lets you manage this kind of loop more easily. http://turboid.net/artikel/real-loops-in-javascript.php

like image 64
MineAndCraft12 Avatar answered Nov 18 '22 04:11

MineAndCraft12


Maybe try to wait for the next javascript frame, It can be useful when dealing with a lot of dom manipulation :

setTimeout(function(){
    document.body.innerHTML = frag.getElementById('contentRoot').innerHTML;
}, 1 );
like image 2
arlg Avatar answered Nov 18 '22 03:11

arlg