ポップアップ

テキスト選択ポップアップ

NoteYahooみたいに、テキストを選択してポップアップを出し、その文字を検索・コピーできると楽ちんだなと思い、実装しました。当初はGoogle検索コトバンク検索コピーの3つでしたが、AIに他に便利な機能あるって尋ねると、「AIに詳しく聞けばいいんじゃね?」と言うので、AI検索も追加しました。

ポップアップ

元々自分で作ってホーバーする位置がうまくいかずお蔵入りしていたコードを基にして、Geminiに聞きながら作ったのがこちら。WordPressSingle.php<?php the_content(); ?>の中だけで使っています。このページでも使えるので、文字を選択してみてください。

const initTextSelection = () => {
 const textArea = document.querySelector('.textSelect');
 if (!textArea) return;

 textArea.style.position = 'relative';

 const btnData = [
  { title: 'AIで解説', url: 'https://www.perplexity.ai/search?q=' },
  { title: 'Google検索', url: 'https://www.google.com/search?q=' },
  { title: 'コトバンク検索', url: 'https://kotobank.jp/search?q=' },
  { title: 'コピー', url: '#' }
 ];

 const removePopup = () => {
 const old = document.querySelector('.selectedText');
  if (old) old.remove();
 };

 // mouseupイベント(textArea内でのみ反応)
 textArea.addEventListener('mouseup', (event) => {
 const selection = window.getSelection();
 const textItem = selection.toString().trim();

 if (!textItem) {
  removePopup();
  return;
 }

 removePopup();

 const range = selection.getRangeAt(0);
 const rect = range.getBoundingClientRect();
 const parentRect = textArea.getBoundingClientRect();

 const newElementUl = document.createElement('ul');
 newElementUl.classList.add('selectedText');

 // 座標計算(親要素からの相対位置 項目を増減すると選択項目の位置がずれるので150の値を変更する)
 const top = rect.top - parentRect.top - 150;
 const left = rect.left - parentRect.left + (rect.width / 2);

 Object.assign(newElementUl.style, {
  position: 'absolute',
  top: `${top}px`,
  left: `${left}px`,
  transform: 'translateX(-50%)',
  zIndex: 100
 });

 btnData.forEach((item, i) => {
  const li = document.createElement('li');
  li.className = 'item__li';
  const isCopy = item.title === 'コピー';
  li.innerHTML = `<a class="item__a" href="#">${isCopy ? item.title : item.title}</a>`;

  li.addEventListener('mousedown', (e) => {
  e.preventDefault();
  e.stopPropagation();
  if (item.title === 'コピー') {
   if (navigator.clipboard) {
    navigator.clipboard.writeText(textItem).then(() => {
     console.log('Copied!');
    });
   }
  } else {
   let query = textItem;
   if (item.title === 'AIで解説') {
    query = `「${textItem}」について詳しく解説して`;
   }
   const url = item.url + encodeURIComponent(query);
   window.open(url, '_blank');
  }
  removePopup();
 });
 newElementUl.appendChild(li);
});

textArea.appendChild(newElementUl);

 // GSAPアニメーション
gsap.fromTo(newElementUl,
 { opacity: 0, y: 10 },
 { opacity: 1, y: 0, duration: 0.3, ease: "power2.out" }
 );
});

// 画面のどこかをクリックしたらポップアップを消す
document.addEventListener('mousedown', (e) => {
 if (!e.target.closest('.selectedText')) removePopup();
 }, { once: true }); // メモリ対策で一度きりに設定
};

initTextSelection();

元々Barbadata.next.containerを使っていたので、コードにcontainerが入っていれば、documentに書き換えてください。ポップアップのアニメーション処理はGSAPで行っているだけなので、CSSのアニメーションに変えても問題ありません。以下はCSStextSelect<?php the_content(); ?>をラップしているクラスです。

.textSelect {
 .selectedText {
   position: absolute; /* JSで動的に位置指定 */
   display: flex;
   align-items: center;
   flex-direction: column;
   align-items: stretch;
   gap: 3px;
   list-style: none;
   min-width: 120px;
   margin: 0;
   padding: 4px;
   background-color: #333;
   border-radius: 4px;
   box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
   white-space: nowrap; /* 改行を防ぐ */
   z-index: 30;
   pointer-events: auto !important; /* 強制的にクリックを有効にする */
   cursor: default;

    /* 矢印(吹き出し風)をつけたい場合 */
    &::after {
     content: "";
     position: absolute;
     top: 100%; /* 下側に配置 */
     left: 50%;
     transform: translateX(-50%);
     border-width: 6px;
     border-style: solid;
     border-color: #333 transparent transparent transparent;
   }

    .item__li {
     position: relative;
     width: 100%;
     margin: 0;
     padding: 0;
     border-radius: 4px;
    }

    /* リンクボタン(a) */
    .item__a {
     display: block;
     pointer-events: auto !important;
     cursor: pointer; /* 指マークにする */
     padding: 8px 12px;
     color: #fff;
     text-decoration: none;
     font-size: 1.2rem;
     // font-weight: bold;
     line-height: 1;
     text-align: center;
     border-radius: 4px;
     transition: background-color 0.2s ease;
      &:hover {
       background-color: #8b0d0d;
       color: #fff;
       text-decoration: none;
       border-radius: 4px;
      }
    }
 }
}

コメント