I've got some Javascript that scrolls the page when the user drags an element near the edge of the window. There's a function something like this (simplified):
var scroll = function() {
var scrollTop = $myElement.scrollTop();
$myElement.scrollTop(scrollTop += delta);
setTimeout(scroll, 25);
}
I'm running into performance issues on older browsers, and I can somewhat mitigate them by reducing the resolution of my scroll() function from 25 (as seen above) to 100 or so.
How can I check if a browser is slower and reduce the resolution?
I would prefer to avoid user agent sniffing.
a human does not notice "slowness" up until 200-300ms, bump up your timer. no one is going to see that lag within reasonable bounds.
also, pushing the timer to be that fast IS the cause of the problem also. old broswer JS aren't that fast. try opening a task manager and see a spike on your CPU while you drag caused by that very fast timer.
Related
I have a legacy webapp which has super complicated resize event bound. It supports desktop-like windows within the page (I know...) and they all scale on resize.
This actually performs fine except in one case: when you click on a PDF link on this app and it downloads to the browser, then opens the PDF in a new tab instead of system viewer (if that is the user's browser setting). Then when closing the tab and going back to the app's tab, it spends an inordinate amount of time on resize logic (normal resize logic timing from click dragging window corner is ~1-3s, this is like ~15-20s)
I think multiple resizes may be triggered simultaneously when the downloads bar pops into the page and the new tab opens. There is already a very large (1500ms) debouncer in place but maybe something about opening a new tab / the download bar resizing instead of click-drag resizing makes the debouncer not apply in this case) and causes this huge slowdown when closing the PDF and going back to the app tab.
we cannot just tell users to have their PDFs open in system viewer, and I need some guidance on how to even start trying to fix this issue. (or help on the path I'm trying).
The path I'm trying is: i want to detect a resize that is from the downloads bar as opposed to for other reasons (maybe the y delta being exactly the same as downloads bar height?? is there a way to do that?) and just return false, we dont care much if that 50-100px is not adjusted for and when they close it, it will resize normally anyway.
This is the code I've used to deal with being flooded with events in the past
import raf from 'raf'
let rafId = null
function onResize() {
if(!rafId) {
rafId = raf(() => {
your_resize_function()
rafId = null
})
}
}
window.addEventListener('resize', onResize)
Its using requestAnimationFrame to delay the kickoff and a simple flag to stop it being called more than once. I find this much better than using debounce() as it adapts to how busy the call stack is.
I'd start by adding a "busy" flag in your procedure, set to True at the start, and False when exiting. This way, at least you can catch recurring events (if true return).
In some quick tests, a simple canvas in a div with auto-resize scales when the browser (tested Chrome and Firefox, Explorer always an outlier) shows the bar. Initial scale - new scale) * original width = pixels different.
How are you detecting the size of the window when the events occur?
I had a similar issue, and discovered a better way to detect the window's size:
let windowHeight = window.document.documentElement.clientHeight;
let windowWidth = window.document.documentElement.clientWidth;
Detecting the size in this exact manner took things into consideration that other ways didn't. It may solve your issue with that download bar.
I've implemented a scheme allowing visitors on my website to tap small photos to see a larger view. The tap/click causes a new page element to appear and expand to the max dimensions available for the screen, displaying a higher resolution image there. The reason is to avoid loading high bandwidth images unless the visitor is interested. Once the better hi-resolution image is displayed, the visitor can further expand the image with finger pinch gestures, as its typical for phones. The problem is, if the visitor does manually enlarge the photo, they will be left with an annoying oversized page after dismissing the larger image. And no matter what I do, the visitor's manual adjustment of the image affects the whole page.
For a long time there was a reasonable solution to this, which I found years ago somewhere here on stack exchange. I'd set up a function like this...
function resetScreenSize() {
var viewport = document.querySelector('meta[name="viewport"]');
if(viewport===null){
// just for test alert("no viewport meta");
return;
}
var original = viewport.getAttribute('content');
var forceScale = original+",maximum-scale=1.0";
viewport.setAttribute('content', forceScale);
setTimeout(function() {
viewport.setAttribute("content", original);
}, 100);
}
The idea was to un-do whatever manual zooming the visitor did by adding the "maximum-scale" value of 1 to the viewport, wait a moment for system to settle, and then return the viewport to its original settings (without a maximum scale). After testing this approach with a simple button, I just set up my code to call the function automatically when the visitor dismissed the zoomed image.
Well it seems that Apple, in their infinite wisdom, has decided that IOS devices will no longer honor viewport "maximum-scale", as well as several other options, like disallowing user scaling. It seems to be blocked on other browsers too like Chrome on IOS. So as a result my scheme no longer works. If a visitor picks a full size image and then expands it further, I have no way to set the viewport back to normal, without doing something drastic like re-loading the page.
I've tried a few other approaches I've found on stackexchange, most involving attempts to block zooming to begin with. That's not what I want.
So is there another solution I could consider? I know Social media giants like Facebook have a way of letting a visitor click an image to bring up a larger view, and no matter how the visitor enlarges it manually, things go back to normal once the photo is dismissed. But I don't know how that do it.
I ran in a similar problem a while back and fixed it in a similar way.
One important trick here was the wait with a 100ms. Using 0 would get the code executed immediately and not actually apply anything.
Now, if Apple changed the rules on these viewport parameters (probably because it got abused by some), then one solution I can think of is to use an IFRAME. I think that Facebook uses that technique whenever they open a "popup" with an image in it (and comments on the side). That makes it really easy to close that window and get back to the previous view. Also the viewport scaling factor will be changed in the IFRAME and not the window in the back. So you should get exactly what's necessary.
Ever since FF 52 was introduced, I am having the same lagging issues when executing on scroll events, especially when using the mouse wheel - the same thing occurs on IE Edge as well, but it was considered a minority, now with the addition of FF some kind of a solution has to be found.
I have created a fiddle online that replicates the problem - the issue is visible on FF and IE edge, while it works smoothly across webkit browsers.
Here is the simple scroll function I am using:
$('.scrollable').on('scroll', function() {
scrollY = $(this).scrollTop();
$(this).find('td:first-child span').attr('style', 'transform:translateY(' + -scrollY + 'px)');
})
https://jsfiddle.net/nfmLa7mn/3/
If you scroll with the mouse wheel the issue is more visible. It's a small lag but it's there. In more complex layouts the issue is more pronounced.
Am I the only one bothered by this? I haven't seen any other similar topics online. Is there any way I am not aware of that can fire scroll events in a better way? Or is there any other way around this issue?
Maybe this is due to the fact that Firefox handles scroll asynchronously.
https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Scroll-linked_effects
Often scrolling effects are implemented by listening for the scroll event and then updating elements on the page in some way (usually the CSS position or transform property.) [...]
These effects work well in browsers where the scrolling is done synchronously on the browser's main thread. However, most browsers now support some sort of asynchronous scrolling in order to provide a consistent 60 frames per second experience to the user. In the asynchronous scrolling model, the visual scroll position is updated in the compositor thread and is visible to the user before the scroll event is updated in the DOM and fired on the main thread. This means that the effects implemented will lag a little bit behind what the user sees the scroll position to be. This can cause the effect to be laggy, janky, or jittery — in short, something we want to avoid.
I worded my question wrong before, my intention wasn't to change the resolution of the user's browser settings. I'm mainly wanting to layout my page for the smallest common resolution size and have it zoom in to fit the resolution if the user's resolution has a higher setting. Like if the user's resolution is 800x600, my website will look just like I designed it, but if the user's resolution is 1280x800, my website will zoom in to fit that resolution without changing the layout of my website, which would make the font and size of everything bigger while keeping the same look and layout of the website. If there is a way, how can I? Thanks in advance :)
No there is not. This would be very annoying for the client. You have to have to change your site to fit the users resolution. As the comments suggest, make it responsive. If you search for responsive web design you'll find a lot of articles on ways to do it. We are sometimes limited in javascript because some things would be a security issue (this one would be more of an annoyance).
Just think about it - while browsing the web - if every website you
visited changed your monitor's resolution!
So to sum up, the reason that so many make their site fit the users resolution is because you can't change the user's resolution.
You can't do that, and you shouldn't.
You'll have to make your web site accommodate the user's current browser window size as best you can. You have no control over the browser window size or screen resolution and even if you did, it would be unprofessional and impolite to change these things without such a change being understood by and initiated by the user.
The browser is not the computer. It is just one application among many. You don't know what the user is doing with his screen--what if he is playing a video in one corner and wants his browser window exactly where it is?
What if he is visually impaired and has his browser zoomed in like crazy? I know someone who wears thick glasses, uses the on-screen screen zoom accessibility box, and still leans in to about 6 inches from the screen to be able to see anything. You would not be doing him a favor to change anything about how his browser and screen are laid out.
Any website that somehow changed how my browser to fit the screen, altered my browser's resolution, or changed my monitor's resolution would:
Be instantly added to my "forever hate list" and never visited again.
Be worth telling all my technical friends about it to get them to laugh
Be submitted to thedailywtf.com for future folks to marvel at
Be in my next blog post about software not being arrogantly evil and not acting like it owns the user's computer, a subject I have never blogged about before but would be utterly compelled to do so in this case
Receive a polite but pointed email that such meddling with my computer without permission is unprofessional and rude, and furthermore that the offending decision-maker who approved it should be soundly whipped.
There are video playing plugins that can maximize to full screen at user request and user request only. Doing such a thing requires a java applet.
As others have stated in the previous answers changing the screen resolution may be a bad idea (if not simply impossible), but you can change the scale of the contents so it exactly fits your window.
I wrote the piece of code below for a web application I designed for devices with exactly a 1280x800 resolution. In the end I decided to also occasionally use it on a 1080p screen, so some scaling was required to get it to fit perfectly. This is what my code below does.
I am using the fullscreen API (this wrapper: http://sindresorhus.com/screenfull.js/) to make the browser go full screen via a button and this same button triggers the auto scaling at the same time. You can find a working example here: http://jsfiddle.net/QT5Nr/5/
Beware this piece of code currently requires jQuery 1.8.3 and screenfull.js, but can easily be changed to work without them. I left my comment on top intact to explain why I override the jQuery offset function. You can also test why I did that by commenting that piece of code out in the jsFiddle, then resizing the frame.
/*****************************************************************************************************************************************\
|** Scale elements to fit page **|
|*****************************************************************************************************************************************|
|** Because we created a static size layout for our target device other devices will not have a 100% sized layout. To fix this we **|
|** change the scale of the body element so that it's contents fit exactly within the available window. **|
|** In addition we have to override jQuery.fn.offset in order to fix problems where the offset returned by this function is exact to **|
|** the current location of the element (with it's scale), while it has to be the exact location of the unscaled element. **|
\*****************************************************************************************************************************************/
function EnableAutoScale() {
function AdjustScale() {
var windowWidth = $(window).width();
var windowHeight = $(window).height();
var viewportWidth = $('#viewport').width();
var viewportHeight = $('#viewport').height();
var horizontalScale = windowWidth / viewportWidth;
var verticalScale = windowHeight / viewportHeight;
$(document.body).css('transform-origin', '0 0');
$(document.body).css('transform', 'scale(' + horizontalScale + ', ' + verticalScale + ')');
document.body.HorizontalScale = horizontalScale;
document.body.VerticalScale = verticalScale;
}
$(AdjustScale);
$(window).resize(AdjustScale);
// Override offset so it continues working with the scale set above
jQuery.fn.originalOffset = jQuery.fn.offset;
jQuery.fn.offset = function () {
var offset = jQuery.fn.originalOffset.apply(this, arguments);
offset.left = offset.left / document.body.HorizontalScale;
offset.top = offset.top / document.body.VerticalScale;
return offset;
};
}
$(function () {
$('#fullscreen').click(function () {
$('#fullscreen').hide();
EnableAutoScale();
if (screenfull.enabled)
screenfull.toggle();
});
});
As an experiment, I am trying to replicate the Sprite functionality of AS3 in JavaScript without using the canvas object. I thought that using absolutely positioned divs and manipulating their css properties would be a no brainer, however in Chrome the animation introduces strange artifacts (seemingly because of redraw issues).
I can not find what I am doing wrong? The code is, in fact, quite simple. Here are some points that I tried which didn't help:
Using relatively positioned divs (as opposed to absolutely positioned.)
Using margins (as opposed to top & left properties.)
Appending objects directly to body (as opposed to appending to a container div.)
Using setTimeout (as opposed to requestAnimationFrame)
You can see a simplified fiddle here: http://jsfiddle.net/BVJYJ/2/
EDIT: http://jsfiddle.net/BVJYJ/4/
And here you can see the artifacts on my browser:
This may be a bug in my setup (Windows 7 64 bit, Chrome 21.0.1180.75). No other browsers exhibit this behaviour. I'd greatly appreciate if someone could comment on what I could be doing wrong. I'm more curious about the reason behind this rather than a workaround btw. That said, every explanation is welcome. :)
EDIT: There was a bug in the sample code which resulted in using setTimeout even when I was under the impression that RAF was used. requestAnimationFrame solves the issue with basic transformation but the issue remains with CSS transformations such as rotation.
I had the same problem with my liteAccordion plugin. It can be fixed by setting the backface visibility to hidden on the element you're animating, as you can see here: http://jsfiddle.net/ZPQBp/1/
Some research shows that setTimeout could cause issues due to various reasons. You really should use requestAnimationFrame:
Timers are not accurate to the millisecond. Here are some common timer
resolutions1:
Internet Explorer 8 and earlier have a timer resolution of 15.625ms
Internet Explorer 9 and later have a timer resolution of 4ms. Firefox
and Safari have a timer resolution of ~10ms.
Chrome has a timer resolution of 4ms.
Internet Explorer prior to version 9 has a timer resolution of 15.625
ms1, so any value between 0 and 15 could be either 0 or 15 but
nothing else. Internet Explorer 9 improved timer resolution to 4 ms,
but that’s still not very specific when it comes to animations.
Chrome’s timer resolution is 4ms while Firefox and Safari’s is 10ms.
So even if you set your interval for optimum display, you’re still
only getting close to the timing you want.
Reference: http://www.nczonline.net/blog/2011/05/03/better-javascript-animations-with-requestanimationframe/
Also
setTimeout doesn’t take into account what else is happening in the
browser. The page could be hidden behind a tab, hogging your CPU when
it doesn’t need to, or the animation itself could have been scrolled
off the page making the update call again unnecessary. Chrome does
throttle setInterval and setTimeout to 1fps in hidden tabs, but this
isn’t to be relied upon for all browsers.
Secondly, setTimeout only updates the screen when it wants to, not
when the computer is able to. That means your poor browser has to
juggle redrawing the animation whilst redrawing the whole screen, and
if your animation frame rate is not in synchronised with the redrawing
of your screen, it could take up more processing power. That means
higher CPU usage and your computer’s fan kicking in, or draining the
battery on your mobile device. Nicolas Zakas does an excellent job
explaining the impact timer resolution has on animation in a related
article.
Reference: http://creativejs.com/resources/requestanimationframe/
It has something to do with subpixel positioning. If you round off to the nearest pixel you won't see those rendering errors:
thisRef.block.style.left = Math.round((x + (mouseX - ox - x) * .125)) + "px";
thisRef.block.style.top = Math.round((y + (mouseY - oy - y) * .125)) + "px";