I'm trying to set up some code to first test if the Vanilla JavaScript .animate() API is Supported, then if it's not check if requestAnimationFrame is supported and if it's not fallback to either setTimeout, setInterval or setImmediate.
I've been reading this e-book on google Smashing Webkit, which says that its always best practice to check for feature support before actually implementing it, so I'm trying to move all my App Animations inside of the checks below, and then implement fallbacks for backwards compatibility and older browsers.
I'm not 100% sure if this has any security concerns as I'm not a web security expert nor am I an expert coder by any standards. I'm still trying to learn JS and am wondering if this code block could be made better, more secure or to run more optimally i.e. removing unnecessary if statements or reworking the code so it is less verbose.
document.addEventListener("DOMContentLoaded",(()=> {
// ::>>. Notes:: ......................
// ::>>. A Handy Function to get the Browser Prefix ~
// Gets the browser prefix
var brsrPrefix;
navigator.sayswho= (function(){
var N = navigator.appName, ua = navigator.userAgent, tem;
var M = ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
if(M && (tem = ua.match(/version\/([\.\d]+)/i))!= null) M[2] = tem[1];
M = M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
M = M[0];
if(M == "Chrome") { brsrPrefix = "webkit"; }
if(M == "Firefox") { brsrPrefix = "moz"; }
if(M == "Safari") { brsrPrefix = "webkit"; }
if(M == "MSIE") { brsrPrefix = "ms"; }
})();
// ::>>. A Handy Function to get the Browser Prefix ~
try{
if(window.animate){
console.log('.animate() API is Supported')
// My Current Animations will be in here.
}
if(window.requestAnimationFrame){
console.log('RequestAF is Supported')
// 1st fallback in here.
} if(!window.requestAnimationFrame) {
window.requestAnimationFrame = window.setImmediate
} else {
let requestAnimationFrame= window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
return window.setTimeout(callback, 1000/60)
};
console.log(requestAnimationFrame);
}
} // ::>>. Closing Bracket for First Try Catch.............
catch(err){
console.log(err)
} // ::>>. Closing Bracket for First Catch.............
// ::>>. RequestAnimation FramePolyFill
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz', 'ms'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
// ::>>. RequestAnimation FramePolyFill
}))
My concerns with the code is that I copied the first snippet from a blog post and I don't fully understand the RegEx that is used within it. I'm finding Regex very, very difficult to learn and is kinda confusing. Can anyone point out to me what tem and ua mean? I'm guessing ua is shorthand for user-agent.
Also, the code above is repeating itself in that its declaring var brsrPrefix & also var vendors = ['webkit', 'moz', 'ms']; Will look into trying to attempt to merge these two functions and compact the code as much as I can.
In my try catch if else statement it's checking to see if window.requestAnimationFrame is supported and then if it's not, its setting let requestAnimationFrame to be any of the browser specific versions. However, if I attempted to call or attach this let to an item it would end up being element.window.requestAnimationFrame which is the wrong syntax.
Also, Firefox Quantum supports the experimental .animate() API (not the jQuery version) but it is not console logging anything for this part, only logs that RAF is enabled.
Is this far too much code, just for doing a simple task such as checking browser support?
No errors in console so far. Can anyone help and review this and post meaningful comments so I can learn better and become a better developer?
ideally the code would test support for:
-> .animate()
-> .requestAnimationFrame()
-> .setInterval()
-> .setTimeout()
-> .setImmediate
in that Order, and Append Browser prefix Where necessary, but might have to research a whole lot more.
this post looks like it will be handy for reference Detecting_CSS_animation_support its has a similar implementation with Js, & Another Js Script on GitHub Similar Detect CSS Animation support and provide object of normalised properties
Here is a new question I asked with Similar Theme but A different Implementation to try and Achieve the same end goal, Using Switch statements instead:
Javascript Anime Support Switch Statement for .animate() Api
3 Answers 3
Firstly, before I start, I'd like to mention that you might not need these JS animation functions. A lot of animations can be achieved through CSS transitions and keyframes, using JS to toggle classes where needed. In my opinion, JS should only step in when the animation becomes too intensive for CSS to handle.
There appears to be a lot of duplication here. I'm going to simplify a lot of it for these reasons:
- requestAnimationFrame has great browser support and shouldn't need to be polyfilled. That said, I do still like consolidating the browser prefixed versions into one just to be on the safe side.
- Most functionality is standardised so you shouldn't need to know the browser prefix.
- Your support checks should exist separately to your animation code, meaning they can be reused for multiple animation blocks.
The support checks look like this (include at the top of your file):
// test for Web Animations API (.animate) support
// test from Modernizr.js
const WEB_ANIMATIONS_API_SUPPORTED = 'animate' in document.createElement('div');
// consolidate browser prefixed versions into one function
let requestAnimationFrame = (
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
return window.setTimeout(callback, 1000 / 60)
}
);
Then when you want to create an animation, you can do:
if ( WEB_ANIMATIONS_API_SUPPORTED ) {
// your .animate call
} else {
requestAnimationFrame(function() {
// your fallback function
});
}
I'll also try and answer your other questions as best as I can here.
its always best practice to check for feature support before actually implementing it,
Absolutely correct and it's a very good practice to get into.
and then implement fallbacks for backwards compatibility and older browsers.
Again this is the best way to handle using new functionality. It's called progressive enhancement - use the lowest level technology to build a base experience and then enhance if the newer functions are available.
I'm not 100% sure if this has any security concerns
I can reliably say there are no security concerns here. If you want to know more about security in JS start by reading around XSS vulnerabilities.
Regex can be difficult to learn and even experienced devs struggle with it. I like to use a visual interface like https://regexr.com/ to see the matches in real time. You can also hover over each regex character to see what it means and how it interacts with characters before and after it.
Yes, ua is shorthand for User Agent, and tem appears to be short for temporary. It's only used to quickly hold the matches from the Regex. Personally, I hate this style of variable naming and always go for longer, self-documenting ones where possible.
As you mention the browser prefix is repeating itself. This is likely because the requestAnimationFrame polyfill you're using is a packaged polyfill, and comes with it's own browser prefix checking. If you were doing a lot of tests, then it would make sense to abstract this out to a separate browser prefixing function.
However, if I attempted to call or attach this let to an item it would end up being element.window.requestAnimationFrame which is the wrong syntax.
I'm not sure what you mean here. If you can give me more info, I'll try and provide an answer.
-
\$\begingroup\$ Tyler Thanks for the Answer, I will read through it fully in a short while, I'm trying to use Js instead of Pure Css As Css does not generally match the browsers refresh rate as far as I know, & you don't get as much control or customization through pure css. \$\endgroup\$Ryan Stone– Ryan Stone2020年02月19日 01:38:35 +00:00Commented Feb 19, 2020 at 1:38
-
\$\begingroup\$ Good response, much appreciated. Ideally I would like to move 'All' my animations into there own worker thread, and just have the browser support checks in my main .js file. But I'm still a far way away from learning all of that. \$\endgroup\$Ryan Stone– Ryan Stone2020年02月19日 01:43:33 +00:00Commented Feb 19, 2020 at 1:43
-
\$\begingroup\$ Do i need to include Modernizr.js for that test to work? \$\endgroup\$Ryan Stone– Ryan Stone2020年02月19日 01:45:17 +00:00Commented Feb 19, 2020 at 1:45
-
\$\begingroup\$ also the reasoning behind trying to use .animate() is because this is the newer version and is More Performant than requestAnimationFrame which in turn is Better suited to animation than Pure CSS. I understand your point that for basic non cpu/gpu animations I can just stick with Css, but the ones I was attempting are very Cpu intensive. Sorry for calling you Tyler. lol \$\endgroup\$Ryan Stone– Ryan Stone2020年02月19日 02:10:19 +00:00Commented Feb 19, 2020 at 2:10
-
\$\begingroup\$ No you don't need to include Modernizr for that - I extracted the relevant parts, so that'll run as a one-liner \$\endgroup\$Adam Taylor– Adam Taylor2020年02月19日 10:42:17 +00:00Commented Feb 19, 2020 at 10:42
Initial thoughts
Looking at the MDN documentation for Element.animate() I see this warning at the top:
This is an experimental technology
Check the Browser compatibility table carefully before using this in production.
Looking at that compatibility table we see it isn't supported at all by a couple browsers...
I searched for "navigator.sayswho" online and found snippets like this gist that matches most of the first function, and I also see this gist for the requestAnimationFrame polyfill by Paul Irish. I read over the comments and noted the comment 7/21/2019 by jalbam claims to have an adaptation that has improved performance. I haven't tested it out but it may work slightly better than the original.
My conclusion is that you basically wrapped those two snippets in a DOM loaded callback (and perhaps modified the variable name browserPrefix to brsrPrefix.
"...am wondering if this code block could be made better, more secure or to run more optimally i.e. removing unnecessary
ifstatements or reworking the code so it is less verbose"
It seems that the first snippet is pointless because:
- nothing is returned from the IIFE that is used to assign
navigator.sayswhoand thus that isundefined, and brsrPrefixdoesn’t appear to be used and its scope is limited to the anonymous/lambda function/closure passed to the DOM loaded event handler.
document.addEventListener("DOMContentLoaded",(()=> {
// ::>>. Notes:: ......................
// ::>>. A Handy Function to get the Browser Prefix ~
// Gets the browser prefix
var brsrPrefix;
navigator.sayswho= (function(){
var N = navigator.appName, ua = navigator.userAgent, tem;
var M = ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
if(M && (tem = ua.match(/version\/([\.\d]+)/i))!= null) M[2] = tem[1];
M = M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
M = M[0];
if(M == "Chrome") { brsrPrefix = "webkit"; }
if(M == "Firefox") { brsrPrefix = "moz"; }
if(M == "Safari") { brsrPrefix = "webkit"; }
if(M == "MSIE") { brsrPrefix = "ms"; }
})();
console.log(' inside DOM Loaded callback - brsrPrefix', brsrPrefix, 'navigator.sayswho: ', navigator.sayswho);
}))
setTimeout(function() {
console.log(' outside DOM Loaded callback - brsrPrefix', brsrPrefix, 'navigator.sayswho: ', navigator.sayswho);
}, 3000);
This hackernoon article about polyfills and transpilers might likely be interesting.
Suggestions
Avoid Es6 features when attempting to target older browsers
The first thing I noticed is that the first line contains an arrow function expression:
document.addEventListener("DOMContentLoaded",(()=> {
Bear in mind the browser support for those- some browsers that wouldn’t support Element.animate() but would support requestAnimationFrame() would not support the syntax of an arrow function.
I tried running the code as is in IE 11 but set to emulate version 10 (both in Document mode and user agent string) since version 10 is the earliest version to support requestAnimationFrame().
It showed a syntax error in the console:
syntax error in IE 10 emulation
There is another es6 feature that leads to an error in IE version 10 and earlier: the let keyword is used:
let requestAnimationFrame= window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { return window.setTimeout(callback, 1000/60) };
Note the browser support for that keyword. So use a traditional function expression instead of an arrow function and the var keyword instead of let.
Users can modify the User agent of their browser
As described above with the IE 10/11 test, users can modify the user agent - including a completely different vendor - this post describes how to change the user agent in Chrome, Firefox and Safari. Because of this it is best to limit dependence on detection using the user agent string.
Browser support for addEventListener()
Another thing to consider is the browser support for addEventListener()
In Internet Explorer versions before IE 9, you have to use
attachEvent(), rather than the standardaddEventListener(). For IE, we modify the preceding example to:if (el.addEventListener) { el.addEventListener('click', modifyText, false); } else if (el.attachEvent) { el.attachEvent('onclick', modifyText); }
If you want to support those versions of IE then you would likely want to modify the code to add the DOM-loaded callback accordingly. Note that whle IE supports DOMContentLoaded starting with version 93 , events tied to that event don't always get triggered in IE. You may have to do something like this:
function checkBrowser() {
// code currently in the anonymous callback to ocument.addEventListener("DOMContentLoaded"
}
// in case the document is already rendered
if (document.readyState!='loading') checkBrowser();
// modern browsers
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', checkBrowser);
// IE <= 8
else document.attachEvent('onreadystatechange', function(){
if (document.readyState=='complete') checkBrowser();
});
-from plainJS.com
Are you sure that code needs to be executed after the DOM has loaded? if not, it could be put into an IIFE to preserve the scope limitation the variables in the callback function.
1https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Other_notes
3https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event#Browser_compatibility
-
1\$\begingroup\$ Given the bounty to you for the most indepth answer on here... Thanks for the input, will use some of your suggestion to build upon my next iterations & code attempts. \$\endgroup\$Ryan Stone– Ryan Stone2020年03月14日 02:56:57 +00:00Commented Mar 14, 2020 at 2:56
This is the Secondary Attempt that I have made & will Add more to it when I have learnt a bit more JS
let webAnimationsSupport = (window.Element.prototype.animate !== undefined);
let rafSupport = (window.requestAnimationFrame !== undefined);
let cssFallback = false;
switch(webAnimationsSupport ? 'true' : 'false'){
case "true":
// Run .animate() functions as normal via Callbacks.
console.log('.animate(); = true');
break;
case "false":
console.log('.animate(); Support = false');
animeFallBack();
// Move onto requestAnimationFrame();
break;
default:
// Default to Css Fallback. ie ``Add Back in the Classes`` That governed the original Animation.
}
function animeFallBack(){
switch(rafSupport ? 'true' : 'false'){
case "true":
// .animate Not Supported Fallback to `request animation frame`.
// Add Callback here which holds RAF Anime Code.
console.log('.raf(); = true');
break;
case "false":
// Fallback option or alert enable Js
console.log('.raf(); = false');
let requestAnimationFrame = (
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
return window.setTimeout(callback, 1000 / 60)
}
);
break;
default:
// Default to Css Fallback.
}
}
```
-
1\$\begingroup\$ What's up with those
switchstatements? The comments seem to indicate that thedefaultclause can be executed, however that is not the case, so they could just be removed. The mapping to"true"/"false"strings is strange. Boolean values can be used just as well:switch (!!rafSupport) { case true: /* ... */ case false: /*...*/. But why not just use anif/elsestatement? \$\endgroup\$RoToRa– RoToRa2020年03月09日 10:12:51 +00:00Commented Mar 9, 2020 at 10:12 -
\$\begingroup\$ @RoToRa Only tried to use switch statement because i'm trying to learn Javascript & these were something that I had not learnt yet. Maybe needs refactoring alot but I prefer them to if/else's for some reason. \$\endgroup\$Ryan Stone– Ryan Stone2020年03月09日 12:18:18 +00:00Commented Mar 9, 2020 at 12:18
-
\$\begingroup\$ @RotoRa you could (and should) add those comments in an answer to the follow-up question where this code appears. \$\endgroup\$2020年03月09日 17:38:42 +00:00Commented Mar 9, 2020 at 17:38
-
\$\begingroup\$ @RoToRa this secondary attempt was just a work in progress & still needs refactoring. Probably Might get it correct on the 3rd, 4th, 5th, 6th or 8th attempt. \$\endgroup\$Ryan Stone– Ryan Stone2020年03月09日 20:10:58 +00:00Commented Mar 9, 2020 at 20:10
You must log in to answer this question.
Explore related questions
See similar questions with these tags.
Whythe code is wriiten the way it is, I understand also why regex has been used, but it is like an alien language to me & struggleing to learn it. \$\endgroup\$AllCode, ever written is or has been previously written by someone else... \$\endgroup\$