JAIL lazy loads images through data-src and a scroll watcher
JqueryAsynchImageLoader Plugin for jQuery
At a glance
- What is it?
- JAIL is a jQuery plugin that defers image downloads until the image scrolls into the viewport, and the interesting parts are the thirteen configuration options and the two invariants it needs from your markup. It moves the real URL into a data-src attribute, leaves a placeholder in src, and expects a noscript fallback so the page still works without JavaScript.
- Who is it for?
- Adopt JAIL if you are on jQuery and want image lazy loading with more control than the browser's native loading attribute gives you, since the trigger, offset and placeholder options cover cases native lazy loading does not. Skip it if you are on a modern framework, because native loading="lazy" now covers most of this, and if you need server-side rendering of the image list.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The markup change is the whole contract
JAIL is a jQuery plugin that lazy loads images, making your page load faster, and the first requirement is that you change your HTML.
The data-src attribute, which is an HTML5 data attribute, should contain the location of the image, and instead the src attribute should contain a placeholder such as a really tiny image, for example 1 pixel by 1 pixel. The suggestion is also to add a noscript block so that in case the user does not have JavaScript enabled, the images will be displayed, which is the progressive enhancement approach.
<img class="lazy" src="/img/blank.gif" data-src="/img/image1.jpg"/>
<noscript>
<img src="/img/image1.jpg"/>
</noscript>Two details are worth noting. The placeholder is not optional in the sense of style, it is how the browser knows the image element has a size before the real file arrives, which is what stops the page from reflowing as images load. And the noscript block duplicates the real URL in a plain img tag, so a reader with JavaScript disabled still sees the pictures rather than a page of broken images.
That noscript duplication is the price of the technique and it is worth being honest about it. Every image appears twice in the source, and the search engines and screen readers have to deal with the pair.
The basic usage is then to import jquery, jail.js and call the jail() function on the images you want to lazy load.
<script src="/lib/jquery.js"></script>
<script src="/src/jail.js"></script>
<script>
$(function(){
$('img.lazy').jail();
});
</script>And it is advisable to call jail() after the DOM has been constructed, in the document ready handler, which is what the $(function(){ ... }) wrapper is doing.
The behaviour is then as expected. The images in the viewport are loaded straight away after the DOM is ready, and as soon as other images become visible in the viewport, for instance after the user scrolls down, they are lazy loaded.
Thirteen options, and the ones that change when loading happens
The config section lists the options you pass when you first call jail(), and they fall into a few groups.
The group that changes when an image loads is the most interesting. The timeout option is the number of milliseconds after which the images will be loaded, defaulting to 10. The triggerElement option is a selector that triggers the images to be loaded, defaulting to null. The event option is the event that triggers the image to be loaded, and you can choose among load, click, mouseover and scroll, defaulting to load.
So there are three different triggers and they are not the same thing. The default, load, means the image loads when it enters the viewport. A timeout means it loads after a fixed delay regardless of visibility, which is a way of using the plugin purely as a deferred batch load. And an event on a trigger element means the image loads when the user interacts with something else, which is the pattern for a gallery where the images appear after you click into the section.
The offset option is the viewport margin. An offset of 500 would cause any images that are less than 500 pixels below the bottom of the window or 500 pixels above the top of the window to load, and the default is 0.
That is the option that trades bandwidth for blank space. With an offset of zero, an image is requested at the moment it appears, which is right for a reader who scrolls slowly. With a large offset, images arrive before they are needed, which avoids the flash of a placeholder if the connection is fast.
The loadHiddenImages option is a boolean, defaulting to false so that hidden images are not loaded, and the documented case is images with or under a display:none, or with hidden visibility, or not visible inside an overflow:hidden element. The documentation example sets it to true, so the naming is worth noting: the option controls whether such images are loaded at all.
Then there is the presentation group. The effect option is any jQuery effect that makes the images display, for example fadeIn, defaulting to null, with a note that if you are loading a large number of images it is best not to use this setting. The speed option is a string or number determining how long the animation will run, defaulting to 400. The placeholder option is the location of an image, such as a loader, to display while waiting for the images to be loaded, defaulting to an empty string.
And two callbacks. The callback is a function called after all the images are loaded, defaulting to an empty string, and callbackAfterEachImage is called after each image is loaded.
The final option is id, a unique identifier for this jail instance, defaulting to jail.
The examples show the patterns you would actually write
The how-to-invoke section gives a series of worked examples, and they read as a catalogue of the situations the plugin is meant for.
The first loads images after clicking on an anchor with the id link, with the images fading in at speed 500 and a placeholder specified.
$(function(){
$('img.lazy').jail({
triggerElement:'a#link',
event: 'click',
effect: 'fadeIn',
speed : 500,
placeholder : 'img/loader.gif',
callback : SA.setActive
});
});The second initially loads the visible images within a container and then, as you scroll inside that container, images become visible. The triggerElement is the container and the event is scroll, which is the key detail: the scroll listener is attached to the scrolling element rather than to the window, which is what you need for an image inside an independently scrollable panel.
The third loads the hidden images after one second, which is the timeout option used on its own with a value of 1000.
The fourth loads the images after hovering on the placeholder for the image, with the event set to mouseover and a placeholder of img/loader.gif.
The fifth loads images that are up to 300 pixels over or below the viewport, which is the offset option at 300.
The sixth uses both callbacks to demonstrate the difference between them, with an alert saying all the images are loaded from the callback and an alert saying one image is loaded from callbackAfterEachImage.
callback : (function(){alert("All the images are loaded");}),
callbackAfterEachImage : function() {alert("one image is loaded");}
});
});The seventh is the hidden images case, with loadHiddenImages set to true, and the documented description is to ignore hidden images to be loaded, covering images with or under a display:none or with hidden visibility or not visible inside an overflow:hidden element.
And the eighth is the one that explains what the id option is for. You create multiple jail instances for various image collections, and it is required when asynchronously rendering templates that contain images that should also be jailed.
requirejs.config({
baseUrl: 'lib',
paths: {
app: 'src'
},
shim: {
'app/jail': ['jquery']
}That last one is a real requirement rather than a convenience. The plugin needs to know which images belong to which instance so it does not double-handle them, and the id is how you say that.
The demo folder holds a few more examples, and the documentation points at example 14 and example 15 there for the AMD case.
AMD support and how the plugin is built
The plugin supports AMD through the define() method, and if you use RequireJS with a version above 2.0 you require it like this.
requirejs.config({
baseUrl: 'lib',
paths: {
app: 'src'
},
shim: {
'app/jail': ['jquery']
}
});
require(["jquery", "app/jail"], function() {
$(function(){
$('img.lazy').jail();
});
});The shim entry is the part that matters, because it declares the jQuery dependency for a module that is not itself written as an AMD module. Without it RequireJS would load jail.js before jQuery.
The build and test section is conventional. After getting node and npm, you install the dependencies with npm install, and you run the Jasmine specs with npm test.
So the project has a test suite, which matters for a plugin whose behaviour depends on scroll positions and viewport measurements, since those are exactly the things that are easy to get subtly wrong and hard to notice.
The build status badge points at Travis CI, which dates the tooling.
The project is JavaScript, MIT licensed, with the homepage at sebarmeli.github.io/JAIL, the last push on 2026-04-10 and no GitHub releases.
Where this sits against native lazy loading
The honest comparison is with the browser's own loading attribute, which was added for exactly this purpose and is supported across the major browsers.
Native lazy loading has the advantage of no JavaScript, no markup change beyond adding the attribute, and no risk of the plugin failing on a page where the script does not run. It also does the deferral at the network layer rather than by measuring the viewport in script, which means it is closer to the browser's own scheduling decisions.
What native lazy loading does not do is the rest of the option set. You cannot ask it to defer until a click on a particular link, you cannot give it an offset so images load before they are needed, you cannot supply a placeholder image, you cannot run a callback per image, and you cannot have it ignore hidden images. Those are the thirteen options, and each of them corresponds to a case where the native attribute has no answer.
The offset is the most generally useful of them. A native lazy load starts fetching an image as it approaches the viewport, and on a slow connection the reader sees a blank space. A generous offset pulls the fetch forward, which on a fast connection is invisible and on a slow one means the image is there by the time it is needed.
The cost of the plugin approach is the one the documentation is upfront about. You duplicate the URL in a noscript block, you add a placeholder in src, and you need the script to run at all for the images to appear. That is a lot of markup churn for a benefit that a modern browser mostly provides on its own.
So the sensible reading is that JAIL is a jQuery-era answer that remains useful in the specific cases where the trigger, the offset or the per-image callbacks matter, and that a new project on jQuery would start from the native attribute and add a small script for the offsets.
Editorial conclusion
Adopt JAIL if you are on jQuery and want image lazy loading with more control than the browser's native loading attribute gives you, since the trigger, offset and placeholder options cover cases native lazy loading does not. Skip it if you are on a modern framework, because native loading="lazy" now covers most of this, and if you need server-side rendering of the image list. Verify first that your markup follows the data-src and noscript pattern, since the plugin reads the real URL from data-src and will do nothing without it, and that the jQuery version you ship supports the plugin.
Frequently asked questions
How do I use JAIL to lazy load images?
Move the image URL into the data-src attribute and leave a placeholder such as a 1 by 1 pixel image in src, ideally with a noscript block that repeats the real URL so the images show without JavaScript. Then import jquery and jail.js and call jail() on the images in the document ready handler, for example `$('img.lazy').jail();`. Images already in the viewport load straight away and the rest load as they scroll into view.
Which JAIL options control when an image is loaded?
Three options. The timeout is the number of milliseconds after which images load, defaulting to 10. The triggerElement is a selector that triggers loading, and the event is the event that triggers it, which you can choose among load, click, mouseover and scroll, defaulting to load. There is also the offset, where an offset of 500 loads images less than 500 pixels below the bottom of the window or above the top, defaulting to 0, and loadHiddenImages, which defaults to false.
Can JAIL load images inside a scrollable container and handle templates loaded later?
Yes, by setting triggerElement to the scrolling container and event to scroll, and by giving separate jail instances distinct id values. The documentation describes creating multiple jail instances for various image collections, which is required when asynchronously rendering templates that contain images that should also be jailed, with the id of page and the id of template used in the two examples.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sebarmeli-jail)Was this useful?