MutationObserver and DOM timings - javascript

I am using said MutationObserver to watch for changes on an HTMLElement.
I dynamically add children to a div and the observer does a nice job at executing the provided callback anytime changes are detected in the div's tree.
I am adding the children all together (by mounting a React component) and there is an action that I need to perform only when I know that all of them are added; if I perform that action also before all the children are added to the DOM, then I would irreparably mess everything up.
The observer possibly would run the callback a bunch of times within a fraction of a second just because I added a bunch of children, so a possible solution would be to set a timeout for this particular action.
Now since the action is vital for the interactivity of the page I need to wait as little as possible.
My question is: is there an order of magnitude for the time needed to add a handful of children to an element? Something like 1ms, 100ms or 1 second? How long is it safe to wait?
But also: are you aware of any other possible solution to the problem?
PS: my question is a generalisation of this question, which has been solved by checking for the appearance of a particular entity (a background-image), but I am working with a reusable custom hook and waiting for the appearance of an element that could be an input as it could be a button and by the way it would not be the only one of its type among the children being added to the div, nor it is granted that it will be present on the DOM once all the children are added. I just need to check for its presence once all the children are added and that's it. A different approach is needed.

Related

React - append-only behavior with createPortal or appendChild?

I have a large number of elements being rendered by a common parent. When the user takes an action, a few of the elements should be removed, and a few additional elements should be appended. Doing this the 'React way,' the parent has an array that owns the state of all the children elements. On user on click, the parent immutably modifies the array and voila...new elements in the DOM. The problem is that it's often sluggish due to having to compare every element on the page, even with element keys and memoization. I have been fighting with React's diffing algorithm to make this work efficiently. Ideally, this parent component would be responsible solely for appending new children...once they're bootstrapped, they would control their own state.
I've just read about Portals, and am trying to understand whether ReactDOM. createPortal(<Child />, document.getElementById('myFavoriteDiv')) will allow this append-only behavior without altering any of the previously-rendered children. The non-declarative way of doing this I think would be appendChild, but I think that's frowned upon. If this doesn't work like that, what alternatives would you suggest?

Google tag manager single page app get element on history change

I have created a few variables for custom dimension on pageviews.
Pageview trigger: windowload or history change
The data is being pushed through, but it's only getting previous pages'
for example,
page/1 div class "page date" is 25th Jul, I would get undefined, but
when I click onto page/2, i would get the page/1's "page date"
function() {
return window.document.getElementsByClassName('page date')[0].innerText;
}
It seems that the history event is triggered before the corresponding page content is loaded into the DOM. This is nothing you can blame GTM for (GTM sees a history change, inspects the DOM, and grabs whatever it finds there, and that's the normal/expected behaviour).
Your solutions:
Make sure content is updated in DOM BEFORE the history event is triggered: this is something to sort out on the application side, and that may not be easily changed (if you use a framework like react it's probably best if you don't start hacking its core behaviour).
Delay the history event triggers: have a look at this solution which describes how to achieve this. Please note that solutions based on delays are never 100% reliable because there's a race condition between your delay and the loading of content, and you don't know for sure who will come first (and increasing the delay too much can cause the side effect of users changing pages in quick successions before analytics had a change to capture them).
Detect DOM changes: a more robust alternative would be to monitor the DOM for a particular element that is unique to each page (eg a <meta> element with the page ID or URL). You could use a tag to initiate the monitoring of this element when you receive the history change, and when the element actually changes it means the DOM has been updated, and you could fire your own trigger. This could be done via the MutationObserver or using a setInterval/setTimeout loop to check manually. However if the DOM is changed in several phases (blocks by blocks) this would not work (your <meta> element would have changed but not the div you're looking for), requiring you to start monitoring on a per-element or per-block level, which will be quite some work.
Push a dataLayer from your application: this would be my preferred option. I would hook into the logic of your application (you should be able to extend the routing method or the app framework should give you event listeners you can bind a custom function of yours with in which you can tell GTM that the page has been changed (eg dataLayer.push({'event': 'page_changed'});)

Is there a normal vanilla JS/HTML (or JQuery) event that fires when an element begins/finishes rendering?

I'd like something similar to the document.ready event, but for any DOM element.
In more details, I'm hacking away at an app which has a custom framework. I'm trying to add a loading screen. This screen should disappear whenever a certain element pops up on the screen.
Now I don't really know how I'd do this in a clean way, or what a recommended way of doing such things would be, but at the moment, for me it would be enough if I could implement this pseudocode
container.show_loading_spinner()
// .. other stuff ...
// this should only trigger when the next line, which starts creating elements begins
container.on_drawing_begin(function()
{container.hide_loading_spinner()})
//when this begins drawing, the spinner should get hidden.
container.create_some_elements()
I realise this is probably quite complex, and kind of hope I won't be disregarded as a noob but I'm mainly focused on backend development. Is this thing that I'm asking even possible, or should I think this through in a completely different way?
[EDIT]
Q: What is my definition of "rendering"?
A: The moment when an element is present in the DOM and visible on the screen, I call it "rendered". The moment when it's not in the DOM, it's unrendered. Rendering is the process which takes the DOM (and corresponding screen - if the element is visible) from the first state to the second.
More casually though I just want "some event" which is triggered very close in time to the moment when an element begins to render. I need the beginning, because that's when I want to turn off the spinner. I don't want the spinner to be present on the screen at the same time as my elements.
What if you trigger a custom event like hideSpinner when you start rendering your elements?
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events

Algorithm to update only the changed innerHTML of the document

I have 2 JS variables. before and after. They contains the SAME html document, but have some modification. About 1%-10% change between them. I want to update the body from before to after. The variablesbefore and after are raw string.
I can do something like that:
document.documentElement.innerHTML=after
The problem is that if I render this way it not look good. The render takes time, and there is a white screen between the renders. I want to show the user 10 modification in a second (video of modifications)
So what I want to do. I want to search and find only the elements that changed only by analyze the HTML text of before and after.
My way of solution:
I can find the changes and the position in the text using Javascript Library for diff & match & patch.
The question is:
After I find the text changes. How to find only the elements who changed. I update only those elements.
I thought, maybe to create a range, that contains every change, and update the range, but how exactly to do that?
If anything unclear, please comment, I will explain better.
I found a very good library for it: https://github.com/patrick-steele-idem/morphdom
Lightweight module for morphing an existing DOM node tree to match a
target DOM node tree. It's fast and works with the real DOM—no virtual
DOM here!
Very easy to use, and doing exactly what I need
If I have understood your question correctly, then what I would have done is,
1) Make a new object (view Object) which will control the rendering of DOM elements. (Similar to MVC)
2) In this object, I would have created 3 functions.
a) init function (contains the event-handlers)
b) render1 function (which will contain elements in before element)
c) render2 function (which will contain elements in after element)
Whenever there is an event where I need to change the HTML of a class/id/body/document, I will change that in init function and call render2 function which contains the after element.
This should not give any error, however the browser has to work to render all the page, but rendering can be divided over multiple elements of document. So, whenever you need to render a part of document, make separate render functions.
p.s. there can be different approaches.
You must implement the LCS(Longest Common Subsequence). To understand better of this algorithm you can watch this youtube video. Also It's easier to first study Longest Common Substring.
I think I have a solution. virtual-dom can do the work for me. I can create two VTree, make a diff, and apply a patch.
From the documentation of virtual-dom:
virtual-dom is what I need.
Manual DOM manipulation is messy and keeping track of the previous DOM
state is hard. A solution to this problem is to write your code as if
you were recreating the entire DOM whenever state changes. Of course,
if you actually recreated the entire DOM every time your application
state changed, your app would be very slow and your input fields would
lose focus.
virtual-dom is a collection of modules designed to provide a
declarative way of representing the DOM for your app. So instead of
updating the DOM when your application state changes, you simply
create a virtual tree or VTree, which looks like the DOM state that
you want. virtual-dom will then figure out how to make the DOM look
like this efficiently without recreating all of the DOM nodes.
virtual-dom allows you to update a view whenever state changes by
creating a full VTree of the view and then patching the DOM
efficiently to look exactly as you described it. This results in
keeping manual DOM manipulation and previous state tracking out of
your application code, promoting clean and maintainable rendering
logic for web applications.
https://github.com/Matt-Esch/virtual-dom

I don't fully understand JavaScript Threading

Before I dive into the question. Let me state that by Event Loop I am referring to http://en.wikipedia.org/wiki/Event_loop. This is something that browsers implement. For more information, read this: http://javascript.info/tutorial/further-javascript-features/events-and-timing-depth.
This question is hard and long, so, please try to bear with it! And I do appreciate all answers!
So. Now, as I understand it, in JavaScript there is a single main thread (in most browser environments, that is). So, code like:
for (var color = 0x000; color < 0xfff; color++) {
$('div').css('background-color', color.toString(16));
}
will produce an animation from black to white, but you won't see that because the rendering is done after the code has been processed (when the next tick happens -- the browser enters the Event Loop).
If you want to see the animation, you could do:
for (var color = 0x000; color < 0xfff; color++) {
setTimeout(function() {
$('div').css('background-color', color.toString(16));
}, 0);
}
The above example would produce a visible animation, because setTimeout pushes a new event to the browser Event Loop stack which will be processed after there is nothing running (it enters the Event Loop to see what to do next).
It seems that the browser in this case have 0xfff (4095) events pushed into the stack, where each of them are processed with a render process in between them. So, my first question (#1) is that when exactly does the rendering take place? Does it always take place in between the processing of two events in the Event Loop stack?
The second question is about the code in the javascript.info website link I gave you.
...
function func() {
timer = setTimeout(func, 0)
div.style.backgroundColor = '#'+i.toString(16)
if (i++ == 0xFFFFFF) stop()
}
timer = setTimeout(func, 0)
....
My question here is that will the browser push a new "rendering" event to the Event Loop stack every time it reaches the div.style. ... = ... line? But does it not first push an event due to the setTimeout-call? So, does the browser end up in a stack like:
setTimeout event
render event
Since the setTimeout call was processed before the div style change? If that's how the stack looks like, then I would assume the next time the browser enters the Event Loop it will process the setTimeout's callback and end up having:
rendering event
setTimeout event
rendering event
and continue with the rendering event that the earlier setTimeout call produced?
Q1: Not necessarily. Browsers to varying degrees implement optimizations. For example, they may wait to collect several style changes before triggering an expensive recalculation of the layout. So the answer is: depends on the specific browser.
Try this: http://taligarsiel.com/Projects/howbrowserswork1.htm#Render_tree_construction (the document is dated Oct 2009 - i.e. it is sufficiently up to date)
Q2: The rendering is not necessarily the same as the JS execution - that's two different engines. Ths JS engine is not responsible for the rendering, it just interfaces with the render engine. It seems to me the main message for your second question is this independence of the JS from the rendering engine. Remember, a browser (or a webpage) does not need Javascript, their main purpose is to render HTML based on CSS style rules. Javascript is just one way to manipulate the HTML (the DOM tree really) and the style rules.
Note that you can force rendering by reading a style definition - at this point the rendering engine has no choice but process any outstanding style changes, especially if it involves any position changes. That's why one should remove objects from the rendering tree (e.g. by setting display:none - visibility:hidden is NOT enough since the element's size is still considered for layout) before doing a lot of style changes or adding a lot of elements, e.g. when lots of rows are added one by one (a "for" loop) to a table.
Not part of the question at all - but since I just mentioned a difference between display:none and visibility:hidden, that's also a consideration when adding hidden position:absolute elements like dialogs. While there is no visible difference whether an absolutely positioned element is hidden from you using one or the other method, internally there IS a big difference: when hidden using visibility:hidden the element is part of the rendering tree, with display:none it is not. So, if one has such an element that needs to be toggled a lot one should use visibility:hidden, because when the "display" style is switched between "none" and e.g. "block" the browser has to render it first.
The article you mention only considers Javascript. A lot more happens in the browser; reflowing and repainting are/can be triggered by a lot more things; take a look at the following links for more info on this.
http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/
http://www.browserscope.org/reflow/about
I wouldn't use setTimeout for this purpose.
Edit:
As per the comments, the recommended way is to use requestAnimationFrame. As of this writing, this is only available in unstable releases of most browsers. There are however several libraries available providing cross-browser access to it, and fall back to using setTimeout if necessary.
Take a look at this demo for an example working in old browsers, as well as in new ones:
http://paulirish.com/2011/requestanimationframe-for-smart-animating/

Categories