1

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 3

2

Like 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

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks @Darren, that's almost exactly what I've got the reverse of (currently). I have written a script which takes all the data-src attributes in the initial DOM and rewrites them as src properties after onload. (Cue all the server-round trips...). But... I can't assume JS-capability by default, so what I actually need is for the Javascript-capable UA to first write those attributes as data-src as the page is being accessed by the UA - so the script probably needs to be in the <head> I'm thinking.
In summary, a non-JS-capable UA, simply reads the attribute as src, while a JS-capable UA, rewrites the attribute as data-src during page-load and then, after onload, rewrites all those data-src attributes (a second time) as src (cue all the server round-trips...)
I presume you're using something that reads data-src if JS enabled? Just add both. data-src will be ignored if no JS, and if there is JS, whatever you're using data-src for will find it.
You're right @Darren, data-src will be ignored if no JS. But... src won't be ignored if there is JS. And that's the behaviour I'm trying to tap into: if there is JS, don't try to load the images now, we'll do that after; if there isn't JS, well okay, you can start loading each image as you come across it (albeit, with all the pre-onload server round-trips, it will take the page a lot longer to appear).
Very frustrating. All I need (from one perspective) is a single .js statement (a statement which will be ignored by every UA which isn't JS-capable) which will inject the 5 character string variable data- immediately prior to every instance of src as the page is downloading... but I am unclear how to include .js variables inside HTML elements. So in the document <head>, I could have: <script>var asyncImg = 'data-';</script>... but then I'm unsure how to proceed from there.
|
1

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>

2 Comments

Haha. I love it. Thank you for this, @Darren. This likely won't be the solution I implement finally but it's always good to be reminded that <noscript> is always there as a last resort.
Right. I am going out for a run. It sometimes leads to inspiration. Thank you for all the time you've spent on this, this afternoon @Darren - very much appreciated.
1

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 comments 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 completed 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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.