- 今時なので、jQueryを使わずIntersectionObserverを使います!   
- 今時なので、TypeScriptでWebコンポーネント(LitElement)にします!!   
下記のPolyfillを使ってES5変換・gzip後でindex.jsのサイズは11K程度。IE11、Safariでも動いてます。
<script src="https://unpkg.com/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
<script src="https://unpkg.com/intersection-observer/intersection-observer.js"></script>
※ 画像はここから借りました。
画像遅延読み込み(lazy-image)
将来的にはlazyload属性やBlink LazyLoad(参考: img要素とiframe要素のlazyload属性)の普及に期待しつつ、Let's Build Web Components! Part 5: LitElementの記事を参考に書くと、
const isInters = ({isIntersecting}:IntersectionObserverEntry) => isIntersecting
@customElement('lazy-image' as any)
export class LazyImage extends LitElement {
  
  @property() load = false
  
  @property() src = ''
  constructor(){
    super()
    new IntersectionObserver((entries, observer)=>{
      if(entries.some(isInters)){
        this.load = true
        observer.unobserve(this)
      }
    }).observe(this)
  }
  render() {
    return html`<img src=${this.load ? this.src : ''}>`
  }
  
}
IntersectionObserverが自身の要素を監視し、画面に表示されたらthis.loadがtrueとなって、画像が表示されます。
const isInters = ({isIntersecting}:IntersectionObserverEntry) => isIntersecting
...
new IntersectionObserver((entries, observer)=>{
  if(entries.some(isInters)){
     ...
は
new IntersectionObserver((entries, observer)=>{
  entries.forEach( entry => {
    if(entry.isIntersecting){
      ...
と基本同じような処理なんですが、使い方かっこいい。。。
無限スクロール(infinite-scroll)
<virtual-scroller>を使ってみたかったのですが、Chromeでしか動かなかったのでlazy-imageを参考に書くと、
@customElement('infinite-scroll' as any)
export class InfiniteScroll extends LitElement {
  
  @property({ type: { fromAttribute: (attr:string) => JSON.parse(attr) } })
  items: string[] = []
  
  @query('button') next?: HTMLButtonElement
  
  firstUpdated() {
    new IntersectionObserver( entries =>{
      if(entries.some(isInters)){
        this.items = [...this.items, ...this.items]
      }
    }).observe(this.next!)
  }
  
  render() {
    return html`
      <style>lazy-image { display: block; width: 100%; height: 480px; }</style>
      ${this.items.map(i=>html`<lazy-image src=${i}></lazy-image>`)}
      <button>next</button>
    `
  }
}
ここではページの最下部にある<button>を監視し、画面に表示されたらitems属性に指定されたデータを読み込み続けます。
<infinite-scroll items='["URL1","URL2","URL3"...]'></infinite-scroll>
LitElementでは属性に渡された値を配列として使いたい場合にfromAttributeを使って変換処理を定義できます(JSONを渡す場合は'シングルクォートを使わないといけない)。
this.items = [...this.items, ...this.items]
は
this.items.push( ...this.items)
でもよいのですが、配列に値が追加されただけではLitElement側で感知してくれないので、
this.items.push( ...this.items)
this.requestUpdate // or this.requestUpdate('item')
で再描画をリクエストする必要があります。
あと、実際に無限ループするページはSEO的によくないらしいので、
new IntersectionObserver((entries, observer)=>{
  if(entries.some(isInters)){
    ...
    if(/* isItemsLast? */){
       this.next!.hidden = true
       observer.unobserve(this.next!)
    }
などで、Observerを止めます。
ビルドについて
rollupを試してみたのですが、結局polymer-cliが一番楽でした、、、
  "dependencies": {
    "@polymer/lit-element": "^0.6.4"
  },
  "devDependencies": {
    "polymer-cli": "^1.9.1",
    "typescript": "^3.1.3"
  }
{
  "entrypoint": "index.html",
  "shell": "index.js",
  "builds": [{
    "name": "default",
    "preset": "es5-bundled",
    "addServiceWorker": false
  }],
  "moduleResolution": "node",
  "npm": true
}
あまり単体で使うものじゃなさそう(デフォルトでServiceWorkerを作ってくれたりとか)ですが、index.htmlも含めたPolyfillやminifyが便利です。
以上です。

