As you can see from the screenshot that Layout is triggered on root element (#document) in every frame which causes next operations like paint.
This Layout is caused by Recalc Style which I am not able to understand why it's happening. There's no script running. Just Recalc Style > Layout > ...
I need to know why such Recalc Style could occur without script?
Additional Information:
This page is using Semantic UI framework
It contains a custom grid
This Recalc Style > Layout operation is performed in ever frame even when the page is idle.
First, is this even a problem? Unless you're experiencing performance issues, don't waste time trying to prematurely optimize.
Next, enable the Chrome rendering settings to show a what areas of the screen are GPU layers and what parts are being repainted.
Style recalculation and painting can happen for many reasons that have nothing to do with javascript. Browser resizing, tables performing rel-layout as new items are loaded during the page load, etc. Since you didn't provide any information about the page that's being loaded, it's hard to say.
My guess would be that you've got some kind of CSS animation that's being applied to a property other than translation, rotation, scale or opacity. All of those properties are GPU accelerated and won't trigger repaints, however, fading background colours, changing border thicknesses, even adjusting top, left, right and bottom properties all cause repainting.
You've mentioned that you're using Semantic UI. Either Semantic or one of its themes are causing this redraw to happen. Your screenshot doesn't actually prove that scripts aren't being called.
Related
I am implementing a functionality where I can add, drag, and delete "sticky notes" on a webpage. The app is built in Vue js but it also renders content in an iframe. All the notes that are added to the page have to be on top of the iframe (which is almost 80% of the main .vue page), therefore positioning here is important and I also have to preserve the positions because I must render the notes on the same position on next page reload. The problem is that there's a lot of lag in dragging the "note" element.
The "sticky note" itself is a very lite weight separate component.
I have noticed that this because of the iframe that's present on the vue page because when I inspect the DOM in the browser and delete the iframe and then try dragging the "note" component then it works smoothly.
Things I have tried:
Use throttling: I tried to use loadsh.throttle but that does not make any difference.
Inject "sticky note" code inside the iframe: I tried to inject a sticky note element into the iframe and append it to iframe's body. It actually makes the dragging very smooth. But I don't want to go ahead with this solution because then I will have to write a lot of extra code to maintain the state of multiple notes (which can be done easily with Vue js).
In this solution, instead of using the document of the main page to attach events for dragging, I attached all the events to iframe.contentDocument.
So the question here is how do I make the drag smooth while using vue.js
Sandbox Link: https://codesandbox.io/s/affectionate-jang-3c1hw?file=/src/components/HelloWorld.vue
In this gif, the background is actually the iframe, I have reduced its opacity to hide it.
I could not actually include the iframe in sandbox code because of cross-origin problems but I have included a lot of extra content in that sandbox to make it heavy.
UPDATE:
Using the chrome task manager, I found that the page is only taking max 200MB memory and the GPU process is taking another 200MB. I am running this on a system with 16GB RAM. So I don't think it's a memory issue. But there is a sudden spike in the CPU consumption when I start dragging the element (up to 40%).
UPDATE:
I have found the fix for this problem. The actual problem wasn't lag but it was mouse trailing i.e. the draggable element wasn't able to catch up with the fast moving mouse cursor. And the slowness was due to e.preventDefault in the dragMouseDown method inside Note.vue. Just removing the e.preventDefault fixed all the problems. Also just adding a return false at the end of dragMouseDown method seems to cause the same amount of lag.
function dragMouseDown(e) {
e = e || window.event;
// e.preventDefault(); --> this line causes the mouse trailing issue
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
// return false; --> adding this line also causes mouse trailing problem.
}
So for now I have just removed the preventDefault from this function. But I tried searching and could not find any explanation of this behavior. Also I am not sure if not cancelling the event can cause any other issue.
The problem is that you're working with mousemove without using requestAnimationFrame to debounce. Here's a working example using debounce (CodeSandbox link)
Generally, anytime you're trying to animate with javascript, you want to use requestAnimationFrame. Another thing you could try is using transform to change the position of the element instead of absolute positions.
Here's the MDN reference on requestAnimationFrame.
Here's an article by Paul Irish about using transform instead of absolute positioning to speed up a drag/drop.
As your extra description in the comments,
It starts to lag when these a lot of other content on screen. There's
a v-for in the sandbox. make it do more iterations and it will start
to laggy
The problem you met is the Dom elements were too many on your page then caused high memory usage. (Actually I tried <div v-for="i in 10000" :key="i">", then it took around 3GBs of memory), finally, everything works slowly and laggy
If your page has tons of Dom elements, you may have to consider dynamically add only visible items into the Dom tree when scrolling.
Even there are some packages that already implements this feature.
Below is one demo which uses RecycleScroller of vue-virtual-scroller:
100K items in the Codepen
You will see even the number of the items are 100,000, it still works smoothly.
PS: you may notice the below statement in the user guide of the above package=vue-virtual-scroller
The browsers have a size limitation on DOM elements, it means that
currently the virtual scroller can't display more than ~500k items
depending on the browser.
Whilst this question references Drift, no prior knowledge of this plugin is required as this is essentially just a CSS issue. In short, it is a chat widget that utilises an auto-inserted iframe to provide it's service (and it is an absolute pain to apply a custom position to).
Background
I have recently installed a chat plugin on my client's site (powered by Drift). After much excruciating pain trying to apply a custom position to the iframe, I finally managed to get it to position correctly (or as best as it would allow).
The reason it is so painful is simply because Drift repositions and resizes it's containing iframe based on numerous, very weird, factors, so these need to be tracked.
The Problem
As mentioned, the Drift JS automatically resizes it's containing iframe according to it's contents. However, sometimes the contents and the calculated height do not match and we are left with an iframe much larger than it needs to be, but due to it's functionality, it must have a higher z-index than anything else on the page, thus the iframe creates a large 'dead' patch where no pointer-events can pass through.
Consider the following:
Can anyone suggest a way in which I can keep the pointer events of the elements within the Drift iframe (red rectangle), but also allow pointer events to pass through in the areas of the iframe that there is no content (yellow area)?
The Problem (Updated)
Please note the red rectangle in the image represents the iframe, not a containing div. i.e. Ignore my annotation
What I have Tried
I have played around with various values for pointer-events but have had no luck. I thought that something along the lines of pointer-events: visiblefill; would work but unfortunately this is only compatible with SVGs...
My last resort solution is to override the calculated height of the Drift iframe but this is extremely dirty and I cannot be sure that my calculated height will be correct, thus the widget may end up looking terrible.
UPDATE: Before reading into this post and saying you need more code to replicate the issue, the simple question I am asking is:
"Have you ever had to remove and re-apply a CSS property on an element in order for that style to apply when that property was already on that element and should have been working by default? If so, what was your solution or did you know what was ultimately causing the problem?"
UPDATE 2: I'm not 100% sure but this may only be an issue with the font-size property and it being changed dynamically using Javascript. The CSS for the outer and/or inner elements that are being re-sized in Javascript works as it should, this problem seems to only occur with font sizing, in particular. Perhaps the surrounding HTML's CSS doesn't catch font-size on memory intensive style changes (like our resize function). Just a guess.
The rest of this post is a means to clarify what I am talking about.
This problem might be exclusive to Chrome. To clarify, our app is used internally by our sales team and the project was specced out to focus exclusively on Chrome, both for development and field use. Already, the styling is totally off when opening this in other browsers, since we've only had to create our CSS to be Chrome-compliant (how I got so lucky to land such a position, I don't know, haha).
I've only encountered this issue a few times in my coding career which I believe to be a bug in CSS but could just be a rare bug between Javascript making style changes in the DOM and parent container CSS not adapting to the change due to possible memory constraints or slight processing lag between Javascript applying a style to the HTML and the CSS not updating to accomdate. I think this is an important question as I think every front-end web developer has or will come across this at some point in their career.
Situation
To put it as short as possible, we have a container inside our body that we use Javascript to size to a 16:9 aspect ratio relative to the window size. Took a screenshot of the entire window. As you can see, we have the inner container sizing proportionally inside our browser window (the black area, which is the body). The inner container is where all the content for our app displays:
Our CSS is built using percentages, wherever possible. We built a 'resizeUI' function that's mainly used to change the font-sizes based on the current width/height ratio of the inner screen, though there are other elements in that function that get re-sized too, on window re-size. The window always re-sizes perfectly and anything needing specific sizing based on the inner window's width/height pixel ratio works as expected, with the child elements' percentage-based CSS adjusting appropriately. But....
The Problem
When the browser window is re-sized by clicking and dragging (incremental re-sizing) everything on the page re-sizes perfectly. However, when the window size changes drastically, and instantly, fullscreen (Maximize) to small (Restore Down), or visa versa, the font-sizes in the top menu will change BUT the CSS of the parent li will not adjust to accommodate the new font-size unless I un-check (remove) it's padding attribute and recheck it (add it back in). When I do that, it works fine. The li container really just has a padding:1% and, thus, normally adjusts it's size correctly based on the inner span's size which changes with the font re-sizes.
The parent li should automatically adjust to the change of the inner span font-size. I'm positive of this because the parent li container will automatically re-size itself when I modify the menu's font-sizes or text lengths, on the fly, in the inspector.
Here's what it normally looks like either on load or with incremental window size changes:
And here is what it looks like when I go from a small window size to full-screen using Maximize:
My (hacky) Fix
Only way I've gotten this to work is to remove the padding for the <li> elements at the top of the re-size function, then re-apply it at the bottom of the re-size function AFTER the line that re-sizes the font. However, this works 50% of the time. Only when placing a 100ms timeout at the bottom of the re-size function to re-apply the padding have I gotten it to work 100% of the time now.
To help, here's a very basic code example of what i'm working with. The ul sizes automatically to it's inner elements:
Relevant HTML
<ul>
<li class="menu-items><span>Item 1</span></li>
<li class="menu-items><span>Item 2</span></li>
<li class="menu-items><span>Item 3</span></li>
<li class="menu-items><span>Item 4</span></li>
<li class="menu-items><span>Item 5</span></li>
</ul>
Relevant CSS
ul {
position:absolute;
top:0;
right:2%;
text-align:center;
}
.menu-items {
padding: 1%;
}
Relevant Javascript
What works about 1/2 the time
function resizeUI(){
$('.menu-items').css('padding','0');
//Random other elements being re-sized
$('.menu-items span').css('font-size', properRatio + "px");
//More elements being resized
$('.menu-items').css('padding','1%');
}
Works every time
function resizeUI(){
$('.menu-items').css('padding','0');
//Random other elements being re-sized
$('.menu-items span').css('font-size', properRatio + "px");
//More elements being re-sized
setTimeout(function(){
$('.menu-items').css('padding','1%');
}, 100);
}
Ultimate Question
Has anyone else encountered similar issues when working with CSS that should adapt to the re-sizing of an element? I sense it's a memory issue or bug between Javascript and CSS, given that having to set a timeout is the only reliable way I have gotten it to work 100% of the time, and, again, this ONLY occurs on a vast, instant change in window size (Maximize to Restore down, or visa versa) but not on manual, smaller, incremental resizing. Sorry for such a verbose post/question but I have encountered this weird bug a few times before with having to remove and reapply a CSS attribute to get it to work properly and I have no doubt other developers have too.
The padding is actually working. Each unit (div or whatever you are using) is overlapping the one previous evenly because you are over 100% of the view width in total. They are being built from left to right and when it gets to the end of the max size allowed, they will all equally push over the ones previous. Also padding affects elements inside of the div and will not affect divs outside of itself. So they are all the correct size but you have not established a rule that would stop divs from overlapping. Margin can do that but if they are over 100% of the desired size you will have to deal with over flow in some way. You can try to use auto for padding, but margin may be better. Otherwise you will have to play with the sizes and percentages to make it fit to the max allowed width. Checkout the CSS Box Model here.
Have you ever had an issue of having to remove and re-apply a CSS
property on an element in order for that style to apply when it was
already there and should have been working by default?
Yes: https://stackoverflow.com/a/34245989/1529630
What was your solution?
Unsetting the styles and resetting them immediately after didn't work, probably because the browser hadn't updated the page yet, and then it detected that I resetted so at the end it did nothing.
Unsetting and resetting in a timeout worked, but caused annoying blinks.
In my case, the solution was removing the element and inserting it back immediately.
window.addEventListener('resize', function() {
// Force a rerender
var parent = element.parentNode,
next = element.nextSibling;
parent.removeChild(element);
parent.insertBefore(element, next);
});
Warning: this will force the browser to do layout, paint and composite operations, which is expensive.
I am designing an interactive web game that takes place entirely in the browser. It uses html5, and everything (including the elements) is part of the game world. Since this is the case, I need some pretty strict control over the positioning of my elements, scroll position, zooming, etc.
One particular level requires that an element be placed off screen (just outside the viewport) so that the user must scroll the page to find it. Unfortunately, after scrolling, the page seems to record the new width of the page including the originally unseen element. When the page is refreshed, the zoom level is adjusted to fit the entire screen with the hidden element into the viewport. This gives away the puzzle and ruins the level.
I know that browsers store information like scroll position so that when a user revisits the page they can pick up right where they left off. This is great for some things, but bad for my purposes. Is there a way to prevent this caching behavior of my browsers? Is there a way to get or set the zoom level of a page using JavaScript?
Currently I am using the code below to reset the scroll position right before the user leaves the page. It works pretty well, but the user can see the page scroll right before leaving.
window.addEventListener("beforeunload",function(event_){
window.scrollTo(0,0);
/* What I would love is if there were a way to do this: */
// window.zoomTo(1.0);
/* But I'm sure that's asking for too much. */
});
I managed to fix my problem by keeping the hidden element out of the html flow all together by setting its css position property to fixed. I simulate page scrolling by changing the elements style.left value with some custom touch event handlers. The page has no need to resize or zoom with the addition of the off screen element because fixed position elements do not effect layout.
This doesn't answer my question about resetting the zoom level, however, and I would still appreciate any insight anyone may have.
I have a question on a big # of dom elmenets and performance.
Let's say I have 6000 dom elements on a page and the number of the elements can be increased as a user interact with the page (user scrolls to create a new dom element) like twitter.
To improve the performance of the page, I can think of only two things.
set display to none to invisible items to avoid reflow
remove invisible items from the dom then re-add them as needed.
Are they any other ways of improving a page with a lot of dom elements?
We had to deal with a similar problem on FoldingText. As the document grew larger, more line elements and associated span elements were created. The browser engine just seemed to choke, and so a better solution needed to be found.
Here's what we did, may or may not be useful for your purposes:
Visualize the entire page as a long document, and the browser viewport as the lens for a specific part of the long document. You really only have to show the part within the lens.
So the first part is to calculate the visible view port. (This depends on how your elements are placed, absolute / fixed / default)
var top = document.scrollTop;
var width = window.innerWidth;
var height = window.innerHeight;
Some more resources to find a more cross-browser based viewport:
How to get the browser viewport dimensions?
Cross-browser method for detecting the scrollTop of the browser window
Second, you need a data structure to know which elements are visible in that area
We already had a balanced binary search tree in place for text editing, so we extended it to manage line heights too, so this part for us was relatively easy. I don't think you'll need a complex data structure for managing your element heights; a simple array or object might do fine. Just make sure you can query heights and dimensions easily on it. Now, how would you get the height data for all your elements. A very simple (but computationally expensive for large amounts of elements!)
var boundingRect = element.getBoundingClientRect()
I'm talking in terms of pure javascript, but if you're using jQuery $.offset, $.position, and methods listed here would be quite helpful.
Again, using a data structure is important only as a cache, but if you want, you could do it on the fly (though as I've stated these operations are expensive). Also, beware of changing css styles and calling these methods. These functions force redraw, so you'll see a performance issue.
Lastly, just replace the elements offscreen with a single, say <div> element with calculated height
Now, you have heights for all the elements stored in your Data structure, query all the elements that lie before the visible viewport.
Create a <div> with css height set (in pixels) to the sum of the element heights
Mark it with a class name so that you know its a filler div
Remove all the elements from the dom that this div covers
insert this newly created div instead
Repeat for elements that lie after the visible viewport.
Look for scroll and resize events. On each scroll, you will need to go back to your data structure, remove the filler divs, create elements that were previously removed from screen, and accordingly add new filler divs.
:) It's a long, complex method, but for large documents it increased our performance by a large margin.
tl;dr
I'm not sure I explained it properly, but the gist of this method is:
Know the vertical dimensions of your elements
Know the scrolled view port
Represent all off-screen elements with a single div (height equal to the sum of all element heights it covers for)
You will need two divs in total at any given time, one for elements above the visible viewport, one for elements below.
Keep track of the view port by listening for scroll and resize events. Recreate the divs and visible elements accordingly
No experience myself with this, but there are some great tips here: http://engineering.linkedin.com/linkedin-ipad-5-techniques-smooth-infinite-scrolling-html5
I had a look at Facebook and they don't seem to do anything in particular on Firefox. As you scroll down, the DOM elements at the top of the page don't change. Firefox's memory usage climbs to about 500 meg before Facebook doesn't allow you to scroll further.
Twitter appears to be the same as Facebook.
Google Maps is a different story - map tiles out of view are removed from the DOM (although not immediately).
It's 2019. The question is really old, but I think it is still relevant and interesting and maybe something changed as of today, as we all now also tend to use React JS.
I noticed that Facebook's timeline seems to use clusters of content which is hidden with display: none !important as soon as the cluster goes out of view, so all the previously rendered elements of the DOM are kept in the DOM, it's just that those out of view are hidden with display: none !important.
Also, the overall height of the hidden cluster is set to the parent div of the hidden cluster.
Here are some screenshots I've made:
As of 2019, what do you think about this approach? Also, for those who use React, how could it be implemented in React? It would be great to receive your opinions and thoughts regarding this tricky topic.
Thank you for the attention!