🔖

【個人開発】Laravel + Vue.jsで作る週刊少年ジャンプ年表アプリ【ジャンプなに読んでた?】

に公開

こんにちは。最近、「昔ジャンプで何読んでたっけ?」と思い出すことが増えたので、週刊少年ジャンプの歴代連載作品の年表サイトを作ってみました。

その名も…

ジャンプなに読んでた?

ogp画像


🔍 どんなサイト?

  • 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', // アニメーション効果
    };
},

これを描画した結果がこちらです。
image.png

コメント機能

作っている途中で、ただの年表では面白くないなと思い、ニコニコ動画風のコメント機能を実装しました。

(特許とか著作権は問題ないですよ!)

入力蘭にコメントを入力し、送信ボタンを押すと、コメントが年表の右から左へ流れていきます。
コメントの送信処理はこんな感じです。

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