Background: Back in December I wanted my (custom) PHP page-building engine to build a page one-way if the User Agent (UA) could handle Javascript and a different way if the UA could not. To achieve this, I needed to learn:
How to detect UA Javascript capability via PHP, prior to PHP building the page on-the-fly: Detecting javascript in PHP, before PHP builds page
Thanks to some very helpful guidance from @skobaljic, @Abhi Beckert and @GolezTrol, I learned that we can use Javascript to place a session cookie which PHP will be able to query on all subsequent page loads. Consequently, for every page after the first one, PHP will know whether the UA is Javascript-capable or not
So far, so good. Now I have reached the stage where I am no longer building pages with PHP on-the-fly but pre-generating them and uploading them to the server as static HTML documents.
If you're still with me, you'll have guessed correctly this means I am currently needing to pre-generate two static documents for each page - one for the non-JS-capable UAs and one for the JS-capable UAs. This isn't really ideal, hence my question below.
On the face of it, this looks like it ought to be a straightforward problem.
For Javascript-non-capable UAs, I want to build a document which contains lines like this:
<img src="/myfolder/myimage.png" alt="My Image" />
For Javascript capable UAs, I want to build the same document which contains lines like this:
<img data-src="/myfolder/myimage.png" alt="My Image" />
(for those who are curious, a javascript after onload
converts all the data-src properties into src properties, following which many tens of server round-trips ensue but - crucially - only after the page has already substantially loaded).
I have already achieved this via PHP and a session cookie (see Background above), but now, if it's possible, I want to execute the whole thing on the front-end.
How can I write an attribute into the <img />
elements in the DOM as src
by default but, additionally, (if javascript is available) prefix that attribute with data-
so that it reads as data-src
...
... such that...
[IMPORTANT BIT]
If the UA is javascript-capable it will automatically and instantaneously write all instances of src
as data-src
as the page is being downloaded by the UA, so that after onload
a javascript can then re-write all those instances of data-src
as src
again.
Background: Back in December I wanted my (custom) PHP page-building engine to build a page one-way if the User Agent (UA) could handle Javascript and a different way if the UA could not. To achieve this, I needed to learn:
How to detect UA Javascript capability via PHP, prior to PHP building the page on-the-fly: Detecting javascript in PHP, before PHP builds page
Thanks to some very helpful guidance from @skobaljic, @Abhi Beckert and @GolezTrol, I learned that we can use Javascript to place a session cookie which PHP will be able to query on all subsequent page loads. Consequently, for every page after the first one, PHP will know whether the UA is Javascript-capable or not
So far, so good. Now I have reached the stage where I am no longer building pages with PHP on-the-fly but pre-generating them and uploading them to the server as static HTML documents.
If you're still with me, you'll have guessed correctly this means I am currently needing to pre-generate two static documents for each page - one for the non-JS-capable UAs and one for the JS-capable UAs. This isn't really ideal, hence my question below.
On the face of it, this looks like it ought to be a straightforward problem.
For Javascript-non-capable UAs, I want to build a document which contains lines like this:
<img src="/myfolder/myimage.png" alt="My Image" />
For Javascript capable UAs, I want to build the same document which contains lines like this:
<img data-src="/myfolder/myimage.png" alt="My Image" />
(for those who are curious, a javascript after onload
converts all the data-src properties into src properties, following which many tens of server round-trips ensue but - crucially - only after the page has already substantially loaded).
I have already achieved this via PHP and a session cookie (see Background above), but now, if it's possible, I want to execute the whole thing on the front-end.
How can I write an attribute into the <img />
elements in the DOM as src
by default but, additionally, (if javascript is available) prefix that attribute with data-
so that it reads as data-src
...
... such that...
[IMPORTANT BIT]
If the UA is javascript-capable it will automatically and instantaneously write all instances of src
as data-src
as the page is being downloaded by the UA, so that after onload
a javascript can then re-write all those instances of data-src
as src
again.
3 Answers
Reset to default 2Like this - not tested but should work:
var imgs = document.getElementsByTagName('img');
for(var i = 0; i < imgs.length; i++) {
var currentSrc = imgs[i].getAttribute('src');
imgs[i].setAttribute('src',''); // remove old src data
imgs[i].setAttribute('data-src','currentSrc');
}
All this does is add a new data-src
attribute, and clears the current src
(if needed)
Add just before the </body>
tag
This is a hacky way of doing it but all browsers (ie6+) support it:
<body>
<noscript>
<style>.img-hide { display: none; }</style>
</noscript>
<noscript><img src="/path/to/image1.jpg"></noscript>
<img class="img-hide" data-src="/path/to/image1.jpg">
<noscript><img src="/path/to/image2.jpg"></noscript>
<img class="img-hide" data-src="/path/to/image2.jpg">
<!-- etc etc etc -->
</body>
After nearly 3 days of head-scratching, I've finally cracked this.
HEAD
<head>
<script>
function initialiseImages() {
var scripts = document.getElementsByTagName('script');
var section = scripts[scripts.length-1].parentNode;
var images = section.getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
var src = images[i].getAttribute('src');
var datasrc = document.createAttribute('data-src');
datasrc.value = src;
images[i].setAttributeNode(datasrc);
images[i].removeAttribute('src');}}
</script>
</head>
BODY
<body>
<section>
<h2><img src="/myfolder/myimage.png" alt="My Image" />Second Level Heading</h2>
<ul><li><img src="/myfolder/myimage.png" alt="My Image" /></li></ul>
<script>initialiseImages();</script>
</section>
<section>
<h3>Third Level Heading</h3>
<ul>
<li><img src="/myfolder/myimage.png" alt="My Image" /></li>
<li><img src="/myfolder/myimage.png" alt="My Image" /></li>
<li><img src="/myfolder/myimage.png" alt="My Image" /></li>
</ul>
<script>initialiseImages();</script>
</section>
</body>
Update 1
Thanks to Dave Walsh's article Referencing a Script's own Tag (http://davidwalsh.name/script-tag), I have re-written the first 3 lines of function initialiseImages()
:
var scripts = document.getElementsByTagName('script');
var section = scripts[scripts.length-1].parentNode;
var images = section.getElementsByTagName('img');
as a single line:
var images = document.currentScript.parentNode.getElementsByTagName('img');
As far as I can tell, this defines var images
fractionally faster and consequently catches more instances of <img src=
before the UA sends a file-request to the server.
Developer Notes follow...
Attempt #1 : Try to get Javascript to behave like PHP
I'm much more familiar with writing pages on-the-fly in PHP than with deploying Javascript, so my original intention was to find a way to use Javascript in a quasi-server-side manner to re-write the DOM instantaneously at the very moment it arrived:
For Javascript-non-capable UAs, I want to build a document which contains lines like this:
<img src="/myfolder/myimage.png" alt="My Image" />
For Javascript capable UAs, I want to build the same document which contains lines like this:
<img data-src="/myfolder/myimage.png" alt="My Image" />
If such an approach is possible, I couldn't find a way to do it - and, to be fair, Javascript certainly isn't designed for instantaneously re-writing element properties the moment they arrive.
Attempt #2 : Block image download via Javascript-added CSS
My second approach was to see if I could prevent image download via a line of CSS.
If I added that line with Javascript, only Javascript-capable UAs would be able to read the image-blocking style rule.
This didn't work either. It turns out, you can straightforwardly prevent CSS background-images
from downloading, by applying display:none;
to the parent container of the element which has the background-image
. But the images I want to block (until after onload) are specifically <img>s
, not background-images
- and it turns out that as soon as the UA hits <img src
, it runs off to the server to download the image.
Attempt #3 : Inline Javascript in every <img>
Inline Javascript is pretty old-school, but @Darren Sweeney has already suggested an imaginative <noscript>
solution, so I didn't want to automatically dismiss old-school. I thought about tackling the issue something like this:
<img src="/myfolder/myimage.png" alt="My Image" onEvent="initialiseImage();" />
The main issue I ran into here was I couldn't find any appropriate onEvent
. I placed the initialiseImage()
function in the <head>
and then I wanted to have that function automatically fire every time the UA reached an <img>
- but after extensive reading, I'm not sure that automatic execution inline is possible. (Perhaps someone can let me know in the ments below, if it is...)
Certainly, I could find no onParse
.
The only element-specific onEvent
that made sense was onLoad
, which, in the case of an <img>
(if I understand it correctly) downloads the <img>
before it fires. So that was no good at all.
Attempt #4 : Success at last!
Thus far:
1) From the start, the issue was that waiting until window.onload
before using Javascript to rewrite every <img src=
attribute was far too late;
2) I couldn't see a way to use Javascript to rewrite every <img src=
instantly as it was downloading;
3) I couldn't see a way to use Javascript to rewrite every <img src=
attribute just at the point the UA had parsed the <img>
;
I reasoned that perhaps I could still use Javascript to rewrite every <img src=
attribute at the moment the UA pleted parsing each <section>
containing each set of <img>s
.
I am delighted to say I have tested the code above fairly thoroughly and it appears (so far) to work perfectly - long before the UA starts to request the <img>
files from the server, the <img src=
attribute has been replaced with a <img data-src=
property and that request is no longer executed.