使用<noscript> fallback </noscript>加载延迟加载的图像

时间:2015-03-19 13:03:49

标签: javascript lazy-loading

我知道(少数几个)非JavaScript用户在那里,我想照顾他们,而不是因为他们的偏好(因为隐私原因或其他原因)给他们较差的体验。

大多数延迟加载的JS库似乎都以同样的方式解决了这个问题,例如见lazysizes

<style>
    .no-js img.lazyload {
        display: none;
    }
</style>

<noscript>
    <img src="image.jpg" />
</noscript>
<img src="grey.jpg" data-src="image.jpg" class="lazyload" />

主要是出于好奇,我想知道是否可以从<noscript>标签中取出后退并使用JavaScript以编程方式将其添加到DOM中,以便图像源不具备要复制在两个图像标签中,这将只留下:

<noscript>
    <img src="image.jpg" class="lazyload" width="600" height="400"/>
</noscript>

这是我一起敲的东西:

(function(attribute) {
    Array.prototype.forEach.call(document.getElementsByTagName("noscript"), function(node) {
        var parser = new DOMParser,
            el = parser.parseFromString(node.textContent, "text/xml").documentElement, // XML => <img/> required
            img = ("img" == el.tagName) ? el : el.getElementsByTagName("img")[0]; // allow for <img/> in <picture>

        img.setAttribute(attribute, img.getAttribute("src"));
        img.setAttribute("src", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC");

        node.insertAdjacentHTML("beforebegin", el.outerHTML);
    });
})("data-src"); // different libraries use different data attribute names

这似乎无处不在(Chrome,Safari,Opera,Firefox),除了Internet Explorer(自然)。我知道IE9之前.textContent不可用,但IE9 +似乎都在最后的障碍中失败 - .outerHTML。我注定要失败,不得不在我的标记中重复自己吗?

更新:为了澄清,我非常希望能够在图片代码中使用任意属性(alt,title等),甚至可以使用自适应标记:

<noscript>
    <picture>
        <source ... />
        <source ... />
        <img src="image.jpg" />
    </picture>
</noscript>

3 个答案:

答案 0 :(得分:7)

我是lazySizes的创建者。这种方法有多个问题:

  1. noscript元素永远不会是渲染,这意味着它是不可检测的,是否可见(或更好地说它总是不可见的)
  2. 您无法使用状态类lazyloadinglazyload向用户提供反馈
  3. 您无法预先占用懒惰嵌入内容的空间(这对于用户体验(无内容跳转)和b)性能(无重排)非常重要
  4. (旧版浏览器存在问题)
  5. 无法使用data-sizes="auto"功能
  6. 但是,如果4.和5.对您来说不是问题,则可以将noscript子元素与lazyload父元素结合使用来实现此目的。

    标记看起来像这样:

    <div class="lazyload" data-noscript="">
        <noscript>
            <p>any kind of content you want to be unveiled</p>
        </noscript>
    </div>
    

    lazySizes插件代码看起来像这样:

    (function(){
        'use strict';
    
        var supportPicture = !!window.HTMLPictureElement;
    
        addEventListener('lazybeforeunveil', function(e){
            if(e.defaultPrevented || e.target.getAttribute('data-noscript') == null){return;}
            var imgs, i;
            var noScript = e.target.getElementsByTagName('noscript')[0] || {};
            var content = noScript.textContent || noScript.innerText || '';
            e.target.innerHTML = content;
    
            if(supportPicture){return;}
    
            imgs = e.target.querySelectorAll('img[srcset], picture > img');
    
            for(i = 0; i < imgs.length; i++){
                lazySizes.uP(imgs[i]);
            }
        });
    })();
    

    如果您喜欢这个,我可能会为此制作一个官方插件。这是插件:https://github.com/aFarkas/lazysizes/tree/master/plugins/noscript

答案 1 :(得分:3)

以下是我如何使用应在所有浏览器中提供的方法

(function(attribute) {
    Array.prototype.forEach.call(document.getElementsByTagName("noscript"), function(node) {
        var content = node.childNodes[0].nodeValue,
            parser  = new DOMParser(),
            doc     = parser.parseFromString(content, "text/html"),
            images  = doc.getElementsByTagName('img');

        for (var i=images.length; i--;) {
            var img    = document.createElement('img');
            img.src    = 'data:image/png;base64,iVBOR ....';
            img.height = images[i].getAttribute('height');
            img.width  = images[i].getAttribute('width');
            img.setAttribute(attribute, images[i].getAttribute('src'));
            node.parentNode.insertBefore(img, node.nextSibling);
        }
    });
})("data-src");

答案 2 :(得分:2)

这是我使用的技巧:

(function() {
  "use strict";
  var config = {
    // If the image gets within 50px in the Y axis, start the download.
    rootMargin: "50px 0px",
    threshold: 0.01
  };
  var observer;
  //If we're using a browser without the IntersectionObserver (IE11, Safari 11), skip the lazy part and just load the resources
  if ("IntersectionObserver" in window) {observer = new IntersectionObserver(onIntersection, config);}
  //If we're using a browser without requestAnimationFrame (IE9, Opera Mini), just run the passed function
  var rAF;
  if ("requestAnimationFrame" in window) rAF = window.requestAnimationFrame;
  else rAF = function(func) { func(); };
  var tempImg = "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
  /**
   * Temporarily replace a expensive resource load with a cheap one
   */
  function storeSourceForLater(lazyItem, tempData) {
    //Store our ACTUAL source for later
    lazyItem.setAttribute("data-lazy-src", lazyItem.getAttribute("src"));
    //Set the item to point to a temporary replacement (like a data URI)
    lazyItem.setAttribute("src", tempData);
    //Now observe the item so that we can start loading when it gets close to the viewport
    observer.observe(lazyItem);
  }
  /**
   * Temporarily prevent expensive resource loading by inserting a <source> tag pointing to a cheap one (like a data URI)
   */
  function jamSourceLoading(lazyItem, tempData) {
    var newSource = document.createElement("source");
    newSource.setAttribute("srcset", tempData);
    newSource.setAttribute("data-lazy-remove", "true");
    //adding this source tag at the start of the picture tag means the browser will load it first
    lazyItem.insertBefore(newSource, lazyItem.firstChild);
    var baseImage = lazyItem.getElementsByTagName("img")[0];
    if (baseImage) {
      //this is a picture tag, so we need to watch the image (as the picture tag is smaller than the image usually)
      observer.observe(baseImage);
    }
  }
  /**
   * Set up the lazy items so that they won't try to load when we add them to the document, but will once the user is close to seeing them
   */
  function prepareLazyContents(lazyArea) {
    var lazyImgs = lazyArea.getElementsByTagName("img");
    for(var i = lazyImgs.length; i--;){
      storeSourceForLater(lazyImgs[i], tempImg);
    }
    var lazyPictures = lazyArea.getElementsByTagName("picture");
    for(var i3 = lazyPictures.length; i3--;) {
      jamSourceLoading(lazyPictures[i3], tempImg);
    }
  }
  /**
   * Put the source back where we found it - now that the element is attached to the document, it will load now
   */
  function restoreSource(lazyItem) {
    lazyItem.setAttribute("src", lazyItem.getAttribute("data-lazy-src"));
    lazyItem.removeAttribute("data-lazy-src");
  }
  /**
   * Remove the source tag preventing the loading of picture/audio/video
   */
  function removeJammingSource(lazyItem) {
    var jammingSource = lazyItem.querySelector("source[data-lazy-remove]");
    if (jammingSource) lazyItem.removeChild(jammingSource);
  }
  /**
   * Handle the intersection postback
   */
  function onIntersection(entries, obsvr) {
    entries.forEach(function(entry) {
      if(entry.intersectionRatio === 0) return;
      //if the item is now visible, load it and stop watching it
      var lazyItem = entry.target;
      obsvr.unobserve(lazyItem);
      //Just in case the img is the decendent of a picture element, check for source tags
      removeJammingSource(lazyItem.parentNode);
      restoreSource(lazyItem);
    });
  }
  /**
   * Retrieve the elements from the 'lazy load' no script tags and prepare them for display
   */
  function setUp() {
    //Get all the noscript tags on the page
    var lazyLoadAreas = document.getElementsByTagName("noscript");
    for(var i = lazyLoadAreas.length; i--;) {
      var noScriptTag = lazyLoadAreas[i];
      //only process the ones marked for lazy loading
      if (!noScriptTag.hasAttribute("data-lazy-load")) continue;
      // The contents of a noscript tag are treated as text to JavaScript
      var lazyAreaHtml = noScriptTag.textContent||noScriptTag.innerHTML;
      // So we stick them in the innerHTML of a new div tag to 'load' them
      var lazyArea = document.createElement("div");
      lazyArea.innerHTML = lazyAreaHtml;
      //Only delay loading if we can use the IntersectionObserver to check for visibility
      if(!observer) {
        noScriptTag.parentNode.replaceChild(lazyArea, noScriptTag);
      } else {
        prepareLazyContents(lazyArea);
        noScriptTag.parentNode.replaceChild(lazyArea, noScriptTag);
      }
    }
  }
  //If the page has loaded already, run setup - if it hasn't, run as soon as it has.
  //Use requestAnimationFrame as this will propably cause repaints
  if (/comp|inter/.test(document.readyState)) {
    rAF(setUp);
  } else if ("addEventListener" in document) {
    document.addEventListener("DOMContentLoaded",
      function(){rAF(setUp);});
  } else {
    document.attachEvent("onreadystatechange", function() {
      if (document.readyState=="complete") {
        setUp();
      }
    });
  }
})();
<p>Scroll down to see lazy loading in action!</p>
<noscript><p>Even with JavaScript turned off, the images should still load.</p></noscript>
<p>Why are the assets in noscript tags? So that they will load for people who have turned JavaScript off!</p>
<p>(The conditional comments are becuase there is no way to fetch the contents of a noscript tag in IE8 and below.)</p>
<hr/>
<div style="height: 600px;"></div>
<hr/>
<!--[if (gt IE 8)|!(IE)]><!--><noscript data-lazy-load><!--<![endif]-->
  <img src="//upload.wikimedia.org/wikipedia/commons/2/27/F-16_Plan_Black_on_Circle_Light_Blue.svg?c=25" alt="This is an image used to demonstrate a lazy-loading trick." width="250" height="250">
  Here is some text on the outside to demonstrate the lack of reflows!
<!--[if (gt IE 8)|!(IE)]><!--></noscript><!--<![endif]-->
<hr/>
<!--[if (gt IE 8)|!(IE)]><!--><noscript data-lazy-load><!--<![endif]-->
  <picture>
    <img src="//upload.wikimedia.org/wikipedia/commons/2/27/F-16_Plan_Black_on_Circle_Light_Blue.svg?c=25" alt="This is an image used to demonstrate a lazy-loading trick." width="250" height="250">
  </picture>
  This one is a reponsive picture element!
<!--[if (gt IE 8)|!(IE)]><!--></noscript><!--<![endif]-->

只有支持Intersection Observer的浏览器上的延迟加载(因此不支持IE,但在撰写本文时约为87% of the world),但是图像将在所有浏览器中显示。