loading-attribute-polyfill
loading-attribute-polyfill copied to clipboard
Polyfill without <noscript>
Hi, is ist somehow possible to use this polyfill without adding a <noscript> wrapper?
@vincentschroeder thanks a lot for your question.
Sadly JavaScript seems the only possibility to hinder the images from being loaded at the very beginning - therefor it's necessary to manipulate an <img> HTML tag somehow to prevent that renderings engine downloader script from parsing that images URL and loading it upfront, but rewriting it to a regular <img> HTML tag after checking for the browsers capabilities (feature detection regarding support for the loading-attribute) and probably attaching an IntersectionObserver it case it doesn't support that loading-attribute.
Therefor manipulating that <img> HTML tag seems to be inevitable. Instead of other general solutions we've chosen to use a wrapping
Thanks for the fast reply. I have one quick question, do I have to add the Tag a second time? Which variant is correct?
Variant 1:
<noscript class="loading-lazy">
<img src="simpleimage.jpg" loading="lazy" alt=".." width="250" height="150" />
</noscript>
Variant 2
<noscript class="loading-lazy">
<img src="simpleimage.jpg" loading="lazy" alt=".." width="250" height="150" />
</noscript>
<img src="simpleimage.jpg" loading="lazy" alt=".." width="250" height="150" />
Easy. And Variant 1 is correct, compare to the source code on the demo page: https://mfranzke.github.io/loading-attribute-polyfill/demo/
Thanks. Unfortunately, I discovered now that the polyfill is not working in SPA React applications as soon you route the noscript tag will block all images form loading.
Do you have the chance to provide an example URL, or a jsfiddle?
@vincentschroeder I could think about to expose another method publicly that would receive some DOM node only to prepare and return it depending on browser capabilities check / feature detection, either to return it directly in case of that the browser is feasible to handle the loading-attribute natively even already, or to return it with the relevant optimizations and bindings regarding IntersectionObserver. Then anyone could easily leave out the noscript tag in case of frontend side handling of this script even only in their usecase. What do you think about that?
Hi @mfranzke, I have the issue when I'm using this in React, because it gives me this error: × TypeError: Cannot read property 'textContent' of null

This is the code I have (I've only changed the default code from React):
<div className="App">
<header className="App-header">
{ /************** HERE ************/}
<noscript className="loading-lazy">
<img src={logo} className="App-logo" alt="logo" />
</noscript>
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
and trying to trigger the prepareElement from a useEffect hook:
useEffect(() => {
loadingAttributePolyfill.prepareElement(document.querySelector('noscript.loading-lazy'));
},[])
What am I doing wrong?
@cyberdelphos, thanks a lot for your feedback. I'm not that fit in react - do you have the chance to prepare a JS Bin, JSFiddle or similar test environment where I could reproduce this?
Thanks. Unfortunately, I discovered now that the polyfill is not working in SPA
Reactapplications as soon you route thenoscripttag will block all images form loading.
@vincentschroeder (and probably @cyberdelphos)
You will need to write your noscript using dangerouslySetInnerHTML prop (see https://github.com/facebook/react/issues/15238), something like:
<noscript
className="loading-lazy"
dangerouslySetInnerHTML={{
__html: '<img alt="hello" loading="lazy" src="./hello.png" />'
}}
/>
worth to mention: this lib works on Client side only. as soon as you import the javascript file, it'll run some lines accessing window and other browser objects that aren't be available in SSR.
Sorry for the heavy react-oriented comment in here @mfranzke - Having said that, perhaps these lines (L25:L47) could be moved into prepareElement?
I seem to be experiencing a similar issue as Svelte blocks out anything in
I was having a similar issue while integrating this polyfill with React. However, I was able to solve the issue by doing the following. It works well on ~~SSR and~~ CSR with React 16.8.x and should work with 17.x.x too. Thank you for the beautiful polyfill.
import React, { useEffect, useRef } from 'react';
import ReadDOMServer from 'react-dom/server';
import loadingAttributePolyfillApi from 'loading-attribute-polyfill';
export function LoadingAttrPolyfill(props) {
const { children } = props;
const staticMarkup = ReadDOMServer.renderToStaticMarkup(children);
const noScriptRef = useRef(null);
useEffect(() => {
if (noScriptRef.current) {
loadingAttributePolyfillApi.prepareElement(noScriptRef.current);
}
}, []);
return <noscript ref={noScriptRef} dangerouslySetInnerHTML={{ __html: staticMarkup }} />;
}
export default function ImageCarousel() {
return (
<div>
<LoadingAttrPolyfill>
<img src='https://picsum.photos/200/300' alt='Random 1' />
</LoadingAttrPolyfill>
<LoadingAttrPolyfill>
<picture>
<source
width='846'
height='480'
srcset='https://picsum.photos/846/480'
media='(min-width: 768px)'
/>
<img width='375' height='250' src='https://picsum.photos/375/250' alt='Random 2' />
</picture>
</LoadingAttrPolyfill>
</div>
);
}
Thanks a lot @oyeharry, you‘re solution looks quite nice (from my limited knowledge about React).
@mfranzke It seems that we will get browser global errors from the polyfill if we try to run the above code on the SSR(NodeJS). For now, as a workaround, we have to load polyfill like the following.
Will you be interested if I open a PR with some defense for browser global so that they do not execute on the server? For instance, we will only execute window, HTMLImageElement, HTMLIFrameElement, and document if we are in the browser environment or maybe run the whole script only in the browser by adding a top-level window check.
import React, { useEffect, useRef } from 'react';
import ReadDOMServer from 'react-dom/server';
let loadingAttributePolyfillApi;
if (typeof window === 'object') {
/**
* import it only in the browser as this polyfill trying to execute globals
* which are only available in the browser.
*/
loadingAttributePolyfillApi = require('loading-attribute-polyfill').default;
}
export function LoadingAttrPolyfill(props) {
const { children } = props;
const staticMarkup = ReadDOMServer.renderToStaticMarkup(children);
const noScriptRef = useRef(null);
useEffect(() => {
if (noScriptRef.current && loadingAttributePolyfillApi) {
loadingAttributePolyfillApi.prepareElement(noScriptRef.current);
}
}, []);
return <noscript ref={noScriptRef} dangerouslySetInnerHTML={{ __html: staticMarkup }} />;
}
export default function ImageCarousel() {
return (
<div>
<LoadingAttrPolyfill>
<img src='https://picsum.photos/200/300' alt='Random 1' />
</LoadingAttrPolyfill>
<LoadingAttrPolyfill>
<picture>
<source
width='846'
height='480'
srcSet='https://picsum.photos/846/480'
media='(min-width: 768px)'
/>
<img width='375' height='250' src='https://picsum.photos/375/250' alt='Random 2' />
</picture>
</LoadingAttrPolyfill>
</div>
);
}
@oyeharry sounds good to me, thanks a lot for your support !
From my point of view there‘s nothing that I remember that could be beneficial to get executed on the server, as it‘s mainly about detecting and fixing browser capabilities, for which you would need that environment.
@mfranzke Exactly. I will open a PR soon for this update. Thanks!