- 今時なので、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が便利です。
以上です。