Showing preview only (248K chars total). Download the full file or copy to clipboard to get everything.
Repository: metafizzy/infinite-scroll
Branch: master
Commit: 38a464fbbbe4
Files: 85
Total size: 228.8 KB
Directory structure:
gitextract_pwag9y0f/
├── .eslintrc.js
├── .github/
│ ├── contributing.md
│ ├── issue_template.md
│ └── workflows/
│ └── nodejs.yml
├── .gitignore
├── .nvmrc
├── LICENSE.txt
├── README.md
├── bin/
│ ├── build-dist.js
│ └── version.js
├── bower.json
├── dist/
│ └── infinite-scroll.pkgd.js
├── js/
│ ├── button.js
│ ├── core.js
│ ├── history.js
│ ├── index.js
│ ├── page-load.js
│ ├── scroll-watch.js
│ └── status.js
├── package.json
├── sandbox/
│ ├── button-class.html
│ ├── button-first.html
│ ├── button-load.html
│ ├── container-scroll.html
│ ├── css/
│ │ ├── blog.css
│ │ ├── loader-ellips.css
│ │ └── masonry-images.css
│ ├── element-scroll.html
│ ├── html-init.html
│ ├── jquery-plugin.html
│ ├── js/
│ │ ├── masonry-images.js
│ │ ├── scroll-loader.js
│ │ ├── unsplash-masonry.js
│ │ └── unsplash.js
│ ├── masonry-images/
│ │ ├── index.html
│ │ ├── page2.html
│ │ ├── page3.html
│ │ ├── page4.html
│ │ └── page5.html
│ ├── page/
│ │ ├── 2.html
│ │ ├── 3.html
│ │ ├── 4.html
│ │ ├── 5.html
│ │ └── 6.html
│ ├── prefill.html
│ ├── scroll-3.html
│ ├── scroll-loader.html
│ ├── unsplash-masonry.html
│ └── unsplash.html
└── test/
├── _get-server.js
├── _with-page.js
├── check-last-page.js
├── dist-jquery.js
├── dist.js
├── history.js
├── html/
│ ├── _serial-t.js
│ ├── dist-jquery.html
│ ├── dist.html
│ ├── history.html
│ ├── outlayer.html
│ ├── page/
│ │ ├── 2.html
│ │ ├── 2.json
│ │ ├── 3.html
│ │ ├── 3.json
│ │ ├── empty.html
│ │ ├── fill.html
│ │ ├── no-access.html
│ │ ├── outlayer2.html
│ │ └── outlayer3.html
│ ├── page-index.html
│ ├── page-load.html
│ ├── path.html
│ ├── prefill.html
│ ├── scroll-watch-element.html
│ ├── scroll-watch-window.html
│ └── test.css
├── load-next-page-promise.js
├── outlayer.js
├── page-index.js
├── page-load-error.js
├── page-load-json.js
├── page-load.js
├── path.js
├── prefill.js
└── scroll-watch.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.js
================================================
/* eslint-env node */
module.exports = {
plugins: [ 'metafizzy' ],
extends: 'plugin:metafizzy/browser',
env: {
browser: true,
commonjs: true,
},
parserOptions: {
ecmaVersion: 2018,
},
globals: {
InfiniteScroll: 'readonly',
Promise: 'readonly',
QUnit: 'readonly',
serialT: 'readonly',
},
rules: {
},
};
================================================
FILE: .github/contributing.md
================================================
## Submitting issues
### Reduced test case required
All bug reports and problem issues require a [**reduced test case**](https://css-tricks.com/reduced-test-cases/) or **live URL**.
+ A reduced test case clearly demonstrates the bug or issue.
+ It contains the bare minimum HTML, CSS, and JavaScript required to demonstrate the bug.
+ A link to your production site is **not** a reduced test case.
Create one by forking any one of the [CodePen demos](https://codepen.io/collection/DZejqa?grid_type=list&sort_by=item_created_at) from [the docs](https://infinite-scroll.com).
+ [Scroll & append](https://codepen.io/desandro/pen/yLaKLop)
+ [Scroll & append, vanilla JS](https://codepen.io/desandro/pen/vYXRYeY)
Providing a reduced test case is the best way to get your issue addressed. They help you point out the problem. They help me verify and debug the problem. They help others understand the problem. Without a reduced test case or live URL, your issue may be closed.
## Pull requests
Contributions are welcome!
+ **For typos and one-line fixes,** send those right in.
+ **For larger features,** open an issue before starting any significant work. Let's discuss to see how your feature fits within Infinite Scroll's vision.
+ **Check code style.** Run `npm run lint`. Spaces in brackets, semicolons, trailing commas.
+ **Do not edit `infinite-scroll.pkgd.js`.** Make your edits to source files in `js/`.
+ **Do not run the `dist` task to update `dist/` files.** I'll take care of this when I create a new release.
Your code will be used as part of a commercial product if merged. By submitting a Pull Request, you are giving your consent for your code to be integrated into Infinite Scroll as part of a commercial product.
================================================
FILE: .github/issue_template.md
================================================
<!-- Thanks for submitting an issue! All bug reports and problem issues require a **reduced test case** or **live URL**. Create one by forking any one of the CodePen examples from the docs. See guidelines link above. -->
**Test case:** https://codepen.io/desandro/pen/yLaKLop
================================================
FILE: .github/workflows/nodejs.yml
================================================
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
# - run: npm run dist
- run: npm test
env:
CI: true
================================================
FILE: .gitignore
================================================
bower_components/
node_modules/
================================================
FILE: .nvmrc
================================================
14
================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)
Copyright (c) Metafizzy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Infinite Scroll
_Automatically add next page_
See [infinite-scroll.com](https://infinite-scroll.com) for complete docs and demos.
## Install
### Download
- [infinite-scroll.pkgd.min.js](https://unpkg.com/infinite-scroll@5/dist/infinite-scroll.pkgd.min.js) minified, or
- [infinite-scroll.pkgd.js](https://unpkg.com/infinite-scroll@5/dist/infinite-scroll.pkgd.js) un-minified
### CDN
Link directly to Infinite Scroll files on [unpkg](https://unpkg.com).
``` html
<script src="https://unpkg.com/infinite-scroll@5/dist/infinite-scroll.pkgd.min.js"></script>
<!-- or -->
<script src="https://unpkg.com/infinite-scroll@5/dist/infinite-scroll.pkgd.js"></script>
```
### Package managers
npm: `npm install infinite-scroll`
Yarn: `yarn add infinite-scroll`
## License
Infinite Scroll v5 is licensed under the MIT License.
Whereas earlier versions of Infinite Scroll were previously dual licensed for commercial and closed-source usage, Infinite Scroll v5 licensing no longer has this distinction. You are free to use Infinite Scroll v5 in commercial and closed-source applications.
## Usage
Infinite Scroll works on a container element with its child item elements
``` html
<div class="container">
<article class="post">...</article>
<article class="post">...</article>
<article class="post">...</article>
...
</div>
```
### Options
``` js
let infScroll = new InfiniteScroll( '.container', {
// defaults listed
path: undefined,
// REQUIRED. Determines the URL for the next page
// Set to selector string to use the href of the next page's link
// path: '.pagination__next'
// Or set with {{#}} in place of the page number in the url
// path: '/blog/page/{{#}}'
// or set with function
// path: function() {
// return return '/articles/P' + ( ( this.loadCount + 1 ) * 10 );
// }
append: undefined,
// REQUIRED for appending content
// Appends selected elements from loaded page to the container
checkLastPage: true,
// Checks if page has path selector element
// Set to string if path is not set as selector string:
// checkLastPage: '.pagination__next'
prefill: false,
// Loads and appends pages on intialization until scroll requirement is met.
responseBody: 'text',
// Sets the method used on the response.
// Set to 'json' to load JSON.
domParseResponse: true,
// enables parsing response body into a DOM
// disable to load flat text
fetchOptions: undefined,
// sets custom settings for the fetch() request
// for setting headers, cors, or POST method
// can be set to an object, or a function that returns an object
outlayer: false,
// Integrates Masonry, Isotope or Packery
// Appended items will be added to the layout
scrollThreshold: 400,
// Sets the distance between the viewport to scroll area
// for scrollThreshold event to be triggered.
elementScroll: false,
// Sets scroller to an element for overflow element scrolling
loadOnScroll: true,
// Loads next page when scroll crosses over scrollThreshold
history: 'replace',
// Changes the browser history and URL.
// Set to 'push' to use history.pushState()
// to create new history entries for each page change.
historyTitle: true,
// Updates the window title. Requires history enabled.
hideNav: undefined,
// Hides navigation element
status: undefined,
// Displays status elements indicating state of page loading:
// .infinite-scroll-request, .infinite-scroll-load, .infinite-scroll-error
// status: '.page-load-status'
button: undefined,
// Enables a button to load pages on click
// button: '.load-next-button'
onInit: undefined,
// called on initialization
// useful for binding events on init
// onInit: function() {
// this.on( 'append', function() {...})
// }
debug: false,
// Logs events and state changes to the console.
})
```
## Browser support
Infinite Scroll v4 supports Chrome 60+, Edge 79+, Firefox 55+, Safari 11+.
For IE10 and Android 4 support, try [Infinite Scroll v3](https://v3.infinite-scroll.com/).
## Development
This package is developed with Node.js v14 and npm v6. Manage Node and npm version with [nvm](https://github.com/nvm-sh/nvm).
``` sh
nvm use
```
Install dependencies
``` sh
npm install
```
Lint
``` sh
npm run lint
```
Run tests
``` sh
npm test
```
---
By [Metafizzy 🌈🐻](https://metafizzy.co)
================================================
FILE: bin/build-dist.js
================================================
const fs = require('fs');
const { execSync } = require('child_process');
const { minify } = require('terser');
const indexPath = 'js/index.js';
const distPath = 'dist/infinite-scroll.pkgd.js';
const distMinPath = 'dist/infinite-scroll.pkgd.min.js';
let indexContent = fs.readFileSync( `./${indexPath}`, 'utf8' );
// get file paths from index.js
let cjsBlockRegex = /module\.exports = factory\([\w ,'.\-()/\n]+;/i;
let cjsBlockMatch = indexContent.match( cjsBlockRegex );
let jsPaths = cjsBlockMatch[0].match( /require\('([.\-/\w]+)'\)/gi );
jsPaths = jsPaths.map( function( path ) {
return path.replace( "require('.", 'js' ).replace( "')", '.js' );
} );
let paths = [
'node_modules/jquery-bridget/jquery-bridget.js',
'node_modules/ev-emitter/ev-emitter.js',
'node_modules/fizzy-ui-utils/utils.js',
...jsPaths,
'node_modules/imagesloaded/imagesloaded.js',
];
// concatenate files
execSync(`cat ${paths.join(' ')} > ${distPath}`);
// add banner
let banner = indexContent.split(' */')[0] + ' */\n\n';
banner = banner.replace( 'Infinite Scroll', 'Infinite Scroll PACKAGED' );
let distJsContent = fs.readFileSync( distPath, 'utf8' );
distJsContent = banner + distJsContent;
fs.writeFileSync( distPath, distJsContent );
// minify
( async function() {
let { code } = await minify( distJsContent, { mangle: true } );
fs.writeFileSync( distMinPath, code );
} )();
================================================
FILE: bin/version.js
================================================
/* eslint-env node */
const fs = require('fs');
const path = require('path');
const { version } = require('../package.json');
function dir( file ) {
return path.resolve( __dirname, file );
}
let content = fs.readFileSync( dir('../js/index.js'), 'utf8' );
content = content.replace( /Infinite Scroll v[\w.-]+/,
`Infinite Scroll v${version}` );
fs.writeFileSync( dir('../js/index.js'), content, 'utf8' );
================================================
FILE: bower.json
================================================
{
"name": "infinite-scroll",
"authors": [
"David DeSandro <desandrocodes@gmail.com>"
],
"description": "infinite scroll",
"main": "js/index.js",
"dependencies": {
"ev-emitter": "^2.1.0",
"fizzy-ui-utils": "^3.0.0"
},
"devDependencies": {},
"keywords": [
"infinite scroll",
"infinite",
"scroll",
"plugin"
],
"license": "MIT",
"homepage": "https://infinite-scroll.com",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"sandbox",
"package.json",
"gulpfile.js"
]
}
================================================
FILE: dist/infinite-scroll.pkgd.js
================================================
/*!
* Infinite Scroll PACKAGED v5.0.0
* Automatically add next page
* MIT License
* https://infinite-scroll.com
* Copyright 2018-2025 Metafizzy
*/
/**
* Bridget makes jQuery widgets
* v3.0.0
* MIT license
*/
( function( window, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('jquery'),
);
} else {
// browser global
window.jQueryBridget = factory(
window,
window.jQuery,
);
}
}( window, function factory( window, jQuery ) {
// ----- utils ----- //
// helper function for logging errors
// $.error breaks jQuery chaining
let console = window.console;
let logError = typeof console == 'undefined' ? function() {} :
function( message ) {
console.error( message );
};
// ----- jQueryBridget ----- //
function jQueryBridget( namespace, PluginClass, $ ) {
$ = $ || jQuery || window.jQuery;
if ( !$ ) {
return;
}
// add option method -> $().plugin('option', {...})
if ( !PluginClass.prototype.option ) {
// option setter
PluginClass.prototype.option = function( opts ) {
if ( !opts ) return;
this.options = Object.assign( this.options || {}, opts );
};
}
// make jQuery plugin
$.fn[ namespace ] = function( arg0, ...args ) {
if ( typeof arg0 == 'string' ) {
// method call $().plugin( 'methodName', { options } )
return methodCall( this, arg0, args );
}
// just $().plugin({ options })
plainCall( this, arg0 );
return this;
};
// $().plugin('methodName')
function methodCall( $elems, methodName, args ) {
let returnValue;
let pluginMethodStr = `$().${namespace}("${methodName}")`;
$elems.each( function( i, elem ) {
// get instance
let instance = $.data( elem, namespace );
if ( !instance ) {
logError( `${namespace} not initialized.` +
` Cannot call method ${pluginMethodStr}` );
return;
}
let method = instance[ methodName ];
if ( !method || methodName.charAt( 0 ) == '_' ) {
logError(`${pluginMethodStr} is not a valid method`);
return;
}
// apply method, get return value
let value = method.apply( instance, args );
// set return value if value is returned, use only first value
returnValue = returnValue === undefined ? value : returnValue;
} );
return returnValue !== undefined ? returnValue : $elems;
}
function plainCall( $elems, options ) {
$elems.each( function( i, elem ) {
let instance = $.data( elem, namespace );
if ( instance ) {
// set options & init
instance.option( options );
instance._init();
} else {
// initialize new instance
instance = new PluginClass( elem, options );
$.data( elem, namespace, instance );
}
} );
}
}
// ----- ----- //
return jQueryBridget;
} ) );
/**
* EvEmitter v2.0.0
* Lil' event emitter
* MIT License
*/
( function( global, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS - Browserify, Webpack
module.exports = factory();
} else {
// Browser globals
global.EvEmitter = factory();
}
}( typeof window != 'undefined' ? window : this, function() {
function EvEmitter() {}
let proto = EvEmitter.prototype;
proto.on = function( eventName, listener ) {
if ( !eventName || !listener ) return this;
// set events hash
let events = this._events = this._events || {};
// set listeners array
let listeners = events[ eventName ] = events[ eventName ] || [];
// only add once
if ( !listeners.includes( listener ) ) {
listeners.push( listener );
}
return this;
};
proto.once = function( eventName, listener ) {
if ( !eventName || !listener ) return this;
// add event
this.on( eventName, listener );
// set once flag
// set onceEvents hash
let onceEvents = this._onceEvents = this._onceEvents || {};
// set onceListeners object
let onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
// set flag
onceListeners[ listener ] = true;
return this;
};
proto.off = function( eventName, listener ) {
let listeners = this._events && this._events[ eventName ];
if ( !listeners || !listeners.length ) return this;
let index = listeners.indexOf( listener );
if ( index != -1 ) {
listeners.splice( index, 1 );
}
return this;
};
proto.emitEvent = function( eventName, args ) {
let listeners = this._events && this._events[ eventName ];
if ( !listeners || !listeners.length ) return this;
// copy over to avoid interference if .off() in listener
listeners = listeners.slice( 0 );
args = args || [];
// once stuff
let onceListeners = this._onceEvents && this._onceEvents[ eventName ];
for ( let listener of listeners ) {
let isOnce = onceListeners && onceListeners[ listener ];
if ( isOnce ) {
// remove listener
// remove before trigger to prevent recursion
this.off( eventName, listener );
// unset once flag
delete onceListeners[ listener ];
}
// trigger listener
listener.apply( this, args );
}
return this;
};
proto.allOff = function() {
delete this._events;
delete this._onceEvents;
return this;
};
return EvEmitter;
} ) );
/**
* Fizzy UI utils v3.0.0
* MIT license
*/
( function( global, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( global );
} else {
// browser global
global.fizzyUIUtils = factory( global );
}
}( this, function factory( global ) {
let utils = {};
// ----- extend ----- //
// extends objects
utils.extend = function( a, b ) {
return Object.assign( a, b );
};
// ----- modulo ----- //
utils.modulo = function( num, div ) {
return ( ( num % div ) + div ) % div;
};
// ----- makeArray ----- //
// turn element or nodeList into an array
utils.makeArray = function( obj ) {
// use object if already an array
if ( Array.isArray( obj ) ) return obj;
// return empty array if undefined or null. #6
if ( obj === null || obj === undefined ) return [];
let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
// convert nodeList to array
if ( isArrayLike ) return [ ...obj ];
// array of single index
return [ obj ];
};
// ----- removeFrom ----- //
utils.removeFrom = function( ary, obj ) {
let index = ary.indexOf( obj );
if ( index != -1 ) {
ary.splice( index, 1 );
}
};
// ----- getParent ----- //
utils.getParent = function( elem, selector ) {
while ( elem.parentNode && elem != document.body ) {
elem = elem.parentNode;
if ( elem.matches( selector ) ) return elem;
}
};
// ----- getQueryElement ----- //
// use element as selector string
utils.getQueryElement = function( elem ) {
if ( typeof elem == 'string' ) {
return document.querySelector( elem );
}
return elem;
};
// ----- handleEvent ----- //
// enable .ontype to trigger from .addEventListener( elem, 'type' )
utils.handleEvent = function( event ) {
let method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
// ----- filterFindElements ----- //
utils.filterFindElements = function( elems, selector ) {
// make array of elems
elems = utils.makeArray( elems );
return elems
// check that elem is an actual element
.filter( ( elem ) => elem instanceof HTMLElement )
.reduce( ( ffElems, elem ) => {
// add elem if no selector
if ( !selector ) {
ffElems.push( elem );
return ffElems;
}
// filter & find items if we have a selector
// filter
if ( elem.matches( selector ) ) {
ffElems.push( elem );
}
// find children
let childElems = elem.querySelectorAll( selector );
// concat childElems to filterFound array
ffElems = ffElems.concat( ...childElems );
return ffElems;
}, [] );
};
// ----- debounceMethod ----- //
utils.debounceMethod = function( _class, methodName, threshold ) {
threshold = threshold || 100;
// original method
let method = _class.prototype[ methodName ];
let timeoutName = methodName + 'Timeout';
_class.prototype[ methodName ] = function() {
clearTimeout( this[ timeoutName ] );
let args = arguments;
this[ timeoutName ] = setTimeout( () => {
method.apply( this, args );
delete this[ timeoutName ];
}, threshold );
};
};
// ----- docReady ----- //
utils.docReady = function( onDocReady ) {
let readyState = document.readyState;
if ( readyState == 'complete' || readyState == 'interactive' ) {
// do async to allow for other scripts to run. metafizzy/flickity#441
setTimeout( onDocReady );
} else {
document.addEventListener( 'DOMContentLoaded', onDocReady );
}
};
// ----- htmlInit ----- //
// http://bit.ly/3oYLusc
utils.toDashed = function( str ) {
return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
return $1 + '-' + $2;
} ).toLowerCase();
};
let console = global.console;
// allow user to initialize classes via [data-namespace] or .js-namespace class
// htmlInit( Widget, 'widgetName' )
// options are parsed from data-namespace-options
utils.htmlInit = function( WidgetClass, namespace ) {
utils.docReady( function() {
let dashedNamespace = utils.toDashed( namespace );
let dataAttr = 'data-' + dashedNamespace;
let dataAttrElems = document.querySelectorAll( `[${dataAttr}]` );
let jQuery = global.jQuery;
[ ...dataAttrElems ].forEach( ( elem ) => {
let attr = elem.getAttribute( dataAttr );
let options;
try {
options = attr && JSON.parse( attr );
} catch ( error ) {
// log error, do not initialize
if ( console ) {
console.error( `Error parsing ${dataAttr} on ${elem.className}: ${error}` );
}
return;
}
// initialize
let instance = new WidgetClass( elem, options );
// make available via $().data('namespace')
if ( jQuery ) {
jQuery.data( elem, namespace, instance );
}
} );
} );
};
// ----- ----- //
return utils;
} ) );
// core
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('ev-emitter'),
require('fizzy-ui-utils'),
);
} else {
// browser global
window.InfiniteScroll = factory(
window,
window.EvEmitter,
window.fizzyUIUtils,
);
}
}( window, function factory( window, EvEmitter, utils ) {
let jQuery = window.jQuery;
// internal store of all InfiniteScroll intances
let instances = {};
function InfiniteScroll( element, options ) {
let queryElem = utils.getQueryElement( element );
if ( !queryElem ) {
console.error( 'Bad element for InfiniteScroll: ' + ( queryElem || element ) );
return;
}
element = queryElem;
// do not initialize twice on same element
if ( element.infiniteScrollGUID ) {
let instance = instances[ element.infiniteScrollGUID ];
instance.option( options );
return instance;
}
this.element = element;
// options
this.options = { ...InfiniteScroll.defaults };
this.option( options );
// add jQuery
if ( jQuery ) {
this.$element = jQuery( this.element );
}
this.create();
}
// defaults
InfiniteScroll.defaults = {
// path: null,
// hideNav: null,
// debug: false,
};
// create & destroy methods
InfiniteScroll.create = {};
InfiniteScroll.destroy = {};
let proto = InfiniteScroll.prototype;
// inherit EvEmitter
Object.assign( proto, EvEmitter.prototype );
// -------------------------- -------------------------- //
// globally unique identifiers
let GUID = 0;
proto.create = function() {
// create core
// add id for InfiniteScroll.data
let id = this.guid = ++GUID;
this.element.infiniteScrollGUID = id; // expando
instances[ id ] = this; // associate via id
// properties
this.pageIndex = 1; // default to first page
this.loadCount = 0;
this.updateGetPath();
// bail if getPath not set, or returns falsey #776
let hasPath = this.getPath && this.getPath();
if ( !hasPath ) {
console.error('Disabling InfiniteScroll');
return;
}
this.updateGetAbsolutePath();
this.log( 'initialized', [ this.element.className ] );
this.callOnInit();
// create features
for ( let method in InfiniteScroll.create ) {
InfiniteScroll.create[ method ].call( this );
}
};
proto.option = function( opts ) {
Object.assign( this.options, opts );
};
// call onInit option, used for binding events on init
proto.callOnInit = function() {
let onInit = this.options.onInit;
if ( onInit ) {
onInit.call( this, this );
}
};
// ----- events ----- //
proto.dispatchEvent = function( type, event, args ) {
this.log( type, args );
let emitArgs = event ? [ event ].concat( args ) : args;
this.emitEvent( type, emitArgs );
// trigger jQuery event
if ( !jQuery || !this.$element ) {
return;
}
// namespace jQuery event
type += '.infiniteScroll';
let $event = type;
if ( event ) {
// create jQuery event
/* eslint-disable-next-line new-cap */
let jQEvent = jQuery.Event( event );
jQEvent.type = type;
$event = jQEvent;
}
this.$element.trigger( $event, args );
};
let loggers = {
initialized: ( className ) => `on ${className}`,
request: ( path ) => `URL: ${path}`,
load: ( response, path ) => `${response.title || ''}. URL: ${path}`,
error: ( error, path ) => `${error}. URL: ${path}`,
append: ( response, path, items ) => `${items.length} items. URL: ${path}`,
last: ( response, path ) => `URL: ${path}`,
history: ( title, path ) => `URL: ${path}`,
pageIndex: function( index, origin ) {
return `current page determined to be: ${index} from ${origin}`;
},
};
// log events
proto.log = function( type, args ) {
if ( !this.options.debug ) return;
let message = `[InfiniteScroll] ${type}`;
let logger = loggers[ type ];
if ( logger ) message += '. ' + logger.apply( this, args );
console.log( message );
};
// -------------------------- methods used amoung features -------------------------- //
proto.updateMeasurements = function() {
this.windowHeight = window.innerHeight;
let rect = this.element.getBoundingClientRect();
this.top = rect.top + window.scrollY;
};
proto.updateScroller = function() {
let elementScroll = this.options.elementScroll;
if ( !elementScroll ) {
// default, use window
this.scroller = window;
return;
}
// if true, set to element, otherwise use option
this.scroller = elementScroll === true ? this.element :
utils.getQueryElement( elementScroll );
if ( !this.scroller ) {
throw new Error(`Unable to find elementScroll: ${elementScroll}`);
}
};
// -------------------------- page path -------------------------- //
proto.updateGetPath = function() {
let optPath = this.options.path;
if ( !optPath ) {
console.error(`InfiniteScroll path option required. Set as: ${optPath}`);
return;
}
// function
let type = typeof optPath;
if ( type == 'function' ) {
this.getPath = optPath;
return;
}
// template string: '/pages/{{#}}.html'
let templateMatch = type == 'string' && optPath.match('{{#}}');
if ( templateMatch ) {
this.updateGetPathTemplate( optPath );
return;
}
// selector: '.next-page-selector'
this.updateGetPathSelector( optPath );
};
proto.updateGetPathTemplate = function( optPath ) {
// set getPath with template string
this.getPath = () => {
let nextIndex = this.pageIndex + 1;
return optPath.replace( '{{#}}', nextIndex );
};
// get pageIndex from location
// convert path option into regex to look for pattern in location
// escape query (?) in url, allows for parsing GET parameters
let regexString = optPath
.replace( /(\\\?|\?)/, '\\?' )
.replace( '{{#}}', '(\\d\\d?\\d?)' );
let templateRe = new RegExp( regexString );
let match = location.href.match( templateRe );
if ( match ) {
this.pageIndex = parseInt( match[1], 10 );
this.log( 'pageIndex', [ this.pageIndex, 'template string' ] );
}
};
let pathRegexes = [
// WordPress & Tumblr - example.com/page/2
// Jekyll - example.com/page2
/^(.*?\/?page\/?)(\d\d?\d?)(.*?$)/,
// Drupal - example.com/?page=1
/^(.*?\/?\?page=)(\d\d?\d?)(.*?$)/,
// catch all, last occurence of a number
/(.*?)(\d\d?\d?)(?!.*\d)(.*?$)/,
];
// try matching href to pathRegexes patterns
let getPathParts = InfiniteScroll.getPathParts = function( href ) {
if ( !href ) return;
for ( let regex of pathRegexes ) {
let match = href.match( regex );
if ( match ) {
let [ , begin, index, end ] = match;
return { begin, index, end };
}
}
};
proto.updateGetPathSelector = function( optPath ) {
// parse href of link: '.next-page-link'
let hrefElem = document.querySelector( optPath );
if ( !hrefElem ) {
console.error(`Bad InfiniteScroll path option. Next link not found: ${optPath}`);
return;
}
let href = hrefElem.getAttribute('href');
let pathParts = getPathParts( href );
if ( !pathParts ) {
console.error(`InfiniteScroll unable to parse next link href: ${href}`);
return;
}
let { begin, index, end } = pathParts;
this.isPathSelector = true; // flag for checkLastPage()
this.getPath = () => begin + ( this.pageIndex + 1 ) + end;
// get pageIndex from href
this.pageIndex = parseInt( index, 10 ) - 1;
this.log( 'pageIndex', [ this.pageIndex, 'next link' ] );
};
proto.updateGetAbsolutePath = function() {
let path = this.getPath();
// path doesn't start with http or /
let isAbsolute = path.match( /^http/ ) || path.match( /^\// );
if ( isAbsolute ) {
this.getAbsolutePath = this.getPath;
return;
}
let { pathname } = location;
// query parameter #829. example.com/?pg=2
let isQuery = path.match( /^\?/ );
// /foo/bar/index.html => /foo/bar
let directory = pathname.substring( 0, pathname.lastIndexOf('/') );
let pathStart = isQuery ? pathname : directory + '/';
this.getAbsolutePath = () => pathStart + this.getPath();
};
// -------------------------- nav -------------------------- //
// hide navigation
InfiniteScroll.create.hideNav = function() {
let nav = utils.getQueryElement( this.options.hideNav );
if ( !nav ) return;
nav.style.display = 'none';
this.nav = nav;
};
InfiniteScroll.destroy.hideNav = function() {
if ( this.nav ) this.nav.style.display = '';
};
// -------------------------- destroy -------------------------- //
proto.destroy = function() {
this.allOff(); // remove all event listeners
// call destroy methods
for ( let method in InfiniteScroll.destroy ) {
InfiniteScroll.destroy[ method ].call( this );
}
delete this.element.infiniteScrollGUID;
delete instances[ this.guid ];
// remove jQuery data. #807
if ( jQuery && this.$element ) {
jQuery.removeData( this.element, 'infiniteScroll' );
}
};
// -------------------------- utilities -------------------------- //
// https://remysharp.com/2010/07/21/throttling-function-calls
InfiniteScroll.throttle = function( fn, threshold ) {
threshold = threshold || 200;
let last, timeout;
return function() {
let now = +new Date();
let args = arguments;
let trigger = () => {
last = now;
fn.apply( this, args );
};
if ( last && now < last + threshold ) {
// hold on to it
clearTimeout( timeout );
timeout = setTimeout( trigger, threshold );
} else {
trigger();
}
};
};
InfiniteScroll.data = function( elem ) {
elem = utils.getQueryElement( elem );
let id = elem && elem.infiniteScrollGUID;
return id && instances[ id ];
};
// set internal jQuery, for Webpack + jQuery v3
InfiniteScroll.setJQuery = function( jqry ) {
jQuery = jqry;
};
// -------------------------- setup -------------------------- //
utils.htmlInit( InfiniteScroll, 'infinite-scroll' );
// add noop _init method for jQuery Bridget. #768
proto._init = function() {};
let { jQueryBridget } = window;
if ( jQuery && jQueryBridget ) {
jQueryBridget( 'infiniteScroll', InfiniteScroll, jQuery );
}
// -------------------------- -------------------------- //
return InfiniteScroll;
} ) );
// page-load
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core'),
);
} else {
// browser global
factory(
window,
window.InfiniteScroll,
);
}
}( window, function factory( window, InfiniteScroll ) {
let proto = InfiniteScroll.prototype;
Object.assign( InfiniteScroll.defaults, {
// append: false,
loadOnScroll: true,
checkLastPage: true,
responseBody: 'text',
domParseResponse: true,
// prefill: false,
// outlayer: null,
} );
InfiniteScroll.create.pageLoad = function() {
this.canLoad = true;
this.on( 'scrollThreshold', this.onScrollThresholdLoad );
this.on( 'load', this.checkLastPage );
if ( this.options.outlayer ) {
this.on( 'append', this.onAppendOutlayer );
}
};
proto.onScrollThresholdLoad = function() {
if ( this.options.loadOnScroll ) this.loadNextPage();
};
let domParser = new DOMParser();
proto.loadNextPage = function() {
if ( this.isLoading || !this.canLoad ) return;
let { responseBody, domParseResponse, fetchOptions } = this.options;
let path = this.getAbsolutePath();
this.isLoading = true;
if ( typeof fetchOptions == 'function' ) fetchOptions = fetchOptions();
let fetchPromise = fetch( path, fetchOptions )
.then( ( response ) => {
if ( !response.ok ) {
let error = new Error( response.statusText );
this.onPageError( error, path, response );
return { response };
}
return response[ responseBody ]().then( ( body ) => {
let canDomParse = responseBody == 'text' && domParseResponse;
if ( canDomParse ) {
body = domParser.parseFromString( body, 'text/html' );
}
if ( response.status == 204 ) {
this.lastPageReached( body, path );
return { body, response };
} else {
return this.onPageLoad( body, path, response );
}
} );
} )
.catch( ( error ) => {
this.onPageError( error, path );
} );
this.dispatchEvent( 'request', null, [ path, fetchPromise ] );
return fetchPromise;
};
proto.onPageLoad = function( body, path, response ) {
// done loading if not appending
if ( !this.options.append ) {
this.isLoading = false;
}
this.pageIndex++;
this.loadCount++;
this.dispatchEvent( 'load', null, [ body, path, response ] );
return this.appendNextPage( body, path, response );
};
proto.appendNextPage = function( body, path, response ) {
let { append, responseBody, domParseResponse } = this.options;
// do not append json
let isDocument = responseBody == 'text' && domParseResponse;
if ( !isDocument || !append ) return { body, response };
let items = body.querySelectorAll( append );
let promiseValue = { body, response, items };
// last page hit if no items. #840
if ( !items || !items.length ) {
this.lastPageReached( body, path );
return promiseValue;
}
let fragment = getItemsFragment( items );
let appendReady = () => {
this.appendItems( items, fragment );
this.isLoading = false;
this.dispatchEvent( 'append', null, [ body, path, items, response ] );
return promiseValue;
};
// TODO add hook for option to trigger appendReady
if ( this.options.outlayer ) {
return this.appendOutlayerItems( fragment, appendReady );
} else {
return appendReady();
}
};
proto.appendItems = function( items, fragment ) {
if ( !items || !items.length ) return;
// get fragment if not provided
fragment = fragment || getItemsFragment( items );
refreshScripts( fragment );
this.element.appendChild( fragment );
};
function getItemsFragment( items ) {
// add items to fragment
let fragment = document.createDocumentFragment();
if ( items ) fragment.append( ...items );
return fragment;
}
// replace <script>s with copies so they load
// <script>s added by InfiniteScroll will not load
// similar to https://stackoverflow.com/questions/610995
function refreshScripts( fragment ) {
let scripts = fragment.querySelectorAll('script');
for ( let script of scripts ) {
let freshScript = document.createElement('script');
// copy attributes
let attrs = script.attributes;
for ( let attr of attrs ) {
freshScript.setAttribute( attr.name, attr.value );
}
// copy inner script code. #718, #782
freshScript.innerHTML = script.innerHTML;
script.parentNode.replaceChild( freshScript, script );
}
}
// ----- outlayer ----- //
proto.appendOutlayerItems = function( fragment, appendReady ) {
let imagesLoaded = InfiniteScroll.imagesLoaded || window.imagesLoaded;
if ( !imagesLoaded ) {
console.error('[InfiniteScroll] imagesLoaded required for outlayer option');
this.isLoading = false;
return;
}
// append once images loaded
return new Promise( function( resolve ) {
imagesLoaded( fragment, function() {
let bodyResponse = appendReady();
resolve( bodyResponse );
} );
} );
};
proto.onAppendOutlayer = function( response, path, items ) {
this.options.outlayer.appended( items );
};
// ----- checkLastPage ----- //
// check response for next element
proto.checkLastPage = function( body, path ) {
let { checkLastPage, path: pathOpt } = this.options;
if ( !checkLastPage ) return;
// if path is function, check if next path is truthy
if ( typeof pathOpt == 'function' ) {
let nextPath = this.getPath();
if ( !nextPath ) {
this.lastPageReached( body, path );
return;
}
}
// get selector from checkLastPage or path option
let selector;
if ( typeof checkLastPage == 'string' ) {
selector = checkLastPage;
} else if ( this.isPathSelector ) {
// path option is selector string
selector = pathOpt;
}
// check last page for selector
// bail if no selector or not document response
if ( !selector || !body.querySelector ) return;
// check if response has selector
let nextElem = body.querySelector( selector );
if ( !nextElem ) this.lastPageReached( body, path );
};
proto.lastPageReached = function( body, path ) {
this.canLoad = false;
this.dispatchEvent( 'last', null, [ body, path ] );
};
// ----- error ----- //
proto.onPageError = function( error, path, response ) {
this.isLoading = false;
this.canLoad = false;
this.dispatchEvent( 'error', null, [ error, path, response ] );
return error;
};
// -------------------------- prefill -------------------------- //
InfiniteScroll.create.prefill = function() {
if ( !this.options.prefill ) return;
let append = this.options.append;
if ( !append ) {
console.error(`append option required for prefill. Set as :${append}`);
return;
}
this.updateMeasurements();
this.updateScroller();
this.isPrefilling = true;
this.on( 'append', this.prefill );
this.once( 'error', this.stopPrefill );
this.once( 'last', this.stopPrefill );
this.prefill();
};
proto.prefill = function() {
let distance = this.getPrefillDistance();
this.isPrefilling = distance >= 0;
if ( this.isPrefilling ) {
this.log('prefill');
this.loadNextPage();
} else {
this.stopPrefill();
}
};
proto.getPrefillDistance = function() {
// element scroll
if ( this.options.elementScroll ) {
return this.scroller.clientHeight - this.scroller.scrollHeight;
}
// window
return this.windowHeight - this.element.clientHeight;
};
proto.stopPrefill = function() {
this.log('stopPrefill');
this.off( 'append', this.prefill );
};
// -------------------------- -------------------------- //
return InfiniteScroll;
} ) );
// scroll-watch
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core'),
require('fizzy-ui-utils'),
);
} else {
// browser global
factory(
window,
window.InfiniteScroll,
window.fizzyUIUtils,
);
}
}( window, function factory( window, InfiniteScroll, utils ) {
let proto = InfiniteScroll.prototype;
// default options
Object.assign( InfiniteScroll.defaults, {
scrollThreshold: 400,
// elementScroll: null,
} );
InfiniteScroll.create.scrollWatch = function() {
// events
this.pageScrollHandler = this.onPageScroll.bind( this );
this.resizeHandler = this.onResize.bind( this );
let scrollThreshold = this.options.scrollThreshold;
let isEnable = scrollThreshold || scrollThreshold === 0;
if ( isEnable ) this.enableScrollWatch();
};
InfiniteScroll.destroy.scrollWatch = function() {
this.disableScrollWatch();
};
proto.enableScrollWatch = function() {
if ( this.isScrollWatching ) return;
this.isScrollWatching = true;
this.updateMeasurements();
this.updateScroller();
// TODO disable after error?
this.on( 'last', this.disableScrollWatch );
this.bindScrollWatchEvents( true );
};
proto.disableScrollWatch = function() {
if ( !this.isScrollWatching ) return;
this.bindScrollWatchEvents( false );
delete this.isScrollWatching;
};
proto.bindScrollWatchEvents = function( isBind ) {
let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
this.scroller[ addRemove ]( 'scroll', this.pageScrollHandler );
window[ addRemove ]( 'resize', this.resizeHandler );
};
proto.onPageScroll = InfiniteScroll.throttle( function() {
let distance = this.getBottomDistance();
if ( distance <= this.options.scrollThreshold ) {
this.dispatchEvent('scrollThreshold');
}
} );
proto.getBottomDistance = function() {
let bottom, scrollY;
if ( this.options.elementScroll ) {
bottom = this.scroller.scrollHeight;
scrollY = this.scroller.scrollTop + this.scroller.clientHeight;
} else {
bottom = this.top + this.element.clientHeight;
scrollY = window.scrollY + this.windowHeight;
}
return bottom - scrollY;
};
proto.onResize = function() {
this.updateMeasurements();
};
utils.debounceMethod( InfiniteScroll, 'onResize', 150 );
// -------------------------- -------------------------- //
return InfiniteScroll;
} ) );
// history
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core'),
require('fizzy-ui-utils'),
);
} else {
// browser global
factory(
window,
window.InfiniteScroll,
window.fizzyUIUtils,
);
}
}( window, function factory( window, InfiniteScroll, utils ) {
let proto = InfiniteScroll.prototype;
Object.assign( InfiniteScroll.defaults, {
history: 'replace',
// historyTitle: false,
} );
let link = document.createElement('a');
// ----- create/destroy ----- //
InfiniteScroll.create.history = function() {
if ( !this.options.history ) return;
// check for same origin
link.href = this.getAbsolutePath();
// MS Edge does not have origin on link
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12236493/
let linkOrigin = link.origin || link.protocol + '//' + link.host;
let isSameOrigin = linkOrigin == location.origin;
if ( !isSameOrigin ) {
console.error( '[InfiniteScroll] cannot set history with different origin: ' +
`${link.origin} on ${location.origin} . History behavior disabled.` );
return;
}
// two ways to handle changing history
if ( this.options.append ) {
this.createHistoryAppend();
} else {
this.createHistoryPageLoad();
}
};
proto.createHistoryAppend = function() {
this.updateMeasurements();
this.updateScroller();
// array of scroll positions of appended pages
this.scrollPages = [
// first page
{
top: 0,
path: location.href,
title: document.title,
},
];
this.scrollPage = this.scrollPages[0];
// events
this.scrollHistoryHandler = this.onScrollHistory.bind( this );
this.unloadHandler = this.onUnload.bind( this );
this.scroller.addEventListener( 'scroll', this.scrollHistoryHandler );
this.on( 'append', this.onAppendHistory );
this.bindHistoryAppendEvents( true );
};
proto.bindHistoryAppendEvents = function( isBind ) {
let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
this.scroller[ addRemove ]( 'scroll', this.scrollHistoryHandler );
window[ addRemove ]( 'unload', this.unloadHandler );
};
proto.createHistoryPageLoad = function() {
this.on( 'load', this.onPageLoadHistory );
};
InfiniteScroll.destroy.history =
proto.destroyHistory = function() {
let isHistoryAppend = this.options.history && this.options.append;
if ( isHistoryAppend ) {
this.bindHistoryAppendEvents( false );
}
};
// ----- append history ----- //
proto.onAppendHistory = function( response, path, items ) {
// do not proceed if no items. #779
if ( !items || !items.length ) return;
let firstItem = items[0];
let elemScrollY = this.getElementScrollY( firstItem );
// resolve path
link.href = path;
// add page data to hash
this.scrollPages.push({
top: elemScrollY,
path: link.href,
title: response.title,
});
};
proto.getElementScrollY = function( elem ) {
if ( this.options.elementScroll ) {
return elem.offsetTop - this.top;
} else {
let rect = elem.getBoundingClientRect();
return rect.top + window.scrollY;
}
};
proto.onScrollHistory = function() {
// cycle through positions, find biggest without going over
let scrollPage = this.getClosestScrollPage();
// set history if changed
if ( scrollPage != this.scrollPage ) {
this.scrollPage = scrollPage;
this.setHistory( scrollPage.title, scrollPage.path );
}
};
utils.debounceMethod( InfiniteScroll, 'onScrollHistory', 150 );
proto.getClosestScrollPage = function() {
let scrollViewY;
if ( this.options.elementScroll ) {
scrollViewY = this.scroller.scrollTop + this.scroller.clientHeight / 2;
} else {
scrollViewY = window.scrollY + this.windowHeight / 2;
}
let scrollPage;
for ( let page of this.scrollPages ) {
if ( page.top >= scrollViewY ) break;
scrollPage = page;
}
return scrollPage;
};
proto.setHistory = function( title, path ) {
let optHistory = this.options.history;
let historyMethod = optHistory && history[ optHistory + 'State' ];
if ( !historyMethod ) return;
history[ optHistory + 'State' ]( null, title, path );
if ( this.options.historyTitle ) document.title = title;
this.dispatchEvent( 'history', null, [ title, path ] );
};
// scroll to top to prevent initial scroll-reset after page refresh
// https://stackoverflow.com/a/18633915/182183
proto.onUnload = function() {
if ( this.scrollPage.top === 0 ) return;
// calculate where scroll position would be on refresh
let scrollY = window.scrollY - this.scrollPage.top + this.top;
// disable scroll event before setting scroll #679
this.destroyHistory();
scrollTo( 0, scrollY );
};
// ----- load history ----- //
// update URL
proto.onPageLoadHistory = function( response, path ) {
this.setHistory( response.title, path );
};
// -------------------------- -------------------------- //
return InfiniteScroll;
} ) );
// button
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core'),
require('fizzy-ui-utils'),
);
} else {
// browser global
factory(
window,
window.InfiniteScroll,
window.fizzyUIUtils,
);
}
}( window, function factory( window, InfiniteScroll, utils ) {
// -------------------------- InfiniteScrollButton -------------------------- //
class InfiniteScrollButton {
constructor( element, infScroll ) {
this.element = element;
this.infScroll = infScroll;
// events
this.clickHandler = this.onClick.bind( this );
this.element.addEventListener( 'click', this.clickHandler );
infScroll.on( 'request', this.disable.bind( this ) );
infScroll.on( 'load', this.enable.bind( this ) );
infScroll.on( 'error', this.hide.bind( this ) );
infScroll.on( 'last', this.hide.bind( this ) );
}
onClick( event ) {
event.preventDefault();
this.infScroll.loadNextPage();
}
enable() {
this.element.removeAttribute('disabled');
}
disable() {
this.element.disabled = 'disabled';
}
hide() {
this.element.style.display = 'none';
}
destroy() {
this.element.removeEventListener( 'click', this.clickHandler );
}
}
// -------------------------- InfiniteScroll methods -------------------------- //
// InfiniteScroll.defaults.button = null;
InfiniteScroll.create.button = function() {
let buttonElem = utils.getQueryElement( this.options.button );
if ( buttonElem ) {
this.button = new InfiniteScrollButton( buttonElem, this );
}
};
InfiniteScroll.destroy.button = function() {
if ( this.button ) this.button.destroy();
};
// -------------------------- -------------------------- //
InfiniteScroll.Button = InfiniteScrollButton;
return InfiniteScroll;
} ) );
// status
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core'),
require('fizzy-ui-utils'),
);
} else {
// browser global
factory(
window,
window.InfiniteScroll,
window.fizzyUIUtils,
);
}
}( window, function factory( window, InfiniteScroll, utils ) {
let proto = InfiniteScroll.prototype;
// InfiniteScroll.defaults.status = null;
InfiniteScroll.create.status = function() {
let statusElem = utils.getQueryElement( this.options.status );
if ( !statusElem ) return;
// elements
this.statusElement = statusElem;
this.statusEventElements = {
request: statusElem.querySelector('.infinite-scroll-request'),
error: statusElem.querySelector('.infinite-scroll-error'),
last: statusElem.querySelector('.infinite-scroll-last'),
};
// events
this.on( 'request', this.showRequestStatus );
this.on( 'error', this.showErrorStatus );
this.on( 'last', this.showLastStatus );
this.bindHideStatus('on');
};
proto.bindHideStatus = function( bindMethod ) {
let hideEvent = this.options.append ? 'append' : 'load';
this[ bindMethod ]( hideEvent, this.hideAllStatus );
};
proto.showRequestStatus = function() {
this.showStatus('request');
};
proto.showErrorStatus = function() {
this.showStatus('error');
};
proto.showLastStatus = function() {
this.showStatus('last');
// prevent last then append event race condition from showing last status #706
this.bindHideStatus('off');
};
proto.showStatus = function( eventName ) {
show( this.statusElement );
this.hideStatusEventElements();
let eventElem = this.statusEventElements[ eventName ];
show( eventElem );
};
proto.hideAllStatus = function() {
hide( this.statusElement );
this.hideStatusEventElements();
};
proto.hideStatusEventElements = function() {
for ( let type in this.statusEventElements ) {
let eventElem = this.statusEventElements[ type ];
hide( eventElem );
}
};
// -------------------------- -------------------------- //
function hide( elem ) {
setDisplay( elem, 'none' );
}
function show( elem ) {
setDisplay( elem, 'block' );
}
function setDisplay( elem, value ) {
if ( elem ) {
elem.style.display = value;
}
}
// -------------------------- -------------------------- //
return InfiniteScroll;
} ) );
/*!
* imagesLoaded v4.1.4
* JavaScript is all like "You images are done yet or what?"
* MIT License
*/
( function( window, factory ) { 'use strict';
// universal module definition
/*global define: false, module: false, require: false */
if ( typeof define == 'function' && define.amd ) {
// AMD
define( [
'ev-emitter/ev-emitter'
], function( EvEmitter ) {
return factory( window, EvEmitter );
});
} else if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('ev-emitter')
);
} else {
// browser global
window.imagesLoaded = factory(
window,
window.EvEmitter
);
}
})( typeof window !== 'undefined' ? window : this,
// -------------------------- factory -------------------------- //
function factory( window, EvEmitter ) {
'use strict';
var $ = window.jQuery;
var console = window.console;
// -------------------------- helpers -------------------------- //
// extend objects
function extend( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
}
var arraySlice = Array.prototype.slice;
// turn element or nodeList into an array
function makeArray( obj ) {
if ( Array.isArray( obj ) ) {
// use object if already an array
return obj;
}
var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
if ( isArrayLike ) {
// convert nodeList to array
return arraySlice.call( obj );
}
// array of single index
return [ obj ];
}
// -------------------------- imagesLoaded -------------------------- //
/**
* @param {Array, Element, NodeList, String} elem
* @param {Object or Function} options - if function, use as callback
* @param {Function} onAlways - callback function
*/
function ImagesLoaded( elem, options, onAlways ) {
// coerce ImagesLoaded() without new, to be new ImagesLoaded()
if ( !( this instanceof ImagesLoaded ) ) {
return new ImagesLoaded( elem, options, onAlways );
}
// use elem as selector string
var queryElem = elem;
if ( typeof elem == 'string' ) {
queryElem = document.querySelectorAll( elem );
}
// bail if bad element
if ( !queryElem ) {
console.error( 'Bad element for imagesLoaded ' + ( queryElem || elem ) );
return;
}
this.elements = makeArray( queryElem );
this.options = extend( {}, this.options );
// shift arguments if no options set
if ( typeof options == 'function' ) {
onAlways = options;
} else {
extend( this.options, options );
}
if ( onAlways ) {
this.on( 'always', onAlways );
}
this.getImages();
if ( $ ) {
// add jQuery Deferred object
this.jqDeferred = new $.Deferred();
}
// HACK check async to allow time to bind listeners
setTimeout( this.check.bind( this ) );
}
ImagesLoaded.prototype = Object.create( EvEmitter.prototype );
ImagesLoaded.prototype.options = {};
ImagesLoaded.prototype.getImages = function() {
this.images = [];
// filter & find items if we have an item selector
this.elements.forEach( this.addElementImages, this );
};
/**
* @param {Node} element
*/
ImagesLoaded.prototype.addElementImages = function( elem ) {
// filter siblings
if ( elem.nodeName == 'IMG' ) {
this.addImage( elem );
}
// get background image on element
if ( this.options.background === true ) {
this.addElementBackgroundImages( elem );
}
// find children
// no non-element nodes, #143
var nodeType = elem.nodeType;
if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
return;
}
var childImgs = elem.querySelectorAll('img');
// concat childElems to filterFound array
for ( var i=0; i < childImgs.length; i++ ) {
var img = childImgs[i];
this.addImage( img );
}
// get child background images
if ( typeof this.options.background == 'string' ) {
var children = elem.querySelectorAll( this.options.background );
for ( i=0; i < children.length; i++ ) {
var child = children[i];
this.addElementBackgroundImages( child );
}
}
};
var elementNodeTypes = {
1: true,
9: true,
11: true
};
ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
var style = getComputedStyle( elem );
if ( !style ) {
// Firefox returns null if in a hidden iframe https://bugzil.la/548397
return;
}
// get url inside url("...")
var reURL = /url\((['"])?(.*?)\1\)/gi;
var matches = reURL.exec( style.backgroundImage );
while ( matches !== null ) {
var url = matches && matches[2];
if ( url ) {
this.addBackground( url, elem );
}
matches = reURL.exec( style.backgroundImage );
}
};
/**
* @param {Image} img
*/
ImagesLoaded.prototype.addImage = function( img ) {
var loadingImage = new LoadingImage( img );
this.images.push( loadingImage );
};
ImagesLoaded.prototype.addBackground = function( url, elem ) {
var background = new Background( url, elem );
this.images.push( background );
};
ImagesLoaded.prototype.check = function() {
var _this = this;
this.progressedCount = 0;
this.hasAnyBroken = false;
// complete if no images
if ( !this.images.length ) {
this.complete();
return;
}
function onProgress( image, elem, message ) {
// HACK - Chrome triggers event before object properties have changed. #83
setTimeout( function() {
_this.progress( image, elem, message );
});
}
this.images.forEach( function( loadingImage ) {
loadingImage.once( 'progress', onProgress );
loadingImage.check();
});
};
ImagesLoaded.prototype.progress = function( image, elem, message ) {
this.progressedCount++;
this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
// progress event
this.emitEvent( 'progress', [ this, image, elem ] );
if ( this.jqDeferred && this.jqDeferred.notify ) {
this.jqDeferred.notify( this, image );
}
// check if completed
if ( this.progressedCount == this.images.length ) {
this.complete();
}
if ( this.options.debug && console ) {
console.log( 'progress: ' + message, image, elem );
}
};
ImagesLoaded.prototype.complete = function() {
var eventName = this.hasAnyBroken ? 'fail' : 'done';
this.isComplete = true;
this.emitEvent( eventName, [ this ] );
this.emitEvent( 'always', [ this ] );
if ( this.jqDeferred ) {
var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
this.jqDeferred[ jqMethod ]( this );
}
};
// -------------------------- -------------------------- //
function LoadingImage( img ) {
this.img = img;
}
LoadingImage.prototype = Object.create( EvEmitter.prototype );
LoadingImage.prototype.check = function() {
// If complete is true and browser supports natural sizes,
// try to check for image status manually.
var isComplete = this.getIsImageComplete();
if ( isComplete ) {
// report based on naturalWidth
this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
return;
}
// If none of the checks above matched, simulate loading on detached element.
this.proxyImage = new Image();
this.proxyImage.addEventListener( 'load', this );
this.proxyImage.addEventListener( 'error', this );
// bind to image as well for Firefox. #191
this.img.addEventListener( 'load', this );
this.img.addEventListener( 'error', this );
this.proxyImage.src = this.img.src;
};
LoadingImage.prototype.getIsImageComplete = function() {
// check for non-zero, non-undefined naturalWidth
// fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671
return this.img.complete && this.img.naturalWidth;
};
LoadingImage.prototype.confirm = function( isLoaded, message ) {
this.isLoaded = isLoaded;
this.emitEvent( 'progress', [ this, this.img, message ] );
};
// ----- events ----- //
// trigger specified handler for event type
LoadingImage.prototype.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
LoadingImage.prototype.onload = function() {
this.confirm( true, 'onload' );
this.unbindEvents();
};
LoadingImage.prototype.onerror = function() {
this.confirm( false, 'onerror' );
this.unbindEvents();
};
LoadingImage.prototype.unbindEvents = function() {
this.proxyImage.removeEventListener( 'load', this );
this.proxyImage.removeEventListener( 'error', this );
this.img.removeEventListener( 'load', this );
this.img.removeEventListener( 'error', this );
};
// -------------------------- Background -------------------------- //
function Background( url, element ) {
this.url = url;
this.element = element;
this.img = new Image();
}
// inherit LoadingImage prototype
Background.prototype = Object.create( LoadingImage.prototype );
Background.prototype.check = function() {
this.img.addEventListener( 'load', this );
this.img.addEventListener( 'error', this );
this.img.src = this.url;
// check if image is already complete
var isComplete = this.getIsImageComplete();
if ( isComplete ) {
this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
this.unbindEvents();
}
};
Background.prototype.unbindEvents = function() {
this.img.removeEventListener( 'load', this );
this.img.removeEventListener( 'error', this );
};
Background.prototype.confirm = function( isLoaded, message ) {
this.isLoaded = isLoaded;
this.emitEvent( 'progress', [ this, this.element, message ] );
};
// -------------------------- jQuery -------------------------- //
ImagesLoaded.makeJQueryPlugin = function( jQuery ) {
jQuery = jQuery || window.jQuery;
if ( !jQuery ) {
return;
}
// set local variable
$ = jQuery;
// $().imagesLoaded()
$.fn.imagesLoaded = function( options, callback ) {
var instance = new ImagesLoaded( this, options, callback );
return instance.jqDeferred.promise( $(this) );
};
};
// try making plugin
ImagesLoaded.makeJQueryPlugin();
// -------------------------- -------------------------- //
return ImagesLoaded;
});
================================================
FILE: js/button.js
================================================
// button
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core'),
require('fizzy-ui-utils'),
);
} else {
// browser global
factory(
window,
window.InfiniteScroll,
window.fizzyUIUtils,
);
}
}( window, function factory( window, InfiniteScroll, utils ) {
// -------------------------- InfiniteScrollButton -------------------------- //
class InfiniteScrollButton {
constructor( element, infScroll ) {
this.element = element;
this.infScroll = infScroll;
// events
this.clickHandler = this.onClick.bind( this );
this.element.addEventListener( 'click', this.clickHandler );
infScroll.on( 'request', this.disable.bind( this ) );
infScroll.on( 'load', this.enable.bind( this ) );
infScroll.on( 'error', this.hide.bind( this ) );
infScroll.on( 'last', this.hide.bind( this ) );
}
onClick( event ) {
event.preventDefault();
this.infScroll.loadNextPage();
}
enable() {
this.element.removeAttribute('disabled');
}
disable() {
this.element.disabled = 'disabled';
}
hide() {
this.element.style.display = 'none';
}
destroy() {
this.element.removeEventListener( 'click', this.clickHandler );
}
}
// -------------------------- InfiniteScroll methods -------------------------- //
// InfiniteScroll.defaults.button = null;
InfiniteScroll.create.button = function() {
let buttonElem = utils.getQueryElement( this.options.button );
if ( buttonElem ) {
this.button = new InfiniteScrollButton( buttonElem, this );
}
};
InfiniteScroll.destroy.button = function() {
if ( this.button ) this.button.destroy();
};
// -------------------------- -------------------------- //
InfiniteScroll.Button = InfiniteScrollButton;
return InfiniteScroll;
} ) );
================================================
FILE: js/core.js
================================================
// core
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('ev-emitter'),
require('fizzy-ui-utils'),
);
} else {
// browser global
window.InfiniteScroll = factory(
window,
window.EvEmitter,
window.fizzyUIUtils,
);
}
}( window, function factory( window, EvEmitter, utils ) {
let jQuery = window.jQuery;
// internal store of all InfiniteScroll intances
let instances = {};
function InfiniteScroll( element, options ) {
let queryElem = utils.getQueryElement( element );
if ( !queryElem ) {
console.error( 'Bad element for InfiniteScroll: ' + ( queryElem || element ) );
return;
}
element = queryElem;
// do not initialize twice on same element
if ( element.infiniteScrollGUID ) {
let instance = instances[ element.infiniteScrollGUID ];
instance.option( options );
return instance;
}
this.element = element;
// options
this.options = { ...InfiniteScroll.defaults };
this.option( options );
// add jQuery
if ( jQuery ) {
this.$element = jQuery( this.element );
}
this.create();
}
// defaults
InfiniteScroll.defaults = {
// path: null,
// hideNav: null,
// debug: false,
};
// create & destroy methods
InfiniteScroll.create = {};
InfiniteScroll.destroy = {};
let proto = InfiniteScroll.prototype;
// inherit EvEmitter
Object.assign( proto, EvEmitter.prototype );
// -------------------------- -------------------------- //
// globally unique identifiers
let GUID = 0;
proto.create = function() {
// create core
// add id for InfiniteScroll.data
let id = this.guid = ++GUID;
this.element.infiniteScrollGUID = id; // expando
instances[ id ] = this; // associate via id
// properties
this.pageIndex = 1; // default to first page
this.loadCount = 0;
this.updateGetPath();
// bail if getPath not set, or returns falsey #776
let hasPath = this.getPath && this.getPath();
if ( !hasPath ) {
console.error('Disabling InfiniteScroll');
return;
}
this.updateGetAbsolutePath();
this.log( 'initialized', [ this.element.className ] );
this.callOnInit();
// create features
for ( let method in InfiniteScroll.create ) {
InfiniteScroll.create[ method ].call( this );
}
};
proto.option = function( opts ) {
Object.assign( this.options, opts );
};
// call onInit option, used for binding events on init
proto.callOnInit = function() {
let onInit = this.options.onInit;
if ( onInit ) {
onInit.call( this, this );
}
};
// ----- events ----- //
proto.dispatchEvent = function( type, event, args ) {
this.log( type, args );
let emitArgs = event ? [ event ].concat( args ) : args;
this.emitEvent( type, emitArgs );
// trigger jQuery event
if ( !jQuery || !this.$element ) {
return;
}
// namespace jQuery event
type += '.infiniteScroll';
let $event = type;
if ( event ) {
// create jQuery event
/* eslint-disable-next-line new-cap */
let jQEvent = jQuery.Event( event );
jQEvent.type = type;
$event = jQEvent;
}
this.$element.trigger( $event, args );
};
let loggers = {
initialized: ( className ) => `on ${className}`,
request: ( path ) => `URL: ${path}`,
load: ( response, path ) => `${response.title || ''}. URL: ${path}`,
error: ( error, path ) => `${error}. URL: ${path}`,
append: ( response, path, items ) => `${items.length} items. URL: ${path}`,
last: ( response, path ) => `URL: ${path}`,
history: ( title, path ) => `URL: ${path}`,
pageIndex: function( index, origin ) {
return `current page determined to be: ${index} from ${origin}`;
},
};
// log events
proto.log = function( type, args ) {
if ( !this.options.debug ) return;
let message = `[InfiniteScroll] ${type}`;
let logger = loggers[ type ];
if ( logger ) message += '. ' + logger.apply( this, args );
console.log( message );
};
// -------------------------- methods used amoung features -------------------------- //
proto.updateMeasurements = function() {
this.windowHeight = window.innerHeight;
let rect = this.element.getBoundingClientRect();
this.top = rect.top + window.scrollY;
};
proto.updateScroller = function() {
let elementScroll = this.options.elementScroll;
if ( !elementScroll ) {
// default, use window
this.scroller = window;
return;
}
// if true, set to element, otherwise use option
this.scroller = elementScroll === true ? this.element :
utils.getQueryElement( elementScroll );
if ( !this.scroller ) {
throw new Error(`Unable to find elementScroll: ${elementScroll}`);
}
};
// -------------------------- page path -------------------------- //
proto.updateGetPath = function() {
let optPath = this.options.path;
if ( !optPath ) {
console.error(`InfiniteScroll path option required. Set as: ${optPath}`);
return;
}
// function
let type = typeof optPath;
if ( type == 'function' ) {
this.getPath = optPath;
return;
}
// template string: '/pages/{{#}}.html'
let templateMatch = type == 'string' && optPath.match('{{#}}');
if ( templateMatch ) {
this.updateGetPathTemplate( optPath );
return;
}
// selector: '.next-page-selector'
this.updateGetPathSelector( optPath );
};
proto.updateGetPathTemplate = function( optPath ) {
// set getPath with template string
this.getPath = () => {
let nextIndex = this.pageIndex + 1;
return optPath.replace( '{{#}}', nextIndex );
};
// get pageIndex from location
// convert path option into regex to look for pattern in location
// escape query (?) in url, allows for parsing GET parameters
let regexString = optPath
.replace( /(\\\?|\?)/, '\\?' )
.replace( '{{#}}', '(\\d\\d?\\d?)' );
let templateRe = new RegExp( regexString );
let match = location.href.match( templateRe );
if ( match ) {
this.pageIndex = parseInt( match[1], 10 );
this.log( 'pageIndex', [ this.pageIndex, 'template string' ] );
}
};
let pathRegexes = [
// WordPress & Tumblr - example.com/page/2
// Jekyll - example.com/page2
/^(.*?\/?page\/?)(\d\d?\d?)(.*?$)/,
// Drupal - example.com/?page=1
/^(.*?\/?\?page=)(\d\d?\d?)(.*?$)/,
// catch all, last occurence of a number
/(.*?)(\d\d?\d?)(?!.*\d)(.*?$)/,
];
// try matching href to pathRegexes patterns
let getPathParts = InfiniteScroll.getPathParts = function( href ) {
if ( !href ) return;
for ( let regex of pathRegexes ) {
let match = href.match( regex );
if ( match ) {
let [ , begin, index, end ] = match;
return { begin, index, end };
}
}
};
proto.updateGetPathSelector = function( optPath ) {
// parse href of link: '.next-page-link'
let hrefElem = document.querySelector( optPath );
if ( !hrefElem ) {
console.error(`Bad InfiniteScroll path option. Next link not found: ${optPath}`);
return;
}
let href = hrefElem.getAttribute('href');
let pathParts = getPathParts( href );
if ( !pathParts ) {
console.error(`InfiniteScroll unable to parse next link href: ${href}`);
return;
}
let { begin, index, end } = pathParts;
this.isPathSelector = true; // flag for checkLastPage()
this.getPath = () => begin + ( this.pageIndex + 1 ) + end;
// get pageIndex from href
this.pageIndex = parseInt( index, 10 ) - 1;
this.log( 'pageIndex', [ this.pageIndex, 'next link' ] );
};
proto.updateGetAbsolutePath = function() {
let path = this.getPath();
// path doesn't start with http or /
let isAbsolute = path.match( /^http/ ) || path.match( /^\// );
if ( isAbsolute ) {
this.getAbsolutePath = this.getPath;
return;
}
let { pathname } = location;
// query parameter #829. example.com/?pg=2
let isQuery = path.match( /^\?/ );
// /foo/bar/index.html => /foo/bar
let directory = pathname.substring( 0, pathname.lastIndexOf('/') );
let pathStart = isQuery ? pathname : directory + '/';
this.getAbsolutePath = () => pathStart + this.getPath();
};
// -------------------------- nav -------------------------- //
// hide navigation
InfiniteScroll.create.hideNav = function() {
let nav = utils.getQueryElement( this.options.hideNav );
if ( !nav ) return;
nav.style.display = 'none';
this.nav = nav;
};
InfiniteScroll.destroy.hideNav = function() {
if ( this.nav ) this.nav.style.display = '';
};
// -------------------------- destroy -------------------------- //
proto.destroy = function() {
this.allOff(); // remove all event listeners
// call destroy methods
for ( let method in InfiniteScroll.destroy ) {
InfiniteScroll.destroy[ method ].call( this );
}
delete this.element.infiniteScrollGUID;
delete instances[ this.guid ];
// remove jQuery data. #807
if ( jQuery && this.$element ) {
jQuery.removeData( this.element, 'infiniteScroll' );
}
};
// -------------------------- utilities -------------------------- //
// https://remysharp.com/2010/07/21/throttling-function-calls
InfiniteScroll.throttle = function( fn, threshold ) {
threshold = threshold || 200;
let last, timeout;
return function() {
let now = +new Date();
let args = arguments;
let trigger = () => {
last = now;
fn.apply( this, args );
};
if ( last && now < last + threshold ) {
// hold on to it
clearTimeout( timeout );
timeout = setTimeout( trigger, threshold );
} else {
trigger();
}
};
};
InfiniteScroll.data = function( elem ) {
elem = utils.getQueryElement( elem );
let id = elem && elem.infiniteScrollGUID;
return id && instances[ id ];
};
// set internal jQuery, for Webpack + jQuery v3
InfiniteScroll.setJQuery = function( jqry ) {
jQuery = jqry;
};
// -------------------------- setup -------------------------- //
utils.htmlInit( InfiniteScroll, 'infinite-scroll' );
// add noop _init method for jQuery Bridget. #768
proto._init = function() {};
let { jQueryBridget } = window;
if ( jQuery && jQueryBridget ) {
jQueryBridget( 'infiniteScroll', InfiniteScroll, jQuery );
}
// -------------------------- -------------------------- //
return InfiniteScroll;
} ) );
================================================
FILE: js/history.js
================================================
// history
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core'),
require('fizzy-ui-utils'),
);
} else {
// browser global
factory(
window,
window.InfiniteScroll,
window.fizzyUIUtils,
);
}
}( window, function factory( window, InfiniteScroll, utils ) {
let proto = InfiniteScroll.prototype;
Object.assign( InfiniteScroll.defaults, {
history: 'replace',
// historyTitle: false,
} );
let link = document.createElement('a');
// ----- create/destroy ----- //
InfiniteScroll.create.history = function() {
if ( !this.options.history ) return;
// check for same origin
link.href = this.getAbsolutePath();
// MS Edge does not have origin on link
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12236493/
let linkOrigin = link.origin || link.protocol + '//' + link.host;
let isSameOrigin = linkOrigin == location.origin;
if ( !isSameOrigin ) {
console.error( '[InfiniteScroll] cannot set history with different origin: ' +
`${link.origin} on ${location.origin} . History behavior disabled.` );
return;
}
// two ways to handle changing history
if ( this.options.append ) {
this.createHistoryAppend();
} else {
this.createHistoryPageLoad();
}
};
proto.createHistoryAppend = function() {
this.updateMeasurements();
this.updateScroller();
// array of scroll positions of appended pages
this.scrollPages = [
// first page
{
top: 0,
path: location.href,
title: document.title,
},
];
this.scrollPage = this.scrollPages[0];
// events
this.scrollHistoryHandler = this.onScrollHistory.bind( this );
this.unloadHandler = this.onUnload.bind( this );
this.scroller.addEventListener( 'scroll', this.scrollHistoryHandler );
this.on( 'append', this.onAppendHistory );
this.bindHistoryAppendEvents( true );
};
proto.bindHistoryAppendEvents = function( isBind ) {
let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
this.scroller[ addRemove ]( 'scroll', this.scrollHistoryHandler );
window[ addRemove ]( 'unload', this.unloadHandler );
};
proto.createHistoryPageLoad = function() {
this.on( 'load', this.onPageLoadHistory );
};
InfiniteScroll.destroy.history =
proto.destroyHistory = function() {
let isHistoryAppend = this.options.history && this.options.append;
if ( isHistoryAppend ) {
this.bindHistoryAppendEvents( false );
}
};
// ----- append history ----- //
proto.onAppendHistory = function( response, path, items ) {
// do not proceed if no items. #779
if ( !items || !items.length ) return;
let firstItem = items[0];
let elemScrollY = this.getElementScrollY( firstItem );
// resolve path
link.href = path;
// add page data to hash
this.scrollPages.push({
top: elemScrollY,
path: link.href,
title: response.title,
});
};
proto.getElementScrollY = function( elem ) {
if ( this.options.elementScroll ) {
return elem.offsetTop - this.top;
} else {
let rect = elem.getBoundingClientRect();
return rect.top + window.scrollY;
}
};
proto.onScrollHistory = function() {
// cycle through positions, find biggest without going over
let scrollPage = this.getClosestScrollPage();
// set history if changed
if ( scrollPage != this.scrollPage ) {
this.scrollPage = scrollPage;
this.setHistory( scrollPage.title, scrollPage.path );
}
};
utils.debounceMethod( InfiniteScroll, 'onScrollHistory', 150 );
proto.getClosestScrollPage = function() {
let scrollViewY;
if ( this.options.elementScroll ) {
scrollViewY = this.scroller.scrollTop + this.scroller.clientHeight / 2;
} else {
scrollViewY = window.scrollY + this.windowHeight / 2;
}
let scrollPage;
for ( let page of this.scrollPages ) {
if ( page.top >= scrollViewY ) break;
scrollPage = page;
}
return scrollPage;
};
proto.setHistory = function( title, path ) {
let optHistory = this.options.history;
let historyMethod = optHistory && history[ optHistory + 'State' ];
if ( !historyMethod ) return;
history[ optHistory + 'State' ]( null, title, path );
if ( this.options.historyTitle ) document.title = title;
this.dispatchEvent( 'history', null, [ title, path ] );
};
// scroll to top to prevent initial scroll-reset after page refresh
// https://stackoverflow.com/a/18633915/182183
proto.onUnload = function() {
if ( this.scrollPage.top === 0 ) return;
// calculate where scroll position would be on refresh
let scrollY = window.scrollY - this.scrollPage.top + this.top;
// disable scroll event before setting scroll #679
this.destroyHistory();
scrollTo( 0, scrollY );
};
// ----- load history ----- //
// update URL
proto.onPageLoadHistory = function( response, path ) {
this.setHistory( response.title, path );
};
// -------------------------- -------------------------- //
return InfiniteScroll;
} ) );
================================================
FILE: js/index.js
================================================
/*!
* Infinite Scroll v5.0.0
* Automatically add next page
* MIT License
* https://infinite-scroll.com
* Copyright 2018-2025 Metafizzy
*/
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
require('./core'),
require('./page-load'),
require('./scroll-watch'),
require('./history'),
require('./button'),
require('./status'),
);
}
} )( window, function factory( InfiniteScroll ) {
return InfiniteScroll;
} );
================================================
FILE: js/page-load.js
================================================
// page-load
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core'),
);
} else {
// browser global
factory(
window,
window.InfiniteScroll,
);
}
}( window, function factory( window, InfiniteScroll ) {
let proto = InfiniteScroll.prototype;
Object.assign( InfiniteScroll.defaults, {
// append: false,
loadOnScroll: true,
checkLastPage: true,
responseBody: 'text',
domParseResponse: true,
// prefill: false,
// outlayer: null,
} );
InfiniteScroll.create.pageLoad = function() {
this.canLoad = true;
this.on( 'scrollThreshold', this.onScrollThresholdLoad );
this.on( 'load', this.checkLastPage );
if ( this.options.outlayer ) {
this.on( 'append', this.onAppendOutlayer );
}
};
proto.onScrollThresholdLoad = function() {
if ( this.options.loadOnScroll ) this.loadNextPage();
};
let domParser = new DOMParser();
proto.loadNextPage = function() {
if ( this.isLoading || !this.canLoad ) return;
let { responseBody, domParseResponse, fetchOptions } = this.options;
let path = this.getAbsolutePath();
this.isLoading = true;
if ( typeof fetchOptions == 'function' ) fetchOptions = fetchOptions();
let fetchPromise = fetch( path, fetchOptions )
.then( ( response ) => {
if ( !response.ok ) {
let error = new Error( response.statusText );
this.onPageError( error, path, response );
return { response };
}
return response[ responseBody ]().then( ( body ) => {
let canDomParse = responseBody == 'text' && domParseResponse;
if ( canDomParse ) {
body = domParser.parseFromString( body, 'text/html' );
}
if ( response.status == 204 ) {
this.lastPageReached( body, path );
return { body, response };
} else {
return this.onPageLoad( body, path, response );
}
} );
} )
.catch( ( error ) => {
this.onPageError( error, path );
} );
this.dispatchEvent( 'request', null, [ path, fetchPromise ] );
return fetchPromise;
};
proto.onPageLoad = function( body, path, response ) {
// done loading if not appending
if ( !this.options.append ) {
this.isLoading = false;
}
this.pageIndex++;
this.loadCount++;
this.dispatchEvent( 'load', null, [ body, path, response ] );
return this.appendNextPage( body, path, response );
};
proto.appendNextPage = function( body, path, response ) {
let { append, responseBody, domParseResponse } = this.options;
// do not append json
let isDocument = responseBody == 'text' && domParseResponse;
if ( !isDocument || !append ) return { body, response };
let items = body.querySelectorAll( append );
let promiseValue = { body, response, items };
// last page hit if no items. #840
if ( !items || !items.length ) {
this.lastPageReached( body, path );
return promiseValue;
}
let fragment = getItemsFragment( items );
let appendReady = () => {
this.appendItems( items, fragment );
this.isLoading = false;
this.dispatchEvent( 'append', null, [ body, path, items, response ] );
return promiseValue;
};
// TODO add hook for option to trigger appendReady
if ( this.options.outlayer ) {
return this.appendOutlayerItems( fragment, appendReady );
} else {
return appendReady();
}
};
proto.appendItems = function( items, fragment ) {
if ( !items || !items.length ) return;
// get fragment if not provided
fragment = fragment || getItemsFragment( items );
refreshScripts( fragment );
this.element.appendChild( fragment );
};
function getItemsFragment( items ) {
// add items to fragment
let fragment = document.createDocumentFragment();
if ( items ) fragment.append( ...items );
return fragment;
}
// replace <script>s with copies so they load
// <script>s added by InfiniteScroll will not load
// similar to https://stackoverflow.com/questions/610995
function refreshScripts( fragment ) {
let scripts = fragment.querySelectorAll('script');
for ( let script of scripts ) {
let freshScript = document.createElement('script');
// copy attributes
let attrs = script.attributes;
for ( let attr of attrs ) {
freshScript.setAttribute( attr.name, attr.value );
}
// copy inner script code. #718, #782
freshScript.innerHTML = script.innerHTML;
script.parentNode.replaceChild( freshScript, script );
}
}
// ----- outlayer ----- //
proto.appendOutlayerItems = function( fragment, appendReady ) {
let imagesLoaded = InfiniteScroll.imagesLoaded || window.imagesLoaded;
if ( !imagesLoaded ) {
console.error('[InfiniteScroll] imagesLoaded required for outlayer option');
this.isLoading = false;
return;
}
// append once images loaded
return new Promise( function( resolve ) {
imagesLoaded( fragment, function() {
let bodyResponse = appendReady();
resolve( bodyResponse );
} );
} );
};
proto.onAppendOutlayer = function( response, path, items ) {
this.options.outlayer.appended( items );
};
// ----- checkLastPage ----- //
// check response for next element
proto.checkLastPage = function( body, path ) {
let { checkLastPage, path: pathOpt } = this.options;
if ( !checkLastPage ) return;
// if path is function, check if next path is truthy
if ( typeof pathOpt == 'function' ) {
let nextPath = this.getPath();
if ( !nextPath ) {
this.lastPageReached( body, path );
return;
}
}
// get selector from checkLastPage or path option
let selector;
if ( typeof checkLastPage == 'string' ) {
selector = checkLastPage;
} else if ( this.isPathSelector ) {
// path option is selector string
selector = pathOpt;
}
// check last page for selector
// bail if no selector or not document response
if ( !selector || !body.querySelector ) return;
// check if response has selector
let nextElem = body.querySelector( selector );
if ( !nextElem ) this.lastPageReached( body, path );
};
proto.lastPageReached = function( body, path ) {
this.canLoad = false;
this.dispatchEvent( 'last', null, [ body, path ] );
};
// ----- error ----- //
proto.onPageError = function( error, path, response ) {
this.isLoading = false;
this.canLoad = false;
this.dispatchEvent( 'error', null, [ error, path, response ] );
return error;
};
// -------------------------- prefill -------------------------- //
InfiniteScroll.create.prefill = function() {
if ( !this.options.prefill ) return;
let append = this.options.append;
if ( !append ) {
console.error(`append option required for prefill. Set as :${append}`);
return;
}
this.updateMeasurements();
this.updateScroller();
this.isPrefilling = true;
this.on( 'append', this.prefill );
this.once( 'error', this.stopPrefill );
this.once( 'last', this.stopPrefill );
this.prefill();
};
proto.prefill = function() {
let distance = this.getPrefillDistance();
this.isPrefilling = distance >= 0;
if ( this.isPrefilling ) {
this.log('prefill');
this.loadNextPage();
} else {
this.stopPrefill();
}
};
proto.getPrefillDistance = function() {
// element scroll
if ( this.options.elementScroll ) {
return this.scroller.clientHeight - this.scroller.scrollHeight;
}
// window
return this.windowHeight - this.element.clientHeight;
};
proto.stopPrefill = function() {
this.log('stopPrefill');
this.off( 'append', this.prefill );
};
// -------------------------- -------------------------- //
return InfiniteScroll;
} ) );
================================================
FILE: js/scroll-watch.js
================================================
// scroll-watch
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core'),
require('fizzy-ui-utils'),
);
} else {
// browser global
factory(
window,
window.InfiniteScroll,
window.fizzyUIUtils,
);
}
}( window, function factory( window, InfiniteScroll, utils ) {
let proto = InfiniteScroll.prototype;
// default options
Object.assign( InfiniteScroll.defaults, {
scrollThreshold: 400,
// elementScroll: null,
} );
InfiniteScroll.create.scrollWatch = function() {
// events
this.pageScrollHandler = this.onPageScroll.bind( this );
this.resizeHandler = this.onResize.bind( this );
let scrollThreshold = this.options.scrollThreshold;
let isEnable = scrollThreshold || scrollThreshold === 0;
if ( isEnable ) this.enableScrollWatch();
};
InfiniteScroll.destroy.scrollWatch = function() {
this.disableScrollWatch();
};
proto.enableScrollWatch = function() {
if ( this.isScrollWatching ) return;
this.isScrollWatching = true;
this.updateMeasurements();
this.updateScroller();
// TODO disable after error?
this.on( 'last', this.disableScrollWatch );
this.bindScrollWatchEvents( true );
};
proto.disableScrollWatch = function() {
if ( !this.isScrollWatching ) return;
this.bindScrollWatchEvents( false );
delete this.isScrollWatching;
};
proto.bindScrollWatchEvents = function( isBind ) {
let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
this.scroller[ addRemove ]( 'scroll', this.pageScrollHandler );
window[ addRemove ]( 'resize', this.resizeHandler );
};
proto.onPageScroll = InfiniteScroll.throttle( function() {
let distance = this.getBottomDistance();
if ( distance <= this.options.scrollThreshold ) {
this.dispatchEvent('scrollThreshold');
}
} );
proto.getBottomDistance = function() {
let bottom, scrollY;
if ( this.options.elementScroll ) {
bottom = this.scroller.scrollHeight;
scrollY = this.scroller.scrollTop + this.scroller.clientHeight;
} else {
bottom = this.top + this.element.clientHeight;
scrollY = window.scrollY + this.windowHeight;
}
return bottom - scrollY;
};
proto.onResize = function() {
this.updateMeasurements();
};
utils.debounceMethod( InfiniteScroll, 'onResize', 150 );
// -------------------------- -------------------------- //
return InfiniteScroll;
} ) );
================================================
FILE: js/status.js
================================================
// status
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core'),
require('fizzy-ui-utils'),
);
} else {
// browser global
factory(
window,
window.InfiniteScroll,
window.fizzyUIUtils,
);
}
}( window, function factory( window, InfiniteScroll, utils ) {
let proto = InfiniteScroll.prototype;
// InfiniteScroll.defaults.status = null;
InfiniteScroll.create.status = function() {
let statusElem = utils.getQueryElement( this.options.status );
if ( !statusElem ) return;
// elements
this.statusElement = statusElem;
this.statusEventElements = {
request: statusElem.querySelector('.infinite-scroll-request'),
error: statusElem.querySelector('.infinite-scroll-error'),
last: statusElem.querySelector('.infinite-scroll-last'),
};
// events
this.on( 'request', this.showRequestStatus );
this.on( 'error', this.showErrorStatus );
this.on( 'last', this.showLastStatus );
this.bindHideStatus('on');
};
proto.bindHideStatus = function( bindMethod ) {
let hideEvent = this.options.append ? 'append' : 'load';
this[ bindMethod ]( hideEvent, this.hideAllStatus );
};
proto.showRequestStatus = function() {
this.showStatus('request');
};
proto.showErrorStatus = function() {
this.showStatus('error');
};
proto.showLastStatus = function() {
this.showStatus('last');
// prevent last then append event race condition from showing last status #706
this.bindHideStatus('off');
};
proto.showStatus = function( eventName ) {
show( this.statusElement );
this.hideStatusEventElements();
let eventElem = this.statusEventElements[ eventName ];
show( eventElem );
};
proto.hideAllStatus = function() {
hide( this.statusElement );
this.hideStatusEventElements();
};
proto.hideStatusEventElements = function() {
for ( let type in this.statusEventElements ) {
let eventElem = this.statusEventElements[ type ];
hide( eventElem );
}
};
// -------------------------- -------------------------- //
function hide( elem ) {
setDisplay( elem, 'none' );
}
function show( elem ) {
setDisplay( elem, 'block' );
}
function setDisplay( elem, value ) {
if ( elem ) {
elem.style.display = value;
}
}
// -------------------------- -------------------------- //
return InfiniteScroll;
} ) );
================================================
FILE: package.json
================================================
{
"name": "infinite-scroll",
"version": "5.0.0",
"description": "Automatically add next page",
"main": "js/index.js",
"files": [
"dist",
"js"
],
"dependencies": {
"ev-emitter": "^2.1.0",
"fizzy-ui-utils": "^3.0.0"
},
"devDependencies": {
"ava": "^3.13.0",
"eslint": "^7.15.0",
"eslint-plugin-metafizzy": "^1.2.1",
"imagesloaded": "^4.1.4",
"jquery": "^3.5.1",
"jquery-bridget": "^3.0.0",
"masonry-layout": "^4.2.2",
"puppeteer": "^5.5.0",
"terser": "^5.5.1"
},
"scripts": {
"dist": "node bin/build-dist.js",
"lint": "npx eslint .",
"test": "npm run lint && ava",
"version": "node bin/version.js && npm run dist && git add -A . dist"
},
"repository": {
"type": "git",
"url": "git://github.com/metafizzy/infinite-scroll.git"
},
"keywords": [
"plugin",
"ui",
"DOM",
"browser"
],
"author": "Metafizzy",
"license": "MIT",
"bugs": {
"url": "https://github.com/metafizzy/infinite-scroll/issues"
},
"homepage": "https://infinite-scroll.com",
"directories": {
"test": "test"
}
}
================================================
FILE: sandbox/button-class.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>button class</title>
<link rel="stylesheet" href="css/blog.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>button class</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/wyday-logos">
<h1 class="post-header__title">wyDay logos</h1>
<p class="post-header__date">22 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes "premium software development tools for high-tech companies." A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before & after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/mealio-logo">
<h1 class="post-header__title">Mealio logo</h1>
<p class="post-header__date">8 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user's preferences. It's like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly & clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>
</div>
</article>
</div>
<div class="scroll-status">
<div class="infinite-scroll-request">
<div class="loader-ellips">
<span class="loader-ellips__dot loader-ellips__dot--1"></span>
<span class="loader-ellips__dot loader-ellips__dot--2"></span>
<span class="loader-ellips__dot loader-ellips__dot--3"></span>
<span class="loader-ellips__dot loader-ellips__dot--4"></span>
</div>
</div>
<p class="infinite-scroll-error">Last page reached</p>
</div>
<p>
<button class="load-more-button">Load more</button>
</p>
<nav class="pagination">
<span class="pagination__current">Page 1 of 17</span>
<a class="pagination__next" href="page/2.html">Next · 2</a>
</nav>
</div>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/button.js"></script>
<script src="../js/status.js"></script>
<script>
var container = document.querySelector('.posts-container');
var infScroll = new InfiniteScroll( container, {
path: '.pagination__next',
append: '.post',
historyTitle: true,
scrollThreshold: false,
history: false,
button: '.load-more-button',
status: '.scroll-status',
debug: true,
});
</script>
</body>
</html>
================================================
FILE: sandbox/button-first.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>button first</title>
<link rel="stylesheet" href="css/blog.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>button first</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/wyday-logos">
<h1 class="post-header__title">wyDay logos</h1>
<p class="post-header__date">22 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes "premium software development tools for high-tech companies." A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before & after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/mealio-logo">
<h1 class="post-header__title">Mealio logo</h1>
<p class="post-header__date">8 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user's preferences. It's like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly & clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>
</div>
</article>
</div>
<p>
<button class="load-more-button">Load more</button>
</p>
<nav class="pagination">
<span class="pagination__current">Page 1 of 17</span>
<a class="pagination__next" href="page/2.html">Next · 2</a>
</nav>
</div>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script>
var container = document.querySelector('.posts-container');
var infScroll = new InfiniteScroll( container, {
path: '.pagination__next',
append: '.post',
historyTitle: true,
loadOnScroll: false,
history: false,
});
var loadMoreButton = document.querySelector('.load-more-button');
loadMoreButton.onclick = function() {
infScroll.options.loadOnScroll = true;
infScroll.loadNextPage();
loadMoreButton.style.display = 'none';
};
</script>
</body>
</html>
================================================
FILE: sandbox/button-load.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>button load</title>
<link rel="stylesheet" href="css/blog.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>button load</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/wyday-logos">
<h1 class="post-header__title">wyDay logos</h1>
<p class="post-header__date">22 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes "premium software development tools for high-tech companies." A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before & after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/mealio-logo">
<h1 class="post-header__title">Mealio logo</h1>
<p class="post-header__date">8 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user's preferences. It's like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly & clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>
</div>
</article>
</div>
<p>
<button class="load-more-button">Load more</button>
</p>
<nav class="pagination">
<span class="pagination__current">Page 1 of 17</span>
<a class="pagination__next" href="page/2.html">Next · 2</a>
</nav>
</div>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/button.js"></script>
<script>
var container = document.querySelector('.posts-container');
var infScroll = new InfiniteScroll( container, {
path: '.pagination__next',
append: '.post',
button: '.load-more-button',
loadOnScroll: false,
history: false,
});
</script>
</body>
</html>
================================================
FILE: sandbox/container-scroll.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>container scroll</title>
<link rel="stylesheet" href="css/blog.css" />
<style>
.sidebar {
width: 300px;
height: 400px;
overflow-y: scroll;
overflow-x: hidden;
border: 1px solid #CCC;
}
.post {
margin-top: 0;
font-size: 12px;
}
</style>
</head>
<body>
<div class="site-header">
<div class="container">
<h1>container scroll</h1>
</div>
</div>
<div class="container">
<div class="sidebar">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/wyday-logos">
<h1 class="post-header__title">wyDay logos</h1>
<p class="post-header__date">22 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes "premium software development tools for high-tech companies." A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before & after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/mealio-logo">
<h1 class="post-header__title">Mealio logo</h1>
<p class="post-header__date">8 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user's preferences. It's like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly & clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>
</div>
</article>
</div> <!-- posts-container -->
</div> <!-- sidebar -->
</div> <!-- container -->
<nav class="pagination">
<span class="pagination__current">Page 1 of 17</span>
<a class="pagination__next" href="page/2.html">Next · 2</a>
</nav>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script>
var postsContainer = document.querySelector('.posts-container');
var infScroll = new InfiniteScroll( postsContainer, {
elementScroll: '.sidebar',
append: '.post',
path: '.pagination__next',
scrollThreshold: 200,
// history: false,
});
</script>
</body>
</html>
================================================
FILE: sandbox/css/blog.css
================================================
body {
line-height: 1.4;
background: #EEE;
color: #444;
}
.container {
padding: 0 20px;
max-width: 700px;
margin: 0 auto;
}
.site-header {
height: 150px;
background: #FFF;
}
.post {
margin: 60px 0;
background: white;
padding: 10px;
}
.post img {
display: block;
max-width: 100%;
}
.load-more-button {
font-size: 20px;
padding: 20px;
}
.site-footer {
padding: 200px 0;
background: #333;
color: white;
}
.scroll-status {
margin: 60px 0;
display: none;
}
================================================
FILE: sandbox/css/loader-ellips.css
================================================
/* loader-ellips
------------------------- */
.loader-ellips {
font-size: 20px;
position: relative;
width: 4em;
height: 1em;
margin: 10px auto;
}
.loader-ellips__dot {
display: block;
width: 1em;
height: 1em;
border-radius: 0.5em;
background: #333;
position: absolute;
animation-duration: 0.5s;
animation-timing-function: ease;
animation-iteration-count: infinite;
}
.loader-ellips__dot--1,
.loader-ellips__dot--2 {
left: 0;
}
.loader-ellips__dot--3 { left: 1.5em; }
.loader-ellips__dot--4 { left: 3em; }
@keyframes reveal {
from { transform: scale(0.001); }
to { transform: scale(1); }
}
@keyframes slide {
to { transform: translateX(1.5em) }
}
.loader-ellips__dot--1 {
animation-name: reveal;
}
.loader-ellips__dot--2,
.loader-ellips__dot--3 {
animation-name: slide;
}
.loader-ellips__dot--4 {
animation-name: reveal;
animation-direction: reverse;
}
================================================
FILE: sandbox/css/masonry-images.css
================================================
.container {
max-width: 1200px;
margin: 0 auto;
padding-bottom: 400px;
}
.grid {
min-height: 600px;
}
.grid__col-sizer,
.grid__item {
width: 30%;
}
.grid__gutter-sizer { width: 3.33%; }
.grid__item {
float: left;
margin-bottom: 30px;
visibility: hidden;; /*hidden by default*/
}
.grid.are-images-ready .grid__item { visibility: visible; }
.grid__item img {
display: block;
max-width: 100%;
}
.scroll-status {
display: none;
}
================================================
FILE: sandbox/element-scroll.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>element scroll</title>
<link rel="stylesheet" href="css/blog.css" />
<style>
.posts-container {
width: 300px;
height: 400px;
overflow-y: scroll;
overflow-x: hidden;
border: 1px solid #CCC;
}
.post {
margin-top: 0;
font-size: 12px;
}
</style>
</head>
<body>
<div class="site-header">
<div class="container">
<h1>element scroll</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/wyday-logos">
<h1 class="post-header__title">wyDay logos</h1>
<p class="post-header__date">22 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes "premium software development tools for high-tech companies." A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before & after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/mealio-logo">
<h1 class="post-header__title">Mealio logo</h1>
<p class="post-header__date">8 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user's preferences. It's like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly & clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>
</div>
</article>
</div> <!-- posts-container -->
</div> <!-- container -->
<nav class="pagination">
<span class="pagination__current">Page 1 of 17</span>
<a class="pagination__next" href="page/2.html">Next · 2</a>
</nav>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script>
var postsContainer = document.querySelector('.posts-container');
var infScroll = new InfiniteScroll( postsContainer, {
elementScroll: true,
append: '.post',
path: '.pagination__next',
// history: false,
});
</script>
</body>
</html>
================================================
FILE: sandbox/html-init.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>html init</title>
<link rel="stylesheet" href="css/blog.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>html init</h1>
</div>
</div>
<div class="container">
<div class="posts-container"
data-infinite-scroll='{
"path": ".pagination__next",
"append": ".post",
"historyTitle": true,
"status": ".scroll-status"
}'>
<article class="post">
<a class="post-header" href="/blog/wyday-logos">
<h1 class="post-header__title">wyDay logos</h1>
<p class="post-header__date">22 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes "premium software development tools for high-tech companies." A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before & after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/mealio-logo">
<h1 class="post-header__title">Mealio logo</h1>
<p class="post-header__date">8 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user's preferences. It's like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly & clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>
</div>
</article>
</div> <!-- posts-container -->
<div class="scroll-status">
<div class="infinite-scroll-request">
<div class="loader-ellips">
<span class="loader-ellips__dot loader-ellips__dot--1"></span>
<span class="loader-ellips__dot loader-ellips__dot--2"></span>
<span class="loader-ellips__dot loader-ellips__dot--3"></span>
<span class="loader-ellips__dot loader-ellips__dot--4"></span>
</div>
</div>
<p class="infinite-scroll-error">Last page reached</p>
</div>
<nav class="pagination">
<span class="pagination__current">Page 1 of 17</span>
<a class="pagination__next" href="page/2.html">Next · 2</a>
</nav>
</div> <!-- container -->
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/status.js"></script>
<script src="../js/button.js"></script>
</body>
</html>
================================================
FILE: sandbox/jquery-plugin.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>jquery plugin</title>
<link rel="stylesheet" href="css/blog.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>jquery plugin</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/wyday-logos">
<h1 class="post-header__title">wyDay logos</h1>
<p class="post-header__date">22 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes "premium software development tools for high-tech companies." A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before & after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/mealio-logo">
<h1 class="post-header__title">Mealio logo</h1>
<p class="post-header__date">8 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user's preferences. It's like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly & clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>
</div>
</article>
</div> <!-- posts-container -->
<div class="scroll-status">
<div class="infinite-scroll-request">
<div class="loader-ellips">
<span class="loader-ellips__dot loader-ellips__dot--1"></span>
<span class="loader-ellips__dot loader-ellips__dot--2"></span>
<span class="loader-ellips__dot loader-ellips__dot--3"></span>
<span class="loader-ellips__dot loader-ellips__dot--4"></span>
</div>
</div>
<p class="infinite-scroll-error">Last page reached</p>
</div>
<nav class="pagination">
<span class="pagination__current">Page 1 of 17</span>
<a class="pagination__next" href="page/2.html">Next · 2</a>
</nav>
</div> <!-- container -->
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../node_modules/jquery/dist/jquery.js"></script>
<script src="../node_modules/jquery-bridget/jquery-bridget.js"></script>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/button.js"></script>
<script src="../js/status.js"></script>
<script>
var $postsContainer = $('.posts-container').infiniteScroll({
path: '.pagination__next',
append: '.post',
status: '.scroll-status',
});
</script>
</body>
</html>
================================================
FILE: sandbox/js/masonry-images.js
================================================
/* globals Masonry, imagesLoaded */
let msnry = new Masonry( '.grid', {
itemSelector: 'none', // select no images on init
columnWidth: '.grid__col-sizer',
gutter: '.grid__gutter-sizer',
percentPosition: true,
stagger: 30,
visibleStyle: {
transform: 'translateY(0)',
opacity: 1,
},
hiddenStyle: {
transform: 'translateY(100px)',
opacity: 0,
},
} );
imagesLoaded( '.grid', function() {
msnry.options.itemSelector = '.grid__item'; // select proper items
document.querySelector('.grid').classList.add('are-images-ready');
let items = document.querySelectorAll('.grid__item');
msnry.appended( items );
} );
window.infScroll = new InfiniteScroll( '.grid', {
path: '.pagination__next',
append: '.grid__item',
debug: true,
outlayer: msnry,
status: '.scroll-status',
scrollThreshold: 1,
} );
================================================
FILE: sandbox/js/scroll-loader.js
================================================
let container = document.querySelector('.posts-container');
window.infScroll = new InfiniteScroll( container, {
path: '.pagination__next',
append: '.post',
nav: '.pagination',
status: '.scroll-status',
debug: true,
// history: false,
} );
================================================
FILE: sandbox/js/unsplash-masonry.js
================================================
/* globals Masonry, imagesLoaded */
let msnry = new Masonry( '.grid', {
itemSelector: '.grid-item',
columnWidth: '.grid-sizer',
percentPosition: true,
stagger: 30,
visibleStyle: { transform: 'translateY(0)', opacity: 1 },
hiddenStyle: { transform: 'translateY(100px)', opacity: 0 },
} );
const clientId = '9ad80b14098bcead9c7de952435e937cc3723ae61084ba8e729adb642daf0251';
let infScroll = new InfiniteScroll( '.grid', {
path: `https://api.unsplash.com/photos?client_id=${clientId}&page={{#}}`,
responseBody: 'json',
status: '.scroll-status',
history: false,
} );
let proxyDiv = document.createElement('div');
infScroll.on( 'load', function( data ) {
// convert data into HTML
let itemsHTML = data.map( getItem ).join('');
// get elements from HTML string
proxyDiv.innerHTML = itemsHTML;
let items = proxyDiv.querySelectorAll('.grid-item');
// append items after imagesLoaded
imagesLoaded( items, function() {
infScroll.appendItems( items );
msnry.appended( items );
} );
} );
// load first page
infScroll.loadNextPage();
// ----- template ----- //
let itemTemplateSrc = document.querySelector('#item-template').innerHTML;
function getItem( photo ) {
return microTemplate( itemTemplateSrc, photo );
}
// micro templating, sort-of
function microTemplate( src, data ) {
// replace {{tags}} in source
return src.replace( /\{\{([\w\-_.]+)\}\}/gi, function( match, key ) {
// walk through objects to get value
let value = data;
key.split('.').forEach( ( part ) => {
value = value[ part ];
} );
return value;
} );
}
================================================
FILE: sandbox/js/unsplash.js
================================================
let container = document.querySelector('.container');
let unsplashID = '9ad80b14098bcead9c7de952435e937cc3723ae61084ba8e729adb642daf0251';
let infScroll = new InfiniteScroll( '.container', {
path: `https://api.unsplash.com/photos?page={{#}}&client_id=${unsplashID}`,
responseBody: 'json',
history: false,
} );
infScroll.on( 'load', function( data ) {
let itemsHTML = data.map( getItem ).join('');
container.innerHTML += itemsHTML;
} );
let itemTemplateSrc = document.querySelector('#item-template').innerHTML;
function getItem( photo ) {
return microTemplate( itemTemplateSrc, photo );
}
// micro templating, sort-of
function microTemplate( src, data ) {
// replace {{tags}} in source
return src.replace( /\{\{([\w\-_.]+)\}\}/gi, function( match, key ) {
// walk through objects to get value
let value = data;
key.split('.').forEach( function( part ) {
value = value[ part ];
} );
return value;
} );
}
// load first page
infScroll.loadNextPage();
================================================
FILE: sandbox/masonry-images/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Masonry images</title>
<link rel="stylesheet" href="../css/masonry-images.css" />
<link rel="stylesheet" href="../css/loader-ellips.css" />
</head>
<body>
<div class="container">
<h1>Masonry images</h1>
<div class="grid">
<div class="grid__col-sizer"></div>
<div class="grid__gutter-sizer"></div>
<div class="grid__item"><img src="https://i.imgur.com/8AYv5Ik.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/2jpy7yO.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/qW7eNlI.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/n3M06QP.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/BNhGcqd.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/TNmDTdL.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/PRc06Uy.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/hHYirXc.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/2Qi40rJ.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/hztiigA.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/3AbaiOH.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/plQzrOk.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/uxYU1h3.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/g4F6d2R.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/s2Tqsfq.jpg" /></div>
</div>
<p><a class="pagination__next" href="page2.html">Next</a></p>
<div class="scroll-status">
<div class="infinite-scroll-request">
<div class="loader-ellips">
<span class="loader-ellips__dot loader-ellips__dot--1"></span>
<span class="loader-ellips__dot loader-ellips__dot--2"></span>
<span class="loader-ellips__dot loader-ellips__dot--3"></span>
<span class="loader-ellips__dot loader-ellips__dot--4"></span>
</div>
</div>
<p class="infinite-scroll-error">No more pages to load</p>
<p class="infinite-scroll-last">Last page loaded</p>
</div>
</div>
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<script src="../js/masonry-images.js"></script>
</body>
</html>
================================================
FILE: sandbox/masonry-images/page2.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Masonry images - page 2</title>
<link rel="stylesheet" href="../css/masonry-images.css" />
<link rel="stylesheet" href="../css/loader-ellips.css" />
</head>
<body>
<div class="container">
<h1>Masonry images - page 2</h1>
<div class="grid">
<div class="grid__col-sizer"></div>
<div class="grid__gutter-sizer"></div>
<div class="grid__item"><img src="https://i.imgur.com/9eHhuCK.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/kwxMKGr.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/fp6SDtQ.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/INAj06W.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/oLQ1tBD.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/2izuAhd.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/qoWEPJe.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/6Wr89AO.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/PQh9aqW.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/uhORS25.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/QeN4jBt.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/ahtrWkN.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/fd1Mmhy.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/AOgABvd.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/ypd73RX.jpg" /></div>
</div>
<p><a class="pagination__next" href="page3.html">Next</a></p>
<div class="scroll-status">
<div class="infinite-scroll-request">
<div class="loader-ellips">
<span class="loader-ellips__dot loader-ellips__dot--1"></span>
<span class="loader-ellips__dot loader-ellips__dot--2"></span>
<span class="loader-ellips__dot loader-ellips__dot--3"></span>
<span class="loader-ellips__dot loader-ellips__dot--4"></span>
</div>
</div>
<p class="infinite-scroll-error">No more pages to load</p>
<p class="infinite-scroll-last">Last page loaded</p>
</div>
</div>
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<script src="../js/masonry-images.js"></script>
</body>
</html>
================================================
FILE: sandbox/masonry-images/page3.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Masonry images - page 3</title>
<link rel="stylesheet" href="../css/masonry-images.css" />
<link rel="stylesheet" href="../css/loader-ellips.css" />
</head>
<body>
<div class="container">
<h1>Masonry images - page 3</h1>
<div class="grid">
<div class="grid__col-sizer"></div>
<div class="grid__gutter-sizer"></div>
<div class="grid__item"><img src="https://i.imgur.com/ZPPFND3.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/EpYbuG7.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/Qmz61wo.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/laIuV0D.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/aPia86B.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/777dcVU.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/iQRKg2a.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/kXUHDn5.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/MV9SvaP.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/qjQ9XWl.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/ZJ088Tk.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/XREWwIc.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/SuZLV2U.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/71H2B0k.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/vxOA4hg.jpg" /></div>
</div>
<p><a class="pagination__next" href="page4.html">Next</a></p>
<div class="scroll-status">
<div class="infinite-scroll-request">
<div class="loader-ellips">
<span class="loader-ellips__dot loader-ellips__dot--1"></span>
<span class="loader-ellips__dot loader-ellips__dot--2"></span>
<span class="loader-ellips__dot loader-ellips__dot--3"></span>
<span class="loader-ellips__dot loader-ellips__dot--4"></span>
</div>
</div>
<p class="infinite-scroll-error">No more pages to load</p>
<p class="infinite-scroll-last">Last page loaded</p>
</div>
</div>
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<script src="../js/masonry-images.js"></script>
</body>
</html>
================================================
FILE: sandbox/masonry-images/page4.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Masonry images - page 4</title>
<link rel="stylesheet" href="../css/masonry-images.css" />
<link rel="stylesheet" href="../css/loader-ellips.css" />
</head>
<body>
<div class="container">
<h1>Masonry images - page 4</h1>
<div class="grid">
<div class="grid__col-sizer"></div>
<div class="grid__gutter-sizer"></div>
<div class="grid__item"><img src="https://i.imgur.com/qEQsC3o.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/RLlbgpi.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/1t5kzGJ.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/FEFyXGY.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/N12L8j8.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/t5r6sHg.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/zKfV3Pu.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/8BR5akz.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/J3cPlfc.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/FTYmLBR.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/kyGu5xM.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/9rEbt6I.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/jK3PhZu.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/1EgrcMv.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/ZemMjK8.jpg" /></div>
</div>
<p><a class="pagination__next" href="page5.html">Next</a></p>
<div class="scroll-status">
<div class="infinite-scroll-request">
<div class="loader-ellips">
<span class="loader-ellips__dot loader-ellips__dot--1"></span>
<span class="loader-ellips__dot loader-ellips__dot--2"></span>
<span class="loader-ellips__dot loader-ellips__dot--3"></span>
<span class="loader-ellips__dot loader-ellips__dot--4"></span>
</div>
</div>
<p class="infinite-scroll-error">No more pages to load</p>
<p class="infinite-scroll-last">Last page loaded</p>
</div>
</div>
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<script src="../js/masonry-images.js"></script>
</body>
</html>
================================================
FILE: sandbox/masonry-images/page5.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Masonry images - page 5</title>
<link rel="stylesheet" href="../css/masonry-images.css" />
</head>
<body>
<div class="container">
<h1>Masonry images - page 5</h1>
<div class="grid">
<div class="grid__col-sizer"></div>
<div class="grid__gutter-sizer"></div>
<div class="grid__item"><img src="https://i.imgur.com/DmSgSF6.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/N9IPWC1.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/EHlzx9S.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/jtLvPpy.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/0WNdAS5.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/oytir6O.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/9T3zb3D.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/utsastL.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/jUfMIMY.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/GSKGpmx.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/QDHO514.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/PuMcFeB.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/9sRo7tL.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/azKDpNP.jpg" /></div>
<div class="grid__item"><img src="https://i.imgur.com/0zhNKir.jpg" /></div>
</div>
</div>
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<script src="../js/masonry-images.js"></script>
</body>
</html>
================================================
FILE: sandbox/page/2.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Page 2</title>
<link rel="stylesheet" href="../css/blog.css" />
<link rel="stylesheet" href="../css/loader-ellips.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>Blog page 2</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/sunset-spark-logo">
<h1 class="post-header__title">Sunset Spark logo</h1>
<p class="post-header__date">6 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/c40SxNg.png" alt="Sunset Spark logo"></p>
<p><a href="http://www.sunsetspark.org/">Sunset Spark</a> is a technology and science school in Sunset Park, Brooklyn, NY. They teach new American families all the subjects I would have devoured as a kid: robotics, creative coding, game development. <a href="https://www.instagram.com/SunsetSparkNYC/">Just take a peak at their Insta</a>. I'm proud to have designed their new logo.</p>
<p>The double-S and bolt logo is a nice balance between approachable/fun and professional/serious. The bolt is front and center. The S's take a back seat formed by the bolt and the outlines.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/every-vector-2016">
<h1 class="post-header__title">Every vector 2016</h1>
<p class="post-header__date">9 Dec 2016</p>
</a>
<div class="post__content">
<p><a href="https://i.imgur.com/mXaAcNO.png"><img src="https://i.imgur.com/mXaAcNO.png" alt="Metafizzy every vector 2016"></a></p>
<p><a href="http://www.draplin.com/1998/01/ddc100_pretty_much_everything_up_to_oct_11_2013_poster.html">Draplin-esque</a> collage. Every vector made in 2016. Accepted, rejected, upcoming, or unused.</p>
<script>
console.log('hello page 2!')
</script>
</div>
</article>
</div>
<nav class="pagination">
<a class="pagination__previous" href="../scroll-loader.js">Previous · 1</a>
<span class="pagination__current">Page 2 of 17</span>
<a class="pagination__next" href="3.html">Next · 3</a>
</nav>
</div>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../js/scroll-loader.js"></script>
</body>
</html>
================================================
FILE: sandbox/page/3.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>#3 page</title>
<link rel="stylesheet" href="../css/blog.css" />
<link rel="stylesheet" href="../css/loader-ellips.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>Blog page 3</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/shepherd-logo">
<h1 class="post-header__title">Shepherd logo & brand</h1>
<p class="post-header__date">6 Dec 2016</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/e0Y9rfd.png" alt="Shepherd brand"></p>
<p>Shepherd is a platform for business owners to build communities and peer groups so they can better face the challenges and opportunities they share (launching soon!). I designed the brand with supervision from Shepherd's lead, Ben. The main logo depicts two crossed shepherd crooks forming an S.</p>
<p>The alternate logo is a dog, an Australian shepherd. Shepherds still use dogs to herd and control their flocks. Dogs are a great symbol of companionship and loyalty, qualities central to Shepherd. Herding breeds like the Aussie and border collie are the smartest.</p>
<p>Erin loves Aussies. Even as her husband, I'll always be second place in her heart, behind her family Aussie, Tess. She was thrilled to find my screen like this:</p>
<p><img src="https://i.imgur.com/N7OuNQS.jpg" alt="Aussie desktop"></p>
<p>Designing an Aussie logo was a fun challenge. Blue merle Aussies have striking coloring, with gray and black patchwork and tan points. My solution was to stylize the whole image: use a striped pattern for the patches, punch up the colors to blue and gold, and stick to an angular grid. It's a dog you won't forget.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/logo-pizza-delivered">
<h1 class="post-header__title">Logo Pizza Delivered</h1>
<p class="post-header__date">2 Oct 2016</p>
</a>
<div class="post__content">
<p><a href="http://logo.pizza"><img src="https://i.imgur.com/u40AMfs.png" alt="Logo Pizza"></a></p>
<p>After shipping <a href="/blog/flickity-v2-released">Flickity v2</a> thus wrapping 2016's huge development project, I didn't have it in me to write another line of code. I discussed my state of mind and motivation in this 3 min podcast.</p>
<p><a class="embedly-card" href="https://bumpers.fm/e/avbd99o4m4ug00o93t30">New thing, shortsightedness on Bumpers</a></p>
<script async src="//cdn.embedly.com/widgets/platform.js"></script>
<p>Looking to change things up, I started making logos. </p>
<p>Man, I love logos. A little piece of imagery that represents the ideal you want your project to be — that's design magic right there. I've been able to work on some great logo projects, but I've been itching to do more. Rather than wait for projects to come my way, I gave myself a project of my own: design 50 logos. <strong>50 logos in 30 days.</strong></p>
<p>Fifty logos is a lot. At least one or two a day for an entire month. I tried pushing myself: exploring different styles, subjects, and techniques. It was like design boot camp: working all those muscles you never use.</p>
</div>
</article>
</div>
<nav class="pagination">
<a class="pagination__previous" href="2.html">Previous · 2</a>
<span class="pagination__current">Page 3 of 17</span>
<a class="pagination__next" href="4.html">Next · 4</a>
</nav>
</div>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../js/scroll-loader.js"></script>
</body>
</html>
================================================
FILE: sandbox/page/4.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>4th page</title>
<link rel="stylesheet" href="../css/blog.css" />
<link rel="stylesheet" href="../css/loader-ellips.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>Blog page 4</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/fetchy-shiba-logo">
<h1 class="post-header__title">Fetchy Shiba logo comin' to get ya</h1>
<p class="post-header__date">18 Aug 2016</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/zwxfN0H.png" alt="Fetchy logos"></p>
<p><a href="https://fetchy.io">Fetchy</a> takes YouTube videos and converts them to downloadable videos and audio files. Thank heavens. Pulling out videos to make clips and gifs has been difficult for too long. Fetchy is a great service that I consistently need. I was pumped to be tasked with designing its logo.</p>
<p>I kicked off the concepts with dogs, lots of dogs. Why over think it? I sketched out a variety of breeds, actions, and styles. I tried using the most meme-able breeds: corgis and pugs. To mix things up, I sketched a couple "f bird" concepts and more straight-forward monogram letters.</p>
<p>Look at that winged puppy. Too bad he didn't get used. Fly high, little guy.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/codepen-showcase-round-1">
<h1 class="post-header__title">CodePen showcase: Round 1</h1>
<p class="post-header__date">9 Aug 2016</p>
</a>
<div class="post__content">
<p>Metafizzy's libraries have hundreds of CodePen demos. One for every feature, option, and behavior. But these demos are simplified examples. They don't do a good job of showing off what they're capable of. So I reached out to the true code artists of CodePen to see how they could make Isotope, Flickity, and Packery shine in the spotlight. Give 'em the old razzle-dazzle.</p>
<p>I'll be collecting these in the <a href="https://codepen.io/collection/AVjkpG/">Metafizzy showcase CodePen collection</a>, as well as individual collections for each library.</p>
<ul>
<li><a href="https://codepen.io/collection/DgkGmy">Isotope showcase</a></li>
<li><a href="https://codepen.io/collection/AYWzeJ">Flickity showcase</a></li>
<li><a href="https://codepen.io/collection/DRRLpZ/">Packery showcase</a></li>
</ul>
<p><a href="https://codepen.io/Kseso">Kseso</a> makes the most of Flickity and CSS, using <code>is-selected</code> classes to trigger transitions for each slide. Within each slide is a great mix of imagery and typography at that!</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="oLyqGg" data-default-tab="result" data-user="Kseso" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/Kseso/pen/oLyqGg/">Playing with Flickity</a> by Kseso (<a href="https://codepen.io/Kseso">@Kseso</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<p><a href="https://codepen.io/jshawl">Jesse Shawl</a> made a slide puzzle with Packery. I can't believe this actually works!</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="ZORWxv" data-default-tab="result" data-user="jshawl" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/jshawl/pen/ZORWxv/">Order the tiles</a> by Jesse Shawl (<a href="https://codepen.io/jshawl">@jshawl</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<p><a href="https://codepen.io/pixelass">Gregor Adams</a> took the Packery concept composed this 3D cube ballet. I love how the cubes align even with staggered animation.</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="wWxPqg" data-default-tab="result" data-user="pixelass" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/pixelass/pen/wWxPqg/">Pack(ev)ery thing</a> by Gregor Adams (<a href="https://codepen.io/pixelass">@pixelass</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<p>But he didn't stop there. Gregor made another Packery demo, this time using simpler rectangles. Check out how it works when you resize it.</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="zBLrRr" data-default-tab="result" data-user="pixelass" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/pixelass/pen/zBLrRr/">Packery hackery</a> by Gregor Adams (<a href="https://codepen.io/pixelass">@pixelass</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<p>Perhaps the best use of Isotope ever, <a href="https://codepen.io/acjdesigns">Antoinette Janus</a> makes sense of many characters and fusions in Steven Universe. I have a hard time remembering the difference between Sugilite and Sardonyx ;)</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="GqXgAE" data-default-tab="result" data-user="acjdesigns" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/acjdesigns/pen/GqXgAE/">Steven Universe x Isotope [Sponsored]</a> by Antoinette Janus (<a href="https://codepen.io/acjdesigns">@acjdesigns</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<p><a href="https://codepen.io/bennettfeely">Bennett Feely</a> makes a 3D, hovering Packery layout. It's melting my mind how this works.</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="JKBJbx" data-default-tab="result" data-user="bennettfeely" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/bennettfeely/pen/JKBJbx/">Packery layout with 3D blocks</a> by Bennett Feely (<a href="https://codepen.io/bennettfeely">@bennettfeely</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<p><a href="https://codepen.io/katydecorah">Katy DeCorah</a> brings her ever-impressive style to make this captivating grid animation using Packery. This one is fun to resize.</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="qNygjp" data-default-tab="result" data-user="katydecorah" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/katydecorah/pen/qNygjp/">Geo scope</a> by Katy DeCorah (<a href="https://codepen.io/katydecorah">@katydecorah</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<p>It's a delight to see what creative coders can come up with.</p>
<p>We have some more artists lined up so stay tuned for Round 2!</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
</div>
</article>
<nav class="pagination">
<a class="pagination__previous" href="/blog/page3">Previous · 3</a>
<span class="pagination__current">Page 4 of 17</span>
<a class="pagination__next" href="5.html">Next · 5</a>
</nav>
</div>
</div>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../js/scroll-loader.js"></script>
</body>
</html>
================================================
FILE: sandbox/page/5.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>The fifth page</title>
<link rel="stylesheet" href="../css/blog.css" />
<link rel="stylesheet" href="../css/loader-ellips.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>Blog page 5</h1>
</div>
</div>
<div class="container">
<article class="post">
<a class="post-header" href="/blog/flickity-takes-the-field">
<h1 class="post-header__title">Flickity takes The Field</h1>
<p class="post-header__date">17 Dec 2015</p>
</a>
<div class="post__content">
<p><a href="http://www.thefieldmag.com/"><em>The Field</em></a> is a new outdoor lifestyle publication, or as founder Chris Stillitano puts it, "<em>The Field</em> is a place for good design and the great outdoors." It's also a great place to see Flickity in use.</p>
<p><em>The Field</em>'s beautiful photo galleries are made with <a href="https://flickity.metafizzy.co/">Flickity</a>. It's great to see how they've taken advantage of Flickity's <a href="https://flickity.metafizzy.co/style.html#previous-next-buttons">customizable previous & buttons</a> to minimally style and position them. The slide counters change <a href="https://flickity.metafizzy.co/api.html#cellselect">on <code>cellSelect</code></a>.</p>
<div class="fit-video">
<iframe src="https://vid.me/e/opMu?muted=1" width="740" height="480" frameborder="0" allowfullscreen webkitallowfullscreen mozallowfullscreen scrolling="no"></iframe>
</div>
<p>The site has a keen design sense, utilizing different layouts for its various content types. So much care went into these photo galleries. Watch how images resize and center while resizing the browser, even with images of varying aspect ratios.</p>
<div class="fit-video">
<iframe src="https://vid.me/e/LIHQ?muted=1" width="806" height="480" frameborder="0" allowfullscreen webkitallowfullscreen mozallowfullscreen scrolling="no"></iframe>
</div>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/fizzy-bear-branded">
<h1 class="post-header__title">Fizzy bear branded</h1>
<p class="post-header__date">1 Dec 2015</p>
</a>
<div class="post__content">
<p>Fizzy's got a brand new brand.</p>
<p><img src="https://i.imgur.com/T0sVgzD.png" alt="Metafizzy brand"></p>
<p><a href="/blog/logotype-james-edmondson">The previous logotype was a beaut</a> and served Metafizzy well. But it did have some issues (No fault of James' — these issues were all self-inflicted as I requested).</p>
<ul>
<li>Legibility trouble: People would read "Meta fuzzy" or worse "Meta furry"</li>
<li>The two line treatment led to people spelling the name as two words "Meta Fizzy"</li>
<li>It did not reduce well. It didn't hold up at small sizes, like a Twitter avatar or favicon</li>
</ul>
</div>
</article>
<nav class="pagination">
<a class="pagination__previous" href="/blog/page4">Previous · 4</a>
<span class="pagination__current">Page 5 of 17</span>
<a class="pagination__next" href="6.html">Next · 6</a>
</nav>
</div>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../js/scroll-loader.js"></script>
</body>
</html>
================================================
FILE: sandbox/page/6.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Page number 6</title>
<link rel="stylesheet" href="../css/blog.css" />
<link rel="stylesheet" href="../css/loader-ellips.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>Blog page 6</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/triggering-jquery-vanilla-js-events">
<h1 class="post-header__title">Triggering jQuery and vanilla JS events</h1>
<p class="post-header__date">3 Jul 2015</p>
</a>
<div class="post__content">
<p>You can now bind to jQuery events in <a href="https://isotope.metafizzy.co/">Isotope</a>, <a href="https://packery.metafizzy.co/">Packery</a>, and <a href="http://desandro.masonry.com">Masonry</a>. The recent upgrades allow you to use standard jQuery event methods <code>.on()</code>, <code>.off</code>, and <code>.one()</code>, rather than using ugly plugin method syntax.</p>
<pre><code class="lang-js"><span class="hljs-comment">// previous plugin method syntax</span>
<span class="hljs-comment">// Isotope <= v2.2.0</span>
$grid.isotope( <span class="hljs-string">'on'</span>, <span class="hljs-string">'layoutComplete'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{...})
<span class="hljs-comment">// standard jQuery event</span>
<span class="hljs-comment">// Isotope >= v2.2.1</span>
$grid.on( <span class="hljs-string">'layoutComplete'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{...})
</code></pre>
<p><a href="https://codepen.io/desandro/pen/scajv">View Isotope layoutComplete demo on CodePen</a>.</p>
<p>This feature is already in <a href="https://flickity.metafizzy.co/">Flickity</a> and <a href="http://draggabilly.desandro.com/">Draggabilly</a>. It was prime time to port it over to the layout libraries.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/dropping-ie8-9-support">
<h1 class="post-header__title">Dropping IE8 and 9 support</h1>
<p class="post-header__date">1 Jul 2015</p>
</a>
<div class="post__content">
<p>2015 will be the last year Metafizzy supports IE8 and 9. In 2016, we'll release new major versions of our libraries that remove support for the Internet Explorers of yore.</p>
<p>The time has come. <a href="http://caniuse.com/usage-table">Global browser usage</a> is especially low for both browsers. IE8 is at 2%. IE9 is at 1.5%.</p>
<ul>
<li><a href="https://www.microsoft.com/en-us/WindowsForBusiness/end-of-xp-support">Microsoft ended support for Windows XP in 2014</a>, <a href="https://support.microsoft.com/en-us/lifecycle/search?sort=PN&alpha=internet%20explorer">support for IE8 will end in January 2016</a>. IE9 will only be supported for Windows Vista.</li>
<li>The New York Times <a href="http://www.nytimes.com/content/help/site/ie8-support.html">dropped IE8 support in 2014</a> and <a href="http://www.nytimes.com/content/help/site/ie9-support.html">IE9 support in June this year</a></li>
<li><a href="http://emberjs.com/blog/2015/04/20/ie8-support-update.html">Ember 2.0 will not support IE8</a>. <a href="https://github.com/emberjs/rfcs/pull/45">Read Ember community input on IE support</a></li>
<li><a href="http://blog.getbootstrap.com/2014/10/29/bootstrap-3-3-0-released/">Bootstrap 4 will drop support for IE8</a></li>
<li><a href="http://analytics.blogspot.com/2014/12/keeping-ga-web-experience-modern.html">Google Analytics dropped support for IE9 in January this year</a></li>
</ul>
<p>IE8 and 9 were the last browsers to have significant feature gaps. Dropping their support will allow a lot of ugly code to be removed. Polyfill libraries can be removed like <a href="https://github.com/desandro/eventie">eventie</a>, <a href="https://github.com/desandro/classie">classie</a>, and <a href="https://github.com/desandro/doc-ready">doc-ready</a>.</p>
<p>If you still require IE8 and 9 support, previous versions will still be completely available to download and view documentation. The old versions will no longer get bug fixes or improved features, but you can continue using them as long as you like.</p>
<p>We've opened issues to track this change for each library. Follow along:</p>
<ul>
<li><a href="https://github.com/metafizzy/isotope/issues/947">Isotope dropping IE8 and 9 support</a></li>
<li><a href="https://github.com/metafizzy/flickity/issues/178">Flickity dropping IE8 and 9 support</a></li>
<li><a href="https://github.com/metafizzy/packery/issues/281">Packery dropping IE8 and 9 support</a></li>
<li><a href="https://github.com/desandro/masonry/issues/719">Masonry dropping IE8 and 9 support</a></li>
</ul>
</div>
</article>
</div>
<nav class="pagination">
<a class="pagination__previous" href="/blog/page5">Previous · 5</a>
<span class="pagination__current">Page 6 of 17</span>
</nav>
</div>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../js/scroll-loader.js"></script>
</body>
</html>
================================================
FILE: sandbox/prefill.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>prefill</title>
<link rel="stylesheet" href="css/blog.css" />
<style>
.post {
max-height: 100px;
margin: 20px 0;
overflow-y: hidden;
}
</style>
</head>
<body>
<div class="site-header">
<div class="container">
<h1>prefill</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/wyday-logos">
<h1 class="post-header__title">wyDay logos</h1>
<p class="post-header__date">22 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes "premium software development tools for high-tech companies." A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before & after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/mealio-logo">
<h1 class="post-header__title">Mealio logo</h1>
<p class="post-header__date">8 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user's preferences. It's like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly & clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>
</div>
</article>
</div> <!-- posts-container -->
<div class="scroll-status">
<div class="infinite-scroll-request">
<div class="loader-ellips">
<span class="loader-ellips__dot loader-ellips__dot--1"></span>
<span class="loader-ellips__dot loader-ellips__dot--2"></span>
<span class="loader-ellips__dot loader-ellips__dot--3"></span>
<span class="loader-ellips__dot loader-ellips__dot--4"></span>
</div>
</div>
<p class="infinite-scroll-error">Last page reached</p>
</div>
<nav class="pagination">
<span class="pagination__current">Page 1 of 17</span>
<a class="pagination__next" href="page/2.html">Next · 2</a>
</nav>
</div> <!-- container -->
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/status.js"></script>
<script>
var infScroll = new InfiniteScroll( '.posts-container', {
path: '.pagination__next',
append: '.post',
prefill: true,
history: false,
});
</script>
</body>
</html>
================================================
FILE: sandbox/scroll-3.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>scroll 3, then button (Facebook)</title>
<link rel="stylesheet" href="css/blog.css" />
<style>
.load-more-button {
display: none;
}
</style>
</head>
<body>
<div class="site-header">
<div class="container">
<h1>scroll 3, then button</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/wyday-logos">
<h1 class="post-header__title">wyDay logos</h1>
<p class="post-header__date">22 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes "premium software development tools for high-tech companies." A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before & after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/mealio-logo">
<h1 class="post-header__title">Mealio logo</h1>
<p class="post-header__date">8 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user's preferences. It's like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly & clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>
</div>
</article>
</div>
<p>
<button class="load-more-button">Load more</button>
</p>
<nav class="pagination">
<span class="pagination__current">Page 1 of 17</span>
<a class="pagination__next" href="page/2.html">Next · 2</a>
</nav>
</div>
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script>
var container = document.querySelector('.posts-container');
var infScroll = new InfiniteScroll( container, {
path: '.pagination__next',
append: '.post',
historyTitle: true,
history: false,
});
var loadMoreButton = document.querySelector('.load-more-button');
var loadPageCount = 0;
infScroll.on( 'load', onPageLoad );
function onPageLoad() {
if ( infScroll.loadCount == 3 ) {
loadMoreButton.style.display = 'inline-block';
infScroll.options.loadOnScroll = false;
infScroll.off( 'load', onPageLoad );
}
}
loadMoreButton.onclick = function() {
infScroll.loadNextPage();
};
</script>
</body>
</html>
================================================
FILE: sandbox/scroll-loader.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>scroll loader</title>
<link rel="stylesheet" href="css/blog.css" />
<link rel="stylesheet" href="css/loader-ellips.css" />
</head>
<body>
<div class="site-header">
<div class="container">
<h1>scroll loader</h1>
</div>
</div>
<div class="container">
<div class="posts-container">
<article class="post">
<a class="post-header" href="/blog/wyday-logos">
<h1 class="post-header__title">wyDay logos</h1>
<p class="post-header__date">22 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes "premium software development tools for high-tech companies." A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before & after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>
</div>
</article>
<article class="post">
<a class="post-header" href="/blog/mealio-logo">
<h1 class="post-header__title">Mealio logo</h1>
<p class="post-header__date">8 Mar 2017</p>
</a>
<div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user's preferences. It's like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly & clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>
</div>
</article>
</div> <!-- posts-container -->
<div class="scroll-status">
<div class="infinite-scroll-request">
<div class="loader-ellips">
<span class="loader-ellips__dot loader-ellips__dot--1"></span>
<span class="loader-ellips__dot loader-ellips__dot--2"></span>
<span class="loader-ellips__dot loader-ellips__dot--3"></span>
<span class="loader-ellips__dot loader-ellips__dot--4"></span>
</div>
</div>
<p class="infinite-scroll-error">No more pages to load</p>
<p class="infinite-scroll-last">Last page loaded</p>
</div>
<nav class="pagination">
<span class="pagination__current">Page 1 of 17</span>
<a class="pagination__next" href="page/2.html">Next · 2</a>
</nav>
</div> <!-- container -->
<footer class="site-footer">
<div class="container">
<p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
</div>
</footer>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/button.js"></script>
<script src="../js/status.js"></script>
<script src="js/scroll-loader.js"></script>
</body>
</html>
================================================
FILE: sandbox/unsplash-masonry.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>unsplash masonry</title>
<link rel="stylesheet" href="css/loader-ellips.css" />
<style>
html { overflow-y: scroll; }
body {
padding-bottom: 400px;
}
.grid-sizer, .grid-item {
width: 25%;
}
.grid-item img {
display: block;
max-width: 100%;
}
.grid-item__caption {
position: absolute;
left: 0;
bottom: 0;
font-size: 14px;
}
.grid-item__caption a {
color: white;
padding: 0 10px;
text-decoration: none;
}
</style>
</head>
<body>
<h1>unsplash masonry</h1>
<p>Loading photos from <a href="https://unsplash.com/developers?utm_source=infinite-scroll-demos&utm_medium=referral&utm_campaign=api-credit">Unsplash API</a></p>
<div class="grid">
<div class="grid-sizer"></div>
</div>
<div class="scroll-status">
<div class="infinite-scroll-request">
<div class="loader-ellips">
<span class="loader-ellips__dot loader-ellips__dot--1"></span>
<span class="loader-ellips__dot loader-ellips__dot--2"></span>
<span class="loader-ellips__dot loader-ellips__dot--3"></span>
<span class="loader-ellips__dot loader-ellips__dot--4"></span>
</div>
</div>
<p class="infinite-scroll-error">No more pages to load</p>
<p class="infinite-scroll-last">Last page loaded</p>
</div>
<script type="text/html" id="item-template">
<div class="grid-item">
<img src="{{urls.small}}" alt="Photo by {{user.name}}" />
<p class="grid-item__caption">
<a href="{{user.links.html}}?utm_source=infinite-scroll-demos&utm_medium=referral&utm_campaign=api-credit">{{user.name}}</a>
</p>
</div>
</script>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../node_modules/get-size/get-size.js"></script>
<script src="../node_modules/outlayer/item.js"></script>
<script src="../node_modules/outlayer/outlayer.js"></script>
<script src="../node_modules/masonry-layout/masonry.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/status.js"></script>
<script src="js/unsplash-masonry.js"></script>
</body>
</html>
================================================
FILE: sandbox/unsplash.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>unsplash</title>
<style>
#item-template { display: none; }
</style>
</head>
<body>
<h1>unsplash</h1>
<p>Loading photos from <a href="https://unsplash.com/developers?utm_source=infinite-scroll-demos&utm_medium=referral&utm_campaign=api-credit">Unsplash API</a></p>
<div class="container"></div>
<script type="text/html" id="item-template">
<div class="item">
<img src="{{urls.small}}" />
<p class="caption">
<a href="{{user.links.html}}?utm_source=infinite-scroll-demos&utm_medium=referral&utm_campaign=api-credit">{{user.name}}</a>
</p>
</div>
</script>
<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/status.js"></script>
<script src="js/unsplash.js"></script>
</body>
</html>
================================================
FILE: test/_get-server.js
================================================
// https://developer.mozilla.org/en-US/docs/Node_server_without_framework
// https://stackoverflow.com/a/29046869
const http = require('http');
const url = require('url');
const fs = require('fs');
const path = require('path');
module.exports = function getServer() {
return http.createServer( listener );
};
const mimeTypes = {
'.ico': 'image/x-icon',
'.html': 'text/html',
'.js': 'text/javascript',
'.json': 'application/json',
'.css': 'text/css',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.wav': 'audio/wav',
'.mp3': 'audio/mpeg',
'.svg': 'image/svg+xml',
'.pdf': 'application/pdf',
'.doc': 'application/msword',
};
function listener( req, res ) {
// if ( req.url.includes('.html') ) {
// console.log( `${req.method} ${req.url}` );
// }
let parsedUrl = url.parse( req.url );
let pathname = `.${parsedUrl.pathname}`;
let ext = path.parse( pathname ).ext;
fs.exists( pathname, function( exist ) {
if ( !exist ) {
// if the file is not found, return 404
res.statusCode = 404;
res.end(`File ${pathname} not found`);
return;
}
// if is a directory search for index file matching the extention
if ( fs.statSync( pathname ).isDirectory() ) {
pathname += '/index' + ext;
}
// read file from file system
fs.readFile( pathname, function( err, data ) {
if ( err ) {
res.statusCode = 500;
res.end(`Error getting the file: ${err}.`);
} else {
// if the file is found, set Content-type and send data
let mimeType = mimeTypes[ ext ] || 'text/plain';
res.setHeader( 'Content-type', mimeType );
res.end( data );
}
} );
} );
}
================================================
FILE: test/_with-page.js
================================================
// https://github.com/avajs/ava/blob/v3.13.0/docs/recipes/puppeteer.md
const puppeteer = require('puppeteer');
const getServer = require('./_get-server.js');
const getPort = require('get-port');
module.exports = async( t, run ) => {
const port = await getPort({ port: getPort.makeRange( 9100, 9200 ) });
let server = getServer();
server.listen( port );
const browser = await puppeteer.launch();
const page = await browser.newPage();
try {
await run( t, page );
} finally {
await page.close();
await browser.close();
server.close();
}
};
================================================
FILE: test/check-last-page.js
================================================
const test = require('ava');
const puppeteer = require('puppeteer');
const getServer = require('./_get-server.js');
const port = 9005;
let server, browser;
test.before( async function() {
server = getServer();
server.listen( port );
browser = await puppeteer.launch();
} );
test.after( async function() {
await browser.close();
server.close();
} );
async function withPage( t, run ) {
const page = await browser.newPage();
await page.goto(`http://localhost:${port}/test/html/page-load.html`);
try {
await run( t, page );
} finally {
await page.close();
}
}
function getPageAssertions() {
return async function() {
let { infScroll, serialT } = window;
// check that last doesn't trigger when not last page
infScroll.on( 'last', onLast );
function onLast() {
serialT.fail('last event should not trigger when not last page');
}
await infScroll.loadNextPage().then( function() {
infScroll.off( 'last', onLast );
} );
let promise = new Promise( function( resolve ) {
infScroll.once( 'last', function() {
serialT.pass(`last event triggered on ${infScroll.pageIndex}`);
resolve( serialT.assertions );
} );
} );
// load page 3
infScroll.loadNextPage();
return promise;
};
}
// ------ tests ------ //
test( 'checkLastPage: true', withPage, async function( t, page ) {
await page.evaluate( function() {
window.infScroll = new InfiniteScroll( '.container', {
append: '.post',
path: '.next-link',
// checkLastPage: true, // true by default
} );
} );
let assertions = await page.evaluate( getPageAssertions() );
assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );
test( 'checkLastPage: ".selector-string"', withPage, async function( t, page ) {
await page.evaluate( function() {
window.infScroll = new InfiniteScroll( '.container', {
append: '.post',
path: 'page/{{#}}.html',
checkLastPage: '.next-link',
} );
} );
let assertions = await page.evaluate( getPageAssertions() );
assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );
test( 'checkLastPage with empty page', withPage, async function( t, page ) {
await page.evaluate( function() {
window.infScroll = new InfiniteScroll( '.container', {
// provide only page/2.html, then falsy
path: function() {
if ( this.pageIndex < 2 ) {
return `page/${this.pageIndex + 1}.html`;
} else {
return 'page/empty.html';
}
},
// checkLastPage: true, // true by default
append: '.post',
} );
} );
let assertions = await page.evaluate( getPageAssertions() );
assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );
test( 'checkLastPage with path: function() {}', withPage, async function( t, page ) {
let assertions = await page.evaluate( function() {
let infScroll = new InfiniteScroll( '.container', {
// provide only page/2.html, then falsy
path: function() {
if ( this.pageIndex < 2 ) {
return `page/${this.pageIndex + 1}.html`;
}
},
// checkLastPage: true, // true by default
append: '.post',
} );
// function returning falsey will trigger last right after pageLoad
let promise = new Promise( function( resolve ) {
infScroll.once( 'last', function() {
serialT.is( infScroll.pageIndex, 2 );
resolve( serialT.assertions );
} );
} );
// load page 2
infScroll.loadNextPage();
return promise;
} );
assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );
================================================
FILE: test/dist-jquery.js
================================================
const test = require('ava');
const puppeteer = require('puppeteer');
const getServer = require('./_get-server.js');
const port = 9011;
let server, browser, page;
test.before( async function() {
server = getServer();
server.listen( port );
browser = await puppeteer.launch();
page = await browser.newPage();
await page.goto(`http://localhost:${port}/test/html/dist-jquery.html`);
} );
test.after( async function() {
page.close();
await browser.close();
server.close();
} );
// ------ tests ------ //
test( 'dist jQuery', async( t ) => {
let assertions = await page.evaluate( function() {
const { jQuery } = window;
let $container = document.querySelector('.container');
$container = jQuery('.container').infiniteScroll({
path: 'page/{{#}}.html',
append: '.post',
});
let infScroll = $container.data('infinite-scroll');
return $container.infiniteScroll('loadNextPage')
.then( function( load ) {
let { response, body, items } = load;
serialT.true( response instanceof Response );
serialT.true( response.ok );
serialT.is( response.status, 200 );
serialT.true( body instanceof HTMLDocument );
serialT.true( items instanceof NodeList );
serialT.is( items.length, 2 );
serialT.true( items[0] == $container[0].children[1] );
serialT.true( items[1] == $container[0].children[2] );
serialT.is( infScroll.pageIndex, 2 );
serialT.is( infScroll.loadCount, 1 );
} )
.then( () => serialT.assertions );
} );
assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );
================================================
FILE: test/dist.js
================================================
const test = require('ava');
const puppeteer = require('puppeteer');
const getServer = require('./_get-server.js');
const port = 9010;
let server, browser, page;
test.before( async function() {
server = getServer();
server.listen( port );
browser = await puppeteer.launch();
page = await browser.newPage();
await page.goto(`http://localhost:${port}/test/html/dist.html`);
} );
test.after( async function() {
page.close();
await browser.close();
server.close();
} );
// ------ tests ------ //
test( 'dist', async( t ) => {
let assertions = await page.evaluate( function() {
let $container = document.querySelector('.container');
let infScroll = new InfiniteScroll( $container, {
path: 'page/{{#}}.html',
append: '.post',
} );
return infScroll.loadNextPage()
.then( function( load ) {
let { response, body, items } = load;
serialT.true( response instanceof Response );
serialT.true( response.ok );
serialT.is( response.status, 200 );
serialT.true( body instanceof HTMLDocument );
serialT.true( items instanceof NodeList );
serialT.is( items.length, 2 );
serialT.true( items[0] == $container.children[1] );
serialT.true( items[1] == $container.children[2] );
serialT.is( infScroll.pageIndex, 2 );
serialT.is( infScroll.loadCount, 1 );
} )
.then( () => serialT.assertions );
} );
assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );
================================================
FILE: test/history.js
================================================
const test = require('ava');
const puppeteer = require('puppeteer');
const getServer = require('./_get-server.js');
const port = 9007;
let server, browser, page;
test.before( async function() {
server = getServer();
server.listen( port );
browser = await puppeteer.launch();
page = await browser.newPage();
await page.goto(`http://localhost:${port}/test/html/history.html`);
} );
test.after( async function() {
await page.close();
await browser.close();
server.close();
} );
// ------ tests ------ //
test( 'history', async( t ) => {
t.plan( 6 );
let assertions = await page.evaluate( function() {
let $container = document.querySelector('.container');
let infScroll = new InfiniteScroll( $container, {
path: 'page/{{#}}.html',
append: '.post',
scrollThreshold: false,
// history: 'replace', // default
historyTitle: true,
} );
function getTop( $elem ) {
return $elem.getBoundingClientRect().top + window.scrollY;
}
let page1Top = getTop( $container ) - 100;
let page2Top, page3Top;
// append next page, scroll to it to trigger history
return new Promise( function( resolve ) {
infScroll.once( 'append', function( response, _path, items ) {
page2Top = getTop( items[0] );
// TODO this block should be its own promise, but can't get it to work
infScroll.once( 'history', function( title, path ) {
serialT.is( path, location.href, `2nd page history url changed to ${path}` );
serialT.is( title, document.title, `document title changed to ${title}` );
resolve();
} );
scrollTo( 0, page2Top - window.innerHeight / 4 );
} );
infScroll.loadNextPage();
} )
// scroll back up to top of page to trigger history on previous page
.then( new Promise( function( resolve ) {
infScroll.once( 'history', function( title, path ) {
serialT.is( path, location.href, `1st page history, url changed to ${path}` );
serialT.is( title, document.title, `document title changed to ${title}` );
resolve();
} );
scrollTo( 0, page1Top );
} ) )
.then( new Promise( function( resolve ) {
infScroll.once( 'append', function( response, _path, items ) {
page3Top = getTop( items[0] );
resolve();
} );
infScroll.loadNextPage();
} ) )
.then( new Promise( function( resolve ) {
infScroll.once( 'history', function( title, path ) {
serialT.is( path, location.href, `3rd page history url changed to ${path}` );
serialT.is( title, document.title, `document title changed to ${title}` );
resolve();
} );
scrollTo( 0, page3Top - window.innerHeight / 4 );
} ) )
.then( () => serialT.assertions );
} );
assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );
================================================
FILE: test/html/_serial-t.js
================================================
// serialT works kinda like t, but to pass serialized assertions out of .evaluate
// serialT.assertions will be passed out of .evaluate
// https://github.com/avajs/ava/blob/master/docs/03-assertions.md#built-in-assertions
const tKeys = [
'pass',
'fail',
'assert',
'truthy',
'falsy',
'true',
'false',
'is',
'not',
'deepEqual',
'notDeepEqual',
];
window.serialT = tKeys.reduce( ( serializedT, method ) => {
serializedT[ method ] = function( ...args ) {
serializedT.assertions.push({ method, args });
};
return serializedT;
}, { assertions: [] } );
================================================
FILE: test/html/dist-jquery.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Infinite Scroll test - dist jQuery</title>
<link rel="stylesheet" href="test.css" />
<!-- dependencies -->
<script src="../../node_modules/jquery/dist/jquery.js"></script>
<script src="../../dist/infinite-scroll.pkgd.min.js"></script>
<!-- test helper -->
<script src="_serial-t.js"></script>
</head>
<body>
<h1>Infinite Scroll test - dist jQuery</h1>
<div class="container">
<div class="post">page 1, post 1</div>
</div>
<p><a class="next-link" href="page/2.html">Next</a></p>
</body>
</html>
================================================
FILE: test/html/dist.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Infinite Scroll test - dist</title>
<link rel="stylesheet" href="test.css" />
<!-- dependencies -->
<script src="../../dist/infinite-scroll.pkgd.min.js"></script>
<!-- test helper -->
<script src="_serial-t.js"></script>
</head>
<body>
<h1>Infinite Scroll test - dist</h1>
<div class="container">
<div class="post">page 1, post 1</div>
</div>
<p><a class="next-link" href="page/2.html">Next</a></p>
</body>
</html>
================================================
FILE: test/html/history.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Infinite Scroll test - history</title>
<link rel="stylesheet" href="test.css" />
<!-- dependencies -->
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<!-- infinite scroll -->
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<!-- test helper -->
<script src="_serial-t.js"></script>
</head>
<body>
<h1>Infinite Scroll test - history</h1>
<div class="container container--big-posts">
<div class="post">page 1, post 1</div>
<div class="post">page 1, post 2</div>
</div>
</body>
</html>
================================================
FILE: test/html/outlayer.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Infinite Scroll test - page-load</title>
<link rel="stylesheet" href="test.css" />
<style>
.outlayer-item,
.col-sizer {
width: calc(100%/3);
}
.outlayer-item {
float: left;
}
.outlayer-item img {
display: block;
max-width: 100%;
}
</style>
<!-- dependencies -->
<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<!-- outlayer -->
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<!-- infinite scroll -->
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<!-- test helper -->
<script src="_serial-t.js"></script>
</head>
<body>
<h1>Infinite Scroll test - outlayer</h1>
<div class="container container--outl
gitextract_pwag9y0f/
├── .eslintrc.js
├── .github/
│ ├── contributing.md
│ ├── issue_template.md
│ └── workflows/
│ └── nodejs.yml
├── .gitignore
├── .nvmrc
├── LICENSE.txt
├── README.md
├── bin/
│ ├── build-dist.js
│ └── version.js
├── bower.json
├── dist/
│ └── infinite-scroll.pkgd.js
├── js/
│ ├── button.js
│ ├── core.js
│ ├── history.js
│ ├── index.js
│ ├── page-load.js
│ ├── scroll-watch.js
│ └── status.js
├── package.json
├── sandbox/
│ ├── button-class.html
│ ├── button-first.html
│ ├── button-load.html
│ ├── container-scroll.html
│ ├── css/
│ │ ├── blog.css
│ │ ├── loader-ellips.css
│ │ └── masonry-images.css
│ ├── element-scroll.html
│ ├── html-init.html
│ ├── jquery-plugin.html
│ ├── js/
│ │ ├── masonry-images.js
│ │ ├── scroll-loader.js
│ │ ├── unsplash-masonry.js
│ │ └── unsplash.js
│ ├── masonry-images/
│ │ ├── index.html
│ │ ├── page2.html
│ │ ├── page3.html
│ │ ├── page4.html
│ │ └── page5.html
│ ├── page/
│ │ ├── 2.html
│ │ ├── 3.html
│ │ ├── 4.html
│ │ ├── 5.html
│ │ └── 6.html
│ ├── prefill.html
│ ├── scroll-3.html
│ ├── scroll-loader.html
│ ├── unsplash-masonry.html
│ └── unsplash.html
└── test/
├── _get-server.js
├── _with-page.js
├── check-last-page.js
├── dist-jquery.js
├── dist.js
├── history.js
├── html/
│ ├── _serial-t.js
│ ├── dist-jquery.html
│ ├── dist.html
│ ├── history.html
│ ├── outlayer.html
│ ├── page/
│ │ ├── 2.html
│ │ ├── 2.json
│ │ ├── 3.html
│ │ ├── 3.json
│ │ ├── empty.html
│ │ ├── fill.html
│ │ ├── no-access.html
│ │ ├── outlayer2.html
│ │ └── outlayer3.html
│ ├── page-index.html
│ ├── page-load.html
│ ├── path.html
│ ├── prefill.html
│ ├── scroll-watch-element.html
│ ├── scroll-watch-window.html
│ └── test.css
├── load-next-page-promise.js
├── outlayer.js
├── page-index.js
├── page-load-error.js
├── page-load-json.js
├── page-load.js
├── path.js
├── prefill.js
└── scroll-watch.js
SYMBOL INDEX (60 symbols across 17 files)
FILE: bin/version.js
function dir (line 7) | function dir( file ) {
FILE: dist/infinite-scroll.pkgd.js
function jQueryBridget (line 45) | function jQueryBridget( namespace, PluginClass, $ ) {
function EvEmitter (line 141) | function EvEmitter() {}
function InfiniteScroll (line 444) | function InfiniteScroll( element, options ) {
function getItemsFragment (line 924) | function getItemsFragment( items ) {
function refreshScripts (line 934) | function refreshScripts( fragment ) {
class InfiniteScrollButton (line 1370) | class InfiniteScrollButton {
method constructor (line 1371) | constructor( element, infScroll ) {
method onClick (line 1383) | onClick( event ) {
method enable (line 1388) | enable() {
method disable (line 1392) | disable() {
method hide (line 1396) | hide() {
method destroy (line 1400) | destroy() {
function hide (line 1511) | function hide( elem ) {
function show (line 1515) | function show( elem ) {
function setDisplay (line 1519) | function setDisplay( elem, value ) {
function extend (line 1576) | function extend( a, b ) {
function makeArray (line 1586) | function makeArray( obj ) {
function ImagesLoaded (line 1609) | function ImagesLoaded( elem, options, onAlways ) {
function onProgress (line 1743) | function onProgress( image, elem, message ) {
function LoadingImage (line 1787) | function LoadingImage( img ) {
function Background (line 1853) | function Background( url, element ) {
FILE: js/button.js
class InfiniteScrollButton (line 24) | class InfiniteScrollButton {
method constructor (line 25) | constructor( element, infScroll ) {
method onClick (line 37) | onClick( event ) {
method enable (line 42) | enable() {
method disable (line 46) | disable() {
method hide (line 50) | hide() {
method destroy (line 54) | destroy() {
FILE: js/core.js
function InfiniteScroll (line 26) | function InfiniteScroll( element, options ) {
FILE: js/page-load.js
function getItemsFragment (line 135) | function getItemsFragment( items ) {
function refreshScripts (line 145) | function refreshScripts( fragment ) {
FILE: js/status.js
function hide (line 84) | function hide( elem ) {
function show (line 88) | function show( elem ) {
function setDisplay (line 92) | function setDisplay( elem, value ) {
FILE: sandbox/js/unsplash-masonry.js
function getItem (line 43) | function getItem( photo ) {
function microTemplate (line 48) | function microTemplate( src, data ) {
FILE: sandbox/js/unsplash.js
function getItem (line 18) | function getItem( photo ) {
function microTemplate (line 23) | function microTemplate( src, data ) {
FILE: test/_get-server.js
function listener (line 28) | function listener( req, res ) {
FILE: test/check-last-page.js
function withPage (line 19) | async function withPage( t, run ) {
function getPageAssertions (line 29) | function getPageAssertions() {
FILE: test/history.js
function getTop (line 38) | function getTop( $elem ) {
FILE: test/outlayer.js
function withPage (line 21) | async function withPage( t, run ) {
function checkItems (line 39) | function checkItems( items ) {
function loadNextPagePromise (line 49) | function loadNextPagePromise() {
FILE: test/page-index.js
function withPage (line 19) | async function withPage( t, run ) {
FILE: test/page-load-error.js
function withPage (line 19) | async function withPage( t, run ) {
FILE: test/path.js
function withPage (line 19) | async function withPage( t, run ) {
FILE: test/prefill.js
function withPage (line 19) | async function withPage( t, run ) {
function onAppend (line 49) | function onAppend() {
function onAppend (line 81) | function onAppend() {
function onAppend (line 115) | function onAppend() {
FILE: test/scroll-watch.js
function updateContainerBottom (line 36) | function updateContainerBottom() {
function waitForDebounce (line 43) | function waitForDebounce() {
function onScrollThresholdFail (line 49) | function onScrollThresholdFail() {
function updateContainerBottom (line 122) | function updateContainerBottom() {
function waitForDebounce (line 128) | function waitForDebounce() {
function getPost (line 134) | function getPost() {
function onScrollThresholdFail (line 140) | function onScrollThresholdFail() {
Condensed preview — 85 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (250K chars).
[
{
"path": ".eslintrc.js",
"chars": 351,
"preview": "/* eslint-env node */\n\nmodule.exports = {\n plugins: [ 'metafizzy' ],\n extends: 'plugin:metafizzy/browser',\n env: {\n "
},
{
"path": ".github/contributing.md",
"chars": 1738,
"preview": "## Submitting issues\n\n### Reduced test case required\n\nAll bug reports and problem issues require a [**reduced test case*"
},
{
"path": ".github/issue_template.md",
"chars": 277,
"preview": "<!-- Thanks for submitting an issue! All bug reports and problem issues require a **reduced test case** or **live URL**."
},
{
"path": ".github/workflows/nodejs.yml",
"chars": 736,
"preview": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versi"
},
{
"path": ".gitignore",
"chars": 32,
"preview": "bower_components/\nnode_modules/\n"
},
{
"path": ".nvmrc",
"chars": 3,
"preview": "14\n"
},
{
"path": "LICENSE.txt",
"chars": 1071,
"preview": "The MIT License (MIT)\n\nCopyright (c) Metafizzy\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "README.md",
"chars": 4380,
"preview": "# Infinite Scroll\n\n_Automatically add next page_\n\nSee [infinite-scroll.com](https://infinite-scroll.com) for complete do"
},
{
"path": "bin/build-dist.js",
"chars": 1378,
"preview": "const fs = require('fs');\nconst { execSync } = require('child_process');\nconst { minify } = require('terser');\n\nconst in"
},
{
"path": "bin/version.js",
"chars": 412,
"preview": "/* eslint-env node */\n\nconst fs = require('fs');\nconst path = require('path');\nconst { version } = require('../package.j"
},
{
"path": "bower.json",
"chars": 575,
"preview": "{\n \"name\": \"infinite-scroll\",\n \"authors\": [\n \"David DeSandro <desandrocodes@gmail.com>\"\n ],\n \"description\": \"infi"
},
{
"path": "dist/infinite-scroll.pkgd.js",
"chars": 50026,
"preview": "/*!\n * Infinite Scroll PACKAGED v5.0.0\n * Automatically add next page\n * MIT License\n * https://infinite-scroll.com\n * C"
},
{
"path": "js/button.js",
"chars": 1942,
"preview": "// button\n( function( window, factory ) {\n // universal module definition\n if ( typeof module == 'object' && module.ex"
},
{
"path": "js/core.js",
"chars": 10165,
"preview": "// core\n( function( window, factory ) {\n // universal module definition\n if ( typeof module == 'object' && module.expo"
},
{
"path": "js/history.js",
"chars": 5051,
"preview": "// history\n( function( window, factory ) {\n // universal module definition\n if ( typeof module == 'object' && module.e"
},
{
"path": "js/index.js",
"chars": 585,
"preview": "/*!\n * Infinite Scroll v5.0.0\n * Automatically add next page\n * MIT License\n * https://infinite-scroll.com\n * Copyright "
},
{
"path": "js/page-load.js",
"chars": 7659,
"preview": "// page-load\n( function( window, factory ) {\n // universal module definition\n if ( typeof module == 'object' && module"
},
{
"path": "js/scroll-watch.js",
"chars": 2501,
"preview": "// scroll-watch\n( function( window, factory ) {\n // universal module definition\n if ( typeof module == 'object' && mod"
},
{
"path": "js/status.js",
"chars": 2454,
"preview": "// status\n( function( window, factory ) {\n // universal module definition\n if ( typeof module == 'object' && module.ex"
},
{
"path": "package.json",
"chars": 1116,
"preview": "{\n \"name\": \"infinite-scroll\",\n \"version\": \"5.0.0\",\n \"description\": \"Automatically add next page\",\n \"main\": \"js/index"
},
{
"path": "sandbox/button-class.html",
"chars": 3879,
"preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "sandbox/button-first.html",
"chars": 3459,
"preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "sandbox/button-load.html",
"chars": 3288,
"preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "sandbox/container-scroll.html",
"chars": 3473,
"preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "sandbox/css/blog.css",
"chars": 500,
"preview": "body {\n line-height: 1.4;\n background: #EEE;\n color: #444;\n}\n\n.container {\n padding: 0 20px;\n max-width: 700px;\n m"
},
{
"path": "sandbox/css/loader-ellips.css",
"chars": 904,
"preview": "/* loader-ellips\n------------------------- */\n\n.loader-ellips {\n font-size: 20px;\n position: relative;\n width: 4em;\n "
},
{
"path": "sandbox/css/masonry-images.css",
"chars": 454,
"preview": ".container {\n max-width: 1200px;\n margin: 0 auto;\n padding-bottom: 400px;\n}\n\n.grid {\n min-height: 600px;\n}\n\n.grid__c"
},
{
"path": "sandbox/element-scroll.html",
"chars": 3399,
"preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "sandbox/html-init.html",
"chars": 3678,
"preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "sandbox/jquery-plugin.html",
"chars": 3827,
"preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "sandbox/js/masonry-images.js",
"chars": 839,
"preview": "/* globals Masonry, imagesLoaded */\nlet msnry = new Masonry( '.grid', {\n itemSelector: 'none', // select no images on i"
},
{
"path": "sandbox/js/scroll-loader.js",
"chars": 251,
"preview": "let container = document.querySelector('.posts-container');\nwindow.infScroll = new InfiniteScroll( container, {\n path: "
},
{
"path": "sandbox/js/unsplash-masonry.js",
"chars": 1597,
"preview": "/* globals Masonry, imagesLoaded */\n\nlet msnry = new Masonry( '.grid', {\n itemSelector: '.grid-item',\n columnWidth: '."
},
{
"path": "sandbox/js/unsplash.js",
"chars": 998,
"preview": "let container = document.querySelector('.container');\n\nlet unsplashID = '9ad80b14098bcead9c7de952435e937cc3723ae61084ba8"
},
{
"path": "sandbox/masonry-images/index.html",
"chars": 3007,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/masonry-images/page2.html",
"chars": 3025,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/masonry-images/page3.html",
"chars": 3025,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/masonry-images/page4.html",
"chars": 3025,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/masonry-images/page5.html",
"chars": 2382,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/page/2.html",
"chars": 2829,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/page/3.html",
"chars": 4186,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/page/4.html",
"chars": 7507,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/page/5.html",
"chars": 3768,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/page/6.html",
"chars": 5665,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/prefill.html",
"chars": 3755,
"preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "sandbox/scroll-3.html",
"chars": 3711,
"preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "sandbox/scroll-loader.html",
"chars": 3692,
"preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "sandbox/unsplash-masonry.html",
"chars": 2444,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "sandbox/unsplash.html",
"chars": 1115,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/_get-server.js",
"chars": 1695,
"preview": "// https://developer.mozilla.org/en-US/docs/Node_server_without_framework\n// https://stackoverflow.com/a/29046869\n\nconst"
},
{
"path": "test/_with-page.js",
"chars": 572,
"preview": "// https://github.com/avajs/ava/blob/v3.13.0/docs/recipes/puppeteer.md\n\nconst puppeteer = require('puppeteer');\nconst ge"
},
{
"path": "test/check-last-page.js",
"chars": 3671,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
},
{
"path": "test/dist-jquery.js",
"chars": 1638,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
},
{
"path": "test/dist.js",
"chars": 1513,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
},
{
"path": "test/history.js",
"chars": 2934,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
},
{
"path": "test/html/_serial-t.js",
"chars": 580,
"preview": "// serialT works kinda like t, but to pass serialized assertions out of .evaluate\n// serialT.assertions will be passed o"
},
{
"path": "test/html/dist-jquery.html",
"chars": 647,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/dist.html",
"chars": 566,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/history.html",
"chars": 957,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/outlayer.html",
"chars": 1888,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/page/2.html",
"chars": 456,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/page/2.json",
"chars": 144,
"preview": "[\n {\n \"id\": 2,\n \"content\": \"venus\"\n },\n {\n \"id\": 3,\n \"content\": \"mother earth\"\n },\n {\n \"id\": 4,\n "
},
{
"path": "test/html/page/3.html",
"chars": 330,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/page/3.json",
"chars": 142,
"preview": "[\n {\n \"id\": 5,\n \"content\": \"jupiter\"\n },\n {\n \"id\": 6,\n \"content\": \"saturn\"\n },\n {\n \"id\": 7,\n \"con"
},
{
"path": "test/html/page/empty.html",
"chars": 208,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/page/fill.html",
"chars": 37,
"preview": "<div class=\"post\">prefill post</div>\n"
},
{
"path": "test/html/page/no-access.html",
"chars": 342,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/page/outlayer2.html",
"chars": 632,
"preview": "<div class=\"outlayer-item\"><img src=\"https://i.imgur.com/qW7eNlI.jpg\" /></div>\n<div class=\"outlayer-item\"><img src=\"http"
},
{
"path": "test/html/page/outlayer3.html",
"chars": 790,
"preview": "<div class=\"outlayer-item\"><img src=\"https://i.imgur.com/ZPPFND3.jpg\" /></div>\n<div class=\"outlayer-item\"><img src=\"http"
},
{
"path": "test/html/page-index.html",
"chars": 897,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/page-load.html",
"chars": 955,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/path.html",
"chars": 925,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/prefill.html",
"chars": 1089,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/scroll-watch-element.html",
"chars": 1195,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/scroll-watch-window.html",
"chars": 969,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "test/html/test.css",
"chars": 287,
"preview": "body {\n font-family: sans-serif;\n}\n\n.post {\n background: #EEE;\n margin-bottom: 10px;\n}\n\n.container {\n border: 1px so"
},
{
"path": "test/load-next-page-promise.js",
"chars": 2655,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
},
{
"path": "test/outlayer.js",
"chars": 3629,
"preview": "/* globals imagesLoaded, Masonry */\n\nconst test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServe"
},
{
"path": "test/page-index.js",
"chars": 2077,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
},
{
"path": "test/page-load-error.js",
"chars": 3623,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
},
{
"path": "test/page-load-json.js",
"chars": 2737,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\ncons"
},
{
"path": "test/page-load.js",
"chars": 4118,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
},
{
"path": "test/path.js",
"chars": 3491,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
},
{
"path": "test/prefill.js",
"chars": 3646,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
},
{
"path": "test/scroll-watch.js",
"chars": 6165,
"preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
}
]
About this extraction
This page contains the full source code of the metafizzy/infinite-scroll GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 85 files (228.8 KB), approximately 63.7k tokens, and a symbol index with 60 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.