こんにちは、田中です。
弊社サービス、るくみーでは保育士のみなさんやプロカメラマンが撮影した写真が日々たくさんアップロードされています。 定期的にこの子ども達の様子が保護者のみなさんに公開・販売されますが、販売が終了したあとの写真は基本的にデータサーバーで眠るのみです。。><
この膨大に蓄積されている園写真を機械学習の力で価値につなげていきたい... !
ということで、その最初の一歩 兼 React勉強の一環として機械学習用データラベリングアプリを作ってみました。 ƪ(•◡•ƪ)"
できたもの
こんな感じです。

写真の枚数が大きいと見込まれるので右側の写真リストは無限スクロールとなるように実装しました。
一番下の写真までスクロールしたときにデータが読み込まれてブラウザのスクロールバーが伸びることが分かると思います。
無限スクロール
FacebookやTwitter、Instagramといった人気のソーシャルメディアでのコンテンツ表示のUIとして使われています。 ユーザが下へスクロールするにつれてページのコンテンツが継続的にロードされるというアレです。
無限スクロール以外の選択肢としてはページネーションがあり、それぞれ長所短所があります
上の記事は主にビジネスコンテンツ目線での長所短所ですが今回はキーボードのショートカット操作だけでも楽に作業できることを重視し無限スクロールを採用しました。 (「進む」「戻る」の操作だけでよい)
react-infinite-scrollerを使った無限スクロール
react-infinite-scroller というパッケージを使いました。 他にも何個かありましたがこれが最も分かりやすく使いやすかったです。
create-react-appで作ったアプリをもとに無限スクロールを実現するコードを紹介します。
一番シンプルな例

App.js
import React, { Component } from 'react';
import './App.css';
import InfiniteScroll from 'react-infinite-scroller'
class App extends Component {
constructor(props) {
super(props)
this.state = {
items: [],
hasMoreItems: true
}
this.loadItems = this.loadItems.bind(this)
}
loadItems() {
const current_item_count = this.state.items.length
const max_items = 300
const page_item_size = 50
if (current_item_count < max_items) {
const new_ids = Array.from(Array(page_item_size).keys()).map((num) => {return num+current_item_count})
const new_items = new_ids.map((id) => {return {'id': id}})
setTimeout(() => {
this.setState({items: this.state.items.concat(new_items)})
}, 500) // add some delay for demo purpose
} else {
this.setState({hasMoreItems: false})
}
}
render() {
const items = this.state.items.map((item) => {
return <div>{item.id}</div>
})
return (
<div className="App">
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<InfiniteScroll
pageStart={0}
loadMore={this.loadItems}
hasMore={this.state.hasMoreItems}
loader={<div>Loading...</div>}
initialLoad={true}
>
{items}
</InfiniteScroll>
</div>
);
}
}
export default App;
<InfiniteScroll></InfiniteScroll>でスクロールさせる要素を囲むloadMoreで新しいデータを読み込み、配列に追加する- 読み込むデータがなくなったら
hasMoreをfalseにする
基本これだけです。簡単ですね :-)
フッター固定の例
続いてフッターを固定で表示する場合です。

App.css
html, body {
height: 100%;
}
.App {
text-align: center;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.scrollContainer {
height: 100%;
overflow: auto;
}
.footer {
height: 30px;
margin: 10px;
}
App.js
import React, { Component } from 'react';
import './App.css';
import InfiniteScroll from 'react-infinite-scroller'
class App extends Component {
constructor(props) {
super(props)
this.state = {
items: [],
hasMoreItems: true
}
this.loadItems = this.loadItems.bind(this)
}
loadItems() {
const current_item_count = this.state.items.length
const max_items = 300
const page_item_size = 50
if (current_item_count < max_items) {
const new_ids = Array.from(Array(page_item_size).keys()).map((num) => {return num+current_item_count})
const new_items = new_ids.map((id) => {return {'id': id}})
setTimeout(() => {
this.setState({items: this.state.items.concat(new_items)})
}, 500) // add some delay for demo purpose
} else {
this.setState({hasMoreItems: false})
}
}
render() {
const items = this.state.items.map((item) => {
return <div>{item.id}</div>
})
return (
<div className="App">
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<div className="scrollContainer">
<InfiniteScroll
pageStart={0}
loadMore={this.loadItems}
hasMore={this.state.hasMoreItems}
loader={<div>Loading...</div>}
initialLoad={true}
useWindow={false}
>
{items}
</InfiniteScroll>
</div>
<div className="footer">Footer</div>
</div>
);
}
}
export default App;
こちらはスタイルを少し工夫する必要があります。
- ウインドウ高さを
height: 100vh;で固定し、overflow:hiddenでスクロールバーを見せない <InfiniteScroll></InfiniteScroll>をラップする<div>要素を作り、height: 100%; overflow: auto;にするuseWindow={false}のパラメータを追加する
create-react-appで適当なアプリを作ってApp.jsとApp.cssを上書きすればすぐにできるので興味あれば試してみてください
おわりに
ラベリングツールは一応の形になりました!
が、これからチマチマとラベリング作業していくのかと思うとちょっと憂鬱。w
でも、頑張りたいと思います。。 (•̀ᴗ•́)و