LoginSignup
6
6

More than 5 years have passed since last update.

今時の画像遅延読み込み(lazyload-image)と無限スクロール(infinite-scroll)

Last updated at Posted at 2018-12-03

動作URLリポジトリ

ezgif-1-d445c4ef9afa.gif

下記のPolyfillを使ってES5変換・gzip後でindex.jsのサイズは11K程度。IE11、Safariでも動いてます。

index.html
<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の記事を参考に書くと、

index.ts#LazyImage
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.loadtrueとなって、画像が表示されます。

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を参考に書くと、

index.ts#infiniteScroll
@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属性に指定されたデータを読み込み続けます。

index.html
<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が一番楽でした、、、

package.json
  "dependencies": {
    "@polymer/lit-element": "^0.6.4"
  },
  "devDependencies": {
    "polymer-cli": "^1.9.1",
    "typescript": "^3.1.3"
  }
polymer.json
{
  "entrypoint": "index.html",
  "shell": "index.js",
  "builds": [{
    "name": "default",
    "preset": "es5-bundled",
    "addServiceWorker": false
  }],
  "moduleResolution": "node",
  "npm": true
}

あまり単体で使うものじゃなさそう(デフォルトでServiceWorkerを作ってくれたりとか)ですが、index.htmlも含めたPolyfillやminifyが便利です。

以上です。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6