【てがろぐ】アンカーをクリックするとポップアップ表示するようにしました

てがろぐはスクリーンショット置き場として使用しているのですが、気軽にゲームのことを呟く場所としても使いたいなーと思っていました。

投稿に対して返信欄のような表示ができたらメモとか感想を繋げて書くのに便利なんだけどなーと思いつつ、実装方法が思い付かなかったので…

実装したかった形
※イメージ画像です※ 本当はこんな形にしたかった…

今回はとりあえずの策として、「アンカーリンクをクリックするとアンカー先の内容をポップアップ表示させる」という形で実装してみました。

現在の構造を調べる

投稿内に 「>>797」のような形で入力するとアンカーリンク(てがろぐ公式サイトでは『指定No.リンク』と書かれています)が挿入されます。

HTMLとしては、

<a href="?postid=番号" class="postidlink">リンクテキスト</a>

という形で書かれているので、この「postidlink」というclassを持つリンクをクリックするとポップアップするようにしました。

アンカー先は同じページ内にあるとは限らないので、クリックするとリンク先のページ(個別の記事ページ)を確認して記事内容(「.onelogbox」のclass名)を抽出し、ポップアップに表示させています。

※記事のHTML構造はこのような形になっています。(skin-onelog.html)

<section class="onelogbox [[POSTSTATUS]]" id="pos[[LOOPCOUNT]]">
  <div>
    <div class="onelog_commentbox"> // 本文用ボックス
      [[COMMENT]]
    </div>
  </div>
  <div class="onelog_datebox"> // 本文下の情報用ボックス
    <a href="[[EDITURL]]" title="No.[[POSTID]]を編集します。" class="Login-Required">
      <span class="te-edit"></span>
    </a>
    <a href="#" title="No.[[POSTID]]へ返信。" data-postid="[[POSTID]]" class="post-link Login-Required">
      <span class="te-reply"></span>
    </a>
    <span class="onelog_date">
      <a href="[[PERMAURL]]">[[DATE:Y.G.N(w) h:m:s]]</a>
    </span>
  </div>
</section>

最初は本文だけ抽出すればいいかな~と思っていたのですが、投稿日等あったほうが分かりやすそうだったので、全部まとめて表示させる形にしました。

仕様

今回の実装仕様は、

  • アンカーリンクをクリックすると記事内容がポップアップする
  • ポップアップの外側をクリックするとポップアップが消える
  • アンカーリンクをもう一度クリックすると、個別記事ページへ移動する
  • ポップアップはアンカーリンク部分の上側に表示させる

です。

2回目のクリックで個別ページに飛ぶようにしたのは、元々がそういう仕様だったので形として残しておこうかなーという理由だけだったりします。

なので、ひょっとしたら今後コード修正して飛べないようにしているかもしれません。

コード

javascript

// 現在表示されているポップアップの状態を追跡する変数
let currentPopupLink = null;

// ミニウィンドウ(ポップアップ)を開く関数
function openPopup(content, x, y) {
  const popup = document.getElementById('popup');
  const popupContent = document.getElementById('popupContent');
  popupContent.innerHTML = content;

  popup.style.left = `${x}px`;
  popup.style.top = `${y}px`;
  popup.style.display = 'block';
}

// ミニウィンドウ(ポップアップ)を閉じる関数
function closePopup() {
  document.getElementById('popup').style.display = 'none';
}

// リンクをクリックした際にデータを取得してポップアップを表示する処理
document.querySelectorAll('.postidlink').forEach(link => {
  link.addEventListener('click', async function(event) {
    event.preventDefault(); // リンクのデフォルト動作をキャンセル

    const href = this.getAttribute('href');

    // 同じリンクがクリックされた場合
    if (currentPopupLink === this) {
      // ポップアップを閉じる
      closePopup();
      // ページ遷移を実行
      window.location.href = href;
      return;
    }

    // 新しいリンクがクリックされた場合
    currentPopupLink = this;

    const response = await fetch(href);
    const text = await response.text();

    // HTMLから記事データを抽出
    const parser = new DOMParser();
    const doc = parser.parseFromString(text, 'text/html');
    const contentElement = doc.querySelector('.onelogbox');

    if (contentElement) {
      const content = contentElement.innerHTML; // 対応する記事の内容を取得

      // クリックされたリンクの位置を取得
      const rect = this.getBoundingClientRect();
      const x = rect.left + window.pageXOffset;
      const y = rect.top + window.pageYOffset - document.getElementById('popup').offsetHeight;

      openPopup(content, x, y); // ポップアップを開く
    } else {
      openPopup('<div class="popup-error">記事が見つかりません。</div>', 0, 0);
    }
  });
});

// クリック時にポップアップを閉じる処理
document.addEventListener('click', function(event) {
  const popup = document.getElementById('popup');
  if (!popup.contains(event.target) && !event.target.classList.contains('postidlink')) {
    closePopup(); // ポップアップを閉じる
    currentPopupLink = null; // 現在のポップアップリンクをリセット
  }
});

css

#popup {
  display: none;
  position: absolute;
  padding: 10px 0;
  background-color: white;
  border: 2px solid #ccc;
  box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
  z-index: 1000;
  width: 300px;
  font-size: 1.4rem;
  transform: translateY(-100%);
}

#popupContent {
  max-height: 400px;
  overflow: auto;
}

html

skin-cover.htmlにポップアップ用のコードを追加しました。

<div id="popup">
  <div id="popupContent"></div>
</div>

本当はコード内に、

<button onclick="closePopup()">閉じる</button>

という閉じるボタン用のコードがあったのですが、必要ないかなーと思って削除しています。

完成

CSSで微調整して、出来上がったのがこちら↓。

完成図
アンカーリンクの上にポップアップが表示されている。

複数の投稿を繋げる場合はうまく動かないかもしれませんが、とりあえず形になって良かったです。

おまけ

実は今回の実装と一緒に追加した機能があります。

各記事の下部に返信用アイコンを追加して、それをクリックすると投稿用テキストエリアに「>>〇〇」形式で入力されるというもの。

<a href="#" title="No.[[POSTID]]へ返信。" data-postid="[[POSTID]]" class="post-link Login-Required">
// すべてのリンクに対してイベントを設定
document.querySelectorAll('.post-link').forEach(function(link) {
    link.addEventListener('click', function(event) {
        event.preventDefault(); // 通常のリンク動作を無効化
        event.stopPropagation(); // イベントの伝搬を停止

        // クリックされたリンクの data-postid を取得
        var postId = this.getAttribute('data-postid');

        // クラス tegalogpost を持つすべてのテキストエリアを取得
        var textAreas = document.querySelectorAll('.tegalogpost');
        
        // すべての tegalogpost テキストエリアに >>postId を挿入
        textAreas.forEach(function(textArea) {
            textArea.value += `>>${postId} `; // 既存の内容に追記
            textArea.focus(); // フォーカスを移動
        });
    });
});

「.post-link」のclassを持つリンクをクリックすると「data-postid」のID番号を取得してテキストエリアに入力します。

最後に

javascriptはまだまだ勉強不足で、chatGPTが教えてくれたコードを見てもどんな動きをしているかよく分かっておらず…

今回の記事も、もう一度コードをchatGPTに投げて「これどういう動きをしているの?」と解説してもらい、それをまとめただけの記事という…😅

てがろぐは他にも実装してみたい機能があるので、まだまだ触っていこうと思います。

関連記事
    新着記事
      ブログ内検索