【Laravelいいね機能】DB使用・会員登録なしで、localStorageでいいね(お気に入り)機能を実装する

やりたいこと

データベースを使用しない、簡易的ないいね機能を実装(各ブラウザのlocalStorageを使用する)

今回はキャラクター一覧・詳細画面で、いいねを付ける機能をサンプルとして実装します。

※キャラクターは固定表示で、キャラクターの登録編集削除部分は実装せず、いいねの部分だけになります。

※今回はjQuery・javascriptでの実装となります。Vue.jsを使用した方法は他記事にまとめています。

Local Storageとは?

ブラウザ(ChromeやSafariなど)にデータを保存できる仕組み

よくECサイトで、会員登録をしていないのにもかかわらず、「最近閲覧した商品」「お気に入り商品」などが表示されているのはlocalStorageを使用している為。

データベースを使用せず、localStorageを使用するメリット

会員登録をする必要が無い

データベースを使用せず、localStorageを使用するデメリット

localStorageを使用してブラウザに保存する為、キャッシュクリアや、別端末、別ブラウザで閲覧されるとお気に入りを共有できない

実装内容詳細

  1. お気に入りの状態によって、表示するアイコンの色を変える
  2. 画面遷移をしても、お気に入りの状態が正しい事(ブラウザバック・一覧へ戻るボタン押下)

バージョン

Laravel:10.3.3

デモ

サンプルソース

githubの「localstorage」ブランチにアップしています。

大まかな流れ

  1. 一覧画面・詳細画面を用意
  2. 表示するデータを用意(今回は固定指定なので、各画面に表示したいデータをべた書き)
  3. お気に入りを追加する処理を、javascript(jQuery)で作る

1)一覧画面・詳細画面を用意

一覧画面と詳細画面を用意します。

必要そうなところだけピックアップしています。

cssや全体的なレイアウトはgitのソースをご確認ください。

jsLikeButtonクラスの要素を確認してもらえれば、中身を設定していないことが分かりますが、

これは要素のクラス名によってcssで表示するアイコン(未いいね、いいね済)を変更しています。

.jsLikeButton.unLike {
    background-image: url(画像パス);
}
.jsLikeButton.liked {
    background-image: url(画像パス);
}

一覧画面

// 一覧画面
<div class="container mt-5">
    <div class="row">
        @foreach($items as $item)
            <a href="/{{ $item['id'] }}" class="col-12 col-md-6 mb-3">
                <div class="card characterCard p-4">
                    <div class="jsLikeButton" id="{{ $item['id'] }}">
                        <!-- いいねアイコンはcssで設定。クラス名によって表示を変更 -->
                    </div>

                    <div class="d-flex mt-2">
                        <!-- 画像 -->
                        <div>
                            <img src="{{ asset('img/character/'.$item['image']) }}" alt="{{ $item['name'] }}">
                        </div>

                        <!-- 説明 -->
                        <div class="ps-3">
                            <h2>{{ $item['name'] }}</h2>
                            <p class="mt-2 text-sm text-muted">
                                {!! $item['description'] !!}
                            </p>
                        </div>
                    </div>
                </div>
            </a>
        @endforeach
    </div>
</div>

詳細画面

// 詳細画面
<div class="container">
    @if ($item)
        <div class="mt-5 characterDetail">
            <div class="jsLikeButton" id="{{ $item['id'] }}">
                <!-- いいねアイコンはcssで設定。クラス名によって表示を変更 -->
            </div>
            <!-- 画像 -->
            <div class="text-center">
                <img src="{{ asset('img/character/'.$item['image']) }}" alt="{{ $item['name'] }}">
            </div>
            <!-- 説明 -->
            <div class="card mt-3 p-4">
                <h2>{{ $item['name'] }}</h2>
                <p class="mt-2 text-sm text-muted">
                    {!! $item['description'] !!}
                </p>
            </div>
        </div>
    @else
        <!-- 該当するキャラクター無し -->
        <div class="mt-5 text-center">
            <p>該当するキャラクターがありませんでした</p>
        </div>
    @endif

    <a href="/" class="backBtn">一覧に戻る</a>
</div>

2)表示するデータを用意(今回は固定指定なので、各画面に表示したいデータをべた書き)

// 表示するデータ
$items = [
    ['id' => 1, 'name' => 'ひよこ隊長', 'image' => 'hiyoko.png', 'description' => '夏が大好きなひよこ隊長。<br>夏はよくフェスに行き、夏を堪能している。'],
    ['id' => 2, 'name' => 'フクロウ先生', 'image' => 'fukurou-sensei.png', 'description' => '森の学校のフクロウ先生。<br>最近物忘れすることを本人は気にしている。'],
    ['id' => 3, 'name' => 'パンダ氏', 'image' => 'panda.png', 'description' => 'パンダ財閥の次男。<br>蝶ネクタイがトレードマーク。'],
    ['id' => 4, 'name' => 'おしゃべりオウム', 'image' => 'oumu.png', 'description' => 'おしゃべり大好きオウム君。<br>おしゃべりしすぎて、フクロウ先生に怒られる事が多い。'],
];

3)お気に入りを追加する処理を、javascript(jQuery)で作る

window.addEventListener('pageshow', () => {}の中にお気に入り取得などの処理を入れ込んでいる理由ですが、ブラウザの戻るボタンで戻った際に、一覧画面や詳細画面のお気に入りの状態が古い状態のままになってしまうのを防ぐためです。

今回はpageshowイベントを使用しましたが、よく他の記事ではwindow.addEventListener('popstate', () => {が使用されています。popstateだと、Chromeではセキュリティの問題で表示した各画面でクリックなど、ユーザーによる1アクションが無ければ発火しません。(※各ブラウザによっても動きが異なる可能性があります)

その為、今回はpageshowイベントを使用しました。ブラウザバック含めた画面を表示するタイミングで発火されるので、重い処理を何回もしていれば遅くなってしまうかもしれませんが。。

$(function () {
    window.addEventListener('pageshow', () => {

        const likes = getLike();

        // いいねアイコンへクラスを設定
        setLikeClass();

        $('.jsLikeButton').on('click', function (event) {
            // a要素の遷移を停止
            event.preventDefault();

            let id = $(this).attr('id');
            setLike(id);
        });

        // localStorageからお気に入りを取得
        function getLike() {
            let values = [];
            let localStorageValues = JSON.parse(localStorage.getItem('likes'));
            if (localStorageValues) {
                values = localStorageValues;
            }
            return values;
        }

        function setLikeClass() {
            $('.jsLikeButton').each(function () {
                let id = $(this).attr('id');

                // お気に入りに関するクラスをすべて削除
                $(this).removeClass('unLike liked');
                let className = 'unLike';
                // 既にお気に入りか
                if (isLiked(id)) {
                    className = 'liked';
                }
                $(this).addClass(className);
            });
        }

        // localStorageにお気に入りを設定
        function setLike(id) {
            // localStorageが存在している
            if (likes) {
                // 既にお気に入り
                if (isLiked(id)) {
                    let indexPosition = likedIndexPosition(id);
                    // お気に入りから該当idを除去
                    likes.splice(indexPosition, 1) ;
                } else {
                    // お気に入り追加
                    likes.push(id);
                }

            } else {
                // localStorageが存在していない場合
                // お気に入り追加
                likes.push(id);
            }

            // 新しい値をlocalStorageに設定
            localStorage.setItem('likes', JSON.stringify(likes));
            // いいねアイコンへクラスを設定
            setLikeClass();
        }

        // 既にお気に入りに設定されているか
        function isLiked(id) {
            let indexPosition = likedIndexPosition(id);
            // 含まれている
            if (likes && 0 <= indexPosition) {
                return true;
            }
            // 含まれていない
            return false;
        }

        function likedIndexPosition(id) {
            // 配列の何番目のインデックスに含まれているか(配列中にマッチする値が含まれなければ-1を返す)
            return $.inArray(id, likes);
        }
    });

});

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です