【個人開発】Laravel + Vue.jsで作る週刊少年ジャンプ年表アプリ【ジャンプなに読んでた?】
こんにちは。最近、「昔ジャンプで何読んでたっけ?」と思い出すことが増えたので、週刊少年ジャンプの歴代連載作品の年表サイトを作ってみました。
その名も…
ジャンプなに読んでた?
🔍 どんなサイト?
- 1968年創刊からの ジャンプ連載作品を年表形式で一覧表示
- 縦スクロールで時代をさかのぼり、作品ごとの連載期間が一目でわかる
- 気になる作品をクリックすると 作品紹介がポップアップ
- さらに、コメントをニコニコ動画風に流せる機能も実装!
💡 技術スタック
技術 | 内容 |
---|---|
Laravel | バックエンド(API、DB、ルーティング) |
Vue.js | フロントエンドUI(SPA) |
MySQL | データベース(Dockerで構築) |
Vite | フロントビルドツール |
🛠 開発の工夫ポイント
今回このサイトを公開するにあたって、使用した技術や工夫した点をいくつか紹介します。
データベース
works
作品テーブルWikipediaの週刊少年ジャンプ連載作品の一覧からデータを拝借。
このページの表をそのままテーブルにしました。
カラム名 | 型 | 説明 |
---|---|---|
id | bigint unsigned | 作品ID(自動採番) |
title | varchar(255) | 作品タイトル |
author | varchar(255) | 作者名 |
original | varchar(255) | 原作名(原作がある場合) |
start_number | varchar(255) | 連載開始号 |
end_number | varchar(255) | 連載終了号 |
note | varchar(255) | 備考(特記事項など) |
back_numbers
バックナンバーテーブルゼブラック(集英社が運営する総合電子書籍サービス)の、週刊少年ジャンプのバックナンバーから情報を取得。
また、過去のバックナンバーはメディア芸術データベースというサイトからスクレイピングで取得したりもしました。
カラム名 | 型 | 説明 |
---|---|---|
number | varchar(255) | バックナンバー号数 |
release_date | date | 発売日 |
この二つのテーブルを結合して、作品ごとの連載期間を算出しています。
クエリはシンプルでこんな感じ
$works = DB::table('works')
->select(
'works.*',
'start_back_numbers.release_date as start_release_date',
'end_back_numbers.release_date as end_release_date'
)
->leftJoin('back_numbers as start_back_numbers', 'start_back_numbers.number', '=', 'works.start_number')
->leftJoin('back_numbers as end_back_numbers', 'end_back_numbers.number', '=', 'works.end_number')
->orderBy('start_back_numbers.release_date', 'asc')
->get();
作品年表の表示
PHPでデータを整形して、Vue.jsで縦スクロールの年表を描画。
これが一番のポイントです。
日付の値をstrtotime()
でタイムスタンプに変換し、その数値をもとに縦位置や連載期間(グラフの長さ)を描画しています。
まずは年表の開始日を指定します。
ジャンプの創刊は1968年8月1日なので、この年1月1日から始まるようにします。
$chronology_start_date = '1968-01-01';
次に、年表の開始日と、作品の連載開始日、連載終了日を全てタイムスタンプに変換し、
この数値をもとに作品の縦位置(top)と連載期間(serialization_period)を計算します。
// $chronology_start_date => '1968-01-01' // 年表の開始日
// $tmp_work['start_release_date'] => '1968-08-01' // 作品の連載開始日
// $tmp_work['end_release_date'] => '1968-12-26' // 作品の連載終了日
// この作品の年表の縦位置は、年表の開始日から6ヶ月後の位置、(1968-08-01 - 1968-01-01 )
// 作品の連載期間を表す縦線の長さは、約5ヶ月分の長さ(1968-12-26 - 1968-08-01 )
// これをそれぞれ、strtotime()でタイムスタンプに変換して計算します。
// 作品の縦位置
$tmp_work['top'] = strtotime($tmp_work['start_release_date']) - strtotime($chronology_start_date);
// => 18403200
// 作品の連載期間
$tmp_work['serialization_period'] = strtotime($tmp_work['end_release_date']) - strtotime($tmp_work['start_release_date']);
// => 12700800
これの値をもとに、Vue.jsで年表を描画します。
<template v-for="work in works" :key="work.id"><!-- 作品ごとにループ -->
<div class="work" :style="workStyle(work)"><!-- 作品のスタイルを適用 -->
<div class="title">{{ work.title }}</div><!-- 作品タイトル -->
</div>
</template>
//作品のスタイル
workStyle(work) {
// scaleは年表の縮尺、1画面にいい感じに収まるように調整するための値
// 作品を表示する縦位置を計算
const top = work.top / this.scale;
// 作品の連載期間を高さに変換
const height = work.serialization_period / this.scale;
// 現在のスクロール位置と作品の位置を比較して、表示するかどうかを決定
// → 連載終了した作品を非表示にするため
const isVisible = top + height > this.scrollTop;
return {
top: top + 'px',
height: height + 'px',
opacity: isVisible ? 1 : 0, //非表示の場合は透明にする
width: isVisible ? '14px' : '0', //非表示の場合は横幅も幅を0にする
transition: 'opacity 0.3s,width 0.3s', // アニメーション効果
};
},
これを描画した結果がこちらです。
コメント機能
作っている途中で、ただの年表では面白くないなと思い、ニコニコ動画風のコメント機能を実装しました。
(特許とか著作権は問題ないですよ!)
入力蘭にコメントを入力し、送信ボタンを押すと、コメントが年表の右から左へ流れていきます。
コメントの送信処理はこんな感じです。
sendComment() {
if (!this.newComment.trim()) return; // 空のコメントは送信しない
const id = self.crypto.randomUUID(); // UUIDを生成して一意のIDを作成
const commentText = this.newComment.trim(); // 入力されたコメントを取得
const chronologyHeight = this.$refs.chronology.clientHeight; // 年表の高さを取得
const topOffset = this.scrollTop + chronologyHeight / 2; // 年表の中央にコメントを配置
const distance = window.innerWidth + 200; // コメントが流れる距離(画面幅 + 200pxの余裕)
const speed = 100; // コメントの流れる速度(px/s)
const duration = distance / speed; // コメントが流れる時間(秒)
const makeCommentStyle = () => ({
position: 'absolute',
whiteSpace: 'nowrap',
top: `${topOffset}px`, // 現在表示されている年表の中央に配置
animation: `move-left ${duration}s linear infinite`, // 左へ流れるアニメーション
});
// コメントオブジェクトを作成
const comment = {
id,
text: commentText,
style: makeCommentStyle(),
};
// コメントをVueのデータに追加
this.comments.push(comment);
this.newComment = '';
// fetchでコメントをサーバーに送信
fetch('/api/comments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({
text: commentText,
position: topOffset
})
}).then(res => {
// return res.json();
});
}
実際にコメントが流れる様子は、ぜひ
こちらのサイトからご確認ください。
🤝 おわりに
今回は主にアプリケーションの実装内容を紹介しましたが、
他にも、スクレイピングでデータを取得したり、Dockerで環境構築をしたりと、いろいろな技術を使いました。
興味のある方は、ぜひコメントしてください。
Discussion