How is this layout issue commonly addressed? - javascript

I have a page where the following occurs:
some stuff is rendered. static content, and content meant to be enhanced with javascript.
some of the divs are instrumented for enhancement via jquery in $()
some of these 3rd party scripts measure the divs they are putting content into so they know how to render it.
in the meantime, as other divs are enhanced, sometimes the page gets long enough that a scroll bar appears. that means the page just got thinner and the measurements that the plugins made are now incorrect.
some divs get enhanced with the wrong width.
ugliness!
If I resize the browser, everything "snaps" into place where it should be.
I can see 2 solutions which I don't like.
somehow force the browser to re-layout everything after every enhancement.
force a vertical scrollbar. http://ryanfait.com/resources/forcing-vertical-scrollbars/
This has to be a fairly common issue. Are there other tricks or suggestions?

KISS method : force the vertical scrollbar to be there.
Forgot to mention, you can also simply use this :
html {overflow-y: scroll;}

You could force the scrollbar to always appear in CSS, or you could set your jQuery code to execute when the page has fully loaded instead of when the DOM is ready, example below:
$(window).bind("load", function() {
// code here
});
This may result in 'jerkiness' as content gets rendered, then is shuffled around as the script configures it for the viewport size.
Personally, I'd just force the scrollbar. Most visitors wouldn't even notice it was there all the time anyway.

Related

In Chrome, SVG elements constructed too far off-screen do not render when brought into view

I am creating a web-site that drives content for a large 2D area off a CMS, so I have a system that runs on a timer, examines the part of the area that is currently on-screen, and loads content that is close enough to the view area that it might soon come into view.
This all seems to work quite nicely, apart from one small glitch.
Some of my content is SVG elements created procedurally via JS (the load mechanism feeds data from the CMS into JS functions, which create the using document.createElementNS and insert it into a div in the correct absolute position).
This content appears fine if is on-screen at the time it is loaded (this happens when the page is initially loaded).
And it also appears if it loaded while an animation is moving the visible area (animation is used to follow paths across the 2D space).
HOWEVER, if I am manually moving the visible area (which I have implemented via click+drag) then the SVG elements are added to the document tree, but when they come into view they do not render.
If I do something to "nudge" the renderer, such as hiding an unrelated element via DevTools, or resizing the window slightly, then they appear.
I am thinking this may be a bug in Chrome? e.g. where it has initially decided the elements need not be drawn and does not reprocess correctly when that needs reconsideration? Or maybe I am missing something, I am only semi-experienced in manipulating HTML documents via JS (but after a quick look I do not see the same behaviour in firefox...)
I am moving the visible area by changing the (left, top) of a parent element (I do not want to use scrolling for that as the size of the 2D space is not defined in advance...)
Otherwise, is there some way I could trick the browser into recalculating what should be drawn? I was wondering about having a small transparent element on screen that I show and hide on a timer, although a workaround that prevents the problem in the first place would be preferable...
Thanks for any advice!
Ian
p.s. I cannot instantly produce demo code for this as the code-base is moderately large, but I will spend time to make a simpler example if that proves necessary...

Executing JavaScript when a div content is changed (or t he div is resized)

This will be a long post, sorry for that, I'd just like to explain what I'm trying to do and why, and what I've tried so far.
I was asked to put a banner (legal disclaimer) on top of a page that stays on top no matter how the page is scrolled. After going down the rabbit hole of trying to make a <div> with position: sticky work, I gave up and went with position: fixed.
(Long story short, there's too much going on the page in terms of css styles that I don't have control over/don't have the resources to investigate and fix, that's causing the sticky to not work and the <div> to scroll with the rest of the page.)
position: fixed made the <div> stick to the top of the page, but it caused another problem: The div now overlaps the top of the scrollable page content. In other words, even when scrolled all the way to the top, a bit of the content is covered by the div. Important content. The top menu bar.
To get around this I put another empty div on the top of the page just to take up space. I effectively made the scrollable content of the page a bit taller. This works fine, as long as the user doesn't resize the window. If they do that, the disclaimer div changes height, but the padding div doesn't. So I turned to JavaScript:
$(window).on('resize', function () {
$("#disclaimer-padding").height($("#disclaimer-container").height());
});
I realize I'm well into the workaround/clumsy hack territory, but what was I supposed to do? The damn sticky thing wouldn't work. Anyway, this works, except for one last usecase:
If the user loads the page while not in fullscreen, or they have a smaller screen, or a device in portrait mode, the padding div height won't match the height of the disclaimer when the page loads.
I naturally tried putting the same code in $(window).on('load') or $(document).on('ready'). But the problem is that the content of the disclaimer, like many other elements on the page, is loaded by AngularJS (which grabs it from backend, which grabs it from the database or server-side cache) and both window.load and document.ready fire long before Angular is done loading and the final height of the div is known.
My next thought was "I'll listen for the context of the div to change, and trigger the resize then". So I used a MutationObserver.
const targetNode = document.getElementById('some-id');
const config = { attributes: true, childList: true, subtree: true };
let observer = new MutationObserver(callback);
function callback() {
$("#disclaimer-padding").height($("#disclaimer-container").height());
}
observer.observe(targetNode , config);
I tried hooking this up in window.load and in document.ready. It didn't work, which really surprised me, because when I tried it in a minimal working test page, it worked like a charm. But alas, in my project, the callback function just never got called. I have no idea why.
In the end I implemented a truly ugly solution (which I found somewhere here on StackOverflow). In window.load I call a function that periodically checks the contents of my diclaimer div. If the content has changed (until it's loaded it says something like #Application.DislaimerContent) then it will set the height, otherwise it will schedule itself to run again in 200 miliseconds using setTimeout().
I don't like this solution, but I was running out of time and this is the only thing that worked. But I feel like if my supervisor ever sees this, he'll give me an earful and make me fix it.
So the question is, how do I fix it? How do I make the code register when the div has loaded, and resize the other div accordingly?

HTML Page render - Correction done by Javascript resize feels a bit jarring

I have a landing page that has a header, a sticky footer and a main area with a Video and placeholder image. Using CSS, I do some calculations to make the Video area of the size that is available, but it doesn't work for all screen sizes, so I have to resort to resizing the elements in Javascript via the onresize event.
The problem is that I see the page render at first with element sizes that need correction, and then it gets corrected by javascript almost immediately ( < 0.5 seconds). I see this happening visually and am wondering if there is a way for me to delay the original render and simply see it when the elements have been correctly sized.
In the past I experimented with making the tag not visible to start and then making it visible in javascript. Is that the best approach to achieve what I am trying ?
You can achieve this by hiding the content using visibility: hidden in CSS and changing visibility to visibility: visible once the JavaScript processing is done.

What actions and events cause a browser to repaint its entire viewport?

I'm trying to implement the wmd-editor from the google code repository (like the one used on stackoverflow right here) and I'm running into an issue.
As you type into the textarea, it kicks off two paint operations in the browser. One to repaint the textarea itself, and one to repaint the preview panel. You can watch this happening on stackoverflow by opening the chrome inspector and using the timeline tab while typing some text into a question field.
But on my page, the browser repaints the entire viewport when it has to do these paint operations. And that takes much longer... about 100ms for each paint operation on my page versus about 1ms on stackoverflow.
In my testing this seems to be css related... I can recreate this behavior in the wmd-new example page by stripping all styles.
My page isn't public yet, but hopefully I can ask in a generic way... what will cause the browser to repaint the entire viewport on a dom change instead of just repainting that portion of the dom?
A view of what I'm talking about here.
AHA! Ah-effing-ha! (forgive the enthusiasm)
The issue is that I was using the box-shadow css property to frame my page. It takes longer to reflow/repaint content when the browser needs to calculate that shadow on each change (~100ms vs ~1ms). And when using wmd-editor, you're updating the dom on each keypress, so that difference adds up. And the effect is most exaggerated when the browser is maximized, as it recalculates the entire viewport.
So maybe that's one of the reasons stackoverflow doesn't have any frames or shadows on the page... just clean edges.
You can see what I mean at this example page. Open it up in firefox, maximized the page, and start typing away. Now use firebug to remove the box-shadow property on the body element, close firebug back up and try again. Big difference.
Thanks to Balpha for his comment, which was spot on.
Check this presentation, around slide 70 and the next ones. They explain a bit what can cause reflow and repaint.
http://www.slideshare.net/nzakas/high-performance-javascript-webdirections-usa-2010
Without the specific code / CSS is hard to answer but I can say something general like, if the fragment DOM that was changed influences other elements in the page :)
Also note that in stackoverflow WMD, when you enter a newline it also causes a whole viewport repaint. So maybe it has something to do with your WYSIWYG area not having width and height well defined? I'm guessing that if you give them width and height they won't affect other elements in the page

innerHTML manipulation in JavaScript

I am developing a web page code, which fetches dynamically the content from the server and then places this content to container nodes using something like
container.innerHTML = content;
Sometimes I have to overwrite some previous content in this node. This works fine, until it happens that previous content occupied more vertical space then a new one would occupy AND a user scrolled the page down -- scrolled more than new content would allow, provided its height.
In this case the page redraws incorrectly -- some artifacts of the old content remain. It works fine, and it is even possible to get rid of artifacts, by minimizing and restoring the browser (or force the window to be redrawn in an other way), however this does not seem very convenient.
I am testing this only under Safari (this is a iPhone-optimized website).
Does anybody have the idea how to deal with this?
The easiest solution that I have found would be to place an anchor tag <a> at the top of the div you are editing:
<a name="ajax-div"></a>
Then when you change the content of the div, you can do this to have the browser jump to your anchor tag:
location.hash = 'ajax-div';
Use this to make sure the user isn't scrolled down too far when you update the content and you shouldn't get the issue in the first place.
(tested in the latest FF beta and latest safari)
It sounds like the webkit rendering engine of Safari is not at first recognizing the content change, at least not fully enough to remove the previous html content. Minimizing and then restoring the windows initiates a redraw event in the browser's rendering engine.
I think I would explore 2 avenues: first could I use an iframe instead of the current 'content' node? Browsers expect IFrames to change, however as you're seeing they're not always so good at changing content of DIV or other elements.
Secondly, perhaps by modifying the scroll position as suggested earlier. You could simply move the scroll back to 0 as suggested or if that is to obtrusive you could try to restore the scroll after the content change. Subtract the height of the old content node from the current scroll position (reseting the browser's scroll to the content node's 0), change the node content, then add the new node's height to the scroll position.
Palehorse is right though (I can't vote his answer up at the moment - no points) an abstraction library like jQuery, Dojo, or even Prototype can often help with these matters. Especially if you see your page / site moving beyond simple DOM manipulation you'll find the tools and enhancements provided by libraries to be a huge help.
It sounds like you are having a problem with the browser itself. Does this problem only occur in one browser?
One thing you might try is using a lightweight library like jQuery. It handles browser differences fairly nicely. To set the inner HTML for a div with the ID of container you would simply write this:
$('#container').html( content );
That will work in most browsers. I do not know if it will fix your problem specifically or not but it may be worth a try.
Would it work to set the scroll position back to the top (element.scrollTop = 0; element.scrollLeft = 0; by heart) before replacing the content?
Set the element's CSS height to 'auto' every time you update innerHTML.
I would try doing container.innerHTML = ''; container.innerHTML = content;

Categories