Why is setting textContent triggering reflow? - performance

I have a simple countUp/Down utility, which utilizes requestAnimationFrame to set the textContent of a specific DOM. Surprisingly every time I make the content update, the browser triggers a reflow. Is there any way to avoid this?
There is a simple demo on requestAnimationFrame found in CodePen, using the performance tool of Chrome dev tool, you can see each callback call is followed by reflow (Layout)
To my knowledge, setting textContent shouldn't cause style recalculation. I have also tried giving the element fixed dimension, but that did not help.

Unlike some methods, it doesn't trigger a reflow synchronously, but when the next painting will occur, the browser will still have to recalculate the layout since the textContent change could have changed the page's layout.
The only way to avoid a reflow at all I can think of, would be to render this text in a <canvas>.

Related

Accessible AJAX page transitions for SPA

I have been tasked with building a SPA that has some complex page transitions.
My biggest concern is that these transitions rely on elements expanding on the page to then become the full page (i.e. you click a blue button, the blue background expands to fill the whole page and then the content is displayed.)
Now this is where I am struggling to come up with an accessible solution. I thought about making the text within the original 'button' a link with padding (as the URL will update and the link is for no JavaScript fallback) and then replacing the content of the parent div (the blue background) with the new content.
The problem I have is that I am not sure what would be the best practice for accessibility from the perspective of letting a screen reader user know that a new page has loaded in. Using aria-live is a terrible idea, but with NVDA if I just replace the contents of the div it can have some strange behaviour.
Has anyone ever come across this before? For example this dribble nearly shows what I mean, you click an element and then it opens up into a new page with the content within the element.
For a 'normal' AJAX site when navigating I would simply replace the whole <main> with the new content, make the <h1> programatically focused with a tabindex="-1", update the page <title> and that works fine, but with this type of navigation I am wondering if the same approach is applicable.
I am thinking replacing everything within the <main> element except for the selected 'button' background (including the original link being removed once the new page has loaded) and then loading the content in and managing the focus as described would work, but I am not sure if there is an accepted pattern for this type of navigation as it is so unusual.
I am thinking replacing everything within the element except
for the selected 'button' background (including the original link
being removed once the new page has loaded) and then loading the
content in and managing the focus as described would work, but I am
not sure if there is an accepted pattern for this type of navigation
as it is so unusual
I think this is your best best. The whole visual expanding thing is just eye candy, so as long as it is there you should be able to do this behind the scenes. Make sure you test with screen readers.

SlickGrid vertical scroll bar not displaying properly unless the filter is displayed/hidden to re-size the viewport

I'm just starting to use SlickGrid but I've had a lot of success creating a remote model that can interact with the paging plugin. The only problem I have seems to be a browser issue, but I'm curious if anyone knows a trick that may resolve the issue.
I'm retrieving the data page by page in my model via AJAX and updating the grid with the grid.setData() and grid.render() methods.
The problem I would like to solve is that when the length of data changes the vertical scroll bar gets really confused, unless I change the current visibility of the filter panel (grid.hideTopPanel() or grid.showTopPanel()). By confused I mean that the scroll handle in the scroll bar doesn't change size to indicate the different number of rows that are now current, and if the list grows, the scroll handle size jumps all over the place as I scroll up and down.
I'm assuming the "fix" works because it forces the browser to recalculate the view port height, but is there any way to force that to happen? Or is there a better way to do what I'm trying to do?
Ok mleibman straightened me out, what I actually was looking for was grid.updateRowCount(). I believe he will be adding a call to this in grid.setData(), but for now just make sure the 2 method calls are used together.
Have you tried grid.invalidate() ? Im my experience that forces the grid to recalculate pretty much everything. It may be more heavy-handed than you need but at least you can see if a complete invalidate solves the problem.
I figured it out. You need to call grid.resizeCanvas() after calling grid.setData(). It seems like this should be part of the grid.setData() method because a call to it will change the number of rows. But anyway, that's what you need to do.

Force UI repaint in Webkit (Safari & Chrome) right before Synchronous "Ajax" request

In a simple Ajax based website we are making some HttpRequests requests synchronously (I realize "synchronous Ajax" is somewhat of an oxymoron). The primary reason this is being done synchronously vs. asynchronously is in order to simplify the programming model for some of those involved (long story).
Anyway, we want to be able to make a styling change (specifically, overlay the screen with semi transparent white as Google search does) right before the request is made, and then take it away when results come back. Essentially this looks like:
load:function(url) {
....
busyMask.className="Shown"; //display=block; absolute positioned full screen semi-transparent
var dta=$.ajax({type:"GET",dataType:"json",url:url,async: false}).responseText;
busyMask.className="Hidden"; //sets display=none;
...
return JSON.parse(dta);
}
It is well known a synchronous requests will lock the UI. So not surprisingly, the white overlay never shows up in Safari and Chrome (it does in Firefox interestingly). I've tried slowing the response way down and using a pink overlay so that it will be painfully obvious, but it just won't update the UI until after the request is complete. Leaving the 'busyMask.className="Hidden"' part out will show the mask-- but only after the ajax request is complete.
I've seen many many tricks for forcing the UI to repaint (e.g. Why HourGlass not working with synchronous AJAX request in Google Chrome?, http://ajaxian.com/archives/forcing-a-ui-redraw-from-javascript), but they all seem to be in conjunction with trying to show actual "permanent" DOM or styling updates, not with temporarily showing a style change while a synchronous request is made.
So is there a way to do this or am I fighting a losing battle? It may be that we'll just need to switch to asynchronous requests on a case by case basis for the worst performing requests, which might be a decent way to tackle the learning curve issue... But I'm hoping there is an outside the box answer here.
Ok for the purpose of this question I will ignore the justification for why you require synchronous XHR requests. I understand that sometimes work constraints don't allow the use of the best practice solution and so we "make do" in order to get the job done. So lets focus on how to get synchronous ajax with visual updated working for you!
Well considering you are using jQuery for your XHR request, I'm going to assume its ok to use jQuery to show/hide the loading indicator and to handle any timing issues.
First let's set up a loading indicator in our markup:
<div id="loading" style="display:none;">Loading...</div>
Now lets create some javascript:
// Show the loading indicator, then start a SYNCRONOUS ajax request
$('#loading').show().delay(100).queue(function() {
var jqxhr = $.ajax({type:"GET",dataType:"json",url:"www.yoururl.com/ajaxHandler",async: false}).done(function(){
//Do your success handling here
}).fail(function() {
//Do your error handling here
}).always(function() {
//This happens regardless of success/failure
$('#loading').hide();
});
$(this).dequeue();
});
First, we want to show our loading indicator and then give the browser a moment delay to repaint before our syncronous XHR request gets started. By using jQuery's .queue() method we are putting our .ajax() call in the default fx queue so that it won't execute until after the .delay() completes, which of course doesn't happen until after the .show() completes.
The jQuery .show() method changes the target element's CSS display style to block (or restores its initial value if assigned). This change in CSS will cause the browser to reflow (aka "redraw") as soon as it is able. The delay ensures that it will be able to reflow before the ajax call. The delay is not necessary in all browsers, but won't hurt any more than the number of milliseconds you specify (as usual, IE will be the limiting factor here, the other browsers are happy with a 1ms delay, IE wanted something a little more significant to repaint).
Here's a jsfiddle for you to test in a few browsers: jsfiddle example
Why do you think:
doSomethingBeforeRequest();
response = synchronousAjax();
doSomethingToTheDataAfterRequest(response);
that much "simpler" than:
doSomethingBeforeRequest();
properAjax(onSuccess(response){
doSomethingToTheDataAfterRequest(response);
};
for your team? I'm not trying to argue, but I'm seriously curious of the justification...
The only benefit of the synchronous code i can think of is that you save a few curly braces; at the cost of freezing the browser.
If the browser doesn't complete the repaint before the request*, the only option I can think of is using a delay (as BenSwayne suggests); which would make the code as complex as the async call, and still make the browser unresponsive during the request.
EDIT (some kind of an answer):
Since JavaScript lacks threads; timeouts and ajax calls (that allows the browser to do something else before it's run; somewhat like sleep() in a threaded language is used ), is fairly fundamental to how you program JavaScript. I know it can be a bit of a learning curve at first (I know I was confused), but there is not really any sensible way to avoid learning it.
One situation I know people may be tempted to make synchronous calls is when several requests have to be made to the server in sequence; but you can do that asynchronous too, by nesting several calls like this:
doSomethingBeforeRequest1();
ajax(onSuccess(response1){
doSomethingToTheDataAfterRequest1(response1);
ajax(onSuccess(response2){
doSomethingToTheDataAfterRequest2(response2);
};
};
But unless each call is fairly slow to finish and you want to indicate progress at each step or something; I would rather recommend that you create a new service to combine the two operations with one call. (This service could just use the two existing services in sequence, if you still need them separately in some other cases).
(* I'm more surprised that Firefox DOES update the dom...)
I've maded some tests and came with some points:
http://jsfiddle.net/xVHWs/1/
Change your code to use jQuery's hide(), show() or animate({ opacity: 'show' }, 'fast') and animate({ opacity: 'hide' }, 'fast')
If you leave de functions without a time param or specify a 0 ms time, Firefox will show the overlay and hides it, the other browsers execute it to fast for you to see. Put a 100 millisecond in show, hide, or animate calls and you will see it.
$.ajaxSetup({async:false});
busyMask.className="Shown"; //display=block; absolute positioned full screen semi-transparent
var dta=$.ajax({type:"GET",dataType:"json",url:url}).responseText;
busyMask.className="Hidden"; //sets display=none;
$.ajaxSetup({async:true});

jScrollPane in Firefox - Dragger fills entire bar and doesn't scroll

My jScrollPane (division) works well in IE and Chrome. It may be of some significance that I am also running JQuery MouseScroll and hoverintent. Again, these functions work fine in IE and Chrome.
The division is displayed properly in Firefox, as is the vertical scrollbar. There's about 50 lines to scroll down through.
In Firefox, the dragger fills the entire length of the vertical bar and doesn't move. The dragger and the arrows do respond to mouseover, but the functions don't work.
Perhaps the scrolling action is functional, just that there is no space to scroll, because the dragger fills the entire region. I am unsure whether my jScrollPane works or doesn't in Opera or Safari.
I checked Google and found little directly associated advice, just to:
"Ensure that the division has a height specified (it does) and to try refreshing (no luck) in case the content (only text) needs to pre-load."
I wonder whether anyone can suggest any checks for me to make from their previous experience before I post any code to wade through.
This is only the second question tagged with jScrollPane & Firefox, so perhaps no-one will be familiar with this 'bug'. In which case I will create and post some reduced code which generates this bug in Firefox but not IE or Chrome.
Update: I created a 'test' scrollbar webpage to see if the bug would be replicated, and that works fine - so no need to post that for error-checking.
The problem must lie somewhere within my code on the site I'm working on.
So far I have identified that all the JavaScripts work and my custom jScrollPane CSS is fine. So it's just a process of elimination through each of the stylesheets now. I have a feeling the source of the bug may be in my custom reset CSS.
I'm confident I'll fix the bug, and will let you folks know either way.
The bug was in the CSS reset as suspected. Specifically, with the rule for column-count.
I had defined them (moz-/wekbit-/column-count) as '1' instead of the defining as 'auto' / not defining it at all.
This was from when I was experimenting with (CSS3) multi-column text. Presumably jScrollPane requires multiple columns. Not enough support yet for multi-column text to be worth implementing yet IMO.

Getting Windows Phone 7 WebBrowser Component VerticalOffset

I want to persist the user's location in the document he or she is browsing, then bring them back to that spot when they return from tombstoning or between sessions.
My first approach was to wrap the browser component in a scrollviewer, but it turns out it handles its own scrolling and the scrollviewer never changes its verticaloffset.
My guess is that the browser component must have a scrollviewer or something like it embedded in it. I need to get the verticaloffset and scroll to an offset.
Any guesses how to get there?
My next approach would be a painful mish-mash of javascript and c# to figure out where they are...
Because of the way the WebBrowser control is built you'll need to track scrolling in Javascript then pass the location to managed code to handle storage of that value.
On resuming you'll need to have the managed code pass the scroll position to a Javascript function to reset the scroll position.
That's the theory but I haven't looked at the funcitonality around javascript scrolling events in the WebBrowser yet. That's the only place I can see possible problems.
Would be good to hear how you get on.
I've accepted Matt's answer, but I want to put in some details here. I'm also going to blog about how I did it once I'm completely done.
Since the WebBrowser component is essentially a black-box, you don't have as much control as I would like. Having said that, it is possible to get and set the vertical offset.
Javascript lets you ask for the value, but different browsers use different variations on HOW to ask. For THIS case I only have one browser to worry about.
First I make a couple of simple javascript functions:
function getVerticalOffset() {
return document.body.scrollTop;
}
function setVerticalOffset(offset) {
document.body.scrollTop = offset;
}
Next I call into the WebBrowser using the InvokeScript method on the browser object.
I'll post an update here with a link to my blog when I get the full write-up done.
I have been writing an eBook reader and had a similar question. Code for setting a scroll position has been easy enough to find.
Code for setting vertical scroll position:
string script = string.Format("window.scrollBy(0,{0});", "put your numeric value here");
wb_view.InvokeScript("eval", script);
Google didn't help much in finding solution for getting the value of current scroll position. Lacking any knowledge in javascript it took me almost two hours to get it right.
Code for getting the vertical scroll position:
var vScroll = wb_view.InvokeScript("eval",
"var vscroll = window.pageYOffset; vscroll.toString();");

Resources