AutoPagerを実装した。つぶやきを20件ずつ表示し、スクロール末尾まで到達すると次の20件を追加する。それを最後まで繰り返す。

ブツ

インストール&実行

NAME='Electron.MyLog.20220916094224'
git clone https://github.com/ytyaru/$NAME
cd $NAME
npm install
npm start

やったこと

AutoPagerを実装した。つぶやきを20件ずつ表示する。スクロール末尾まで到達すると次の20件を表示する。それを最後まで繰り返す。

これまでは最初に全件表示していた。だが、それだともし1000件あったらとても長い時間かかってしまう。初回表示を高速化するため最新順に20件ずつ表示するようにした。

  1. SQLite3で20件ずつ取得する
  2. スクロールバーの末尾到達判定
  3. つぶやきを20件ずつHTML表示する

最大20件ずつ取得するには以下。

select * from comments order by created desc limit ${limit} offset ${offset};

スクロールバーの末尾到達判定は以下。

#isFullScrolled(event) {
    const adjustmentValue = 60 // ブラウザ設定にもよる。一番下までいかずとも許容する
    const positionWithAdjustmentValue = event.target.clientHeight + event.target.scrollTop + adjustmentValue
    return positionWithAdjustmentValue >= event.target.scrollHeight
}

ほか、細かい計算、イベントやDOM、パフォーマンス調整処理を実装した。

1. SQLite3で20件ずつ取得する

select * from comments order by created desc limit ${limit} offset ${offset};
項目 意味
limit 最大取得件数 20
offset 先頭からすっ飛ばす件数 0

Electron IPC通信インタフェース化する

main.js

ipcMain.handle('getPage', async(event, limit, offset) => {
    const res = lib.get(`DB`).exec(`select * from comments order by created desc limit ${limit} offset ${offset};`)
    console.log(res)
    return (0 === res.length) ? res : res[0].values
})

preload.js

getPage:async(limit, offset)=>await ipcRenderer.invoke('getPage', limit, offset),

2. スクロールバーの末尾到達判定

計算式は以下。

#isFullScrolled(event) {
    const adjustmentValue = 60 // ブラウザ設定にもよる。一番下までいかずとも許容する
    const positionWithAdjustmentValue = event.target.clientHeight + event.target.scrollTop + adjustmentValue
    return positionWithAdjustmentValue >= event.target.scrollHeight
}

これをつぶやき一覧表示する親要素のスクロールイベント時に実行する。

this.ui.addEventListener('scroll', async(event) => {
    if (this.#isFullScrolled(event)) { ... }
})

ただしイベント発火回数が多くなりすぎて処理が重くなる。そのため200ms以内に完了した最後の一回のみ実行するようにする。

this.ui.addEventListener('scroll', async(event) => {
    clearTimeout(this.timeoutId);
    this.timeoutId = setTimeout(async()=>{
        if (this.#isFullScrolled(event)) { ... }
    }, 200);
})

3. つぶやきを20件ずつHTML表示する

スクロール最下端に達するたび#next()を呼び出す。こいつで20件ずつ取得する。

async #next() {
    console.log('AutoPager.next()')
    if (this.offset < this.count) {
        this.page++;
        this.offset = this.limit * this.page
        console.log(this.limit, this.offset)
        return await window.myApi.getPage(this.limit, this.offset)
    } else { return [] }
}

すでに取得済みの件数だけすっ飛ばす。その値offsetlimit * pageで計算する。limitは一回あたりの取得件数20件のこと。pageはスクロール最下端に到達した回数。

項目 意味
limit 最大取得件数 20
page スクロール最下端に到達した回数 0
offset 先頭からすっ飛ばす件数 0

該当するレコードを取得したら全件HTML化する。すべて文字列として作成したあと末尾に追記する。insertAdjacentHTMLを使って。

#toHtml(records) {
    console.log(records)
    this.ui.insertAdjacentHTML('beforeend', records.map(r=>TextToHtml.toHtml(r[0], r[1], r[2], this.setting.mona.address)).join(''))

あとはそれをスクロールイベント時に呼び出すようにすれば完成。

this.ui.addEventListener('scroll', async(event) => {
    clearTimeout(this.timeoutId);
    this.timeoutId = setTimeout(async()=>{
        if (this.#isFullScrolled(event)) {
            this.#toHtml(await this.#next())
        }
    }, 200);
})

auto-pager.js

20件ずつ表示するAutoPager処理をまとめたクラスを書いた。

class AutoPager {
    constructor(setting) {
        this.limit = 20
        this.page = -1
        this.offset = this.limit * this.page
        this.count = 0
        this.setting = setting
        this.ui = document.querySelector('#post-list')
        this.timeoutId = 0
        console.log('AutoPager.count:', this.count, this.offset)
    }
    async setup() {
        console.log('AutoPager.setup()')
        this.count = await window.myApi.count()
        this.ui.addEventListener('scroll', async(event) => {
            clearTimeout(this.timeoutId);
            this.timeoutId = setTimeout(async()=>{
                if (this.#isFullScrolled(event)) {
                    this.#toHtml(await this.#next())
                }
            }, 200);
        })
        this.#toHtml(await this.#next())
    }
    #isFullScrolled(event) {
        const adjustmentValue = 60 // ブラウザ設定にもよる。一番下までいかずとも許容する
        const positionWithAdjustmentValue = event.target.clientHeight + event.target.scrollTop + adjustmentValue
        console.log(`isFullScrolled: ${positionWithAdjustmentValue >= event.target.scrollHeight}`)
        return positionWithAdjustmentValue >= event.target.scrollHeight
    }
    async #next() {
        console.log('AutoPager.next()')
        if (this.offset < this.count) {
            this.page++;
            this.offset = this.limit * this.page
            console.log(this.limit, this.offset)
            return await window.myApi.getPage(this.limit, this.offset)
        } else { return [] }
    }
    #toHtml(records) {
        console.log(records)
        this.ui.insertAdjacentHTML('beforeend', records.map(r=>TextToHtml.toHtml(r[0], r[1], r[2], this.setting.mona.address)).join(''))
    }
}

あとはこれをrenderer.jsで以下のように呼び出せばいい。

const pager = new AutoPager(setting)
await pager.setup() 

以前までは以下のように全件取得していた。これを削除しておく。

document.getElementById('post-list').innerHTML = await db.toHtml(document.getElementById('address').value)