こんにちは、こがたです。
PythonのWeb開発フレームワーク「Django」で無限スクロールを実装する方法を紹介します。
- Djangoで無限スクロールを実装したい
- Pagenationを利用したデータの流れを理解したい
- 大量のデータでPagenationを利用したらどうなるのか知りたい
最初に無限スクロールとページネーション、それぞれのメリットデメリットを説明します。
開発にあたりどちらが向いているのか検討材料にしてください。
実装コードだけ知りたい方は読み飛ばしてください。
次にDjangoの必要部分のコードを紹介しながら解説していきます。
Djangoの基礎知識がある上での解説になります。
他言語でも同じようにデータを利用すれば実装できるので参考になるでしょう。
最後に紹介するコードの懸念点を説明します。
無限スクロールの用途
一覧ページを表示するのは「無限スクロール」と「ページネーション」があります。
それぞれのメリット、デメリットを説明するので、どちらがよいのか検討してみてください。
無限スクロール
TwitterやFacebookなどのように下にスクロールしていけばデータが読み込まれて表示される方法です。
- 流してみることができる
- 利用者の手間が少ない
なにかをクリックしたりすくことなく、次のデータを見ることができるので、ユーザビリティは高いです。
- フッターが見えない
- 一気に飛ばしてみることができない
データがある限りスクロールすることができるので、フッターを表示することは基本的にできません。
※フロントをカスタマイズすれば表示可能
スクロールすればみることができますが、逆にいうとスクロールをしなければいけません。
先の情報をみたい場合、その分スクロールしなければいけません。
Twitterで一年前の情報を探そうとしてもスクロールだと大変ですよね、、、
そのため、欲しい情報を見つけるための検索フォームがあるとよいでしょう。
ページネーション
Google検索などよく利用されている方法です。
- 情報がどこにあるのか明確になる
- 先のデータに飛びやすい
一般的にページネーションで次のページに移動するとURLが変わります。
つまり、特定の一覧ページをブックマークすることができます。
また、10ページ先などに簡単に遷移することができます。
- 次にページにいくのにクリックという手間がかかる
- 先のページが読まれづらくなる
次のページに移動するためにクリックやURLの変更が必要になります。
そのため、無限スクロールと比べて手間となり、ページビューが落ちやすいです。
Google検索でも、ほとんど最初の10件しか見られません。
Djangoで無限スクロールを実装
これから実際に無限スクロールを実装していきます。
この記事ではDjango3.1を使用しています。
どれもフロントサイドものになります。
jQueryはこちらからダウンロードしてください。
この記事では「jquery-3.5.1.min.js」を使用します。
Waypoints、infinite-scrollはこちらからダウンロードできます。
(Git,npmなど)
- lib/jquery.waypoints.min.js
- lib/shortcuts/infinite.min.js
それではDjangoのコードにはいっていきます。
ここでは商品一覧ページでを無限スクロールを実装します。
「items」というアプリ名にしています。
※Djangoの基本はおさえている前提になります
models
シンプルな商品モデルを利用します。
# items/models.py
from django.db import models
class Item(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=6000)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
商品名、説明分、作成日をもったシンプルなモデルです。
views
次にViewを実装します。
# items/views.py
from django.views import generic
from .models import Item
class ItemListScroll(generic.ListView):
model = Item
template_name = 'items/item_list_scroll.html'
paginate_by = 10
def get_queryset(self):
return self.model.objects.all().order_by('created_at')
ここではListView(汎用ビュー)を使用しています。
「paginate_by」で一回で表示するデータ数を設定しています。
これはページネーションで表示する方法と同様です。
公式ドキュメントはこちら。
「get_queryset」は親クラスのメソッドでオーバライドしています。
オーバーライドしなければ↓↓↓のように動きます。
def get_queryset(self):
return self.model.objects.all()
つまり、Itemオブジェクトを全件取得します。
オーバーライドしたのは、作成日順に表示するためです。
(必要ない場合は削除してください)
html
表示画面を作成していきます。
ここでは共通パーツ(base.html)を作成して読み込んでいます。
また、BootStrapも読み込んでいます。
<!doctype html>
<html lang="ja">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
{% load static %}
<script src="{% static 'items/js/jquery-3.5.1.min.js' %}"></script>
<script src="{% static 'items/js/jquery.waypoints.min.js' %}"></script>
<script src="{% static 'items/js/infinite.min.js' %}"></script>
{% block extrajs %}{% endblock %}
<title>商品サンプル</title>
</head>
<body>
<!-- メインコンテント -->
<div class="container mt-3">
{% block content %}{% endblock %}
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"
integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"
integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm"
crossorigin="anonymous"></script>
</body>
</html>
重要なのは12行目〜16行目です。
12行目〜15行目で準備した「jQuery」「Waypoints」「infinate-scroll」を読み込んでいます。
16行目の「{% block extrajs %}{% endblock %}」で各ページごとのJSコードを記載することができるようになります。
商品一覧ページ(items/templates/items/item_list_scroll.html)はこのようになります。
{% extends "items/base.html" %}
{% block content %}
<div class="infinite-container">
<div>
<p>タイトル</p>
</div>
{% for item in object_list %}
<div class="infinite-item">
<p>**************</p>
<p>{{ item.name }}</p>
<p style="width:70%; word-wrap:break-word;">{{ item.description }}</p>
<br>
<br>
</div>
{% endfor %}
</div>
<div class="loading" style="display: none;">
読み込み中...
</div>
{% if page_obj.has_next %}
<a class="infinite-more-link" href="?page={{ page_obj.next_page_number }}">さらに読み込む</a>
{% endif %}
{% endblock %}
{% block extrajs %}
<script>
var infinite = new Waypoint.Infinite({
element: $('.infinite-container')[0],
onBeforePageLoad: function () {
$('.loading').show();
},
onAfterPageLoad: function ($items) {
$('.loading').hide();
}
});
</script>
{% endblock %}
次のデータを読み込んでいる最中に「読み込み中…」が表示されます。
一覧のコンテンツに「infinite-container」クラスを指定してください。
読み込まれる各データコンテンツのクラスは「infinite-item」を指定します。
ここで注意するのは「infinite-item」に内包的なDOMは利用できないということです。
「li」に「infinite-item」をすると新しいデータを読み込むとこのようになります。
<div class="infinite-container">
<ul>
<li class="infinite-item">初期表示データ</li>
<li class="infinite-item">初期表示データ</li>
<li class="infinite-item">初期表示データ</li>
<li class="infinite-item">初期表示データ</li>
</ul>
<li class="infinite-item">新しく読み込んだデータ</li>
<li class="infinite-item">新しく読み込んだデータ</li>
<li class="infinite-item">新しく読み込んだデータ</li>
<li class="infinite-item">新しく読み込んだデータ</li>
</div>
このように「ul」の外で追加されていきます。
「page_obj」はページの情報を保持しています。
- has_next :次のページの有無
- has_previous :前のページの有無
- next_page_number :次のページ番号
- previous_page_number :前のページ番号
「next_page_number」「previous_page_number」は次、前のページの有無を確認した上で利用してください。
エラーが発生します。
実際に読み込んだらこのようになります。
デザインはあててないので、シンプルな表示になっています
無限スクロールコードの懸念点
勘の鋭い方は気づいたかもしれませんが、views.pyの「get_queryset」で全てのデータを取得しているとう点です。
def get_queryset(self):
return self.model.objects.all().order_by('created_at')
読み込む度に全データを読み込んでいたらレスポンス遅くならない?
そこで以下のviewsに変更して100万データで試してみました。
from django.shortcuts import render
from django.views import generic
import random, string
from .models import Item
class ItemListScroll(generic.ListView):
model = Item
template_name = 'items/item_list_scroll.html'
paginate_by = 10
def get_queryset(self):
return self.model.objects.all().order_by('created_at')
class ItemListAll(generic.ListView):
model = Item
template_name = 'items/item_list_all.html'
def get_queryset(self):
return self.model.objects.all().order_by('created_at')
class MakeData(generic.TemplateView):
"""ユーザーの詳細ページ"""
template_name = 'items/make_data.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
for i in range(1000000):
item = Item()
item.name = 'test_' + str(i+1)
item.description = ''.join(random.choices(string.ascii_letters + string.digits, k=2000))
item.save()
return context
「ItemListAll」「MakeData」のhtmlはDjangoの基本的な書き方になるので省略します。
「MakeData」関数でランダムな2000文字の説明文を持った商品を100万件作成します。
この状態で全件を1ページで表示する「ItemListAll」を実行すると表示されるまで1分以上かかりました、、、
これまで紹介してきた無限スクロールのページを表示してみると、読み込む度に1〜2秒ほどかかるようになりました。
100万件で1,2秒ならギリギリ許容範囲内かと思います!
html作成時間が短縮できるので実行時間が短く済むと予想されます。
効率的にWEBシステムを開発できるようになる方法
WEBフレームワークを理解しただけでは、WEBシステムを公開することはできません。。。
- ネットワーク(セキュリティ等)について
- ハードウェア(サーバ等)について
- ミドルウェアについて:Linux,Windows
- ソフトウェアについて:Webサーバ,プログラミング言語,フレームワーク
- データベース
Python初心者の方は『PyQ』で学習することをオススメします!
Pythonができることを広く浅く学習することができます!
ただし、プログラミング中級者にとっては多少物足りない内容になっています。
プログラミング中級者の方は自分で調べながら開発していくことができる状態の場合です。
中級者の方が学習する場合は『Udemy』で学習するのがオススメです!
10万以上の講座があり、自分のレベルに合わせてピンポイントに学習することができます。
セール中であれば、1,000円代まで値下げされるので安く、しっかり学ぶことが可能です!
最後に
無限スクロールはユーザビリティの高い表示方法です。
実装方法がムズカしくみえたかもしれませんが、やっていることは単純です。
仕組みを理解することができたらどの言語でも実装できるようになります。
開発の参考になれば幸いです。
最後まで読んでくださり、ありがとうございました!!!
コメント