Can a script in <head> load a css file async - javascript

Was looking for some ways to load a css file async in my application.
Currently I have a place holder that allows users to insert some extra
html in the head usually reserved for things like meta tags etc. If I put a script like this inside of <head> would it load the file async or would it block the render while its making the request.
<script>~function (document) {
var elem = document.createElement('link');
var head = document.getElementsByTagName('head')[0];
elem.rel = 'stylesheet';
elem.href = 'http://fonts.googleapis.com/css?family=Cardo:700|Merriweather:400italic,400,700';
elem.media = 'only x';
head.appendChild(elem);
setTimeout(function () {
elem.media = 'all';
});
}(document);</script>

Related

Dynamically add script tag and execute $(document).ready, without using iframe or document.write

I have a string which comes from api response. Now i want to integrate this script and style tag in my application and execute script.
const styleScriptText = '<style type="text/css">#checkoutmodal .checkoutmodal-box{background:#FFF !important}</style><script src="https://someurl/lib/jquery.min.js" type="text/javascript"></script><script type="text/javascript">$(document).ready(function() { console.log("test")});</script>'
I tried to load it using iframe and i could achieve expected result
const iframe = document.createElement('iframe');
const html = `<body>${styleScriptText}</body>`;
iframe.srcdoc = html;
iframe.style.width = "47%";
iframe.style.left = "25%";
iframe.style.height = "100vh";
iframe.style.position = "relative";
document.getElementById("parentId").appendChild(iframe);
But i don't want to use iframe as it has future constraints i have to redirect to bank page and when it comes back whole application is iframed which i don't want
Next i tried it using document.write as below
const html = `<html>
<head>
</head>
<body>
${styleScriptText}
</body>
</html>`;
document.open("text/html", "replace");
document.write(html);
document.close();
But problem with above approach is i am getting below error
A parser-blocking, cross site (i.e. different eTLD+1) script, https:externalscript.js, is invoked via document.write
If i take any other approch $(document).ready function in script doesnot execute.
Tried almost everything but not able to figure out how can i load and run script coming from api response.
Goal here is i need to take a script coming as string and load it in html and execute every script files
this should work, use it at your own risk:
const styleScriptText =
'<style type="text/css">body {background: #000}</style><script type="text/javascript">alert("OK")</' + 'script' + '>';
const insertStyleScriptText = () => {
const div = document.createElement('div');
div.innerHTML = styleScriptText;
div.childNodes.forEach(child => {
document.head.appendChild(child.cloneNode(true));
if (child.tagName === 'SCRIPT')
eval(child.textContent);
});
};
<button onclick="insertStyleScriptText()">insert StyleScriptText</button>

Load CSS file only if element with class is present on the page

I am trying to optimise my loading of CSSS files as I am loading some large CSS files on pages where they aren't used. Is there any way for me to enqueue them only if an element is present with a class on that page.
I've tried the following however, it does not work:
jQuery(document).ready(function($) {
//Script Checkers
var wowJS = $('.wow');
if (wowJS.length > 0) {
$.getScript('/wp-content/themes/gowebsites/gw-addon/js/wow.js', function() {
new WOW().init();
});
var head = document.getElementsByTagName('head')[0];
var cssNode = document.createTextNode("link");
cssNode.href = "/wp-content/themes/gowebsites/gw-addon/css/animations.css";
cssNode.rel = "stylesheet";
//console.log("CSS Node: "+cssNode); = [object Text]
head.appendChild(cssNode);
}
});
I have seen functions that work for adding css files to the head however, none of them allow the ability to make it conditional.
EDIT: I've since just used the getScripts() jQuery function however, I am still in need of knowing how to add css to the header only if required.
EDIT: For future reference for anyone, this is the final working code:
jQuery(document).ready(function($) {
//Script Checkers
var wowJS = $('.wow');
if (wowJS.length > 0) {
$.getScript('/wp-content/themes/gowebsites/gw-addon/js/wow.js', function() {
new WOW().init();
});
var head = document.getElementsByTagName('head')[0];
var cssNode = document.createElement("link");
cssNode.href = "/wp-content/themes/gowebsites/gw-addon/css/animations.css";
cssNode.rel = "stylesheet";
head.appendChild(cssNode);
}
});
Create the nodes first then append then using the appendChild() method, like :
var scriptNode = document.createElement("script");
scriptNode.src = "/wp-content/themes/gowebsites/gw-addon/js/wow.js";
var cssNode = document.createElement("link");
cssNode.href = "/wp-content/themes/gowebsites/gw-addon/css/animations.css";
cssNode.rel = "stylesheet";
head.appendChild(scriptNode);
head.appendChild(cssNode);
You should use insertAdjacentHTML
head.insertAdjacentHTML("afterend",'<script language="javascript" src="/wp-content/themes/gowebsites/gw-addon/js/wow.js"></script>');
head.insertAdjacentHTML("afterend",'<link href="/wp-content/themes/gowebsites/gw-addon/css/animations.css" rel="stylesheet">');

Link an external CSS file to a JavaScript File [duplicate]

Is it possible to import css stylesheets into a html page using Javascript? If so, how can it be done?
P.S the javascript will be hosted on my site, but I want users to be able to put in the <head> tag of their website, and it should be able to import a css file hosted on my server into the current web page. (both the css file and the javascript file will be hosted on my server).
Here's the "old school" way of doing it, which hopefully works across all browsers. In theory, you would use setAttribute unfortunately IE6 doesn't support it consistently.
var cssId = 'myCss'; // you could encode the css path itself to generate id..
if (!document.getElementById(cssId))
{
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.id = cssId;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'http://website.example/css/stylesheet.css';
link.media = 'all';
head.appendChild(link);
}
This example checks if the CSS was already added so it adds it only once.
Put that code into a JavaScript file, have the end-user simply include the JavaScript, and make sure the CSS path is absolute so it is loaded from your servers.
VanillaJS
Here is an example that uses plain JavaScript to inject a CSS link into the head element based on the filename portion of the URL:
<script type="text/javascript">
var file = location.pathname.split( "/" ).pop();
var link = document.createElement( "link" );
link.href = file.substr( 0, file.lastIndexOf( "." ) ) + ".css";
link.type = "text/css";
link.rel = "stylesheet";
link.media = "screen,print";
document.getElementsByTagName( "head" )[0].appendChild( link );
</script>
Insert the code just before the closing head tag and the CSS will be loaded before the page is rendered. Using an external JavaScript (.js) file will cause a Flash of unstyled content (FOUC) to appear.
If you use jquery:
$('head').append('<link rel="stylesheet" type="text/css" href="style.css">');
I guess something like this script would do:
<script type="text/javascript" src="/js/styles.js"></script>
This JS file contains the following statement:
if (!document.getElementById) document.write('<link rel="stylesheet" type="text/css" href="/css/versions4.css">');
The address of the javascript and css would need to be absolute if they are to refer to your site.
Many CSS import techniques are discussed in this "Say no to CSS hacks with branching techniques" article.
But the "Using JavaScript to dynamically add Portlet CSS stylesheets" article mentions also the CreateStyleSheet possibility (proprietary method for IE):
<script type="text/javascript">
//<![CDATA[
if(document.createStyleSheet) {
document.createStyleSheet('http://server/stylesheet.css');
}
else {
var styles = "#import url(' http://server/stylesheet.css ');";
var newSS=document.createElement('link');
newSS.rel='stylesheet';
newSS.href='data:text/css,'+escape(styles);
document.getElementsByTagName("head")[0].appendChild(newSS);
}
//]]>
Element.insertAdjacentHTML has very good browser support, and can add a stylesheet in one line.
document.getElementsByTagName("head")[0].insertAdjacentHTML(
"beforeend",
"<link rel=\"stylesheet\" href=\"path/to/style.css\" />");
If you want to know (or wait) until the style itself has loaded this works:
// this will work in IE 10, 11 and Safari/Chrome/Firefox/Edge
// add ES6 poly-fill for the Promise, if needed (or rewrite to use a callback)
let fetchStyle = function(url) {
return new Promise((resolve, reject) => {
let link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.onload = () => resolve();
link.onerror = () => reject();
link.href = url;
let headScript = document.querySelector('script');
headScript.parentNode.insertBefore(link, headScript);
});
};
Usage:
fetchStyle(url)
.then(
() => console.log("style loaded succesfully"),
() => console.error("style could not be loaded"),
);
Use this code:
var element = document.createElement("link");
element.setAttribute("rel", "stylesheet");
element.setAttribute("type", "text/css");
element.setAttribute("href", "external.css");
document.getElementsByTagName("head")[0].appendChild(element);
In a modern browser you can use promise like this. Create a loader function with a promise in it:
function LoadCSS( cssURL ) {
// 'cssURL' is the stylesheet's URL, i.e. /css/styles.css
return new Promise( function( resolve, reject ) {
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.href = cssURL;
document.head.appendChild( link );
link.onload = function() {
resolve();
console.log( 'CSS has loaded!' );
};
} );
}
Then obviously you want something done after the CSS has loaded. You can call the function that needs to run after CSS has loaded like this:
LoadCSS( 'css/styles.css' ).then( function() {
console.log( 'Another function is triggered after CSS had been loaded.' );
return DoAfterCSSHasLoaded();
} );
Useful links if you want to understand in-depth how it works:
Official docs on promises
Useful guide to promises
A great intro video on promises
I know this is a pretty old thread but here comes my 5 cents.
There is another way to do this depending on what your needs are.
I have a case where i want a css file to be active only a while. Like css switching. Activate the css and then after another event deativate it.
Instead of loading the css dynamically and then removing it you can add a Class/an id in front of all elements in the new css and then just switch that class/id of the base node of your css (like body tag).
You would with this solution have more css files initially loaded but you have a more dynamic way of switching css layouts.
Have you ever heard of Promises? They work on all modern browsers and are relatively simple to use. Have a look at this simple method to inject css to the html head:
function loadStyle(src) {
return new Promise(function (resolve, reject) {
let link = document.createElement('link');
link.href = src;
link.rel = 'stylesheet';
link.onload = () => resolve(link);
link.onerror = () => reject(new Error(`Style load error for ${src}`));
document.head.append(link);
});
}
You can implement it as follows:
window.onload = function () {
loadStyle("https://fonts.googleapis.com/css2?family=Raleway&display=swap")
.then(() => loadStyle("css/style.css"))
.then(() => loadStyle("css/icomoon.css"))
.then(() => {
alert('All styles are loaded!');
}).catch(err => alert(err));
}
It's really cool, right? This is a way to decide the priority of the styles using Promises.
To see a multi-style loading implementation see: https://stackoverflow.com/a/63936671/13720928
Here's a one line example, that uses plain JavaScript to inject a CSS link into the head element based on the filename portion of the URL:
document.head.innerHTML += '<link rel="stylesheet" href="css/style.css">';
Most browsers support it. See the browser compatibility.
There is a general jquery plugin that loads css and JS files synch and asych on demand.
It also keeps track off what is already been loaded :)
see: http://code.google.com/p/rloader/
Here's a way with jQuery's element creation method (my preference) and with callback onLoad:
var css = $("<link>", {
"rel" : "stylesheet",
"type" : "text/css",
"href" : "style.css"
})[0];
css.onload = function(){
console.log("CSS IN IFRAME LOADED");
};
document
.getElementsByTagName("head")[0]
.appendChild(css);
Below a full code using for loading JS and/or CSS
function loadScript(directory, files){
var head = document.getElementsByTagName("head")[0]
var done = false
var extension = '.js'
for (var file of files){
var path = directory + file + extension
var script = document.createElement("script")
script.src = path
script.type = "text/javascript"
script.onload = script.onreadystatechange = function() {
if ( !done && (!this.readyState ||
this.readyState == "loaded" || this.readyState == "complete") ) {
done = true
script.onload = script.onreadystatechange = null // cleans up a little memory:
head.removeChild(script) // to avoid douple loading
}
};
head.appendChild(script)
done = false
}
}
function loadStyle(directory, files){
var head = document.getElementsByTagName("head")[0]
var extension = '.css'
for (var file of files){
var path = directory + file + extension
var link = document.createElement("link")
link.href = path
link.type = "text/css"
link.rel = "stylesheet"
head.appendChild(link)
}
}
(() => loadScript('libraries/', ['listen','functions', 'speak', 'commands', 'wsBrowser', 'main'])) ();
(() => loadScript('scripts/', ['index'])) ();
(() => loadStyle('styles/', ['index'])) ();
var elem = document.createElement('link');
elem.rel = ' stylesheet'
elem.href= 'style.css';//Link of the css file
document.head.appendChild(elem);
This function uses memorization. And could be called many times with no conflicts of loading and running the same stylesheet twice. Also it's not resolving sooner than the stylesheet is actually loaded.
const loadStyle = function () {
let cache = {};
return function (src) {
return cache[src] || (cache[src] = new Promise((resolve, reject) => {
let s = document.createElement('link');
s.rel = 'stylesheet';
s.href = src;
s.onload = resolve;
s.onerror = reject;
document.head.append(s);
}));
}
}();
Please notice the parentheses () after the function expression.
Parallel loading of stylesheets:
Promise.all([
loadStyle('/style1.css'),
loadStyle('/style2.css'),
// ...
]).then(() => {
// do something
})
You can use the same method for dynamic loading scripts.
I'd like to share one more way to load not only css but all the assets (js, css, images) and handle onload event for the bunch of files. It's async-assets-loader. See the example below:
<script src="https://unpkg.com/async-assets-loader"></script>
<script>
var jsfile = "https://code.jquery.com/jquery-3.4.1.min.js";
var cssfile = "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css";
var imgfile = "https://logos.keycdn.com/keycdn-logo-black.png";
var assetsLoader = new asyncAssetsLoader();
assetsLoader.load([
{uri: jsfile, type: "script"},
{uri: cssfile, type: "style"},
{uri: imgfile, type: "img"}
], function () {
console.log("Assets are loaded");
console.log("Img width: " + assetsLoader.getLoadedTags()[imgfile].width);
});
</script>
According to the async-assets-loader docs
Answer from future.
In 2022, we have import assertions api for import css file.
import mycss from "./style/mycss.css" assert { type: "css" };
document.adoptedStyleSheets = [sheet];
shadowRoot.adoptedStyleSheets = [sheet];
Browser support:
till september 2022, only chromium based browsers supported.
Read more at:
v8 import assertions post
tc39 github
t39 import assertions proposal
var fileref = document.createElement("link")
fileref.setAttribute("rel", "stylesheet")
fileref.setAttribute("type", "text/css")
fileref.setAttribute("th:href", "#{/filepath}")
fileref.setAttribute("href", "/filepath")
I'm using thymeleaf and this is work fine. Thanks
use:
document.getElementById("of head/body tag")
.innerHTML += '<link rel="stylesheet" type="text/css" href="style.css">';

Loading scripts with document.write in an extension's content script

If a script contains:
document.write("<iframe>ads here</iframe>");
If it's included in the html before the page is requested for load, it might look something like this:
<html>
<!-- stuff !-->
<div><script src="document_write.js" type="text/javascript"></script></div>
<body>
</html>
Loading an html page with the code similar to above will result in the <iframe> being placed in the <div> tag which housed the script. If the document.write() is called after the page load, it will overwrite the whole page.
Chrome extensions' content scripts will also overwrite a page with document.write, or crash it - depending on when in the lifecycle of a page it was called.
Is there a way to insert scripts containing document.write() in Chrome's content scripts?
I had faced the same problem when I was working with some conversion tracking scripts on my ajax site. I ended up overriding document.write, which fixed the problem.
$(document).ready(function() {
document.write = function(str) {
var moz = !window.opera && !/Apple/.test(navigator.vendor);
if (str.match(/^<\//))
return;
if (!window.opera)
str = str.replace(/&(?![#a-z0-9]+;)/g, "&");
str = str.replace(/<([a-z]+)(.*[^\/])>$/, "<$1$2></$1>");
if (!moz)
str = str.replace(/(<[a-z]+)/g, "$1 xmlns='http://www.w3.org/1999/xhtml'");
var div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
div.innerHTML = str;
var pos;
if (!moz) {
pos = document.getElementsByTagName("*");
pos = pos[pos.length - 1];
} else {
pos = document;
while (pos.lastChild && pos.lastChild.nodeType == 1)
pos = pos.lastChild;
}
var nodes = div.childNodes;
while (nodes.length)
pos.parentNode.appendChild(nodes[0]);
};
});

Load a javascript file and css file depending on user agent

Just like in the title.
I got two files: one is javascript file and one is css file. And if user-agent is an iPad I want to load those files - but only when user-agent is iPad. So below two lines are only loaded when user-agent is an iPad. how can i achieve that
<link rel="stylesheet" href="/c/dropkick.css" type="text/css"/>
<script src="/s/jquery.dropkick-1.0.0.js" type="text/javascript"></script>
if (navigator.userAgent.match(/iPad/i) != null){ // may need changing?
var js = document.createElement('script');
js.type = "text/javascript";
js.src = "/s/jquery.dropkick-1.0.0.js";
var css = document.createElement('link');
css.type = "text/css";
css.rel = "stylesheet";
css.href = "/c/dropkick.css";
var h = document.getElementsByTagName('head')[0];
h.appendChild(js);
h.appendChild(css);
}
Or whatever would be in the User-Agent header for an iPad.
References:
window.navigator.userAgent
document.createElement
node.appendChild
You can use document.createElement to create link and script elements, and then append them to the document (for instance, append them to document.getElementsByTagName('head')[0] or similar).
This answer here on SO suggests that you can dtect an iPad by just looking for the string "ipad" in the navigator.userAgent field. Of course, the user agent field can be spoofed.
So for example:
<script>
(function() {
var elm, head;
if (navigator.userAgent.indexOf("ipad") !== -1) {
head = document.getElementsByTagName('head')[0] || document.body || document.documentElement;
elm = document.createElement('link');
elm.rel = "stylesheet";
elm.href = "/c/dropkick.css";
head.appendChild(elm);
elm = document.createElement('script');
elm.src = "/s/jquery.dropkick-1.0.0.js";
head.appendChild(elm);
}
})();
</script>
...but that's off-the-cuff, untested.
(Note that there's no reason to put the type on either link or script; in the case of link, the type comes from the content-type of the response. In the case of script, the default is JavaScript.)

Categories