Skip to content

Commit

Permalink
Resolved #10 and #11
Browse files Browse the repository at this point in the history
  • Loading branch information
malchata committed Aug 11, 2017
1 parent 6d214b3 commit 3aab5f3
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 81 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# yall.js (Yet Another Lazy Loader)
### (Y'all need to lazy load images)

yall.js is a very small image lazy loader for reasonably modern browsers (back to IE10) that weighs in at **1.09 KB** uglified. It depends on `classList`, `querySelectorAll`, supports the `<picture>` element and the `srcset` attribute. yall.js also uses `IntersectionObserver` if available, but will fall back to more traditional means if it's not. If you want to try out yall.js, grab the copy in the `dist` folder. Or you can clone the repo and check out the `test` folder. If you want to tinker, work with the copy in the `src` folder and build using `npm run build` (requires `npx`).
yall.js is a very small image lazy loader for reasonably modern browsers (back to IE10) that weighs in at 1.24 KB uglified (621 bytes with gzip/507 bytes with Brotli). It depends on `classList`, `querySelectorAll`, supports the `<picture>` element and the `srcset` attribute. yall.js also uses `IntersectionObserver` if available, but will fall back to more traditional means if it's not. If you want to try out yall.js, grab the copy in the `dist` folder. Or you can clone the repo and check out the `test` folder. If you want to tinker, work with the copy in the `src` folder and build using `npm run build` (requires `npx`).

## Usage Pattern

yall.js assumes a lot, but because it does, it's very straightforward. Here's the simplest `<img>` element use case. All you need to do is add a class of `lazy` to the `<img>` element you want to place lazy loading behavior on, and point the `data-src` attribute to an image source to lazy load:
yall.js assumes a lot, but because it does, it's very straightforward. Here's the simplest `<img>` element use case: Just add a class of `lazy` to an `<img>` element you want to lazy load, and point the `data-src` attribute to the desired image source:

```html
<img class="lazy" data-src="/img/image-to-lazy-load.jpg" src="/img/placeholder.jpg" alt="Alternative text to describe image.">
```

An optional placeholder can be specified in the `src` attribute. This will be replaced by the lazy loader when the element is scrolled into view.
An optional (but recommended) placeholder can be specified in the `src` attribute. This will be replaced by the lazy loader when the element is scrolled into view.

You can also use yall.js on `srcset` attributes, too:

Expand All @@ -31,7 +31,7 @@ You can use it on `<picture>` elements, too!

## What about users without JavaScript?

Easy solution. Slap on some `<noscript>` goodness:
Easy! Slap on some `<noscript>` goodness:

```html
<img class="lazy" data-src="/img/image-to-lazy-load.jpg" src="/img/placeholder.jpg" alt="Alternative text to describe image.">
Expand All @@ -40,36 +40,36 @@ Easy solution. Slap on some `<noscript>` goodness:
</noscript>
```

Then slap a `no-js` class onto the `<html>` element, and add this JavaScript one-liner in the `<head>` of the document:
Then place a `no-js` class on the `<html>` element, and finally add this JavaScript one-liner in the `<head>` of the document:

```html
<script>document.documentElement.classList.remove("no-js");</script>
```

From here, you can add some CSS that hides elements with a class of `lazy` when the `no-js` class is present on the `<html>` element:
This snippet will remove the `no-js` class from the `<html>` element as the page loads, but if JavaScript is turned off, this will never happen. From there, you can add some CSS that hides elements with a class of `lazy` when the `no-js` class is present on the `<html>` element:

```css
.no-js .lazy{
display: none;
}
```

If you want to see everything in action, check out the demo in the `test` folder.
To see everything in action, check out the demos in the `test` folder.

## Limitations

Yall.js will only attach listeners to markup that has been sent by the server. Lazy-loaded markup injected into the DOM using this pattern will not be recognized. This makes yall.js just fine for the vast majority of use cases, but if you have some complex rendering and injection going on, you should find a more feature-rich lazy loader. I don't have any intention to add this functionality.
yall.js will only attach listeners to markup that has been sent by the server. Lazy-loaded markup injected into the DOM using this pattern will not be recognized. This makes yall.js just fine for the vast majority of use cases, but if you have some really complex stuff going on, you should find a more feature-rich lazy loader.

Yall.js also doesn't try to guess at placeholder sizes to minimize disruption to the DOM. I would highly recommend you specify a placeholder `src` in your `<img>` tags. For example, you could serve an extremely low quality version of an image that has a heavy gaussian blur effect. This technique signals to the user that an image will appear in that space, but doesn't push many extra bytes down the wire.
yall.js also doesn't try to guess at placeholder sizes to minimize layout shifting. I would *highly recommend* you specify a placeholder `src` in your `<img>` tags, or develop a CSS placeholder solution. For example, you could serve an extremely low quality version of an image that has a heavy gaussian blur effect. This technique signals to the user that an image will appear in that space, but doesn't push many extra bytes down the wire.

## Contributing

I'm not interested in expanding the functionality of this package or refactoring it for readability. The goal of yall.js is to be as *small as humanly possible* while maintaining functionality. This means the source may not be the most readable. If you look at the source and can find ways to make this library more terse than it already is without breaking it, I welcome your contributions.
I'm not interested in expanding the functionality by much unless a good case can be made for increasing its performance without making it huge. The goal of yall.js is to be as small as reasonably possible while maintaining functionality. If you look at the source and can find ways to make this script's uglified output smaller than it already is without breaking it, I welcome your contributions.

## Special thanks

Props to [Kamran Ayub](https://github.com/kamranayub) for breaking the 1 KB barrier. Good lord, is he good at JS.
Props to [Kamran Ayub](https://github.com/kamranayub) for his tremendous help with performance tuning this script.

## Why another dumb lazy loader?

I explain how to write a lazy loader in Chapter 6 of my book [Web Performance in Action](https://www.manning.com/books/web-performance-in-action?a_aid=webopt&a_bid=63c31090) from Manning Publications. Otherwise, I wouldn't have bothered.
I explain how to write a lazy loader in Chapter 6 of my book [Web Performance in Action](https://www.manning.com/books/web-performance-in-action?a_aid=webopt&a_bid=63c31090) from Manning Publications. Otherwise, I wouldn't have bothered. The lazy loader in the book is much different than what you'll find here.
1 change: 0 additions & 1 deletion dist/yall-1.1.0.min.js

This file was deleted.

1 change: 1 addition & 0 deletions dist/yall-1.1.1.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion dist/yall.js

This file was deleted.

1 change: 1 addition & 0 deletions dist/yall.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "yall",
"version": "1.1.0",
"version": "1.1.1",
"description": "Yet Another Lazy Loader",
"scripts": {
"build": "npx uglifyjs --verbose -c -m --toplevel -o ./dist/yall.js ./src/yall.js"
"build": "npx uglifyjs --verbose --compress warnings --mangle -o ./dist/yall.js ./src/yall.js"
},
"repository": {
"type": "git",
Expand Down
121 changes: 57 additions & 64 deletions src/yall.js
Original file line number Diff line number Diff line change
@@ -1,98 +1,91 @@
/**
* yall.js version 1.1.0
* yall.js version 1.1.1
* Yet Another Lazy loader
* This library is intended to be very small. As such, some of may not be very readable.
* I don't normally code like this, but I wanted to see just how small I could get it!
**/

(function(window, document){
/**
* Everything below is defined in such a way to reduce
* uglifier output. If a method or string is only used once, it
* will be invoked normally. Otherwise, method shorthands are used.
**/

var
// Placeholders used for common method names.
qsa = "querySelectorAll",
fe = "forEach",
pn = "parentNode",
pr = "prototype",
gbcr = "getBoundingClientRect",
io = "IntersectionObserver",
// Placeholders used for "data-src" and "data-srcset" attribute references.
dss = "data-srcset",
// Placeholders used for event handler strings.
y = ["scroll", "touchmove"],
z = ["orientationchange", "resize"],
documentEvents = ["scroll", "touchmove"],
windowEvents = ["orientationchange", "resize"],
// Tracks if yall is currently processing. Used for throttling. Only matters if IntersectionObserver is unsupported.
active = 0,
// Replaces target attribute value with source attribute, if applicable
replaceAttr = function(node, sattr, tattr){
var v = node.getAttribute(sattr);
if(v){
node[tattr] = v;
node.removeAttribute(sattr);
}
},
// The handler to load the image
l = function(img){
if(img[pn].tagName == "PICTURE"){
Array[pr].slice.call(img[pn][qsa]("source"))[fe](function(source){
ra(source, dss, "srcset");
loadImage = function(img){
if(img.parentNode.tagName == "PICTURE"){
Array.prototype.slice.call(img.parentNode.querySelectorAll("source")).forEach(function(source){
replaceAttr(source, "data-srcset", "srcset");
});
}

ra(img, "data-src", "src");
ra(img, dss, "srcset");
replaceAttr(img, "data-src", "src");
replaceAttr(img, "data-srcset", "srcset");
img.classList.remove("lazy");
els.splice(els.indexOf(img), 1);
elements.splice(elements.indexOf(img), 1);
},
// Tracks if yall is currently processing. Used for throttling. Only matters if IntersectionObserver is unsupported.
a = 0,
// A multiple event binding handler.
b = function(obj, handlers, fn, add){
handlers[fe](function(handler){
add ? obj.addEventListener(handler, fn) : obj.removeEventListener(handler, fn);
multiBind = function(obj, handlers, fn, remove){
handlers.forEach(function(handler){
remove ? obj.removeEventListener(handler, fn) : obj.addEventListener(handler, fn);
});
},
// Replaces target attribute value with source attribute, if applicable
ra = function(node, sattr, tattr){
var v = node.getAttribute(sattr);
if(v){
node[tattr] = v;
node.removeAttribute(sattr);
}
},
// The guts of the lazy loader (now only used when IntersectionObserver is not supported)
ll = function(){
if(!els.length) (b(document, y, ll), b(window, z, ll));
yall = function(){
if(!elements.length){
multiBind(document, documentEvents, yall, 1);
multiBind(window, windowEvents, yall, 1);
}

if(!active){
active = 1;

if(!a){
a = 1;
setTimeout(function(){
els[fe](function(img){
if((img[gbcr]().top <= window.innerHeight && img[gbcr]().bottom >= 0) && getComputedStyle(img).display != "none") l(img);
elements.forEach(function(img){
if((img.getBoundingClientRect().top <= window.innerHeight && img.getBoundingClientRect().bottom >= 0) && getComputedStyle(img).display != "none"){
loadImage(img);
}
});

a = 0;
active = 0;
}, 200);
}
};

// Everything's kicked off on DOMContentLoaded
b(document, ["DOMContentLoaded"], function(){
els = Array[pr].slice.call(document[qsa]("img.lazy"));
multiBind(document, ["DOMContentLoaded"], function(){
elements = Array.prototype.slice.call(document.querySelectorAll("img.lazy"));

if(elements.length){
// This compatibility check has been taken from https://github.com/WICG/IntersectionObserver/blob/gh-pages/polyfill/intersection-observer.js
if("IntersectionObserver" in window && "IntersectionObserverEntry" in window && "intersectionRatio" in IntersectionObserverEntry.prototype){
ob = new IntersectionObserver(function(entries, observer){
entries.forEach(function(entry){
if(entry.isIntersecting){
loadImage(entry.target);
if(!elements.length) observer.disconnect();
}
});
});

if(els.length){
if(io in window && io+"Entry" in window && "intersectionRatio" in window[io+"Entry"][pr]){
els[fe](function(img){
new window[io](function(entries, observer){
entries[fe](function(entry){
if(entry.isIntersecting){
l(img);
observer.disconnect();
}
});
}).observe(img);
elements.forEach(function(img){
ob.observe(img);
});

return;
}

ll();
b(document, y, ll, 1);
b(window, z, ll, 1);
// If IntersectionObserver isn't available, we'll do things the old way.
yall();
multiBind(document, documentEvents, yall);
multiBind(window, windowEvents, yall);
}
}, 1);
});
})(window, document);
2 changes: 1 addition & 1 deletion test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,6 @@ <h2>Lazy loading <code>&lt;picture&gt;</code></h2>
<img src="test-768w.jpg" alt="&lt;noscript&gt; fallback for &lt;img&gt; with src" title="&lt;noscript&gt; fallback for &lt;img&gt; with src">
</picture>
</noscript>
<script src="../dist/yall-1.1.0.min.js" defer></script>
<script src="../dist/yall-1.1.1.min.js" defer></script>
</body>
</html>

0 comments on commit 3aab5f3

Please sign in to comment.