Comparing version 1.4.3 to 2.0.0
/*! | ||
* Layzr.js 1.4.2 - A small, fast, modern, and dependency-free library for lazy loading. | ||
* Copyright (c) 2015 Michael Cavalea - http://callmecavs.github.io/layzr.js/ | ||
* Layzr.js 2.0.0 - A small, fast, and modern library for lazy loading images. | ||
* Copyright (c) 2016 Michael Cavalea - http://callmecavs.github.io/layzr.js/ | ||
* License: MIT | ||
*/ | ||
!function(t,i){"function"==typeof define&&define.amd?define([],i):"object"==typeof exports?module.exports=i():t.Layzr=i()}(this,function(){"use strict";function t(t){this._lastScroll=0,this._ticking=!1,t=t||{},this._optionsContainer=document.querySelector(t.container)||window,this._optionsSelector=t.selector||"[data-layzr]",this._optionsAttr=t.attr||"data-layzr",this._optionsAttrRetina=t.retinaAttr||"data-layzr-retina",this._optionsAttrBg=t.bgAttr||"data-layzr-bg",this._optionsAttrHidden=t.hiddenAttr||"data-layzr-hidden",this._optionsThreshold=t.threshold||0,this._optionsCallback=t.callback||null,this._retina=window.devicePixelRatio>1,this._srcAttr=this._retina?this._optionsAttrRetina:this._optionsAttr,this._nodes=document.querySelectorAll(this._optionsSelector),this._handlerBind=this._requestScroll.bind(this),this._create()}return t.prototype._requestScroll=function(){this._lastScroll=this._optionsContainer===window?window.pageYOffset:this._optionsContainer.scrollTop+this._getOffset(this._optionsContainer),this._requestTick()},t.prototype._requestTick=function(){this._ticking||(requestAnimationFrame(this.update.bind(this)),this._ticking=!0)},t.prototype._getOffset=function(t){return t.getBoundingClientRect().top+window.pageYOffset},t.prototype._getContainerHeight=function(){return this._optionsContainer.innerHeight||this._optionsContainer.offsetHeight},t.prototype._create=function(){this._handlerBind(),this._optionsContainer.addEventListener("scroll",this._handlerBind,!1),this._optionsContainer.addEventListener("resize",this._handlerBind,!1)},t.prototype._destroy=function(){this._optionsContainer.removeEventListener("scroll",this._handlerBind,!1),this._optionsContainer.removeEventListener("resize",this._handlerBind,!1)},t.prototype._inViewport=function(t){var i=this._lastScroll,e=i+this._getContainerHeight(),o=this._getOffset(t),n=o+this._getContainerHeight(),s=this._optionsThreshold/100*window.innerHeight;return n>=i-s&&e+s>=o&&!t.hasAttribute(this._optionsAttrHidden)},t.prototype._reveal=function(t){var i=t.getAttribute(this._srcAttr)||t.getAttribute(this._optionsAttr);t.hasAttribute(this._optionsAttrBg)?t.style.backgroundImage="url("+i+")":t.setAttribute("src",i),"function"==typeof this._optionsCallback&&this._optionsCallback.call(t),t.removeAttribute(this._optionsAttr),t.removeAttribute(this._optionsAttrRetina),t.removeAttribute(this._optionsAttrBg),t.removeAttribute(this._optionsAttrHidden)},t.prototype.updateSelector=function(){this._nodes=document.querySelectorAll(this._optionsSelector)},t.prototype.update=function(){for(var t=this._nodes.length,i=0;t>i;i++){var e=this._nodes[i];e.hasAttribute(this._optionsAttr)&&this._inViewport(e)&&this._reveal(e)}this._ticking=!1},t}); | ||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Layzr=e()}}(function(){var e;return function t(e,n,r){function o(u,f){if(!n[u]){if(!e[u]){var s="function"==typeof require&&require;if(!f&&s)return s(u,!0);if(i)return i(u,!0);var c=new Error("Cannot find module '"+u+"'");throw c.code="MODULE_NOT_FOUND",c}var a=n[u]={exports:{}};e[u][0].call(a.exports,function(t){var n=e[u][1][t];return o(n?n:t)},a,a.exports,t,e,n,r)}return n[u].exports}for(var i="function"==typeof require&&require,u=0;u<r.length;u++)o(r[u]);return o}({1:[function(t,n,r){(function(o){!function(t){if("object"==typeof r&&"undefined"!=typeof n)n.exports=t();else if("function"==typeof e&&e.amd)e([],t);else{var i;i="undefined"!=typeof window?window:"undefined"!=typeof o?o:"undefined"!=typeof self?self:this,i.Knot=t()}}(function(){return function e(n,r,o){function i(f,s){if(!r[f]){if(!n[f]){var c="function"==typeof t&&t;if(!s&&c)return c(f,!0);if(u)return u(f,!0);var a=new Error("Cannot find module '"+f+"'");throw a.code="MODULE_NOT_FOUND",a}var d=r[f]={exports:{}};n[f][0].call(d.exports,function(e){var t=n[f][1][e];return i(t?t:e)},d,d.exports,e,n,r,o)}return r[f].exports}for(var u="function"==typeof t&&t,f=0;f<o.length;f++)i(o[f]);return i}({1:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n["default"]=function(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];return e.events={},e.on=function(t,n){return e.events[t]=e.events[t]||[],e.events[t].push(n),e},e.once=function(t,n){return n._once=!0,e.on(t,n),e},e.off=function(t,n){return 2===arguments.length?e.events[t].splice(e.events[t].indexOf(n),1):delete e.events[t],e},e.emit=function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),o=1;n>o;o++)r[o-1]=arguments[o];var i=e.events[t]&&e.events[t].slice();return i&&i.forEach(function(n){n._once&&e.off(t,n),n.apply(e,r)}),e},e},t.exports=n["default"]},{}]},{},[1])(1)})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],2:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(n,"__esModule",{value:!0});var o=e("knot.js"),i=r(o);n["default"]=function(){function e(){a=window.scrollY||window.pageYOffset,t()}function t(){d||(requestAnimationFrame(function(){return f()}),d=!0)}function n(e){return e.getBoundingClientRect().top+a}function r(e){var t=a,r=t+v,o=n(e),i=o+e.offsetHeight,u=100*p.threshold/v;return i>=t-u&&r+u>=o}function o(e){if(w.emit("src:before",e),m&&e.hasAttribute(p.srcset))e.setAttribute("srcset",e.getAttribute(p.srcset));else{var t=h>1&&e.getAttribute(p.retina);e.setAttribute("src",t||e.getAttribute(p.normal))}w.emit("src:after",e),[p.normal,p.retina,p.srcset].forEach(function(t){return e.removeAttribute(t)}),s()}function u(t){var n=t?"addEventListener":"removeEventListener";return["scroll","resize"].forEach(function(t){return window[n](t,e)}),this}function f(){return v=window.innerHeight,l.forEach(function(e){return r(e)&&o(e)}),d=!1,this}function s(){return l=Array.prototype.slice.call(document.querySelectorAll("["+p.normal+"]")),this}var c=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],a=void 0,d=void 0,l=void 0,v=void 0,p={normal:c.normal||"data-normal",retina:c.retina||"data-retina",srcset:c.srcset||"data-srcset",threshold:c.threshold||0},m=document.body.classList.contains("srcset")||"srcset"in document.createElement("img"),h=window.devicePixelRatio||window.screen.deviceXDPI/window.screen.logicalXDPI,w=(0,i["default"])({handlers:u,check:f,update:s});return w},t.exports=n["default"]},{"knot.js":1}]},{},[2])(2)}); |
{ | ||
"name": "layzr.js", | ||
"version": "1.4.3", | ||
"description": "A small, fast, modern, and dependency-free library for lazy loading.", | ||
"version": "2.0.0", | ||
"description": "A small, fast, and modern library for lazy loading images.", | ||
"homepage": "http://callmecavs.github.io/layzr.js/", | ||
"main": "dist/layzr.js", | ||
"main": "dist/layzr.min.js", | ||
@@ -26,13 +26,23 @@ "author": "Michael Cavalea", | ||
"dependencies": { | ||
"knot.js": "^1.0.1" | ||
}, | ||
"devDependencies": { | ||
"gulp": "^3.8.10", | ||
"gulp-connect": "^2.2.0", | ||
"gulp-header": "^1.2.2", | ||
"gulp-load-plugins": "^0.8.0", | ||
"gulp-plumber": "^1.0.0", | ||
"gulp-rename": "^1.2.2", | ||
"gulp-uglify": "^1.0.2", | ||
"gulp-umd": "^0.1.3", | ||
"node-notifier": "^4.1.2" | ||
"babel-core": "^6.4.0", | ||
"babel-plugin-add-module-exports": "^0.1.2", | ||
"babel-preset-es2015": "^6.3.13", | ||
"babelify": "^7.2.0", | ||
"browser-sync": "^2.11.0", | ||
"browserify": "^13.0.0", | ||
"gulp": "^3.9.0", | ||
"gulp-header": "^1.7.1", | ||
"gulp-sourcemaps": "^1.6.0", | ||
"gulp-uglify": "^1.5.1", | ||
"lodash.assign": "^4.0.0", | ||
"node-notifier": "^4.4.0", | ||
"vinyl-buffer": "^1.0.0", | ||
"vinyl-source-stream": "^1.1.0", | ||
"watchify": "^3.7.0" | ||
} | ||
} |
308
README.md
# Layzr.js | ||
[![Layzr.js on NPM](https://img.shields.io/npm/v/layzr.js.svg)](https://www.npmjs.com/package/layzr.js) [![Layzr.js on Bower](https://img.shields.io/bower/v/layzr.js.svg)](http://bower.io/search/?q=layzr.js) [![Layzr.js on Gitter](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/callmecavs/layzr.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) | ||
[![Layzr.js on NPM](https://img.shields.io/npm/v/layzr.js.svg)](https://www.npmjs.com/package/layzr.js) [![Layzr.js on Gitter](https://img.shields.io/badge/gitter-join%20chat-green.svg)](https://gitter.im/callmecavs/layzr.js) | ||
A small, fast, modern, and dependency-free library for lazy loading. | ||
A small, fast, and modern library for lazy loading images. | ||
Lazy loading boosts page speed by deferring the loading of images until they're in (or near) the viewport. This library makes it completely painless - maximizing speed by keeping options to a minimum. | ||
* [Demo Page](http://callmecavs.github.io/layzr.js/) | ||
## Usage | ||
## Getting Started | ||
Follow these steps: | ||
1. [Install](https://github.com/callmecavs/layzr.js#install) | ||
2. [Image Setup](https://github.com/callmecavs/layzr.js#image-setup) | ||
3. [Instance Creation](https://github.com/callmecavs/layzr.js#instance-creation) | ||
1. [Install](#install) | ||
2. [Setup Images](#setup-images) | ||
3. [Instantiate](#instantiate) | ||
4. [Review Options](#options) | ||
5. [Review Events](#events) | ||
6. [Review API](#api) | ||
7. **[Review Example Code](https://github.com/callmecavs/layzr.js/tree/master/examples)** | ||
* Examples progress like a coffee addiction: small -> medium -> large | ||
### Install | ||
## Install | ||
Load the script. | ||
Layzr was developed with a modern JavaScript workflow in mind. Though not required, it's convenient to have a build system in place that can transpile ES6, and bundle modules. For a minimal boilerplate that does so, try [outset](https://github.com/callmecavs/outset). | ||
[Download it](https://github.com/callmecavs/layzr.js/archive/master.zip), install it with [NPM](https://www.npmjs.com/package/layzr.js), or install it with [Bower](http://bower.io/search/?q=layzr.js). | ||
Choose an installation option based on your workflow: | ||
```html | ||
<script src="layzr.js"></script> | ||
* [npm](#npm) | ||
* [CDN](#cdn) | ||
* [Download](#download) | ||
Refer to the [releases](https://github.com/callmecavs/layzr.js/releases) page for version specific information. | ||
### npm | ||
Install Layzr, and add it to your `package.json` dependencies. | ||
``` | ||
$ npm install layzr.js --save | ||
``` | ||
#### CDN | ||
Then `import` it into the file where you'll use it. | ||
The script is also available via CDN. | ||
```es6 | ||
import Layzr from 'layzr.js' | ||
``` | ||
In the examples below, replace `{version}` with your desired version. Refer to the [releases](https://github.com/callmecavs/layzr.js/releases) page for version info. | ||
### CDN | ||
##### [cdnjs](https://cdnjs.com/libraries/layzr.js) | ||
Copy and paste one of the following `<script>` tags. Note the version number in the `src` attributes. | ||
#### [jsDelivr](http://www.jsdelivr.com/projects/layzr.js) | ||
```html | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/layzr.js/{version}/layzr.min.js"></script> | ||
<script src="https://cdn.jsdelivr.net/layzr.js/2.0.0/layzr.min.js"></script> | ||
``` | ||
##### [jsDelivr](http://www.jsdelivr.com/#!layzr.js) | ||
#### [cdnjs](https://cdnjs.com/libraries/layzr.js) | ||
```html | ||
<script src="https://cdn.jsdelivr.net/layzr.js/{version}/layzr.min.js"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/layzr.js/2.0.0/layzr.min.js"></script> | ||
``` | ||
### Image Setup | ||
### Download | ||
For each `img` and/or `iframe` you want to lazy load, put the `src` in the `data-layzr` attribute. | ||
[Download](https://github.com/callmecavs/layzr.js/archive/master.zip) the latest version, and load the script in the `dist` folder. | ||
```html | ||
<img data-layzr="image/source"> | ||
<iframe data-layzr="media/source"></iframe> | ||
<script src="layzr.min.js"></script> | ||
``` | ||
This is the only _required_ attribute. Advanced, _optional_ configuration follows: | ||
## Setup Images | ||
#### (Optional) Placeholders | ||
Layzr intelligently chooses the best image source available **based on an image's data attributes and browser feature detection**. | ||
Include a placeholder, via the `src` attribute. | ||
* In browsers that [support `srcset`](http://caniuse.com/#search=srcset), if available, it will be used to determine the source. | ||
* In browsers that don't, the normal or retina source will be chosen based on the [devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) and availability. | ||
Images without a placeholder - _before_ they're loaded - may impact layout (no width/height), or appear broken. | ||
Note that all attribute names are configureable via the [options](#options) passed to Layzr. To indicate potential sources, add the following attributes to your images: | ||
| Name | Required | Optional | | ||
| :----------------------------- | :------: | :------: | | ||
| [`data-normal`](#data-normal) | ✓ | | | ||
| [`data-retina`](#data-retina) | | ✓ | | ||
| [`data-srcset`](#data-srcset) | | ✓ | | ||
### data-normal | ||
Put the _normal resolution_ source in the `data-normal` attribute. | ||
```html | ||
<img src="optional/placeholder" data-layzr="image/source"> | ||
<img data-normal="normal.jpg"> | ||
``` | ||
#### (Optional) Retina Support | ||
Note that Layzr **selects elements using this attribute**. Elements without it won't be tracked, and will never load. | ||
Include a retina (high-resolution) version of the image in the `data-layzr-retina` attribute. This source will only be loaded if the [devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) is greater than 1. | ||
### data-retina | ||
Ensure the proper CSS is in place to display both regular and retina images correctly. This library handles the loading, but not the displaying, of elements. | ||
Add the _retina/high resolution_ source in the `data-retina` attribute. | ||
```html | ||
<img data-layzr="image/source" data-layzr-retina="optional/retina/source"> | ||
<img data-normal="normal.jpg" data-retina="retina.jpg"> | ||
``` | ||
#### (Optional) Background Images | ||
### data-srcset | ||
Include the `data-layzr-bg` attribute to load the source as a background image. | ||
Add the _source set_ in the `data-srcset` attribute. For information on the proper syntax, read the official [specification](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img). | ||
The `data-layzr-bg` attribute should be valueless - the image source is still taken from the `data-layzr` and `data-layzr-retina` attributes. | ||
```html | ||
<img data-layzr="image/source" data-layzr-bg> | ||
<img data-normal="normal.jpg" data-retina="retina.jpg" data-srcset="small.jpg 320w, medium.jpg 768w, large.jpg 1024w"> | ||
``` | ||
#### (Optional) Hidden Images | ||
## Instantiate | ||
Include the `data-layzr-hidden` attribute to prevent an image from loading. | ||
Create an instance, optionally passing in your [options](#options). | ||
Removing this attribute _will not load the image_ - the user will still need to scroll, and the element will still need to be in the viewport. | ||
Be sure to **assign your Layzr instance to a variable**. Using your instance, you can: | ||
```html | ||
<img data-layzr="image/source" data-layzr-hidden> | ||
``` | ||
* start and stop the event listeners | ||
* add and remove event handlers | ||
* accommodate dynamically added elements | ||
### Instance Creation | ||
```es6 | ||
// using the default options | ||
Create a new instance, and that's it! | ||
const instance = Layzr() | ||
```javascript | ||
var layzr = new Layzr(); | ||
// using custom options | ||
const instance = Layzr({ | ||
// ... | ||
}) | ||
``` | ||
Documentation for all options follows: | ||
Options are explained in the following section. | ||
## Options | ||
Defaults shown below: | ||
Default options are shown below, and an explanation of each follows: | ||
```javascript | ||
var layzr = new Layzr({ | ||
container: null, | ||
selector: '[data-layzr]', | ||
attr: 'data-layzr', | ||
retinaAttr: 'data-layzr-retina', | ||
bgAttr: 'data-layzr-bg', | ||
hiddenAttr: 'data-layzr-hidden', | ||
threshold: 0, | ||
callback: null | ||
}); | ||
```es6 | ||
const instance = Layzr({ | ||
normal: 'data-normal', | ||
retina: 'data-retina', | ||
srcset: 'data-srcset', | ||
threshold: 0 | ||
}) | ||
``` | ||
Explanation of each follows: | ||
### normal | ||
### container | ||
Customize the attribute the normal resolution source is taken from. | ||
Customize the container that holds the elements to lazy load - using CSS selector syntax. This option may be useful when building single page applications. | ||
```es6 | ||
const instance = Layzr({ | ||
normal: 'data-normal' | ||
}) | ||
``` | ||
Note that `window` is assumed to be the container if this option is set to `null`. | ||
### retina | ||
```javascript | ||
var layzr = new Layzr({ | ||
container: null | ||
}); | ||
Customize the attribute the retina/high resolution source is taken from. | ||
```es6 | ||
const instance = Layzr({ | ||
retina: 'data-retina' | ||
}) | ||
``` | ||
### selector | ||
### srcset | ||
Customize the selector used to find elements to lazy load - using CSS selector syntax. | ||
Customize the attribute the source set is taken from. | ||
```javascript | ||
var layzr = new Layzr({ | ||
selector: '[data-layzr]' | ||
}); | ||
```es6 | ||
const instance = Layzr({ | ||
srcset: 'data-srcset' | ||
}) | ||
``` | ||
### attr / retinaAttr | ||
### threshold | ||
Customize the data attributes that image sources are taken from. | ||
Adjust when images load, relative to the viewport. _Positive values make images load sooner, negative values make images load later_. | ||
```javascript | ||
var layzr = new Layzr({ | ||
attr: 'data-layzr', | ||
retinaAttr: 'data-layzr-retina' | ||
}); | ||
Threshold is a percentage of the viewport height, identical to the CSS `vh` unit. | ||
```es6 | ||
const instance = Layzr({ | ||
threshold: 0 | ||
}) | ||
``` | ||
### bgAttr | ||
## Events | ||
Customize the data attribute that loads images as a background. | ||
Layzr instances are extended with [Knot.js](https://github.com/callmecavs/knot.js), a browser-based event emitter. Use the event emitter syntax to add and remove handlers. Review the emitter syntax [here](https://github.com/callmecavs/knot.js#api). | ||
```javascript | ||
var layzr = new Layzr({ | ||
bgAttr: 'data-layzr-bg' | ||
}); | ||
Layzr emits the following events: | ||
* [src:before](#srcbefore) | ||
* [src:after](#srcafter) | ||
### src:before | ||
This event is emitted immediately _before an image source is set_. The image node is passed to the event handler. | ||
```es6 | ||
instance.on('src:before', (element) => { | ||
// 'this' is your Layzr instance | ||
// 'element' is the image node | ||
// ... | ||
}) | ||
``` | ||
### hiddenAttr | ||
Load event handlers should be attached using this event. See the [example](https://github.com/callmecavs/layzr.js/blob/v2.0.0/examples/large.js), and note the [caveats](https://api.jquery.com/load-event/) associated with image load events before proceeding. | ||
Customize the data attribute that prevents images from loading. | ||
### src:after | ||
```javascript | ||
var layzr = new Layzr({ | ||
hiddenAttr: 'data-layzr-hidden' | ||
}); | ||
This event is emitted immediately _after an image source is set_. The image node is passed to the event handler. | ||
```es6 | ||
instance.on('src:after', (element) => { | ||
// 'this' is your Layzr instance | ||
// 'element' is the image node | ||
// ... | ||
}) | ||
``` | ||
### threshold | ||
Note that the image is not necessarily done loading when this event fires. | ||
Adjust when images load, relative to the viewport. Positive values make elements load _sooner_. | ||
## API | ||
Threshold is a percentage of the viewport height - think of it as similar to the CSS `vh` unit. | ||
All API methods are **chainable**, including those from the emitter. | ||
```javascript | ||
// load images when they're half the viewport height away from the bottom of the viewport | ||
### .handlers(flag) | ||
var layzr = new Layzr({ | ||
threshold: 50 | ||
}); | ||
Add or remove the `scroll` and `resize` event handlers. | ||
```es6 | ||
instance | ||
.handlers(true) // 'true' adds them | ||
.handlers(false) // 'false' removes them | ||
``` | ||
### callback | ||
### .check() | ||
Invoke a callback function each time an image is loaded. | ||
Manually check if elements are in the viewport. | ||
The image _may not be fully loaded before the function is called_. Detecting image load is inconsistent at best in modern browsers. | ||
This method is called while the `window` is scrolled or resized. | ||
```javascript | ||
// in the callback function, "this" refers to the image node | ||
```es6 | ||
instance.check() | ||
``` | ||
var layzr = new Layzr({ | ||
callback: function() { | ||
this.classList.add('class'); | ||
} | ||
}); | ||
### .update() | ||
Update the elements Layzr is checking. | ||
```es6 | ||
instance.update() | ||
``` | ||
**Dynamically added elements** should be handled using this method. See the [example](https://github.com/callmecavs/layzr.js/blob/v2.0.0/examples/large.js). | ||
## Browser Support | ||
Layzr natively supports **IE10+**. | ||
Layzr depends on the following browser APIs: | ||
To add support for older browsers, consider including [Paul Irish's polyfill](https://gist.github.com/paulirish/1579671) for [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame). | ||
* [classList](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList) | ||
* ES5 array methods: [forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach), [slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) | ||
* [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) | ||
There are currently no plans to include the polyfill in the library, in the interest of file size. | ||
It supports the following natively: | ||
* Chrome 24+ | ||
* Firefox 23+ | ||
* Safari 6.1+ | ||
* Opera 15+ | ||
* Edge 12+ | ||
* IE 10+ | ||
* iOS Safari 7.1+ | ||
* Android Browser 4.4+ | ||
To support older browsers, consider including [polyfills/shims](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills) for the APIs listed above. There are **no plans to include any in the library**, in the interest of file size. | ||
## Colophon | ||
@@ -220,15 +288,7 @@ | ||
* Stock Photos: [Unsplash](https://unsplash.com/) | ||
* Heading Links: [heading-links.js](https://github.com/callmecavs/heading-links.js) | ||
* Google Analytics Isogram: [isogrammer](http://isogrammer.com/) | ||
* Inspiration: [Headroom.js](http://wicky.nillia.ms/headroom.js/), [jQuery Unveil](http://luis-almeida.github.io/unveil/) | ||
* Education: [Paul Lewis](http://www.html5rocks.com/en/tutorials/speed/animations/) | ||
## License | ||
MIT. © 2015 Michael Cavalea | ||
MIT. © 2016 Michael Cavalea | ||
## Roadmap | ||
- [x] Add to CDN? | ||
[![Built With Love](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) |
204
src/layzr.js
@@ -1,157 +0,121 @@ | ||
'use strict'; | ||
import knot from 'knot.js' | ||
// CONSTRUCTOR | ||
export default (options = {}) => { | ||
// private | ||
function Layzr(options) { | ||
// debounce | ||
this._lastScroll = 0; | ||
this._ticking = false; | ||
let prevLoc | ||
let ticking | ||
let nodes | ||
let windowHeight | ||
// options | ||
options = options || {}; | ||
this._optionsContainer = document.querySelector(options.container) || window; | ||
this._optionsSelector = options.selector || '[data-layzr]'; | ||
this._optionsAttr = options.attr || 'data-layzr'; | ||
this._optionsAttrRetina = options.retinaAttr || 'data-layzr-retina'; | ||
this._optionsAttrBg = options.bgAttr || 'data-layzr-bg'; | ||
this._optionsAttrHidden = options.hiddenAttr || 'data-layzr-hidden'; | ||
this._optionsThreshold = options.threshold || 0; | ||
this._optionsCallback = options.callback || null; | ||
const settings = { | ||
normal: options.normal || 'data-normal', | ||
retina: options.retina || 'data-retina', | ||
srcset: options.srcset || 'data-srcset', | ||
threshold: options.threshold || 0 | ||
} | ||
// properties | ||
this._retina = window.devicePixelRatio > 1; | ||
this._srcAttr = this._retina ? this._optionsAttrRetina : this._optionsAttr; | ||
// feature detection | ||
// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/img/srcset.js | ||
// nodelist | ||
this._nodes = document.querySelectorAll(this._optionsSelector); | ||
const srcset = document.body.classList.contains('srcset') || 'srcset' in document.createElement('img') | ||
// scroll and resize handler | ||
this._handlerBind = this._requestScroll.bind(this); | ||
// device pixel ratio | ||
// not supported in IE10 - https://msdn.microsoft.com/en-us/library/dn265030(v=vs.85).aspx | ||
// call to create | ||
this._create(); | ||
} | ||
const dpr = window.devicePixelRatio || window.screen.deviceXDPI / window.screen.logicalXDPI | ||
// DEBOUNCE HELPERS | ||
// adapted from: http://www.html5rocks.com/en/tutorials/speed/animations/ | ||
// instance | ||
Layzr.prototype._requestScroll = function() { | ||
if(this._optionsContainer === window) { | ||
this._lastScroll = window.pageYOffset; | ||
} | ||
else { | ||
this._lastScroll = this._optionsContainer.scrollTop + this._getOffset(this._optionsContainer); | ||
} | ||
const instance = knot({ | ||
handlers: handlers, | ||
check: check, | ||
update: update | ||
}) | ||
this._requestTick(); | ||
}; | ||
return instance | ||
Layzr.prototype._requestTick = function() { | ||
if(!this._ticking) { | ||
requestAnimationFrame(this.update.bind(this)); | ||
this._ticking = true; | ||
// debounce helpers | ||
function requestScroll() { | ||
prevLoc = window.scrollY || window.pageYOffset | ||
requestFrame() | ||
} | ||
}; | ||
// OFFSET HELPER | ||
// remember, getBoundingClientRect is relative to the viewport | ||
function requestFrame() { | ||
if(!ticking) { | ||
requestAnimationFrame(() => check()) | ||
ticking = true | ||
} | ||
} | ||
Layzr.prototype._getOffset = function(node) { | ||
return node.getBoundingClientRect().top + window.pageYOffset; | ||
}; | ||
// offset helper | ||
// HEIGHT HELPER | ||
function getOffset(node) { | ||
return node.getBoundingClientRect().top + prevLoc | ||
} | ||
Layzr.prototype._getContainerHeight = function() { | ||
return this._optionsContainer.innerHeight | ||
|| this._optionsContainer.offsetHeight; | ||
} | ||
// in viewport helper | ||
// LAYZR METHODS | ||
function inViewport(node) { | ||
const viewTop = prevLoc | ||
const viewBot = viewTop + windowHeight | ||
Layzr.prototype._create = function() { | ||
// fire scroll event once | ||
this._handlerBind(); | ||
const nodeTop = getOffset(node) | ||
const nodeBot = nodeTop + node.offsetHeight | ||
// bind scroll and resize event | ||
this._optionsContainer.addEventListener('scroll', this._handlerBind, false); | ||
this._optionsContainer.addEventListener('resize', this._handlerBind, false); | ||
}; | ||
const offset = (settings.threshold * 100) / windowHeight | ||
Layzr.prototype._destroy = function() { | ||
// unbind scroll and resize event | ||
this._optionsContainer.removeEventListener('scroll', this._handlerBind, false); | ||
this._optionsContainer.removeEventListener('resize', this._handlerBind, false); | ||
}; | ||
return nodeBot >= viewTop - offset | ||
&& nodeTop <= viewBot + offset | ||
} | ||
Layzr.prototype._inViewport = function(node) { | ||
// get viewport top and bottom offset | ||
var viewportTop = this._lastScroll; | ||
var viewportBottom = viewportTop + this._getContainerHeight(); | ||
// source helper | ||
// get node top and bottom offset | ||
var nodeTop = this._getOffset(node); | ||
var nodeBottom = nodeTop + this._getContainerHeight(); | ||
function setSource(node) { | ||
instance.emit('src:before', node) | ||
// calculate threshold, convert percentage to pixel value | ||
var threshold = (this._optionsThreshold / 100) * window.innerHeight; | ||
// prefer srcset, fallback to pixel density | ||
if(srcset && node.hasAttribute(settings.srcset)) { | ||
node.setAttribute('srcset', node.getAttribute(settings.srcset)) | ||
} | ||
else { | ||
const retina = dpr > 1 && node.getAttribute(settings.retina) | ||
node.setAttribute('src', retina || node.getAttribute(settings.normal)) | ||
} | ||
// return if node in viewport | ||
return nodeBottom >= viewportTop - threshold | ||
&& nodeTop <= viewportBottom + threshold | ||
&& !node.hasAttribute(this._optionsAttrHidden); | ||
}; | ||
instance.emit('src:after', node) | ||
Layzr.prototype._reveal = function(node) { | ||
// get node source | ||
var source = node.getAttribute(this._srcAttr) || node.getAttribute(this._optionsAttr); | ||
;[settings.normal, settings.retina, settings.srcset].forEach(attr => node.removeAttribute(attr)) | ||
// set node src or bg image | ||
if(node.hasAttribute(this._optionsAttrBg)) { | ||
node.style.backgroundImage = 'url(' + source + ')'; | ||
update() | ||
} | ||
else { | ||
node.setAttribute('src', source); | ||
} | ||
// call the callback | ||
if(typeof this._optionsCallback === 'function') { | ||
// "this" will be the node in the callback | ||
this._optionsCallback.call(node); | ||
} | ||
// API | ||
// remove node data attributes | ||
node.removeAttribute(this._optionsAttr); | ||
node.removeAttribute(this._optionsAttrRetina); | ||
node.removeAttribute(this._optionsAttrBg); | ||
node.removeAttribute(this._optionsAttrHidden); | ||
}; | ||
function handlers(flag) { | ||
const action = flag | ||
? 'addEventListener' | ||
: 'removeEventListener' | ||
Layzr.prototype.updateSelector = function() { | ||
// update cached list of nodes matching selector | ||
this._nodes = document.querySelectorAll(this._optionsSelector); | ||
}; | ||
;['scroll', 'resize'].forEach(event => window[action](event, requestScroll)) | ||
return this | ||
} | ||
Layzr.prototype.update = function() { | ||
// cache nodelist length | ||
var nodesLength = this._nodes.length; | ||
function check() { | ||
windowHeight = window.innerHeight | ||
// loop through nodes | ||
for(var i = 0; i < nodesLength; i++) { | ||
// cache node | ||
var node = this._nodes[i]; | ||
nodes.forEach(node => inViewport(node) && setSource(node)) | ||
// check if node has mandatory attribute | ||
if(node.hasAttribute(this._optionsAttr)) { | ||
// check if node in viewport | ||
if(this._inViewport(node)) { | ||
// reveal node | ||
this._reveal(node); | ||
} | ||
} | ||
ticking = false | ||
return this | ||
} | ||
// allow for more animation frames | ||
this._ticking = false; | ||
}; | ||
function update() { | ||
nodes = Array.prototype.slice.call(document.querySelectorAll(`[${ settings.normal }]`)) | ||
return this | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
12
294
22793
1
15
237
1
2
2
+ Addedknot.js@^1.0.1
+ Addedknot.js@1.1.5(transitive)